Consolidate desktop controls with editable holidays and a running-window tray

This commit is contained in:
aria committed 2026-10-28 06:02:46 +09:00
1 parent 9908dde9f8
commit 326465952a
55 files changed
+4207 -450

No files matched your search

+3 -3
View File
@@ -40,7 +40,7 @@ impl Default for Profile {
expanded_width: 340.,
workspaces_height: 176.,
media_height: 170.,
calendar_height: 230.,
calendar_height: 650.,
notifications_height: 176.,
controls_height: 248.,
expanded_radius: 26.,
@@ -415,7 +415,7 @@ mod tests {
assert!(!hub.active());
hub.retarget(Mode::Expanded(Content::Calendar), [800., 600.])
.unwrap();
assert_eq!(hub.target_size(), [340., 230.]);
assert_eq!(hub.target_size(), [340., 600.]); // clipped to the output
hub.advance(Duration::from_millis(120), false);
assert_eq!(hub.sample().opacity, 0.);
assert_eq!(hub.sample().previous_opacity, 0.);
@@ -469,7 +469,7 @@ mod tests {
}
let overshoot = (peak - 340.) / (340. - 150.);
assert!((0.04..0.05).contains(&overshoot));
assert_eq!(hub.sample().geometry.bounds, Rect::new(0., 0., 340., 230.));
assert_eq!(hub.sample().geometry.bounds, Rect::new(0., 0., 340., 600.));
assert!(!hub.active());
}
+160 -15
View File
@@ -127,6 +127,7 @@ pub enum ItemKind {
style: WorkspaceStyle,
},
FocusedWindow,
WindowTray,
Media,
Activities,
QuickSettings,
@@ -152,6 +153,7 @@ impl ItemKind {
pub fn minimum_width(self) -> Option<f32> {
match self {
Self::FocusedWindow => Some(48.),
Self::WindowTray => Some(30.),
Self::Workspaces { .. } => Some(24.),
Self::Media => Some(112.),
_ => None,
@@ -164,6 +166,7 @@ impl ItemKind {
Self::Overview => "Overview",
Self::Workspaces { .. } => "Workspaces",
Self::FocusedWindow => "Focused window",
Self::WindowTray => "Window tray",
Self::Media => "Media",
Self::Activities => "Activities",
Self::QuickSettings => "Quick Settings",
@@ -188,7 +191,7 @@ impl ItemKind {
&[Wide, Compact, Icon]
}
Self::Battery { percentage: true } => &[Wide, Icon],
Self::Workspaces { .. } | Self::FocusedWindow => &[Wide],
Self::Workspaces { .. } | Self::FocusedWindow | Self::WindowTray => &[Wide],
_ => &[Icon],
}
}
@@ -311,7 +314,10 @@ impl Item {
metadata: BTreeMap::new(),
overflow: if matches!(
kind,
ItemKind::Overview | ItemKind::Workspaces { .. } | ItemKind::FocusedWindow
ItemKind::Overview
| ItemKind::Workspaces { .. }
| ItemKind::FocusedWindow
| ItemKind::WindowTray
) {
OverflowPolicy::Never
} else {
@@ -698,6 +704,84 @@ impl Panel {
.find(|item| &item.id == id)
}
/// Project older panel files onto the consolidated time/control entry without
/// rewriting their source or changing surviving item identities.
pub fn consolidate_clock_controls(&mut self) {
let kinds: Vec<_> = [&self.start, &self.center, &self.end]
.into_iter()
.flat_map(|z| &z.groups)
.flat_map(|g| &g.items)
.map(|i| i.kind)
.collect();
// Only migrate legacy presets. A deliberately removed or hidden tray in
// a current composition must stay removed or hidden.
if kinds.iter().any(|k| {
matches!(
k,
ItemKind::QuickSettings | ItemKind::Network | ItemKind::NightLight
)
}) && !kinds.contains(&ItemKind::WindowTray)
{
let mut suffix = 0;
let mut id = ItemId("window-tray".into());
while self.item(&id).is_some() {
suffix += 1;
id = ItemId(format!("window-tray-{suffix}"));
}
if let Some(group) = self.end.groups.first_mut() {
group
.items
.insert(0, Item::new(&id.0, ItemKind::WindowTray));
}
}
let has_clock = [&self.start, &self.center, &self.end]
.into_iter()
.flat_map(|z| &z.groups)
.flat_map(|g| &g.items)
.any(|i| i.kind == ItemKind::Clock);
let mut clock_exists = has_clock;
for zone in [&mut self.start, &mut self.center, &mut self.end] {
for group in &mut zone.groups {
group.items.retain_mut(|item| match item.kind {
ItemKind::QuickSettings if !clock_exists => {
item.kind = ItemKind::Clock;
clock_exists = true;
true
}
ItemKind::QuickSettings | ItemKind::Network | ItemKind::NightLight => false,
_ => true,
});
}
}
if !clock_exists {
return;
}
let mut notices = Vec::new();
for zone in [&mut self.start, &mut self.center, &mut self.end] {
for group in &mut zone.groups {
group.items.retain(|item| {
if item.kind == ItemKind::Notifications {
notices.push(item.clone());
false
} else {
true
}
});
}
}
for zone in [&mut self.start, &mut self.center, &mut self.end] {
for group in &mut zone.groups {
if let Some(index) = group.items.iter().position(|i| i.kind == ItemKind::Clock) {
for item in &mut notices {
item.gap_before = Some(0.);
}
group.items.splice(index + 1..index + 1, notices);
return;
}
}
}
}
/// Build a stable initial composition; presets are an editor convenience.
#[must_use]
pub fn from_preset(preset: PanelStyle) -> Self {
@@ -714,18 +798,17 @@ impl Panel {
);
let title = item("focused-window", FocusedWindow);
let controls = vec![
item("window-tray", WindowTray),
item("media", Media),
item("quick-settings", QuickSettings),
item("tray", Tray),
item("night-light", NightLight),
item("network", Network),
item("audio", Audio),
item("recording", Recording),
item("capture-status", CaptureStatus),
item("notifications", Notifications),
item("battery", Battery { percentage: true }),
];
let clock = item("clock", Clock);
let mut notifications = item("notifications", Notifications);
notifications.gap_before = Some(0.);
let display = item("display-mode", DisplayMode);
let (start, center, end) = match preset {
@@ -737,13 +820,13 @@ impl Panel {
Group::new("title", vec![title], [0, 0], padding),
vec![
Group::new("status", controls, [2, 3], padding),
Group::new("time", vec![clock], [2, 3], padding),
Group::new("time", vec![clock, notifications], [2, 3], padding),
Group::new("display", vec![display], [0, 0], padding),
],
),
_ => {
let mut controls = controls;
controls.extend([clock, display]);
controls.extend([clock, notifications, display]);
(
vec![Group::new(
"navigation",
@@ -835,17 +918,15 @@ mod tests {
"overview",
"workspaces",
"focused-window",
"window-tray",
"media",
"quick-settings",
"tray",
"night-light",
"network",
"audio",
"recording",
"capture-status",
"notifications",
"battery",
"clock",
"notifications",
"display-mode",
];
for panel in [&continuous, &islands] {
@@ -876,8 +957,7 @@ mod tests {
"overview",
"workspaces",
"focused-window",
"quick-settings",
"night-light",
"window-tray",
"recording",
"clock"
]
@@ -898,7 +978,72 @@ mod tests {
.map(|item| item.id.0.as_str())
.collect::<Vec<_>>()
);
assert_eq!(items(&panel).len(), 15);
assert_eq!(items(&panel).len(), 13);
}
#[test]
fn legacy_window_tray_is_seeded_once_without_resurrecting_current_removed_items() {
let mut panel = Panel::default();
panel.end.groups[0]
.items
.retain(|i| i.kind != ItemKind::WindowTray);
panel.consolidate_clock_controls();
assert!(!items(&panel).iter().any(|i| i.kind == ItemKind::WindowTray));
panel.end.groups[0]
.items
.push(Item::new("old-controls", ItemKind::QuickSettings));
// The instance name can already belong to a different authored item.
panel.start.groups[0].items[0].id = ItemId("window-tray".into());
panel.consolidate_clock_controls();
let tray = items(&panel)
.into_iter()
.find(|i| i.kind == ItemKind::WindowTray)
.unwrap();
assert_eq!(tray.id.0, "window-tray-1");
validate(std::slice::from_ref(&panel)).unwrap();
let once = panel.clone();
panel.consolidate_clock_controls();
assert_eq!(panel, once);
}
#[test]
fn legacy_controls_consolidate_beside_the_clock_and_preserve_notification_state() {
let mut panel = Panel::default();
let mut notice = panel.end.groups[1].items.pop().unwrap();
notice.visible = false;
notice.metadata.insert("custom".into(), "retained".into());
panel.start.groups[0].items.extend([
Item::new("old-network", ItemKind::Network),
Item::new("old-controls", ItemKind::QuickSettings),
notice.clone(),
]);
panel.consolidate_clock_controls();
let all = items(&panel);
assert!(!all.iter().any(|i| matches!(
i.kind,
ItemKind::Network | ItemKind::QuickSettings | ItemKind::NightLight
)));
let clock = all.iter().position(|i| i.kind == ItemKind::Clock).unwrap();
let moved = all[clock + 1];
assert_eq!(moved.id, notice.id);
assert!(!moved.visible);
assert_eq!(moved.metadata, notice.metadata);
let once = panel.clone();
panel.consolidate_clock_controls();
assert_eq!(panel, once);
for zone in [&mut panel.start, &mut panel.center, &mut panel.end] {
for group in &mut zone.groups {
group.items.retain(|i| i.kind != ItemKind::Clock);
}
}
panel.end.groups[0]
.items
.push(Item::new("controls-only", ItemKind::QuickSettings));
panel.consolidate_clock_controls();
assert_eq!(
panel.item(&ItemId("controls-only".into())).unwrap().kind,
ItemKind::Clock
);
}
#[test]
+2 -2
View File
@@ -840,14 +840,14 @@ mod tests {
.groups
.iter_mut()
.flat_map(|group| &mut group.items)
.find(|item| item.id.0 == "network")
.find(|item| item.id.0 == "audio")
.unwrap()
.overflow = OverflowPolicy::Always;
let mut measured = measurements(&panel);
measured.retain(|item| item.id.0 != "media");
let resolution = resolve(&panel, &measured, 2000., 32.);
assert!(!resolution.items.contains_key(&ItemId("media".into())));
assert_eq!(resolution.overflow, [ItemId("network".into())]);
assert_eq!(resolution.overflow, [ItemId("audio".into())]);
}
#[test]
fn priority_controls_displacement_and_expansion_restores_requested_sizes() {