Add panel-wide styles and the Morph hub (#26)
Panel appearance now applies to the whole panel, with seven styles: Floating, Capsules, Notch, Edge to edge, Fused islands, Adaptive and Morph. - Share composition, geometry, and motion through the renderer-independent `ferese-panel` crate, including native materials, input regions, and Settings previews. - Add the Morph hub with media, calendar, notifications, controls, workspace previews, hover feedback, and attached mode. Keep recording and low-battery status visible and respect window clearance. - Cache workspace preview geometry, draw thumbnails directly, centralize Morph transitions, and keep rendering settings local to each shell instance. - Put Record Screen in the Morph header and show the display control only when an external monitor is connected. Update panel documentation, the desktop screenshot, and native check style names. Local validation included 660 compositor tests, 191 shell tests, isolated preview-cache checks, GPU Morph captures at 1×/1.25×/1.5×, formatting, and Clippy with warnings denied. The final header change passed all 23 Morph tests. Release workflow jobs are skipped for this PR.
This commit is contained in:
1 parent
42c3b290dd
commit
e22f0ebef9
131 files changed
+12562
-1634
No files matched your search
+178
-180
@@ -1,11 +1,13 @@
|
||||
#[cfg(test)]
|
||||
use super::Background;
|
||||
use super::{
|
||||
Background, BarMetrics, Color, Element, FereseShell, Length, Message, ShellSnapshot, ShellTheme, alignment,
|
||||
bar_icon, button, color, container, control, motion, row, status_ui, text, theme, window,
|
||||
BarMetrics, Color, Element, FereseShell, Length, Message, ShellSnapshot, ShellTheme, alignment, bar_icon, button,
|
||||
color, container, control, motion, row, status_ui, theme, window,
|
||||
};
|
||||
use crate::panel::{Availability, Item, ItemKind, Panel, Zone};
|
||||
#[cfg(test)]
|
||||
use ferese_config::PanelPreset;
|
||||
use ferese_config::panel::{GroupSurface, PanelSurface};
|
||||
use ferese_panel::PanelStyle;
|
||||
use ferese_panel::{GroupSurface, PanelSurface};
|
||||
|
||||
use ferese_theme::panel as adaptive;
|
||||
pub(super) mod presentation;
|
||||
@@ -37,16 +39,17 @@ impl FereseShell {
|
||||
|
||||
pub(super) fn view_layer(&self, id: window::Id) -> Element<'_, cosmic::Action<Message>> {
|
||||
if self.bar_hidden(id) {
|
||||
return container(text("")).width(Length::Fill).height(Length::Fill).into();
|
||||
return container(self.render.text(""))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into();
|
||||
}
|
||||
let panel = self.panel_for_bar(id);
|
||||
if panel.style == ferese_panel::PanelStyle::Morph {
|
||||
return self.view_hub(id);
|
||||
}
|
||||
let panel = &self.config.panels[0];
|
||||
let mut shell_theme = self.config.theme.for_bar();
|
||||
shell_theme.bar_background[3] = (shell_theme.panel_opacity(panel.background_opacity) * 255.).round() as u8;
|
||||
let corners = panel
|
||||
.resolved_radius(shell_theme.bar_radius)
|
||||
.at_edge(panel.edge, panel.geometry.edge_margin == 0);
|
||||
let mode = panel.surface;
|
||||
let islands = mode == PanelSurface::None;
|
||||
let output = self.outputs.iter().find(|output| output.bar == id);
|
||||
let effects = output.and_then(|output| output.effects.as_ref());
|
||||
let compositor_material = effects.is_some();
|
||||
@@ -74,7 +77,7 @@ impl FereseShell {
|
||||
id,
|
||||
item,
|
||||
None,
|
||||
panel.resolved_surface(group) == GroupSurface::Island,
|
||||
panel.group_surface() == GroupSurface::Island,
|
||||
representation,
|
||||
)
|
||||
.0;
|
||||
@@ -103,25 +106,34 @@ impl FereseShell {
|
||||
id,
|
||||
&overflow_group.items[0],
|
||||
None,
|
||||
panel.resolved_surface(&overflow_group) == GroupSurface::Island,
|
||||
panel.group_surface() == GroupSurface::Island,
|
||||
Representation::Icon,
|
||||
)
|
||||
.0,
|
||||
});
|
||||
// Measure the control itself; reserve group decoration separately.
|
||||
let overflow_width = 2.0 * f32::from(overflow_group.padding[1])
|
||||
+ if panel.resolved_surface(&overflow_group) == GroupSurface::Island {
|
||||
+ if panel.group_surface() == GroupSurface::Island {
|
||||
2.0 * overflow_group.island_padding
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let materials = [&panel.start, &panel.center, &panel.end]
|
||||
.into_iter()
|
||||
.flat_map(|zone| &zone.groups)
|
||||
.chain(std::iter::once(&overflow_group))
|
||||
.filter(|group| islands && panel.resolved_surface(group) != GroupSurface::None)
|
||||
.map(|group| (group_material_id(panel, group), 1.))
|
||||
.collect();
|
||||
let groups = [
|
||||
(ferese_panel::shape::ZonePosition::Start, &panel.start),
|
||||
(ferese_panel::shape::ZonePosition::Center, &panel.center),
|
||||
(ferese_panel::shape::ZonePosition::End, &panel.end),
|
||||
]
|
||||
.into_iter()
|
||||
.flat_map(|(position, zone)| {
|
||||
zone.groups
|
||||
.iter()
|
||||
.map(move |group| (group_material_id(panel, group), position))
|
||||
})
|
||||
.chain(std::iter::once((
|
||||
group_material_id(panel, &overflow_group),
|
||||
ferese_panel::shape::ZonePosition::End,
|
||||
)))
|
||||
.collect();
|
||||
let content = adaptive::frame(
|
||||
std::borrow::Cow::Borrowed(panel),
|
||||
samples,
|
||||
@@ -183,7 +195,7 @@ impl FereseShell {
|
||||
.any(|item| matches!(resolution.items.get(&item.id), Some(Placement::Visible { width, .. }) if *width > 0.0));
|
||||
if end_has_items {
|
||||
row![trigger, end]
|
||||
.spacing(panel.end.spacing)
|
||||
.spacing(panel.zone_spacing(&panel.end))
|
||||
.align_y(cosmic::iced::Alignment::Center)
|
||||
.into()
|
||||
} else {
|
||||
@@ -204,18 +216,46 @@ impl FereseShell {
|
||||
let content = container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.padding([0, panel.geometry.inner_padding.round() as u16])
|
||||
.class(theme::Container::custom(move |_| {
|
||||
let mut style = bar_style(shell_theme, compositor_material, islands);
|
||||
style.border.radius = corners.0.into();
|
||||
style
|
||||
}))
|
||||
.padding([0, panel.resolved_geometry().inner_padding.round() as u16])
|
||||
.into();
|
||||
presentation::frame(
|
||||
let content = ferese_theme::panel_surface::frame(
|
||||
content,
|
||||
ferese_theme::panel_surface::Options {
|
||||
parameters: panel.shape_parameters(),
|
||||
bounds: None,
|
||||
groups,
|
||||
scale: output.map_or(1., |entry| entry.scale),
|
||||
high_contrast: shell_theme.high_contrast,
|
||||
background: color(shell_theme.bar_background),
|
||||
border: if panel.border {
|
||||
color(shell_theme.border)
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
},
|
||||
divider: color(shell_theme.border),
|
||||
native: compositor_material,
|
||||
},
|
||||
move |scene| {
|
||||
if let Some(effects) = effects
|
||||
&& let Err(error) = effects.set_panel_presentation(scene)
|
||||
{
|
||||
eprintln!("ferese-shell: could not update panel material: {error}");
|
||||
}
|
||||
},
|
||||
Some(Box::new(move |scene| {
|
||||
cosmic::Action::App(Message::PanelSurfaceChanged(id, Box::new(scene.clone())))
|
||||
})),
|
||||
);
|
||||
self.with_workspace_hover(id, content)
|
||||
}
|
||||
|
||||
pub(super) fn with_workspace_hover<'a>(
|
||||
&'a self,
|
||||
id: window::Id,
|
||||
content: Element<'a, cosmic::Action<Message>>,
|
||||
) -> Element<'a, cosmic::Action<Message>> {
|
||||
presentation::hover(
|
||||
content,
|
||||
mode,
|
||||
corners.max(),
|
||||
materials,
|
||||
self.workspace_ui
|
||||
.hover_target(id)
|
||||
.map(|(workspace, target)| presentation::HoverTarget {
|
||||
@@ -224,19 +264,6 @@ impl FereseShell {
|
||||
cosmic::Action::App(Message::HoverWorkspace(id, workspace, target.clone(), false))
|
||||
}),
|
||||
}),
|
||||
move |regions, opacities| {
|
||||
if let Some(effects) = effects
|
||||
&& let Err(error) = effects.set_presentation(
|
||||
regions,
|
||||
1.,
|
||||
opacities.iter().copied(),
|
||||
super::ferese_surface_effects_v1::Role::Panel,
|
||||
)
|
||||
{
|
||||
eprintln!("ferese-shell: could not update bar material: {error}");
|
||||
}
|
||||
},
|
||||
move |regions| cosmic::Action::App(Message::BarRegionsChanged(id, regions)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -249,7 +276,7 @@ impl FereseShell {
|
||||
representation: Representation,
|
||||
) -> (Element<'_, cosmic::Action<Message>>, bool) {
|
||||
let shell_theme = self.config.theme.for_bar();
|
||||
let bar = BarMetrics::from(self.config.panels[0].geometry);
|
||||
let bar = BarMetrics::from(self.panel_for_bar(id).resolved_geometry());
|
||||
let control_height = if islands {
|
||||
bar.group_item_height
|
||||
} else {
|
||||
@@ -264,6 +291,7 @@ impl FereseShell {
|
||||
});
|
||||
let element = match item.kind {
|
||||
ItemKind::Overview => motion::button(
|
||||
self.render,
|
||||
button::custom(overview_control(
|
||||
BarMetrics { control_height, ..bar },
|
||||
color(shell_theme.accent),
|
||||
@@ -276,7 +304,7 @@ impl FereseShell {
|
||||
1.0,
|
||||
),
|
||||
ItemKind::Workspaces { style } => {
|
||||
let workspaces = self.view_workspace_item(id, style, &item.id.0);
|
||||
let workspaces = self.view_workspace_item(id, style, &item.id.0, 1.);
|
||||
if let Some(width) = width {
|
||||
cosmic::iced::widget::scrollable(workspaces)
|
||||
.direction(cosmic::iced::widget::scrollable::Direction::Horizontal(
|
||||
@@ -296,7 +324,9 @@ impl FereseShell {
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let element: Element<'_, cosmic::Action<Message>> = text(title)
|
||||
let element: Element<'_, cosmic::Action<Message>> = self
|
||||
.render
|
||||
.text(title)
|
||||
.size(bar.text_size)
|
||||
.width(width.map_or(Length::Shrink, Length::Fixed))
|
||||
.height(control_height)
|
||||
@@ -332,19 +362,22 @@ impl FereseShell {
|
||||
.align_y(cosmic::iced::Alignment::Center);
|
||||
if representation == Representation::Wide && !date.is_empty() {
|
||||
content = content.push(
|
||||
text(date)
|
||||
self.render
|
||||
.text(date)
|
||||
.size(12)
|
||||
.wrapping(cosmic::iced::widget::text::Wrapping::None)
|
||||
.class(theme::Text::Color(foreground)),
|
||||
);
|
||||
}
|
||||
let content = content.push(
|
||||
text(time)
|
||||
self.render
|
||||
.text(time)
|
||||
.size(bar.text_size)
|
||||
.wrapping(cosmic::iced::widget::text::Wrapping::None)
|
||||
.class(theme::Text::Color(foreground)),
|
||||
);
|
||||
motion::button(
|
||||
self.render,
|
||||
button::custom(container(content).center_y(bar.group_item_height))
|
||||
.padding([0, 4])
|
||||
.height(bar.group_item_height)
|
||||
@@ -366,6 +399,7 @@ impl FereseShell {
|
||||
)
|
||||
}
|
||||
ItemKind::DisplayMode => motion::button(
|
||||
self.render,
|
||||
button::custom(bar_content(
|
||||
bar_icon(ferese_theme::icons::DISPLAY, bar.icon_size, foreground),
|
||||
control_height,
|
||||
@@ -414,7 +448,7 @@ impl FereseShell {
|
||||
let Some(output) = self.outputs.iter().find(|output| output.bar == menu.anchor.parent) else {
|
||||
return rows;
|
||||
};
|
||||
let panel = &self.config.panels[0];
|
||||
let panel = self.panel_for_bar(output.bar);
|
||||
let Some(resolution) = &output.panel_resolution else {
|
||||
return rows;
|
||||
};
|
||||
@@ -445,20 +479,25 @@ impl FereseShell {
|
||||
) => cosmic::Action::App(Message::PanelAction(Box::new(message))),
|
||||
other => other,
|
||||
});
|
||||
rows = rows.push(cosmic::widget::column![text(item.kind.label()).size(12), element].spacing(4));
|
||||
rows = rows.push(cosmic::widget::column![self.render.text(item.kind.label()).size(12), element].spacing(4));
|
||||
}
|
||||
rows
|
||||
}
|
||||
|
||||
fn view_workspace_item(
|
||||
pub(super) fn view_workspace_item(
|
||||
&self,
|
||||
id: window::Id,
|
||||
style: ferese_config::panel::WorkspaceStyle,
|
||||
style: ferese_panel::WorkspaceStyle,
|
||||
instance: &str,
|
||||
opacity: f32,
|
||||
) -> Element<'_, cosmic::Action<Message>> {
|
||||
let focused_output = self.output_for_bar(id);
|
||||
let shell_theme = self.config.theme.for_bar();
|
||||
let bar = BarMetrics::from(self.config.panels[0].geometry);
|
||||
let mut palette = shell_theme.palette();
|
||||
palette.text.a *= opacity;
|
||||
palette.muted.a *= opacity;
|
||||
palette.accent.a *= opacity;
|
||||
let bar = BarMetrics::from(self.panel_for_bar(id).resolved_geometry());
|
||||
let mut workspace_buttons = row::with_capacity(self.snapshot.workspaces.len())
|
||||
.spacing(1)
|
||||
.align_y(cosmic::iced::Alignment::Center);
|
||||
@@ -471,7 +510,7 @@ impl FereseShell {
|
||||
.output
|
||||
.and_then(|id| self.snapshot.outputs.iter().find(|output| output.id == id));
|
||||
let transition = self.workspace_ui.transition(workspace.id, active);
|
||||
let apps = if style == ferese_config::panel::WorkspaceStyle::AppIcons {
|
||||
let apps = if style == ferese_panel::WorkspaceStyle::AppIcons {
|
||||
self.workspace_ui.apps(workspace.id, &self.snapshot)
|
||||
} else {
|
||||
Vec::new()
|
||||
@@ -490,12 +529,17 @@ impl FereseShell {
|
||||
ferese_theme::workspaces::indicator(
|
||||
style,
|
||||
details.clone(),
|
||||
shell_theme.palette(),
|
||||
super::shell_font(),
|
||||
palette,
|
||||
self.render.font,
|
||||
bar.group_item_height,
|
||||
transition.progress(now),
|
||||
apps.iter()
|
||||
.map(|handle| cosmic::widget::icon::icon(handle.clone()).size(16).into())
|
||||
.map(|handle| {
|
||||
cosmic::widget::icon::icon(handle.clone())
|
||||
.size(16)
|
||||
.opacity(opacity)
|
||||
.into()
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
},
|
||||
@@ -514,6 +558,7 @@ impl FereseShell {
|
||||
.on_press(cosmic::Action::App(Message::ActivateWorkspace(workspace.id)));
|
||||
|
||||
let selector = motion::button(
|
||||
self.render,
|
||||
workspace_button,
|
||||
color(if active {
|
||||
shell_theme.accent
|
||||
@@ -521,7 +566,7 @@ impl FereseShell {
|
||||
shell_theme.text_primary
|
||||
}),
|
||||
false,
|
||||
1.0,
|
||||
opacity,
|
||||
);
|
||||
let target =
|
||||
cosmic::iced::advanced::widget::Id::new(format!("workspace:{id:?}:{instance}:{}", workspace.id));
|
||||
@@ -562,20 +607,19 @@ fn view_zone<'a>(
|
||||
panel: &Panel,
|
||||
zone: &Zone,
|
||||
availability: Availability,
|
||||
shell_theme: ShellTheme,
|
||||
compositor_material: bool,
|
||||
_shell_theme: ShellTheme,
|
||||
_compositor_material: bool,
|
||||
visible: impl Fn(&Item) -> bool,
|
||||
render: impl Fn(&Item, GroupSurface) -> (Element<'a, cosmic::Action<Message>>, bool),
|
||||
) -> Element<'a, cosmic::Action<Message>> {
|
||||
let bar = BarMetrics::from(panel.geometry);
|
||||
let bar = BarMetrics::from(panel.resolved_geometry());
|
||||
let mut groups = row::with_capacity(zone.groups.len())
|
||||
.spacing(zone.spacing)
|
||||
.spacing(panel.zone_spacing(zone))
|
||||
.align_y(cosmic::iced::Alignment::Center);
|
||||
for group in &zone.groups {
|
||||
let surface = panel.resolved_surface(group);
|
||||
let surface = panel.group_surface();
|
||||
let mut controls = row::with_capacity(group.items.len()).align_y(cosmic::iced::Alignment::Center);
|
||||
let mut count = 0;
|
||||
let mut has_content = false;
|
||||
for item in group
|
||||
.items
|
||||
.iter()
|
||||
@@ -585,8 +629,7 @@ fn view_zone<'a>(
|
||||
controls =
|
||||
controls.push(cosmic::iced::widget::Space::new().width(item.gap_before.unwrap_or(group.spacing)));
|
||||
}
|
||||
let (element, content) = render(item, surface);
|
||||
has_content |= content;
|
||||
let (element, _) = render(item, surface);
|
||||
let element: Element<'_, cosmic::Action<Message>> = if matches!(item.kind, ItemKind::FocusedWindow) {
|
||||
element
|
||||
} else {
|
||||
@@ -598,57 +641,26 @@ fn view_zone<'a>(
|
||||
if count == 0 {
|
||||
continue;
|
||||
}
|
||||
let opacity = shell_theme.panel_opacity(panel.background_opacity);
|
||||
let corners = panel
|
||||
.resolved_radius(shell_theme.bar_radius)
|
||||
.at_edge(
|
||||
panel.edge,
|
||||
surface == GroupSurface::Island && panel.geometry.edge_margin == 0,
|
||||
)
|
||||
.0;
|
||||
let border = panel.border;
|
||||
let content: Element<'_, cosmic::Action<Message>> = container(controls)
|
||||
.padding(group.padding)
|
||||
.height(bar.group_height)
|
||||
let horizontal = f32::from(group.padding[1])
|
||||
+ if panel.group_surface() == GroupSurface::Island {
|
||||
group.island_padding
|
||||
} else {
|
||||
0.
|
||||
};
|
||||
let content = container(controls)
|
||||
.padding(cosmic::iced::Padding {
|
||||
top: f32::from(group.padding[0]),
|
||||
bottom: f32::from(group.padding[0]),
|
||||
left: horizontal,
|
||||
right: horizontal,
|
||||
})
|
||||
.height(bar.height)
|
||||
.align_y(alignment::Vertical::Center)
|
||||
.class(theme::Container::custom(move |_| {
|
||||
ferese_theme::panel::group_border(color(shell_theme.border), corners, border && has_content)
|
||||
}))
|
||||
.id(format!("panel:{}:group:{}", panel.id.0, group.id.0));
|
||||
let content: Element<'_, cosmic::Action<Message>> = container(content)
|
||||
.id(group_material_id(panel, group))
|
||||
.height(bar.height)
|
||||
.into();
|
||||
let content = container(content)
|
||||
.id(format!("panel:{}:group:{}", panel.id.0, group.id.0))
|
||||
.into();
|
||||
let panel_surface = panel.surface;
|
||||
let material = compositor_material && panel.surface == PanelSurface::None;
|
||||
let content = match surface {
|
||||
GroupSurface::None => content,
|
||||
GroupSurface::Inset => container(content)
|
||||
.id(group_material_id(panel, group))
|
||||
.class(theme::Container::custom(move |_| {
|
||||
group_surface_style(shell_theme, panel_surface, material, opacity, corners)
|
||||
}))
|
||||
.into(),
|
||||
GroupSurface::Island => {
|
||||
let content = island(
|
||||
content,
|
||||
shell_theme,
|
||||
panel.geometry,
|
||||
group.island_padding,
|
||||
has_content,
|
||||
material || panel.surface == PanelSurface::Solid,
|
||||
Some((group_material_id(panel, group), opacity, corners)),
|
||||
);
|
||||
if panel.surface == PanelSurface::Solid {
|
||||
container(content)
|
||||
.class(theme::Container::custom(move |_| {
|
||||
group_surface_style(shell_theme, PanelSurface::Solid, false, opacity, corners)
|
||||
}))
|
||||
.into()
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
};
|
||||
groups = groups.push(content);
|
||||
}
|
||||
groups.into()
|
||||
@@ -725,6 +737,7 @@ pub(super) fn workspace_active_on_bar(workspace: u64, output: Option<&control::O
|
||||
output.is_some_and(|output| output.active_workspace == workspace)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(super) fn bar_style(theme: ShellTheme, compositor_material: bool, islands: bool) -> container::Style {
|
||||
let mut style = if islands {
|
||||
container::Style::default()
|
||||
@@ -740,6 +753,7 @@ pub(super) fn bar_style(theme: ShellTheme, compositor_material: bool, islands: b
|
||||
style
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(super) fn island<'a>(
|
||||
content: Element<'a, cosmic::Action<Message>>,
|
||||
theme: ShellTheme,
|
||||
@@ -754,7 +768,7 @@ pub(super) fn island<'a>(
|
||||
}
|
||||
|
||||
let bar = BarMetrics::from(geometry);
|
||||
let fallback = ferese_config::panel::CornerRadii::uniform(theme.bar_radius)
|
||||
let fallback = ferese_panel::CornerRadii::uniform(theme.bar_radius)
|
||||
.at_top_edge(geometry.edge_margin == 0)
|
||||
.0;
|
||||
let (id, opacity, corners) =
|
||||
@@ -780,26 +794,6 @@ pub(super) fn island<'a>(
|
||||
.into()
|
||||
}
|
||||
|
||||
fn group_surface_style(
|
||||
theme: ShellTheme,
|
||||
parent: PanelSurface,
|
||||
material: bool,
|
||||
opacity: f32,
|
||||
corners: [f32; 4],
|
||||
) -> cosmic::widget::container::Style {
|
||||
let mut style = bar_style(theme, material, false);
|
||||
style.border.radius = corners.into();
|
||||
if parent == PanelSurface::Solid {
|
||||
style.background = Some(Background::Color(Color {
|
||||
a: opacity * 0.08,
|
||||
..color(theme.text_primary)
|
||||
}));
|
||||
} else if let Some(Background::Color(fill)) = style.background {
|
||||
style.background = Some(Background::Color(Color { a: opacity, ..fill }));
|
||||
}
|
||||
style
|
||||
}
|
||||
|
||||
pub(super) fn input_region(
|
||||
mode: PanelSurface,
|
||||
hidden: bool,
|
||||
@@ -964,7 +958,7 @@ mod island_tests {
|
||||
shell.clock = "10 oct, 4:35 pm".into();
|
||||
let item = Item::new("clock", ItemKind::Clock);
|
||||
for height in [28., 31., 48.] {
|
||||
shell.config.panels[0].geometry.height = height;
|
||||
shell.config.panels[0].geometry.height = Some(height);
|
||||
for representation in [Representation::Wide, Representation::Compact] {
|
||||
let mut view = shell
|
||||
.view_panel_item(shell.outputs[0].bar, &item, None, true, representation)
|
||||
@@ -1015,7 +1009,7 @@ mod island_tests {
|
||||
availability,
|
||||
theme,
|
||||
true,
|
||||
|_| true,
|
||||
|item| title_content || !matches!(item.kind, ItemKind::FocusedWindow),
|
||||
|item, _| {
|
||||
let width = match item.kind {
|
||||
ItemKind::Overview => 28.,
|
||||
@@ -1036,14 +1030,14 @@ mod island_tests {
|
||||
},
|
||||
);
|
||||
let mut view: Element<'_, cosmic::Action<Message>> = container(view)
|
||||
.height(panel.geometry.height)
|
||||
.height(panel.resolved_geometry().height)
|
||||
.align_y(alignment::Vertical::Center)
|
||||
.into();
|
||||
let mut tree = widget::Tree::new(&view);
|
||||
let node = view.as_widget_mut().layout(
|
||||
&mut tree,
|
||||
&renderer,
|
||||
&layout::Limits::new(Size::ZERO, Size::new(1200., panel.geometry.height)),
|
||||
&layout::Limits::new(Size::ZERO, Size::new(1200., panel.resolved_geometry().height)),
|
||||
);
|
||||
let mut bounds = Bounds {
|
||||
group_ids: zone
|
||||
@@ -1076,19 +1070,19 @@ mod island_tests {
|
||||
|
||||
#[test]
|
||||
fn islands_cover_the_full_panel_height_independently_of_group_decoration() {
|
||||
let mut panel = Panel::from_preset(PanelPreset::Islands);
|
||||
let mut panel = Panel::from_preset(PanelStyle::Floating);
|
||||
for margin in [0, 6] {
|
||||
let theme = ShellTheme::default();
|
||||
panel.geometry.height = 36.;
|
||||
panel.geometry.edge_margin = margin;
|
||||
panel.geometry.height = Some(36.);
|
||||
panel.geometry.edge_margin = Some(margin);
|
||||
for border in [false, true] {
|
||||
panel.border = border;
|
||||
let bounds = measure_zone_with_theme(&panel, &panel.start, Availability::default(), true, theme);
|
||||
assert_eq!(bounds.islands[0].y, 0.);
|
||||
assert_eq!(bounds.islands[0].height, 36.);
|
||||
assert_eq!(
|
||||
bounds.groups[0].height, 32.,
|
||||
"decoration must not give the inner content a second full-height background"
|
||||
bounds.groups[0].height, 36.,
|
||||
"group layout follows the same baseline as its shared surface"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1097,7 +1091,7 @@ mod island_tests {
|
||||
#[test]
|
||||
fn composition_preserves_default_group_gaps_and_material_geometry() {
|
||||
for padding in [0., 4., 13.5] {
|
||||
for background in [PanelPreset::Continuous, PanelPreset::Islands] {
|
||||
for background in [PanelStyle::Flat, PanelStyle::Floating] {
|
||||
let mut panel = Panel::from_preset(background);
|
||||
for group in [&mut panel.start, &mut panel.center, &mut panel.end]
|
||||
.into_iter()
|
||||
@@ -1110,14 +1104,17 @@ mod island_tests {
|
||||
start.items.iter().map(|(id, _)| id.as_str()).collect::<Vec<_>>(),
|
||||
["overview", "workspaces"]
|
||||
);
|
||||
let islands = background == PanelPreset::Islands;
|
||||
let islands = background == PanelStyle::Floating;
|
||||
assert_eq!(start.items[0].1.x, if islands { padding + 3. } else { 0. });
|
||||
assert_eq!(start.items[1].1.x - start.items[0].1.x, if islands { 29. } else { 34. });
|
||||
assert_eq!(start.islands.len(), usize::from(islands));
|
||||
assert_eq!(start.islands.len(), panel.start.groups.len());
|
||||
if islands {
|
||||
assert_eq!(
|
||||
start.islands[0],
|
||||
Rectangle::new((0., 0.).into(), (107. + 2. * padding, 28.).into())
|
||||
Rectangle::new(
|
||||
(0., 0.).into(),
|
||||
(107. + 2. * padding, panel.resolved_geometry().height).into()
|
||||
)
|
||||
);
|
||||
}
|
||||
let end = measure_zone(&panel, &panel.end, Availability::default(), true);
|
||||
@@ -1130,19 +1127,22 @@ mod island_tests {
|
||||
if islands {
|
||||
assert_eq!(
|
||||
end.islands[0],
|
||||
Rectangle::new((0., 0.).into(), (108. + 2. * padding, 28.).into())
|
||||
Rectangle::new(
|
||||
(0., 0.).into(),
|
||||
(108. + 2. * padding, panel.resolved_geometry().height).into()
|
||||
)
|
||||
);
|
||||
}
|
||||
let center = measure_zone(&panel, &panel.center, Availability::default(), true);
|
||||
assert_eq!(center.islands.len(), usize::from(islands));
|
||||
assert_eq!(center.islands.len(), panel.center.groups.len());
|
||||
if islands {
|
||||
assert_eq!(center.islands[0].width, 80. + 2. * padding);
|
||||
}
|
||||
let empty_title = measure_zone(&panel, &panel.center, Availability::default(), false);
|
||||
assert!(empty_title.islands.is_empty());
|
||||
assert_eq!(
|
||||
empty_title.items[0].1.width, 80.,
|
||||
"keep the current empty title reservation"
|
||||
assert!(
|
||||
empty_title.items.is_empty(),
|
||||
"an unavailable title leaves no group behind"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1150,7 +1150,7 @@ mod island_tests {
|
||||
|
||||
#[test]
|
||||
fn group_renderer_obeys_order_and_instance_identity_and_skips_unavailable_groups() {
|
||||
let mut panel = Panel::from_preset(PanelPreset::Islands);
|
||||
let mut panel = Panel::from_preset(PanelStyle::Floating);
|
||||
let group = &mut panel.end.groups[0];
|
||||
let mut clock = group
|
||||
.items
|
||||
@@ -1206,7 +1206,7 @@ mod island_tests {
|
||||
#[test]
|
||||
fn default_groups_have_no_fill_or_outline_in_either_arrangement() {
|
||||
let theme = ShellTheme::default();
|
||||
for background in [PanelPreset::Continuous, PanelPreset::Islands] {
|
||||
for background in [PanelStyle::Flat, PanelStyle::Floating] {
|
||||
let panel = Panel::from_preset(background);
|
||||
let group = ferese_theme::panel::group_border(color(theme.border), theme.bar_radius, panel.border);
|
||||
assert!(group.background.is_none());
|
||||
@@ -1239,27 +1239,25 @@ mod island_tests {
|
||||
assert_eq!(inner.border.width, 1.);
|
||||
}
|
||||
#[test]
|
||||
fn mixed_group_surfaces_use_independent_heights_and_padding() {
|
||||
let mut panel = Panel::from_preset(PanelPreset::Islands);
|
||||
panel.geometry.height = 36.;
|
||||
panel.center.groups[0].surface = Some(GroupSurface::None);
|
||||
panel.end.groups[0].surface = Some(GroupSurface::Inset);
|
||||
let navigation = measure_zone(&panel, &panel.start, Availability::default(), true);
|
||||
let title = measure_zone(&panel, &panel.center, Availability::default(), true);
|
||||
let status = measure_zone(&panel, &panel.end, Availability::default(), true);
|
||||
assert_eq!(navigation.islands.len(), 1);
|
||||
assert_eq!(navigation.islands[0].height, 36.);
|
||||
assert!(title.islands.is_empty());
|
||||
assert_eq!(title.items.len(), 1);
|
||||
assert_eq!(status.islands.len(), 1);
|
||||
assert_eq!(status.islands[0].height, BarMetrics::from(panel.geometry).group_height);
|
||||
assert_eq!(status.islands[0].width, status.groups[0].width);
|
||||
assert!(navigation.islands[0].width > navigation.groups[0].width);
|
||||
// The same media island can sit on a continuous panel.
|
||||
panel.surface = PanelSurface::Solid;
|
||||
panel.group_surface = GroupSurface::None;
|
||||
panel.end.groups[0].surface = Some(GroupSurface::Island);
|
||||
let status = measure_zone(&panel, &panel.end, Availability::default(), true);
|
||||
assert_eq!(status.islands[0].height, 36.);
|
||||
fn panel_surface_policy_applies_to_every_occupied_group() {
|
||||
let mut panel = Panel::from_preset(PanelStyle::Floating);
|
||||
panel.geometry.height = Some(36.);
|
||||
for zone in [&panel.start, &panel.center, &panel.end] {
|
||||
let measured = measure_zone(&panel, zone, Availability::default(), true);
|
||||
assert_eq!(measured.islands.len(), measured.groups.len());
|
||||
for (island, group) in measured.islands.iter().zip(&measured.groups) {
|
||||
assert_eq!(island.height, 36.);
|
||||
assert_eq!(island.width, group.width);
|
||||
}
|
||||
}
|
||||
panel.apply_preset(PanelStyle::Flat);
|
||||
for zone in [&panel.start, &panel.center, &panel.end] {
|
||||
let measured = measure_zone(&panel, zone, Availability::default(), true);
|
||||
assert_eq!(
|
||||
measured.islands.len(),
|
||||
measured.groups.len(),
|
||||
"changing the shared shape preserves layout bounds"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user