Format Rust sources at 100 columns

This commit is contained in:
Samuel Onoja committed 2026-10-22 01:28:03 +01:00
1 parent 4661f595f8
commit 610478f8c7
336 files changed
+23497 -7892

No files matched your search

+5 -1
View File
@@ -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 {
+49 -22
View File
@@ -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));
+41 -9
View File
@@ -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")
+36 -7
View File
@@ -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,
+98 -36
View File
@@ -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() {
+29 -9
View File
@@ -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]
+30 -8
View File
@@ -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()
}
+43 -14
View File
@@ -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;
+78 -34
View File
@@ -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,
+125 -60
View File
@@ -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,
+50 -16
View File
@@ -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;