Add live config reload, session locking and managed daemons
Refine focus gradients, shell workspace controls and popup motion. Correct floating stacking and preserve client-chosen dialog sizes. Add lock protocol probes and session integration.
This commit is contained in:
1 parent
b422448d88
commit
672b4e2b60
41 files changed
+2694
-298
No files matched your search
@@ -5,7 +5,6 @@ use cosmic::widget::{column, slider};
|
||||
use status::{Action, Snapshot};
|
||||
|
||||
const DEVICE_LIST_HEIGHT: f32 = 180.0;
|
||||
const OPEN_MS: f32 = 160.0;
|
||||
|
||||
fn device_list_height(count: usize) -> Option<f32> {
|
||||
(count > 4).then_some(DEVICE_LIST_HEIGHT)
|
||||
@@ -48,30 +47,30 @@ impl Menu {
|
||||
pub struct OpenMenu {
|
||||
pub id: window::Id,
|
||||
pub kind: Menu,
|
||||
opened: Instant,
|
||||
pub motion: super::motion::PopupMotion,
|
||||
pub confirm: Option<Action>,
|
||||
pub effects: Option<EffectsBinding>,
|
||||
pub regions: super::motion::Regions,
|
||||
}
|
||||
impl OpenMenu {
|
||||
pub fn progress(&self) -> f32 {
|
||||
let ease = |t: f32| {
|
||||
let t = t.clamp(0.0, 1.0);
|
||||
1.0 - (1.0 - t).powi(3)
|
||||
};
|
||||
ease(self.opened.elapsed().as_secs_f32() * 1000.0 / OPEN_MS)
|
||||
self.motion.progress()
|
||||
}
|
||||
pub fn animating(&self) -> bool {
|
||||
self.progress() < 1.0
|
||||
self.motion.animating()
|
||||
}
|
||||
}
|
||||
|
||||
impl FereseShell {
|
||||
pub fn open_menu(&mut self, kind: Menu, anchor: Rectangle<i32>) -> Task<Message> {
|
||||
if self.menu.as_ref().is_some_and(|menu| menu.kind == kind) {
|
||||
// Close atomically. Per-widget alpha cannot fade SVG/text caches
|
||||
// as one surface and leaves bright fragments during the fade.
|
||||
return self.destroy_menu();
|
||||
if let Some(menu) = &mut self.menu {
|
||||
if menu.motion.closing() {
|
||||
menu.motion.retarget(1.0, Instant::now());
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
return self.close_menu();
|
||||
}
|
||||
let destroy = self.destroy_menu();
|
||||
if !kind.available(&self.status) {
|
||||
@@ -81,7 +80,7 @@ impl FereseShell {
|
||||
self.menu = Some(OpenMenu {
|
||||
id,
|
||||
kind,
|
||||
opened: Instant::now(),
|
||||
motion: super::motion::PopupMotion::new(self.config.animations),
|
||||
confirm: None,
|
||||
effects: None,
|
||||
regions: Default::default(),
|
||||
@@ -124,6 +123,36 @@ impl FereseShell {
|
||||
))
|
||||
})
|
||||
}
|
||||
pub fn close_menu(&mut self) -> Task<Message> {
|
||||
let Some(menu) = &mut self.menu else {
|
||||
return Task::none();
|
||||
};
|
||||
if menu.motion.closing() {
|
||||
return Task::none();
|
||||
}
|
||||
if menu
|
||||
.effects
|
||||
.as_ref()
|
||||
.is_some_and(|effects| effects.surface.version() >= 3)
|
||||
{
|
||||
menu.motion.retarget(0.0, Instant::now());
|
||||
if menu.animating() {
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
self.destroy_menu()
|
||||
}
|
||||
pub fn animate_menu(&mut self) -> Task<Message> {
|
||||
if let Some(menu) = &self.menu {
|
||||
if let Some(effects) = &menu.effects {
|
||||
let _ = effects.set_opacity(menu.progress());
|
||||
}
|
||||
if menu.motion.closing() && !menu.animating() {
|
||||
return self.destroy_menu();
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
pub fn optimistic_status(&mut self, action: &Action) {
|
||||
match action {
|
||||
Action::Volume(v) => {
|
||||
@@ -221,7 +250,6 @@ impl FereseShell {
|
||||
((metrics.height - f32::from(metrics.icon_size)) * 0.5).max(4.0),
|
||||
])
|
||||
.height(metrics.height)
|
||||
.class(button_style(foreground, selected, 1.0))
|
||||
.on_press_with_rectangle(move |offset, bounds| {
|
||||
cosmic::Action::App(Message::OpenMenu(
|
||||
kind,
|
||||
@@ -233,7 +261,7 @@ impl FereseShell {
|
||||
},
|
||||
))
|
||||
});
|
||||
controls = controls.push(control);
|
||||
controls = controls.push(motion::button(control, foreground, selected, 1.0));
|
||||
}
|
||||
controls.into()
|
||||
}
|
||||
@@ -243,7 +271,8 @@ impl FereseShell {
|
||||
return text("").into();
|
||||
};
|
||||
let theme = self.config.theme;
|
||||
let p = menu.progress();
|
||||
// The compositor fades the complete surface, not individual caches.
|
||||
let p = 1.0;
|
||||
let kind = menu.kind;
|
||||
let primary = color_with_opacity(theme.text_primary, p);
|
||||
let muted = color_with_opacity(theme.text_muted, p);
|
||||
@@ -615,7 +644,7 @@ impl FereseShell {
|
||||
}));
|
||||
// Keep the outer surface transparent; only inner cards paint a background.
|
||||
cosmic::widget::autosize::autosize(
|
||||
super::motion::animated(panel.into(), p, menu.regions.clone()),
|
||||
super::motion::animated(panel.into(), menu.progress(), menu.regions.clone()),
|
||||
cosmic::iced::advanced::widget::Id::new("ferese-status-menu"),
|
||||
)
|
||||
.limits(
|
||||
@@ -661,12 +690,15 @@ fn status_summary<'a>(
|
||||
..Default::default()
|
||||
}));
|
||||
let badge: Element<'_, cosmic::Action<Message>> = if let Some((_, action)) = toggle {
|
||||
button::custom(badge)
|
||||
.padding(0)
|
||||
.name(if enabled { "Turn off" } else { "Turn on" })
|
||||
.class(button_style(badge_color, false, opacity))
|
||||
.on_press(cosmic::Action::App(Message::Control(action)))
|
||||
.into()
|
||||
motion::button(
|
||||
button::custom(badge)
|
||||
.padding(0)
|
||||
.name(if enabled { "Turn off" } else { "Turn on" })
|
||||
.on_press(cosmic::Action::App(Message::Control(action))),
|
||||
badge_color,
|
||||
false,
|
||||
opacity,
|
||||
)
|
||||
} else {
|
||||
badge.into()
|
||||
};
|
||||
@@ -800,13 +832,15 @@ fn connection_control<'a>(
|
||||
.padding(4)
|
||||
.into();
|
||||
};
|
||||
container(
|
||||
container(motion::button(
|
||||
button::custom(content)
|
||||
.width(Length::FillPortion(1))
|
||||
.padding(4)
|
||||
.class(button_style(foreground, false, opacity))
|
||||
.on_press(cosmic::Action::App(Message::Control(action))),
|
||||
)
|
||||
foreground,
|
||||
false,
|
||||
opacity,
|
||||
))
|
||||
.id("ferese-blur-card")
|
||||
.width(Length::FillPortion(1))
|
||||
.into()
|
||||
@@ -818,11 +852,14 @@ fn menu_button<'a>(
|
||||
foreground: Color,
|
||||
opacity: f32,
|
||||
) -> Element<'a, cosmic::Action<Message>> {
|
||||
button::custom(text(label).size(13))
|
||||
.padding([6, 8])
|
||||
.class(button_style(foreground, false, opacity))
|
||||
.on_press(cosmic::Action::App(message))
|
||||
.into()
|
||||
motion::button(
|
||||
button::custom(text(label).size(13))
|
||||
.padding([6, 8])
|
||||
.on_press(cosmic::Action::App(message)),
|
||||
foreground,
|
||||
false,
|
||||
opacity,
|
||||
)
|
||||
}
|
||||
fn toggle_row<'a>(
|
||||
label: &'a str,
|
||||
@@ -843,24 +880,6 @@ fn toggle_row<'a>(
|
||||
.align_y(Alignment::Center)
|
||||
.into()
|
||||
}
|
||||
pub(super) fn button_style(foreground: Color, selected: bool, opacity: f32) -> theme::Button {
|
||||
let style = move |strength: f32| button::Style {
|
||||
text_color: Some(foreground),
|
||||
icon_color: Some(foreground),
|
||||
border_radius: 6.0.into(),
|
||||
background: Some(Background::Color(Color {
|
||||
a: strength * opacity,
|
||||
..foreground
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
theme::Button::Custom {
|
||||
active: Box::new(move |_, _| style(if selected { 0.14 } else { 0.0 })),
|
||||
hovered: Box::new(move |_, _| style(if selected { 0.16 } else { 0.08 })),
|
||||
pressed: Box::new(move |_, _| style(0.20)),
|
||||
disabled: Box::new(move |_| style(0.0)),
|
||||
}
|
||||
}
|
||||
|
||||
fn slider_row(
|
||||
source: &'static [u8],
|
||||
@@ -1071,10 +1090,12 @@ mod tests {
|
||||
#[test]
|
||||
fn completed_open_stops_requesting_animation_ticks() {
|
||||
let past = Instant::now() - Duration::from_secs(1);
|
||||
let mut motion = super::motion::PopupMotion::new(Default::default());
|
||||
motion.begin(past);
|
||||
let menu = OpenMenu {
|
||||
id: window::Id::unique(),
|
||||
kind: Menu::Network,
|
||||
opened: past,
|
||||
motion,
|
||||
confirm: None,
|
||||
effects: None,
|
||||
regions: Default::default(),
|
||||
|
||||
Reference in new issue
Block a user