Format Rust sources at 100 columns
This commit is contained in:
1 parent
4661f595f8
commit
610478f8c7
336 files changed
+23497
-7892
No files matched your search
@@ -13,7 +13,11 @@ pub(crate) enum State {
|
||||
Disabled,
|
||||
}
|
||||
|
||||
pub(crate) fn accent_button_style(theme: &cosmic::Theme, state: State, focused: bool) -> button::Style {
|
||||
pub(crate) fn accent_button_style(
|
||||
theme: &cosmic::Theme,
|
||||
state: State,
|
||||
focused: bool,
|
||||
) -> button::Style {
|
||||
let native = theme.cosmic();
|
||||
let component = &native.accent_button;
|
||||
let fill: Color = match state {
|
||||
|
||||
@@ -61,7 +61,11 @@ fn grid_with_density<'a, M: Clone + 'a>(
|
||||
navigation: [Element<'a, M>; 3],
|
||||
compact: bool,
|
||||
) -> Element<'a, M> {
|
||||
let cell_width = if compact { Length::Fill } else { Length::Fixed(30.) };
|
||||
let cell_width = if compact {
|
||||
Length::Fill
|
||||
} else {
|
||||
Length::Fixed(30.)
|
||||
};
|
||||
let cell_height = if compact { 20. } else { 25. };
|
||||
let spacing = if compact { 2. } else { 3. };
|
||||
let months = [
|
||||
@@ -96,12 +100,17 @@ fn grid_with_density<'a, M: Clone + 'a>(
|
||||
.spacing(2);
|
||||
let mut grid = column::with_capacity(8)
|
||||
.spacing(spacing)
|
||||
.width(if compact { Length::Fill } else { Length::Shrink })
|
||||
.width(if compact {
|
||||
Length::Fill
|
||||
} else {
|
||||
Length::Shrink
|
||||
})
|
||||
.push(header);
|
||||
let mut weekdays =
|
||||
row::with_capacity(7)
|
||||
.spacing(spacing)
|
||||
.width(if compact { Length::Fill } else { Length::Shrink });
|
||||
let mut weekdays = row::with_capacity(7).spacing(spacing).width(if compact {
|
||||
Length::Fill
|
||||
} else {
|
||||
Length::Shrink
|
||||
});
|
||||
for label in ["M", "T", "W", "T", "F", "S", "S"] {
|
||||
weekdays = weekdays.push(
|
||||
container(
|
||||
@@ -115,27 +124,33 @@ fn grid_with_density<'a, M: Clone + 'a>(
|
||||
}
|
||||
grid = grid.push(weekdays);
|
||||
for week in 0..(start + days).div_ceil(7) {
|
||||
let mut dates =
|
||||
row::with_capacity(7)
|
||||
.spacing(spacing)
|
||||
.width(if compact { Length::Fill } else { Length::Shrink });
|
||||
let mut dates = row::with_capacity(7).spacing(spacing).width(if compact {
|
||||
Length::Fill
|
||||
} else {
|
||||
Length::Shrink
|
||||
});
|
||||
for weekday in 0..7 {
|
||||
let cell = week * 7 + weekday;
|
||||
let day = cell.checked_sub(start).map(|day| day + 1).filter(|day| *day <= days);
|
||||
let is_today = year == today.year() && month == today.month() && day == Some(today.day() as usize);
|
||||
let day = cell
|
||||
.checked_sub(start)
|
||||
.map(|day| day + 1)
|
||||
.filter(|day| *day <= days);
|
||||
let is_today =
|
||||
year == today.year() && month == today.month() && day == Some(today.day() as usize);
|
||||
let label = day.map_or(String::new(), |day| day.to_string());
|
||||
dates = dates.push(
|
||||
container(crate::text(label, font).size(12).class(theme::Text::Color(if is_today {
|
||||
primary
|
||||
} else {
|
||||
muted
|
||||
})))
|
||||
container(
|
||||
crate::text(label, font)
|
||||
.size(12)
|
||||
.class(theme::Text::Color(if is_today { primary } else { muted })),
|
||||
)
|
||||
.width(cell_width)
|
||||
.height(cell_height)
|
||||
.center_x(cell_width)
|
||||
.center_y(cell_height)
|
||||
.class(theme::Container::custom(move |_| container::Style {
|
||||
background: is_today.then_some(Background::Color(palette.accent.scale_alpha(0.2 * opacity))),
|
||||
background: is_today
|
||||
.then_some(Background::Color(palette.accent.scale_alpha(0.2 * opacity))),
|
||||
border: Border {
|
||||
shape: BorderShape::Continuous,
|
||||
radius: palette.radius.min(12.).into(),
|
||||
@@ -155,9 +170,18 @@ mod tests {
|
||||
#[test]
|
||||
fn clock_uses_lowercase_date_and_twelve_hour_time() {
|
||||
for (stamp, expected) in [
|
||||
("2026-09-28T21:32:00+01:00[Africa/Lagos]", "28 sept, 9:32 pm"),
|
||||
("2026-09-28T00:05:00+01:00[Africa/Lagos]", "28 sept, 12:05 am"),
|
||||
("2026-09-28T12:00:00+01:00[Africa/Lagos]", "28 sept, 12:00 pm"),
|
||||
(
|
||||
"2026-09-28T21:32:00+01:00[Africa/Lagos]",
|
||||
"28 sept, 9:32 pm",
|
||||
),
|
||||
(
|
||||
"2026-09-28T00:05:00+01:00[Africa/Lagos]",
|
||||
"28 sept, 12:05 am",
|
||||
),
|
||||
(
|
||||
"2026-09-28T12:00:00+01:00[Africa/Lagos]",
|
||||
"28 sept, 12:00 pm",
|
||||
),
|
||||
] {
|
||||
assert_eq!(super::format_bar_time(&stamp.parse().unwrap()), expected);
|
||||
}
|
||||
@@ -169,7 +193,10 @@ mod tests {
|
||||
let (first, start, days) = super::month(february, 0);
|
||||
assert_eq!((first.year(), first.month(), start, days), (2024, 2, 3, 29));
|
||||
let (first, start, days) = super::month(february, -2);
|
||||
assert_eq!((first.year(), first.month(), start, days), (2023, 12, 4, 31));
|
||||
assert_eq!(
|
||||
(first.year(), first.month(), start, days),
|
||||
(2023, 12, 4, 31)
|
||||
);
|
||||
let june = jiff::civil::Date::new(2025, 6, 1).unwrap();
|
||||
let (_, start, days) = super::month(june, 0);
|
||||
assert_eq!((start, days), (6, 30));
|
||||
|
||||
@@ -76,12 +76,27 @@ pub fn apply(native: &mut cosmic::cosmic_theme::Theme, preferred: Color) {
|
||||
let surface: Color = native.primary(false).base.into();
|
||||
for component in [&mut native.accent, &mut native.accent_button] {
|
||||
let (base, on) = accent_pair(composite(component.base.into(), surface), preferred);
|
||||
component.hover = rgba(readable_fill(composite(component.hover.into(), surface), base, on));
|
||||
component.pressed = rgba(readable_fill(composite(component.pressed.into(), surface), base, on));
|
||||
component.selected = rgba(readable_fill(composite(component.selected.into(), surface), base, on));
|
||||
component.hover = rgba(readable_fill(
|
||||
composite(component.hover.into(), surface),
|
||||
base,
|
||||
on,
|
||||
));
|
||||
component.pressed = rgba(readable_fill(
|
||||
composite(component.pressed.into(), surface),
|
||||
base,
|
||||
on,
|
||||
));
|
||||
component.selected = rgba(readable_fill(
|
||||
composite(component.selected.into(), surface),
|
||||
base,
|
||||
on,
|
||||
));
|
||||
component.base = rgba(base);
|
||||
component.on = rgba(on);
|
||||
component.selected_text = rgba(foreground(composite(component.selected.into(), surface), on));
|
||||
component.selected_text = rgba(foreground(
|
||||
composite(component.selected.into(), surface),
|
||||
on,
|
||||
));
|
||||
component.disabled = rgba(composite(Color { a: 0.5, ..base }, surface));
|
||||
component.on_disabled = rgba(foreground(composite(Color { a: 0.5, ..base }, surface), on));
|
||||
}
|
||||
@@ -125,7 +140,10 @@ mod tests {
|
||||
let preferred = Color::from_rgb8(240, 242, 246);
|
||||
assert_eq!(foreground(Color::BLACK, preferred), preferred);
|
||||
assert_eq!(foreground(Color::WHITE, preferred), Color::BLACK);
|
||||
assert_eq!(foreground(Color::from_rgb8(61, 123, 230), preferred), Color::BLACK);
|
||||
assert_eq!(
|
||||
foreground(Color::from_rgb8(61, 123, 230), preferred),
|
||||
Color::BLACK
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -153,7 +171,12 @@ mod tests {
|
||||
.build();
|
||||
apply(&mut native, preferred);
|
||||
let theme = cosmic::Theme::custom(std::sync::Arc::new(native));
|
||||
for state in [State::Active, State::Hovered, State::Pressed, State::Disabled] {
|
||||
for state in [
|
||||
State::Active,
|
||||
State::Hovered,
|
||||
State::Pressed,
|
||||
State::Disabled,
|
||||
] {
|
||||
let actual = accent_button_style(&theme, state, false);
|
||||
assert_eq!(actual.text_color, Some(preferred));
|
||||
assert_eq!(actual.icon_color, Some(preferred));
|
||||
@@ -201,11 +224,20 @@ mod tests {
|
||||
for fill in [component.base, component.hover, component.pressed] {
|
||||
assert!(contrast(fill.into(), component.on.into()) >= 4.5);
|
||||
}
|
||||
assert!(contrast(component.selected.into(), component.selected_text.into()) >= 4.5);
|
||||
assert!(contrast(component.disabled.into(), component.on_disabled.into()) >= 4.5);
|
||||
assert!(
|
||||
contrast(component.selected.into(), component.selected_text.into()) >= 4.5
|
||||
);
|
||||
assert!(
|
||||
contrast(component.disabled.into(), component.on_disabled.into()) >= 4.5
|
||||
);
|
||||
}
|
||||
let theme = cosmic::Theme::custom(std::sync::Arc::new(native));
|
||||
for state in [State::Active, State::Hovered, State::Pressed, State::Disabled] {
|
||||
for state in [
|
||||
State::Active,
|
||||
State::Hovered,
|
||||
State::Pressed,
|
||||
State::Disabled,
|
||||
] {
|
||||
let style = accent_button_style(&theme, state, false);
|
||||
let Some(Background::Color(fill)) = style.background else {
|
||||
panic!("No fill")
|
||||
|
||||
@@ -91,7 +91,11 @@ pub(crate) fn switch_colors(palette: Palette, enabled: bool, hovered: bool) -> (
|
||||
let track = if enabled {
|
||||
palette.accent
|
||||
} else {
|
||||
mix(palette.card, palette.text, if hovered { 0.18 } else { 0.12 })
|
||||
mix(
|
||||
palette.card,
|
||||
palette.text,
|
||||
if hovered { 0.18 } else { 0.12 },
|
||||
)
|
||||
};
|
||||
let thumb = crate::foreground(track, palette.text);
|
||||
(track, thumb)
|
||||
@@ -148,7 +152,13 @@ pub fn select<'a, M: 'a>(
|
||||
}))
|
||||
}
|
||||
|
||||
fn styled_button(p: Palette, selected: bool, navigation: bool, opacity: f32, focus_visible: bool) -> theme::Button {
|
||||
fn styled_button(
|
||||
p: Palette,
|
||||
selected: bool,
|
||||
navigation: bool,
|
||||
opacity: f32,
|
||||
focus_visible: bool,
|
||||
) -> theme::Button {
|
||||
let style = move |hover: bool, focused: bool| {
|
||||
let background = if selected {
|
||||
mix(p.sidebar, p.accent, if hover { 0.24 } else { 0.17 })
|
||||
@@ -177,7 +187,12 @@ fn styled_button(p: Palette, selected: bool, navigation: bool, opacity: f32, foc
|
||||
};
|
||||
|
||||
let fill = if selected {
|
||||
p.selected_background(if hover { 0.24 } else { 0.17 }, background.a, on, background)
|
||||
p.selected_background(
|
||||
if hover { 0.24 } else { 0.17 },
|
||||
background.a,
|
||||
on,
|
||||
background,
|
||||
)
|
||||
} else {
|
||||
Background::Color(background)
|
||||
};
|
||||
@@ -220,7 +235,10 @@ pub fn authentication_button(palette: Palette) -> theme::Button {
|
||||
let candidate = mix(palette.accent, palette.text, interaction);
|
||||
crate::accent_pair(crate::composite(candidate, palette.card), palette.text)
|
||||
} else {
|
||||
(crate::composite(palette.card, palette.background), palette.muted)
|
||||
(
|
||||
crate::composite(palette.card, palette.background),
|
||||
palette.muted,
|
||||
)
|
||||
};
|
||||
button::Style {
|
||||
shape: Some(BorderShape::Continuous),
|
||||
@@ -275,7 +293,11 @@ pub fn authentication_input(palette: Palette) -> theme::TextInput {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lock_input(radius: f32, accent: cosmic::iced::Color, surface: cosmic::iced::Color) -> theme::TextInput {
|
||||
pub fn lock_input(
|
||||
radius: f32,
|
||||
accent: cosmic::iced::Color,
|
||||
surface: cosmic::iced::Color,
|
||||
) -> theme::TextInput {
|
||||
let appearance = move |focused: bool| widget::text_input::Appearance {
|
||||
shape: Some(BorderShape::Continuous),
|
||||
outline: None,
|
||||
@@ -329,7 +351,9 @@ pub fn shell_button(
|
||||
a: if pressed {
|
||||
0.20 * opacity
|
||||
} else {
|
||||
((if selected { 0.14 } else { 0.0 }) + progress * if selected { 0.02 } else { 0.08 }) * opacity
|
||||
((if selected { 0.14 } else { 0.0 })
|
||||
+ progress * if selected { 0.02 } else { 0.08 })
|
||||
* opacity
|
||||
},
|
||||
..foreground
|
||||
})),
|
||||
@@ -349,7 +373,12 @@ pub fn surface_appearance(background: Color, radius: f32) -> container::Style {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notification_button(foreground: Color, hover: Color, radius: f32, filled: bool) -> theme::Button {
|
||||
pub fn notification_button(
|
||||
foreground: Color,
|
||||
hover: Color,
|
||||
radius: f32,
|
||||
filled: bool,
|
||||
) -> theme::Button {
|
||||
let style = move |active: bool| button::Style {
|
||||
shape: Some(BorderShape::Continuous),
|
||||
outline: None,
|
||||
|
||||
@@ -38,11 +38,14 @@ pub fn tile<M: Clone + 'static>(
|
||||
.or(family.dark.as_ref())
|
||||
.map(|palette| crate::parse_color(&palette.text).unwrap())
|
||||
.unwrap_or(palette.text);
|
||||
let mut caption = row([]).spacing(6).align_y(cosmic::iced::Alignment::Center).push(
|
||||
crate::text(family.name.clone(), font)
|
||||
.size(12)
|
||||
.class(cosmic::theme::Text::Color(label_color)),
|
||||
);
|
||||
let mut caption = row([])
|
||||
.spacing(6)
|
||||
.align_y(cosmic::iced::Alignment::Center)
|
||||
.push(
|
||||
crate::text(family.name.clone(), font)
|
||||
.size(12)
|
||||
.class(cosmic::theme::Text::Color(label_color)),
|
||||
);
|
||||
if family.light.is_none() || family.dark.is_none() {
|
||||
caption = caption.push(
|
||||
crate::text(
|
||||
@@ -129,14 +132,21 @@ fn preview_with_radius(
|
||||
radius: f32,
|
||||
) -> cosmic::widget::icon::Icon {
|
||||
use cosmic::widget::icon;
|
||||
let key = format!("{}/{variant:?}/{active:?}/{selected}/{}", family.id, radius.to_bits());
|
||||
static CACHE: std::sync::OnceLock<std::sync::Mutex<std::collections::HashMap<String, (Family, icon::Handle)>>> =
|
||||
std::sync::OnceLock::new();
|
||||
let key = format!(
|
||||
"{}/{variant:?}/{active:?}/{selected}/{}",
|
||||
family.id,
|
||||
radius.to_bits()
|
||||
);
|
||||
static CACHE: std::sync::OnceLock<
|
||||
std::sync::Mutex<std::collections::HashMap<String, (Family, icon::Handle)>>,
|
||||
> = std::sync::OnceLock::new();
|
||||
let cache = CACHE.get_or_init(Default::default);
|
||||
let mut cache = cache.lock().unwrap();
|
||||
if cache.get(&key).is_none_or(|(cached, _)| cached != family) {
|
||||
let handle = icon::from_svg_bytes(svg_with_radius(family, variant, active, selected, radius).into_bytes())
|
||||
.symbolic(false);
|
||||
let handle = icon::from_svg_bytes(
|
||||
svg_with_radius(family, variant, active, selected, radius).into_bytes(),
|
||||
)
|
||||
.symbolic(false);
|
||||
cache.insert(key.clone(), (family.clone(), handle));
|
||||
}
|
||||
let handle = cache[&key].1.clone();
|
||||
@@ -164,7 +174,12 @@ fn desktop(p: &Palette) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn svg(family: &Family, variant: Option<Appearance>, active: Option<Appearance>, selected: bool) -> String {
|
||||
pub fn svg(
|
||||
family: &Family,
|
||||
variant: Option<Appearance>,
|
||||
active: Option<Appearance>,
|
||||
selected: bool,
|
||||
) -> String {
|
||||
svg_with_radius(family, variant, active, selected, 10.)
|
||||
}
|
||||
|
||||
@@ -180,9 +195,12 @@ fn svg_with_radius(
|
||||
<clipPath id="light"><rect width="100" height="126"/></clipPath><clipPath id="dark"><rect x="100" width="100" height="126"/></clipPath>
|
||||
<pattern id="missing" width="8" height="8" patternUnits="userSpaceOnUse"><rect width="8" height="8" fill="#D9DDE2"/><path d="M-2 2l4-4M0 8l8-8M6 10l4-4" stroke="#C4C9D1"/></pattern>"##,
|
||||
);
|
||||
let outline =
|
||||
cosmic::iced::border::Outline::new([0., 0., 200., 126.], [f64::from(radius); 4], BorderShape::Continuous)
|
||||
.expect("finite preview dimensions");
|
||||
let outline = cosmic::iced::border::Outline::new(
|
||||
[0., 0., 200., 126.],
|
||||
[f64::from(radius); 4],
|
||||
BorderShape::Continuous,
|
||||
)
|
||||
.expect("finite preview dimensions");
|
||||
// Rasterized once per cached preview; no shaped rendering layer per tile.
|
||||
let points = outline.polygon(0.02).expect("positive contour tolerance");
|
||||
result.push_str("<clipPath id=\"preview\"><polygon points=\"");
|
||||
@@ -338,7 +356,11 @@ fn radio_with_tab_stop<'a, M: Clone + 'a>(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn group<'a, M: Clone + 'a>(content: impl Into<Element<'a, M>>, id: Id, label: &str) -> Element<'a, M> {
|
||||
pub fn group<'a, M: Clone + 'a>(
|
||||
content: impl Into<Element<'a, M>>,
|
||||
id: Id,
|
||||
label: &str,
|
||||
) -> Element<'a, M> {
|
||||
Element::new(Radio {
|
||||
content: content.into(),
|
||||
id,
|
||||
@@ -366,18 +388,36 @@ impl<M: Clone> Widget<M, Theme, Renderer> for Radio<'_, M> {
|
||||
fn size(&self) -> Size<Length> {
|
||||
self.content.as_widget().size()
|
||||
}
|
||||
fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
|
||||
fn layout(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, limits)
|
||||
}
|
||||
fn operate(&mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation<()>) {
|
||||
fn operate(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
if self.group {
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.operate(&mut tree.children[0], layout, renderer, operation);
|
||||
self.content.as_widget_mut().operate(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
operation,
|
||||
);
|
||||
} else if self.tab_stop {
|
||||
operation.focusable(Some(&self.id), layout.bounds(), tree.state.downcast_mut::<Focus>());
|
||||
operation.focusable(
|
||||
Some(&self.id),
|
||||
layout.bounds(),
|
||||
tree.state.downcast_mut::<Focus>(),
|
||||
);
|
||||
} else {
|
||||
tree.state.downcast_mut::<Focus>().0 = false;
|
||||
}
|
||||
@@ -398,19 +438,25 @@ impl<M: Clone> Widget<M, Theme, Renderer> for Radio<'_, M> {
|
||||
if !iced_accessibility::IdEq::eq(&id.0, &self.id.0) {
|
||||
return;
|
||||
}
|
||||
if matches!(request.action, accesskit::Action::Focus | accesskit::Action::Click)
|
||||
&& let Some(message) = self
|
||||
.navigate
|
||||
.as_ref()
|
||||
.and_then(|navigate| navigate(&keyboard::key::Named::Enter))
|
||||
if matches!(
|
||||
request.action,
|
||||
accesskit::Action::Focus | accesskit::Action::Click
|
||||
) && let Some(message) = self
|
||||
.navigate
|
||||
.as_ref()
|
||||
.and_then(|navigate| navigate(&keyboard::key::Named::Enter))
|
||||
{
|
||||
shell.publish(message);
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if matches!(event, Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))) {
|
||||
tree.state.downcast_mut::<Focus>().0 = self.tab_stop && cursor.is_over(layout.bounds());
|
||||
if matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
) {
|
||||
tree.state.downcast_mut::<Focus>().0 =
|
||||
self.tab_stop && cursor.is_over(layout.bounds());
|
||||
}
|
||||
}
|
||||
if !self.group && self.tab_stop && tree.state.downcast_ref::<Focus>().0 {
|
||||
@@ -460,9 +506,15 @@ impl<M: Clone> Widget<M, Theme, Renderer> for Radio<'_, M> {
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
self.content
|
||||
.as_widget()
|
||||
.draw(&tree.children[0], renderer, theme, style, layout, cursor, viewport);
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor,
|
||||
viewport,
|
||||
);
|
||||
if !self.group && self.tab_stop && tree.state.downcast_ref::<Focus>().0 {
|
||||
use renderer::Renderer as _;
|
||||
renderer.fill_quad(
|
||||
@@ -489,16 +541,26 @@ impl<M: Clone> Widget<M, Theme, Renderer> for Radio<'_, M> {
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content
|
||||
.as_widget()
|
||||
.mouse_interaction(&tree.children[0], layout, cursor, viewport, renderer)
|
||||
self.content.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
layout,
|
||||
cursor,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
fn a11y_nodes(&self, layout: Layout<'_>, tree: &Tree, cursor: mouse::Cursor) -> A11yTree {
|
||||
let children = self.content.as_widget().a11y_nodes(layout, &tree.children[0], cursor);
|
||||
let children = self
|
||||
.content
|
||||
.as_widget()
|
||||
.a11y_nodes(layout, &tree.children[0], cursor);
|
||||
if self.group {
|
||||
let mut node = accesskit::Node::new(accesskit::Role::RadioGroup);
|
||||
node.set_label(self.label.clone());
|
||||
A11yTree::node_with_child_tree(iced_accessibility::A11yNode::new(node, self.id.clone()), children)
|
||||
A11yTree::node_with_child_tree(
|
||||
iced_accessibility::A11yNode::new(node, self.id.clone()),
|
||||
children,
|
||||
)
|
||||
} else {
|
||||
let mut children = children;
|
||||
for node in children.root_mut() {
|
||||
|
||||
@@ -42,7 +42,8 @@ pub const BATTERY_75: &[u8] = include_bytes!("../assets/icons/status/battery-75.
|
||||
pub const BATTERY_CHARGING: &[u8] = include_bytes!("../assets/icons/status/battery-charging.svg");
|
||||
pub const BATTERY_EMPTY: &[u8] = include_bytes!("../assets/icons/status/battery-empty.svg");
|
||||
pub const BATTERY_FULL: &[u8] = include_bytes!("../assets/icons/status/battery-full.svg");
|
||||
pub const BLUETOOTH_CONNECTED: &[u8] = include_bytes!("../assets/icons/status/bluetooth-connected.svg");
|
||||
pub const BLUETOOTH_CONNECTED: &[u8] =
|
||||
include_bytes!("../assets/icons/status/bluetooth-connected.svg");
|
||||
pub const BLUETOOTH_OFF: &[u8] = include_bytes!("../assets/icons/status/bluetooth-off.svg");
|
||||
pub const BLUETOOTH_ON: &[u8] = include_bytes!("../assets/icons/status/bluetooth-on.svg");
|
||||
pub const BRIGHTNESS: &[u8] = include_bytes!("../assets/icons/status/brightness.svg");
|
||||
@@ -95,7 +96,10 @@ pub fn tinted(source: &'static [u8], size: u16, foreground: Color) -> icon::Icon
|
||||
}
|
||||
|
||||
pub fn symbolic(source: &'static [u8], size: u16) -> icon::Icon {
|
||||
icon::from_svg_bytes(source).symbolic(true).icon().size(size)
|
||||
icon::from_svg_bytes(source)
|
||||
.symbolic(true)
|
||||
.icon()
|
||||
.size(size)
|
||||
}
|
||||
|
||||
const TINT_CACHE_ENTRIES: usize = 64;
|
||||
@@ -141,7 +145,8 @@ impl TintCache {
|
||||
let bytes = svg.capacity();
|
||||
let handle = icon::from_svg_bytes(svg).symbolic(false);
|
||||
if bytes <= TINT_CACHE_BYTES {
|
||||
while self.entries.len() >= TINT_CACHE_ENTRIES || self.bytes + bytes > TINT_CACHE_BYTES {
|
||||
while self.entries.len() >= TINT_CACHE_ENTRIES || self.bytes + bytes > TINT_CACHE_BYTES
|
||||
{
|
||||
self.bytes -= self.entries.pop_front().unwrap().bytes;
|
||||
}
|
||||
self.bytes += bytes;
|
||||
@@ -176,8 +181,8 @@ pub fn accented(source: &'static [u8], size: u16, foreground: Color, accent: Col
|
||||
.size(size)
|
||||
.width(Length::Fixed(f32::from(size) * aspect))
|
||||
.content_fit(ContentFit::Contain)
|
||||
.class(theme::Svg::custom(move |_| cosmic::iced::widget::svg::Style {
|
||||
color: None,
|
||||
.class(theme::Svg::custom(move |_| {
|
||||
cosmic::iced::widget::svg::Style { color: None }
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -232,10 +237,22 @@ mod tests {
|
||||
};
|
||||
let first = cache.get(super::BATTERY_50, foreground, accent);
|
||||
let second = cache.get(super::BATTERY_50, foreground, accent);
|
||||
assert_eq!(bytes(&first), bytes(&second), "rebuilds must reuse the same allocation");
|
||||
let changed_accent = cache.get(super::BATTERY_50, foreground, Color::from_rgb8(42, 90, 180));
|
||||
assert_eq!(
|
||||
bytes(&first),
|
||||
bytes(&second),
|
||||
"rebuilds must reuse the same allocation"
|
||||
);
|
||||
let changed_accent =
|
||||
cache.get(super::BATTERY_50, foreground, Color::from_rgb8(42, 90, 180));
|
||||
assert_ne!(bytes(&first), bytes(&changed_accent));
|
||||
let translucent = cache.get(super::BATTERY_50, Color { a: 0.5, ..foreground }, accent);
|
||||
let translucent = cache.get(
|
||||
super::BATTERY_50,
|
||||
Color {
|
||||
a: 0.5,
|
||||
..foreground
|
||||
},
|
||||
accent,
|
||||
);
|
||||
assert_ne!(bytes(&first), bytes(&translucent));
|
||||
assert_eq!(cache.entries.len(), 3);
|
||||
}
|
||||
@@ -252,7 +269,10 @@ mod tests {
|
||||
assert!(cache.entries.len() <= super::TINT_CACHE_ENTRIES);
|
||||
assert!(cache.bytes <= super::TINT_CACHE_BYTES);
|
||||
}
|
||||
assert_eq!(cache.entries.back().unwrap().colors[3], (511_f32 / 512.).to_bits());
|
||||
assert_eq!(
|
||||
cache.entries.back().unwrap().colors[3],
|
||||
(511_f32 / 512.).to_bits()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -19,9 +19,12 @@ pub fn row<'a, M: 'a>(
|
||||
trailing: Element<'a, M>,
|
||||
font: cosmic::font::Font,
|
||||
) -> Element<'a, M> {
|
||||
iced_row![crate::text(label, font).width(Length::Fill).size(14), trailing]
|
||||
.align_y(Alignment::Center)
|
||||
.into()
|
||||
iced_row![
|
||||
crate::text(label, font).width(Length::Fill).size(14),
|
||||
trailing
|
||||
]
|
||||
.align_y(Alignment::Center)
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn heading<'a, M: 'a>(
|
||||
@@ -34,7 +37,12 @@ pub fn heading<'a, M: 'a>(
|
||||
.align_y(Alignment::Center)
|
||||
}
|
||||
|
||||
pub fn badge<'a, M: 'a>(content: Element<'a, M>, accent: Color, opacity: f32, radius: f32) -> Element<'a, M> {
|
||||
pub fn badge<'a, M: 'a>(
|
||||
content: Element<'a, M>,
|
||||
accent: Color,
|
||||
opacity: f32,
|
||||
radius: f32,
|
||||
) -> Element<'a, M> {
|
||||
container(content)
|
||||
.width(36)
|
||||
.height(36)
|
||||
@@ -47,7 +55,12 @@ pub fn badge<'a, M: 'a>(content: Element<'a, M>, accent: Color, opacity: f32, ra
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn section<'a, M: 'a>(content: Element<'a, M>, foreground: Color, opacity: f32, radius: f32) -> Element<'a, M> {
|
||||
pub fn section<'a, M: 'a>(
|
||||
content: Element<'a, M>,
|
||||
foreground: Color,
|
||||
opacity: f32,
|
||||
radius: f32,
|
||||
) -> Element<'a, M> {
|
||||
container(content)
|
||||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
@@ -73,7 +86,10 @@ pub fn section_label<'a>(
|
||||
|
||||
pub fn separator<'a, M: 'a>(foreground: Color, opacity: f32) -> Element<'a, M> {
|
||||
container(cosmic::widget::Space::new().width(Length::Fill).height(1))
|
||||
.class(crate::controls::surface(foreground.scale_alpha(0.12 * opacity), 0.))
|
||||
.class(crate::controls::surface(
|
||||
foreground.scale_alpha(0.12 * opacity),
|
||||
0.,
|
||||
))
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -85,7 +101,10 @@ pub fn switch<'a, M: 'a>(enabled: bool, palette: crate::Palette, opacity: f32) -
|
||||
.width(16)
|
||||
.height(16)
|
||||
.class(crate::controls::surface(
|
||||
Color { a: opacity, ..thumb },
|
||||
Color {
|
||||
a: opacity,
|
||||
..thumb
|
||||
},
|
||||
crate::inner_radius(track_radius, padding),
|
||||
));
|
||||
container(knob)
|
||||
@@ -97,7 +116,10 @@ pub fn switch<'a, M: 'a>(enabled: bool, palette: crate::Palette, opacity: f32) -
|
||||
} else {
|
||||
cosmic::iced::alignment::Horizontal::Left
|
||||
})
|
||||
.class(crate::controls::surface(track.scale_alpha(opacity), track_radius))
|
||||
.class(crate::controls::surface(
|
||||
track.scale_alpha(opacity),
|
||||
track_radius,
|
||||
))
|
||||
.into()
|
||||
}
|
||||
|
||||
|
||||
@@ -27,12 +27,16 @@ impl Palette {
|
||||
sidebar: color(&theme.tokens.colors.surface_base),
|
||||
card: color(&theme.tokens.colors.surface_raised),
|
||||
accent: color(&theme.tokens.colors.accent),
|
||||
accent_gradient: theme.tokens.focus_ring.effective_gradient().map(|gradient| {
|
||||
// Iced's zero angle points up; theme angles start left-to-right.
|
||||
Linear::new((gradient.angle.rem_euclid(360.) as f32 + 90.).to_radians())
|
||||
.add_stop(0., color(&gradient.from))
|
||||
.add_stop(1., color(&gradient.to))
|
||||
}),
|
||||
accent_gradient: theme
|
||||
.tokens
|
||||
.focus_ring
|
||||
.effective_gradient()
|
||||
.map(|gradient| {
|
||||
// Iced's zero angle points up; theme angles start left-to-right.
|
||||
Linear::new((gradient.angle.rem_euclid(360.) as f32 + 90.).to_radians())
|
||||
.add_stop(0., color(&gradient.from))
|
||||
.add_stop(1., color(&gradient.to))
|
||||
}),
|
||||
on_accent: color(&theme.tokens.colors.on_accent),
|
||||
text: color(&theme.tokens.colors.text_primary),
|
||||
muted: color(&theme.tokens.colors.text_muted),
|
||||
@@ -63,13 +67,19 @@ impl Palette {
|
||||
};
|
||||
|
||||
for stop in gradient.stops.iter_mut().flatten() {
|
||||
stop.color = mix(self.sidebar, crate::composite(stop.color, self.sidebar), strength);
|
||||
stop.color = mix(
|
||||
self.sidebar,
|
||||
crate::composite(stop.color, self.sidebar),
|
||||
strength,
|
||||
);
|
||||
}
|
||||
|
||||
// Bound every interpolated color, rather than sampling a few points.
|
||||
// Adjust both stops together so one foreground stays readable.
|
||||
let minimum = if self.high_contrast { 7. } else { 4.5 };
|
||||
let shade = if crate::contrast(Color::BLACK, foreground) > crate::contrast(Color::WHITE, foreground) {
|
||||
let shade = if crate::contrast(Color::BLACK, foreground)
|
||||
> crate::contrast(Color::WHITE, foreground)
|
||||
{
|
||||
Color::BLACK
|
||||
} else {
|
||||
Color::WHITE
|
||||
@@ -153,7 +163,11 @@ pub fn parse_color(value: &str) -> Option<Color> {
|
||||
}
|
||||
|
||||
pub fn mix(a: Color, b: Color, t: f32) -> Color {
|
||||
Color::from_rgb(a.r + (b.r - a.r) * t, a.g + (b.g - a.g) * t, a.b + (b.b - a.b) * t)
|
||||
Color::from_rgb(
|
||||
a.r + (b.r - a.r) * t,
|
||||
a.g + (b.g - a.g) * t,
|
||||
a.b + (b.b - a.b) * t,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn surface_shade(base: Color) -> Color {
|
||||
@@ -223,14 +237,23 @@ mod tests {
|
||||
let native = palette.native_theme();
|
||||
for opacity in [1., 0.5] {
|
||||
let class = crate::controls::material_button_style(palette, true, opacity);
|
||||
for style in [native.active(false, true, &class), native.hovered(false, true, &class)] {
|
||||
let Some(Background::Gradient(cosmic::iced::Gradient::Linear(gradient))) = style.background else {
|
||||
for style in [
|
||||
native.active(false, true, &class),
|
||||
native.hovered(false, true, &class),
|
||||
] {
|
||||
let Some(Background::Gradient(cosmic::iced::Gradient::Linear(gradient))) =
|
||||
style.background
|
||||
else {
|
||||
panic!("{} selected control lost its gradient", preset.id);
|
||||
};
|
||||
let from = gradient.stops[0].unwrap().color;
|
||||
let to = gradient.stops[1].unwrap().color;
|
||||
assert_eq!(from.a, to.a);
|
||||
assert!(if opacity == 1. { from.a == 1. } else { from.a < 1. });
|
||||
assert!(if opacity == 1. {
|
||||
from.a == 1.
|
||||
} else {
|
||||
from.a < 1.
|
||||
});
|
||||
for step in 0..=32 {
|
||||
let fill = crate::composite(
|
||||
Color {
|
||||
@@ -239,7 +262,11 @@ mod tests {
|
||||
},
|
||||
palette.sidebar,
|
||||
);
|
||||
assert!(crate::contrast(fill, style.text_color.unwrap()) >= 4.5, "{}", preset.id);
|
||||
assert!(
|
||||
crate::contrast(fill, style.text_color.unwrap()) >= 4.5,
|
||||
"{}",
|
||||
preset.id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,7 +284,9 @@ mod tests {
|
||||
let mut resolved = ferese_config::theme::default_theme();
|
||||
resolved.tokens.focus_ring.gradient.as_mut().unwrap().angle = 0.;
|
||||
let palette = Palette::from_resolved(&resolved);
|
||||
assert!((palette.accent_gradient.unwrap().angle.0 - std::f32::consts::FRAC_PI_2).abs() < 0.001);
|
||||
assert!(
|
||||
(palette.accent_gradient.unwrap().angle.0 - std::f32::consts::FRAC_PI_2).abs() < 0.001
|
||||
);
|
||||
resolved.tokens.focus_ring.style = ferese_theme_model::PaintStyle::Solid;
|
||||
assert!(Palette::from_resolved(&resolved).accent_gradient.is_none());
|
||||
resolved.tokens.focus_ring.style = ferese_theme_model::PaintStyle::Auto;
|
||||
|
||||
@@ -89,7 +89,11 @@ impl<M> Widget<M, Theme, cosmic::Renderer> for Adaptive<'_, M> {
|
||||
}
|
||||
fn children(&self) -> Vec<widget::Tree> {
|
||||
std::iter::once(widget::Tree::new(&self.content))
|
||||
.chain(self.samples.iter().map(|sample| widget::Tree::new(&sample.view)))
|
||||
.chain(
|
||||
self.samples
|
||||
.iter()
|
||||
.map(|sample| widget::Tree::new(&sample.view)),
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
fn diff(&mut self, tree: &mut widget::Tree) {
|
||||
@@ -117,15 +121,22 @@ impl<M> Widget<M, Theme, cosmic::Renderer> for Adaptive<'_, M> {
|
||||
let mut measurements: Vec<Measurement> = Vec::new();
|
||||
// Measure preferred content before imposing the panel's width. Otherwise
|
||||
// a clipped wide control can look as small as its compact alternative.
|
||||
let measurement_limits = layout::Limits::new(Size::ZERO, Size::new(f32::INFINITY, limits.max().height));
|
||||
let measurement_limits =
|
||||
layout::Limits::new(Size::ZERO, Size::new(f32::INFINITY, limits.max().height));
|
||||
for (index, sample) in self.samples.iter_mut().enumerate() {
|
||||
let node = sample
|
||||
.view
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[index + 1], renderer, &measurement_limits);
|
||||
let node = sample.view.as_widget_mut().layout(
|
||||
&mut tree.children[index + 1],
|
||||
renderer,
|
||||
&measurement_limits,
|
||||
);
|
||||
let width = node.size().width;
|
||||
if let Some(measurement) = measurements.iter_mut().find(|measurement| measurement.id == sample.id) {
|
||||
measurement.alternatives.push((sample.representation, width));
|
||||
if let Some(measurement) = measurements
|
||||
.iter_mut()
|
||||
.find(|measurement| measurement.id == sample.id)
|
||||
{
|
||||
measurement
|
||||
.alternatives
|
||||
.push((sample.representation, width));
|
||||
} else {
|
||||
measurements.push(Measurement {
|
||||
id: sample.id.clone(),
|
||||
@@ -162,7 +173,9 @@ impl<M> Widget<M, Theme, cosmic::Renderer> for Adaptive<'_, M> {
|
||||
tree: &widget::Tree,
|
||||
cursor: mouse::Cursor,
|
||||
) -> iced_accessibility::A11yTree {
|
||||
self.content.as_widget().a11y_nodes(layout, &tree.children[0], cursor)
|
||||
self.content
|
||||
.as_widget()
|
||||
.a11y_nodes(layout, &tree.children[0], cursor)
|
||||
}
|
||||
fn update(
|
||||
&mut self,
|
||||
@@ -186,8 +199,10 @@ impl<M> Widget<M, Theme, cosmic::Renderer> for Adaptive<'_, M> {
|
||||
viewport,
|
||||
);
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
if matches!(event, Event::Window(cosmic::iced::window::Event::RedrawRequested(_)))
|
||||
&& state.published.as_ref() != Some(&self.resolution)
|
||||
if matches!(
|
||||
event,
|
||||
Event::Window(cosmic::iced::window::Event::RedrawRequested(_))
|
||||
) && state.published.as_ref() != Some(&self.resolution)
|
||||
{
|
||||
state.published = Some(self.resolution.clone());
|
||||
shell.publish((self.changed)(self.resolution.clone()));
|
||||
@@ -203,9 +218,15 @@ impl<M> Widget<M, Theme, cosmic::Renderer> for Adaptive<'_, M> {
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
self.content
|
||||
.as_widget()
|
||||
.draw(&tree.children[0], renderer, theme, style, layout, cursor, viewport);
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
@@ -215,9 +236,13 @@ impl<M> Widget<M, Theme, cosmic::Renderer> for Adaptive<'_, M> {
|
||||
viewport: &Rectangle,
|
||||
renderer: &cosmic::Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content
|
||||
.as_widget()
|
||||
.mouse_interaction(&tree.children[0], layout, cursor, viewport, renderer)
|
||||
self.content.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
layout,
|
||||
cursor,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
fn operate(
|
||||
&mut self,
|
||||
@@ -238,9 +263,13 @@ impl<M> Widget<M, Theme, cosmic::Renderer> for Adaptive<'_, M> {
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<cosmic::iced::advanced::overlay::Element<'a, M, Theme, cosmic::Renderer>> {
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.overlay(&mut tree.children[0], layout, renderer, viewport, translation)
|
||||
self.content.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,9 +323,10 @@ mod tests {
|
||||
for opacity in [0., 0.5, 1.] {
|
||||
for border in [false, true] {
|
||||
renderer.reset(bounds);
|
||||
let group = cosmic::widget::container(widgets::Space::new().width(40).height(40)).class(
|
||||
cosmic::theme::Container::custom(move |_| group_border(Color::WHITE, 8., border)),
|
||||
);
|
||||
let group = cosmic::widget::container(widgets::Space::new().width(40).height(40))
|
||||
.class(cosmic::theme::Container::custom(move |_| {
|
||||
group_border(Color::WHITE, 8., border)
|
||||
}));
|
||||
let mut view: Element<'_, ()> = cosmic::widget::container(group)
|
||||
.class(cosmic::theme::Container::custom(move |_| {
|
||||
crate::controls::surface_appearance(
|
||||
@@ -309,9 +339,11 @@ mod tests {
|
||||
}))
|
||||
.into();
|
||||
let mut tree = widget::Tree::new(&view);
|
||||
let node =
|
||||
view.as_widget_mut()
|
||||
.layout(&mut tree, &renderer, &layout::Limits::new(Size::ZERO, bounds.size()));
|
||||
let node = view.as_widget_mut().layout(
|
||||
&mut tree,
|
||||
&renderer,
|
||||
&layout::Limits::new(Size::ZERO, bounds.size()),
|
||||
);
|
||||
view.as_widget().draw(
|
||||
&tree,
|
||||
&mut renderer,
|
||||
@@ -325,7 +357,8 @@ mod tests {
|
||||
mouse::Cursor::Unavailable,
|
||||
&bounds,
|
||||
);
|
||||
let pixels = Headless::screenshot(&mut renderer, Size::new(40, 40), 1., Color::TRANSPARENT);
|
||||
let pixels =
|
||||
Headless::screenshot(&mut renderer, Size::new(40, 40), 1., Color::TRANSPARENT);
|
||||
let alpha = pixels[(20 * 40 + 20) * 4 + 3];
|
||||
assert!(
|
||||
i16::from(alpha).abs_diff((opacity * 255.).round() as i16) <= 1,
|
||||
@@ -370,10 +403,12 @@ mod tests {
|
||||
id: id.clone(),
|
||||
representation,
|
||||
minimum: None,
|
||||
view: button::custom(text(label).wrapping(cosmic::iced::widget::text::Wrapping::None))
|
||||
.height(28)
|
||||
.on_press(Probe::MeasurementClick)
|
||||
.into(),
|
||||
view: button::custom(
|
||||
text(label).wrapping(cosmic::iced::widget::text::Wrapping::None),
|
||||
)
|
||||
.height(28)
|
||||
.on_press(Probe::MeasurementClick)
|
||||
.into(),
|
||||
})
|
||||
.chain(std::iter::once(Sample {
|
||||
id: ItemId("_overflow".into()),
|
||||
@@ -413,14 +448,23 @@ mod tests {
|
||||
let limits = layout::Limits::new(Size::ZERO, viewport.size());
|
||||
let node = view.as_widget_mut().layout(tree, &renderer, &limits);
|
||||
let built = builds.get();
|
||||
assert_eq!(built, 2, "initial measurement must replace the unallocated content");
|
||||
assert_eq!(
|
||||
built, 2,
|
||||
"initial measurement must replace the unallocated content"
|
||||
);
|
||||
view.as_widget_mut().layout(tree, &renderer, &limits);
|
||||
assert_eq!(builds.get(), built, "unchanged allocation must reuse its controls");
|
||||
assert_eq!(
|
||||
builds.get(),
|
||||
built,
|
||||
"unchanged allocation must reuse its controls"
|
||||
);
|
||||
let mut messages = Vec::new();
|
||||
for _ in 0..4 {
|
||||
view.as_widget_mut().update(
|
||||
tree,
|
||||
&Event::Window(cosmic::iced::window::Event::RedrawRequested(std::time::Instant::now())),
|
||||
&Event::Window(cosmic::iced::window::Event::RedrawRequested(
|
||||
std::time::Instant::now(),
|
||||
)),
|
||||
Layout::new(&node),
|
||||
mouse::Cursor::Unavailable,
|
||||
&renderer,
|
||||
|
||||
@@ -86,7 +86,12 @@ impl widget::Operation for Collect<'_> {
|
||||
}
|
||||
|
||||
impl<M> Surface<'_, M> {
|
||||
fn measure(&mut self, tree: &mut widget::Tree, layout: Layout<'_>, renderer: &cosmic::Renderer) {
|
||||
fn measure(
|
||||
&mut self,
|
||||
tree: &mut widget::Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &cosmic::Renderer,
|
||||
) {
|
||||
let mut collector = Collect {
|
||||
targets: &self.options.groups,
|
||||
origin: layout.bounds().position(),
|
||||
@@ -94,17 +99,20 @@ impl<M> Surface<'_, M> {
|
||||
bounds_id: self.options.bounds.as_ref(),
|
||||
bounds: None,
|
||||
};
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.operate(&mut tree.children[0], layout, renderer, &mut collector);
|
||||
self.content.as_widget_mut().operate(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
&mut collector,
|
||||
);
|
||||
let size = layout.bounds().size();
|
||||
let scene = PixelGrid::new(self.options.scale)
|
||||
.and_then(|grid| {
|
||||
ferese_panel::scene::derive(
|
||||
self.options.parameters,
|
||||
collector
|
||||
.bounds
|
||||
.unwrap_or_else(|| ferese_layout::Rect::new(0., 0., size.width.into(), size.height.into())),
|
||||
collector.bounds.unwrap_or_else(|| {
|
||||
ferese_layout::Rect::new(0., 0., size.width.into(), size.height.into())
|
||||
}),
|
||||
&collector.groups,
|
||||
grid,
|
||||
)
|
||||
@@ -162,7 +170,9 @@ impl<M> Widget<M, Theme, cosmic::Renderer> for Surface<'_, M> {
|
||||
tree: &widget::Tree,
|
||||
cursor: mouse::Cursor,
|
||||
) -> iced_accessibility::A11yTree {
|
||||
self.content.as_widget().a11y_nodes(layout, &tree.children[0], cursor)
|
||||
self.content
|
||||
.as_widget()
|
||||
.a11y_nodes(layout, &tree.children[0], cursor)
|
||||
}
|
||||
fn update(
|
||||
&mut self,
|
||||
@@ -185,7 +195,10 @@ impl<M> Widget<M, Theme, cosmic::Renderer> for Surface<'_, M> {
|
||||
shell,
|
||||
viewport,
|
||||
);
|
||||
if matches!(event, Event::Window(cosmic::iced::window::Event::RedrawRequested(_))) && !shell.is_layout_invalid()
|
||||
if matches!(
|
||||
event,
|
||||
Event::Window(cosmic::iced::window::Event::RedrawRequested(_))
|
||||
) && !shell.is_layout_invalid()
|
||||
{
|
||||
self.measure(tree, layout, renderer);
|
||||
(self.present)(&tree.state.downcast_ref::<State>().scene);
|
||||
@@ -250,7 +263,9 @@ impl<M> Widget<M, Theme, cosmic::Renderer> for Surface<'_, M> {
|
||||
};
|
||||
frame.stroke(
|
||||
&Path::line(Point::new(0., y), Point::new(bounds.width, y)),
|
||||
Stroke::default().with_color(self.options.border).with_width(width),
|
||||
Stroke::default()
|
||||
.with_color(self.options.border)
|
||||
.with_width(width),
|
||||
);
|
||||
} else if self.options.parameters.style == ferese_panel::PanelStyle::Notch
|
||||
|| (self.options.parameters.style == ferese_panel::PanelStyle::Morph
|
||||
@@ -264,30 +279,45 @@ impl<M> Widget<M, Theme, cosmic::Renderer> for Surface<'_, M> {
|
||||
let border = Path::new(|builder| {
|
||||
for contour in contours {
|
||||
if let Some((first, rest)) = contour.split_first() {
|
||||
builder.move_to(Point::new(first[0] as f32, first[1] as f32));
|
||||
builder.move_to(Point::new(
|
||||
first[0] as f32,
|
||||
first[1] as f32,
|
||||
));
|
||||
for point in rest {
|
||||
builder.line_to(Point::new(point[0] as f32, point[1] as f32));
|
||||
builder.line_to(Point::new(
|
||||
point[0] as f32,
|
||||
point[1] as f32,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
frame.stroke(
|
||||
&border,
|
||||
Stroke::default().with_color(self.options.border).with_width(width),
|
||||
Stroke::default()
|
||||
.with_color(self.options.border)
|
||||
.with_width(width),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
frame.stroke(
|
||||
&path,
|
||||
Stroke::default().with_color(self.options.border).with_width(width),
|
||||
Stroke::default()
|
||||
.with_color(self.options.border)
|
||||
.with_width(width),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
for [x1, y1, x2, y2] in &state.scene.dividers {
|
||||
frame.stroke(
|
||||
&Path::line(Point::new(*x1 as f32, *y1 as f32), Point::new(*x2 as f32, *y2 as f32)),
|
||||
Stroke::default().with_color(self.options.divider).with_width(hairline),
|
||||
&Path::line(
|
||||
Point::new(*x1 as f32, *y1 as f32),
|
||||
Point::new(*x2 as f32, *y2 as f32),
|
||||
),
|
||||
Stroke::default()
|
||||
.with_color(self.options.divider)
|
||||
.with_width(hairline),
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -300,9 +330,15 @@ impl<M> Widget<M, Theme, cosmic::Renderer> for Surface<'_, M> {
|
||||
// Keep content after cached vector geometry in the renderer's layer order.
|
||||
// Parent-layer text can otherwise be batched underneath the background.
|
||||
renderer.with_layer(*viewport, |renderer| {
|
||||
self.content
|
||||
.as_widget()
|
||||
.draw(&tree.children[0], renderer, theme, style, layout, cursor, viewport);
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor,
|
||||
viewport,
|
||||
);
|
||||
});
|
||||
}
|
||||
fn mouse_interaction(
|
||||
@@ -313,9 +349,13 @@ impl<M> Widget<M, Theme, cosmic::Renderer> for Surface<'_, M> {
|
||||
viewport: &Rectangle,
|
||||
renderer: &cosmic::Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content
|
||||
.as_widget()
|
||||
.mouse_interaction(&tree.children[0], layout, cursor, viewport, renderer)
|
||||
self.content.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
layout,
|
||||
cursor,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
fn operate(
|
||||
&mut self,
|
||||
@@ -336,9 +376,13 @@ impl<M> Widget<M, Theme, cosmic::Renderer> for Surface<'_, M> {
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<cosmic::iced::advanced::overlay::Element<'a, M, Theme, cosmic::Renderer>> {
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.overlay(&mut tree.children[0], layout, renderer, viewport, translation)
|
||||
self.content.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,11 +397,9 @@ mod tests {
|
||||
fn shared_surface_publishes_layout_changes_and_leaves_native_fill_to_the_compositor() {
|
||||
use cosmic::iced::advanced::clipboard;
|
||||
use std::cell::RefCell;
|
||||
let mut renderer = cosmic::iced::futures::executor::block_on(<cosmic::Renderer as Headless>::new(
|
||||
Font::default(),
|
||||
Pixels(14.),
|
||||
Some("tiny-skia"),
|
||||
))
|
||||
let mut renderer = cosmic::iced::futures::executor::block_on(
|
||||
<cosmic::Renderer as Headless>::new(Font::default(), Pixels(14.), Some("tiny-skia")),
|
||||
)
|
||||
.unwrap();
|
||||
let ids = [widget::Id::unique(), widget::Id::unique()];
|
||||
let mut tree = None;
|
||||
@@ -379,7 +421,10 @@ mod tests {
|
||||
.spacing(20);
|
||||
let published = RefCell::new(Vec::new());
|
||||
let mut view = frame(
|
||||
cosmic::widget::container(content).width(200).height(32).into(),
|
||||
cosmic::widget::container(content)
|
||||
.width(200)
|
||||
.height(32)
|
||||
.into(),
|
||||
Options {
|
||||
parameters: Parameters {
|
||||
style,
|
||||
@@ -403,14 +448,18 @@ mod tests {
|
||||
);
|
||||
let tree = tree.get_or_insert_with(|| widget::Tree::new(&view));
|
||||
tree.diff(&mut view);
|
||||
let node = view
|
||||
.as_widget_mut()
|
||||
.layout(tree, &renderer, &layout::Limits::new(Size::ZERO, viewport.size()));
|
||||
let node = view.as_widget_mut().layout(
|
||||
tree,
|
||||
&renderer,
|
||||
&layout::Limits::new(Size::ZERO, viewport.size()),
|
||||
);
|
||||
let mut messages = Vec::new();
|
||||
for _ in 0..3 {
|
||||
view.as_widget_mut().update(
|
||||
tree,
|
||||
&Event::Window(cosmic::iced::window::Event::RedrawRequested(std::time::Instant::now())),
|
||||
&Event::Window(cosmic::iced::window::Event::RedrawRequested(
|
||||
std::time::Instant::now(),
|
||||
)),
|
||||
Layout::new(&node),
|
||||
mouse::Cursor::Unavailable,
|
||||
&renderer,
|
||||
@@ -425,9 +474,15 @@ mod tests {
|
||||
usize::from(!(style == Style::Floating && width == 40. && native))
|
||||
);
|
||||
let scene = published.borrow()[0].clone();
|
||||
assert_eq!(scene.input.len(), if style == Style::Floating { 2 } else { 1 });
|
||||
assert_eq!(
|
||||
scene.input.len(),
|
||||
if style == Style::Floating { 2 } else { 1 }
|
||||
);
|
||||
if style == Style::Floating {
|
||||
assert!(scene.distance([width as f64 + 10., 16.]) > 0., "the gap stays empty");
|
||||
assert!(
|
||||
scene.distance([width as f64 + 10., 16.]) > 0.,
|
||||
"the gap stays empty"
|
||||
);
|
||||
}
|
||||
renderer.reset(viewport);
|
||||
view.as_widget().draw(
|
||||
@@ -450,11 +505,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn border_coverage_tracks_physical_scale_and_high_contrast() {
|
||||
let mut renderer = cosmic::iced::futures::executor::block_on(<cosmic::Renderer as Headless>::new(
|
||||
Font::default(),
|
||||
Pixels(14.),
|
||||
Some("tiny-skia"),
|
||||
))
|
||||
let mut renderer = cosmic::iced::futures::executor::block_on(
|
||||
<cosmic::Renderer as Headless>::new(Font::default(), Pixels(14.), Some("tiny-skia")),
|
||||
)
|
||||
.unwrap();
|
||||
for scale in [0.75, 1., 1.25, 1.5, 2.] {
|
||||
for high_contrast in [false, true] {
|
||||
@@ -480,9 +533,11 @@ mod tests {
|
||||
);
|
||||
let mut tree = widget::Tree::new(&view);
|
||||
let bounds = Rectangle::with_size(Size::new(64., 32.));
|
||||
let node =
|
||||
view.as_widget_mut()
|
||||
.layout(&mut tree, &renderer, &layout::Limits::new(Size::ZERO, bounds.size()));
|
||||
let node = view.as_widget_mut().layout(
|
||||
&mut tree,
|
||||
&renderer,
|
||||
&layout::Limits::new(Size::ZERO, bounds.size()),
|
||||
);
|
||||
renderer.reset(bounds);
|
||||
view.as_widget().draw(
|
||||
&tree,
|
||||
@@ -500,7 +555,10 @@ mod tests {
|
||||
let size = Size::new((64. * scale) as u32, (32. * scale) as u32);
|
||||
let pixels = renderer.screenshot(size, scale as f32, Color::TRANSPARENT);
|
||||
let coverage: f64 = (0..size.height)
|
||||
.map(|y| f64::from(pixels[((y * size.width + size.width / 2) * 4 + 3) as usize]) / 255.)
|
||||
.map(|y| {
|
||||
f64::from(pixels[((y * size.width + size.width / 2) * 4 + 3) as usize])
|
||||
/ 255.
|
||||
})
|
||||
.sum();
|
||||
let expected = if high_contrast { scale.max(1.) } else { 1. };
|
||||
assert!(
|
||||
@@ -513,11 +571,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn the_surface_background_keeps_parent_layer_text_visible() {
|
||||
let mut renderer = cosmic::iced::futures::executor::block_on(<cosmic::Renderer as Headless>::new(
|
||||
Font::default(),
|
||||
Pixels(14.),
|
||||
Some("tiny-skia"),
|
||||
))
|
||||
let mut renderer = cosmic::iced::futures::executor::block_on(
|
||||
<cosmic::Renderer as Headless>::new(Font::default(), Pixels(14.), Some("tiny-skia")),
|
||||
)
|
||||
.unwrap();
|
||||
for scale in [1., 1.25, 1.5] {
|
||||
let content = cosmic::widget::container(
|
||||
@@ -547,9 +603,11 @@ mod tests {
|
||||
);
|
||||
let mut tree = widget::Tree::new(&view);
|
||||
let viewport = Rectangle::with_size(Size::new(150., 34.));
|
||||
let node =
|
||||
view.as_widget_mut()
|
||||
.layout(&mut tree, &renderer, &layout::Limits::new(Size::ZERO, viewport.size()));
|
||||
let node = view.as_widget_mut().layout(
|
||||
&mut tree,
|
||||
&renderer,
|
||||
&layout::Limits::new(Size::ZERO, viewport.size()),
|
||||
);
|
||||
renderer.reset(viewport);
|
||||
view.as_widget().draw(
|
||||
&tree,
|
||||
@@ -569,7 +627,10 @@ mod tests {
|
||||
scale as f32,
|
||||
Color::BLACK,
|
||||
);
|
||||
let ink = pixels.chunks_exact(4).filter(|pixel| pixel[0] > 128).count();
|
||||
let ink = pixels
|
||||
.chunks_exact(4)
|
||||
.filter(|pixel| pixel[0] > 128)
|
||||
.count();
|
||||
assert!(
|
||||
ink > 25,
|
||||
"clock text was covered by the surface background at scale {scale}"
|
||||
@@ -605,9 +666,11 @@ mod tests {
|
||||
let b = widget::Id::unique();
|
||||
let content = cosmic::widget::container(cosmic::widget::row![
|
||||
cosmic::widget::Space::new().width(4),
|
||||
cosmic::widget::container(cosmic::widget::Space::new().width(20).height(16)).id(a.clone()),
|
||||
cosmic::widget::container(cosmic::widget::Space::new().width(20).height(16))
|
||||
.id(a.clone()),
|
||||
cosmic::widget::Space::new().width(4),
|
||||
cosmic::widget::container(cosmic::widget::Space::new().width(20).height(16)).id(b.clone()),
|
||||
cosmic::widget::container(cosmic::widget::Space::new().width(20).height(16))
|
||||
.id(b.clone()),
|
||||
])
|
||||
.width(96)
|
||||
.height(64)
|
||||
@@ -640,9 +703,11 @@ mod tests {
|
||||
);
|
||||
let mut tree = widget::Tree::new(&view);
|
||||
let bounds = Rectangle::new(Point::ORIGIN, Size::new(96., 64.));
|
||||
let node =
|
||||
view.as_widget_mut()
|
||||
.layout(&mut tree, &renderer, &layout::Limits::new(Size::ZERO, bounds.size()));
|
||||
let node = view.as_widget_mut().layout(
|
||||
&mut tree,
|
||||
&renderer,
|
||||
&layout::Limits::new(Size::ZERO, bounds.size()),
|
||||
);
|
||||
renderer.reset(bounds);
|
||||
view.as_widget().draw(
|
||||
&tree,
|
||||
|
||||
@@ -55,7 +55,11 @@ pub fn indicator<'a, M: 'a>(
|
||||
) -> Element<'a, M> {
|
||||
let progress = progress.clamp(0., 1.);
|
||||
let occupied = workspace.window_count > 0;
|
||||
let neutral = if occupied { palette.text } else { palette.muted };
|
||||
let neutral = if occupied {
|
||||
palette.text
|
||||
} else {
|
||||
palette.muted
|
||||
};
|
||||
let foreground = crate::mix(neutral, palette.accent, progress);
|
||||
let content: Element<'a, M> = match style {
|
||||
WorkspaceStyle::Numbers | WorkspaceStyle::Tabs => {
|
||||
@@ -66,7 +70,11 @@ pub fn indicator<'a, M: 'a>(
|
||||
};
|
||||
|
||||
crate::text(label, font)
|
||||
.size(if style == WorkspaceStyle::Tabs { 11 } else { 12 })
|
||||
.size(if style == WorkspaceStyle::Tabs {
|
||||
11
|
||||
} else {
|
||||
12
|
||||
})
|
||||
.width(width(style) - 8.)
|
||||
.align_x(cosmic::iced::alignment::Horizontal::Center)
|
||||
.wrapping(cosmic::iced::widget::text::Wrapping::None)
|
||||
@@ -81,7 +89,11 @@ pub fn indicator<'a, M: 'a>(
|
||||
.height(6)
|
||||
.class(theme::Container::custom(move |_| {
|
||||
let mut paint = crate::controls::surface_appearance(
|
||||
foreground.scale_alpha(if occupied { 0.65 + 0.35 * progress } else { progress }),
|
||||
foreground.scale_alpha(if occupied {
|
||||
0.65 + 0.35 * progress
|
||||
} else {
|
||||
progress
|
||||
}),
|
||||
3.,
|
||||
);
|
||||
paint.border.width = 1.;
|
||||
@@ -97,15 +109,25 @@ pub fn indicator<'a, M: 'a>(
|
||||
};
|
||||
let svg = format!(
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 26 22\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linejoin=\"round\">{back}<rect x=\"2\" y=\"7\" width=\"16\" height=\"12\" rx=\"2.5\"/>{}</svg>",
|
||||
if occupied { "<path d=\"M2 11h16\"/>" } else { "" }
|
||||
if occupied {
|
||||
"<path d=\"M2 11h16\"/>"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
);
|
||||
|
||||
cosmic::widget::icon::from_svg_bytes(crate::icons::tint_svg(svg.as_bytes(), foreground, foreground))
|
||||
.symbolic(false)
|
||||
.icon()
|
||||
.size(20)
|
||||
.class(theme::Svg::custom(|_| cosmic::iced::widget::svg::Style { color: None }))
|
||||
.into()
|
||||
cosmic::widget::icon::from_svg_bytes(crate::icons::tint_svg(
|
||||
svg.as_bytes(),
|
||||
foreground,
|
||||
foreground,
|
||||
))
|
||||
.symbolic(false)
|
||||
.icon()
|
||||
.size(20)
|
||||
.class(theme::Svg::custom(|_| cosmic::iced::widget::svg::Style {
|
||||
color: None,
|
||||
}))
|
||||
.into()
|
||||
}
|
||||
WorkspaceStyle::AppIcons => {
|
||||
let mut icons = row::with_capacity(2).spacing(3).align_y(Alignment::Center);
|
||||
@@ -128,7 +150,10 @@ pub fn indicator<'a, M: 'a>(
|
||||
let marker = container(Space::new())
|
||||
.width(10)
|
||||
.height(2)
|
||||
.class(crate::controls::surface(foreground.scale_alpha(progress), 1.));
|
||||
.class(crate::controls::surface(
|
||||
foreground.scale_alpha(progress),
|
||||
1.,
|
||||
));
|
||||
cosmic::iced::widget::stack![
|
||||
face,
|
||||
container(marker)
|
||||
@@ -150,7 +175,8 @@ pub fn indicator<'a, M: 'a>(
|
||||
Color::TRANSPARENT
|
||||
} else if progress > 0. {
|
||||
palette.accent.scale_alpha(0.16 * progress)
|
||||
} else if style == WorkspaceStyle::Tabs || occupied && style == WorkspaceStyle::Numbers {
|
||||
} else if style == WorkspaceStyle::Tabs || occupied && style == WorkspaceStyle::Numbers
|
||||
{
|
||||
palette.text.scale_alpha(0.045)
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
@@ -177,7 +203,8 @@ pub fn sample<'a, M: 'a>(
|
||||
|
||||
for (index, count, active) in [(1, 2, true), (2, 1, false), (3, 0, false)] {
|
||||
let apps = if style == WorkspaceStyle::AppIcons && count > 0 {
|
||||
let mut apps = vec![crate::icons::tinted(crate::icons::SETTINGS, 16, palette.text).into()];
|
||||
let mut apps =
|
||||
vec![crate::icons::tinted(crate::icons::SETTINGS, 16, palette.text).into()];
|
||||
|
||||
if count > 1 {
|
||||
apps.push(crate::icons::tinted(crate::icons::FERESE, 16, palette.accent).into());
|
||||
@@ -210,7 +237,9 @@ pub fn sample<'a, M: 'a>(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use cosmic::iced::advanced::{Layout, Renderer, layout, renderer, renderer::Headless, widget::Tree};
|
||||
use cosmic::iced::advanced::{
|
||||
Layout, Renderer, layout, renderer, renderer::Headless, widget::Tree,
|
||||
};
|
||||
use cosmic::iced::{Font, Pixels, Rectangle, Size, mouse};
|
||||
|
||||
#[test]
|
||||
@@ -241,7 +270,9 @@ mod tests {
|
||||
(3, 1., "Design and development"),
|
||||
] {
|
||||
let apps = (0..count.min(2))
|
||||
.map(|_| crate::icons::tinted(crate::icons::SETTINGS, 16, palette.text).into())
|
||||
.map(|_| {
|
||||
crate::icons::tinted(crate::icons::SETTINGS, 16, palette.text).into()
|
||||
})
|
||||
.collect();
|
||||
let mut view: Element<'_, ()> = indicator(
|
||||
style,
|
||||
@@ -314,7 +345,10 @@ mod tests {
|
||||
active: true,
|
||||
visible_elsewhere: false,
|
||||
};
|
||||
assert_eq!(tooltip_label(&workspace), "Workspace 2 · Design · 1 window · Active");
|
||||
assert_eq!(
|
||||
tooltip_label(&workspace),
|
||||
"Workspace 2 · Design · 1 window · Active"
|
||||
);
|
||||
workspace.name = "2".into();
|
||||
workspace.window_count = 0;
|
||||
workspace.active = false;
|
||||
|
||||
Reference in new issue
Block a user