Add Live Activities as configurable bar items (#30)

* Add Live Activities as configurable bar items

* Fix activity owner monitoring and Morph list interactions

* Keep activity rows stable during updates and tighten their layout

* Use subtle theme tint for activity cards

* Tighten activity title and progress spacing

* Use theme accent for active progress rings

* Fit compact activities to measured titles and badge

* Expose Live Activities display controls in Settings

* Open activity terminals and streamline the Morph header

* Give Morph continuous icons and prioritize workspace navigation

* Show activity progress inside its ring

* Inset activity progress rings and strengthen their stroke

* Fix activity lifecycle, command output handling and list navigation

* Reduce activity update work and harden publisher lifetimes

* Set up Live Activities for the detected user shell during installation

* Document Activities setup, daily use and troubleshooting

* Preserve Morph idle width across empty activity updates

* Fix dimming flicker, hide inactive music tab and consolidate Activities docs

* Fix panel clearance across styles and match previews to theme settings

* Prevent panel blur from repainting neighboring islands

---------

Co-authored-by: Samuel Onoja <[email protected]>
This commit is contained in:
Samuel OnojaandSamuel Onoja authored and GitHub committed 2026-10-12 22:52:25 +01:00
1 parent 7a7df0106c
commit 6b0d903ad0
107 files changed
+9340 -291

No files matched your search

+56 -13
View File
@@ -7,6 +7,7 @@ use ferese_theme::panel::{Sample, Spacing, frame};
use crate::{App, Element, Message, panel_controls, visuals};
const SAMPLE_AVAILABILITY: Availability = Availability {
activities: true,
media: true,
network: true,
audio: true,
@@ -19,6 +20,18 @@ fn available(item: &Item) -> bool {
item.available(SAMPLE_AVAILABILITY)
}
fn panel_background(theme: &ferese_config::theme::ResolvedTheme, panel: &Panel) -> cosmic::iced::Color {
let fill = if panel.style == ferese_panel::PanelStyle::Morph {
&theme.tokens.colors.surface_raised
} else {
&theme.tokens.surface.bar.background
};
cosmic::iced::Color {
a: theme.panel_opacity(panel.background_opacity),
..visuals::color(fill, cosmic::iced::Color::TRANSPARENT)
}
}
impl App {
pub(super) fn panel_preview(&self) -> Element<'_, Message> {
let panel = match self.preview_panel() {
@@ -115,14 +128,7 @@ impl App {
groups: Vec::new(),
scale: self.render_scale,
high_contrast: palette.high_contrast,
background: cosmic::iced::Color {
a: if self.resolved.presented.accessibility.reduce_transparency {
1.
} else {
panel.background_opacity.unwrap_or(palette.card.a)
},
..palette.card
},
background: panel_background(&self.resolved.presented, &panel),
border: if panel.border {
visuals::color(
&self.resolved.presented.tokens.colors.border,
@@ -194,7 +200,7 @@ impl App {
ferese_panel::shape::ZonePosition::End,
)))
.collect();
let opacity = self.resolved.presented.panel_opacity(panel.background_opacity);
let background = panel_background(&self.resolved.presented, &panel);
let height = panel.resolved_geometry().height;
let padding = panel.resolved_geometry().inner_padding;
let border = panel.border;
@@ -265,10 +271,7 @@ impl App {
groups,
scale: self.render_scale,
high_contrast: palette.high_contrast,
background: cosmic::iced::Color {
a: opacity,
..palette.card
},
background,
border: if border {
visuals::color(
&self.resolved.presented.tokens.colors.border,
@@ -367,6 +370,7 @@ impl App {
ItemKind::Workspaces { .. } => icons::OVERVIEW,
ItemKind::FocusedWindow => icons::DISPLAY,
ItemKind::Media => icons::MEDIA,
ItemKind::Activities => icons::FERESE,
ItemKind::QuickSettings => icons::CONTROL_CENTER,
ItemKind::Network => icons::WIFI_FULL,
ItemKind::Audio => icons::VOLUME_HIGH,
@@ -380,6 +384,14 @@ impl App {
let label = match (item.kind, representation) {
(ItemKind::Workspaces { .. }, _) => "1 2 3",
(ItemKind::FocusedWindow, _) => "Settings",
(ItemKind::Activities, Representation::Wide) => {
if self.draft.boolean("activities.show_badge", true) {
"Build · 62% +1"
} else {
"Build · 62%"
}
}
(ItemKind::Activities, Representation::Compact) => "Build",
(ItemKind::Media, Representation::Wide) => "Song title · Artist ▷",
(ItemKind::Media, Representation::Compact) => "Song title",
(ItemKind::Battery { percentage: true }, Representation::Wide) => "75%",
@@ -478,6 +490,37 @@ mod tests {
use cosmic::iced::advanced::{Layout, Shell, clipboard, layout, mouse, renderer::Headless, widget};
use cosmic::iced::{Event, Font, Pixels, Point, Rectangle, Size};
#[test]
fn preview_uses_bar_tokens_and_inherited_opacity_for_every_available_style() {
let mut theme = ferese_config::theme::default_theme();
theme.tokens.colors.surface_raised = "#112233".into();
theme.tokens.surface.bar.background = "#abcdef".into();
theme.tokens.material.style = "translucent".into();
theme.tokens.material.opacity = 0.35;
for style in ferese_panel::PanelStyle::AVAILABLE {
let mut panel = Panel::from_preset(style);
let expected = if style == ferese_panel::PanelStyle::Morph {
cosmic::iced::Color::from_rgb8(0x11, 0x22, 0x33)
} else {
cosmic::iced::Color::from_rgb8(0xab, 0xcd, 0xef)
};
for (override_opacity, opacity) in [(None, 0.35), (Some(0.), 0.), (Some(0.7), 0.7)] {
panel.background_opacity = override_opacity;
for reduce in [false, true] {
theme.accessibility.reduce_transparency = reduce;
assert_eq!(
panel_background(&theme, &panel),
cosmic::iced::Color {
a: if reduce { 1. } else { opacity },
..expected
},
"{style:?}, opacity={override_opacity:?}, reduce={reduce}",
);
}
}
}
}
#[derive(Default)]
struct Bounds {
targets: Vec<(widget::Id, ItemId)>,