diff --git a/cosmic-app-list/src/app.rs b/cosmic-app-list/src/app.rs index d4d579a1..932ffd6c 100755 --- a/cosmic-app-list/src/app.rs +++ b/cosmic-app-list/src/app.rs @@ -187,11 +187,6 @@ impl DockItem { dot_border_radius: [f32; 4], window_id: window::Id, filter: Option<&dyn Fn(&ToplevelInfo) -> bool>, - // Yoda: multiplier on the computed icon size (1.0 = default, - // >1.0 = magnified e.g. on hover for the macOS Tahoe effect). - // Applied to the icon's rendered width/height only — indicator - // dot and surrounding layout stay at base size. - icon_scale: f32, ) -> Element<'_, Message> { let Self { toplevels, @@ -210,35 +205,17 @@ impl DockItem { }; let toplevel_count = filtered_toplevels.len(); - // Cairo-like : pastille plus petite + atténuée quand toutes les fenêtres - // de cette app sont minimisées. - let all_minimized = toplevel_count > 0 - && filtered_toplevels - .iter() - .all(|(info, _)| info.state.contains(&State::Minimized)); - let app_icon = AppletIconData::new(applet); - // Yoda: scaled icon size for hover magnification. Clamped so - // tiny floats don't round to 0 and huge ones stay within u16. - let scaled_icon_size = ((f32::from(app_icon.icon_size) * icon_scale).round() as i32) - .clamp(1, u16::MAX as i32) as u16; let cosmic_icon = cosmic::widget::icon( fde::IconSource::from_unknown(desktop_info.icon().unwrap_or_default()).as_cosmic_icon(), ) // sets the preferred icon size variant .size(128) - .width(scaled_icon_size.into()) - .height(scaled_icon_size.into()); + .width(app_icon.icon_size.into()) + .height(app_icon.icon_size.into()); let indicator = { - // Padding réduit quand minimisée → pastille plus petite. - let effective_radius = if all_minimized { - (app_icon.dot_radius * 0.55).max(1.0) - } else { - app_icon.dot_radius - }; - let container = if toplevel_count <= 1 { vertical_space().height(Length::Fixed(0.0)) } else { @@ -252,34 +229,22 @@ impl DockItem { } } .apply(container) - .padding(effective_radius); + .padding(app_icon.dot_radius); if toplevel_count == 0 { container } else { - container.class(theme::Container::custom(move |theme| { - let cosmic = theme.cosmic(); - let accent: iced::Color = cosmic.accent_color().into(); - let on_bg: iced::Color = cosmic.on_bg_color().into(); - // Teinte neutre atténuée quand toutes les fenêtres sont minimisées. - let muted = iced::Color { a: 0.45, ..on_bg }; - - let fill = if all_minimized { - muted - } else if is_focused { - accent + container.class(theme::Container::custom(move |theme| container::Style { + background: if is_focused { + Some(Background::Color(theme.cosmic().accent_color().into())) } else { - on_bg - }; - - container::Style { - background: Some(Background::Color(fill)), - border: Border { - radius: dot_border_radius.into(), - ..Default::default() - }, + Some(Background::Color(theme.cosmic().on_bg_color().into())) + }, + border: Border { + radius: dot_border_radius.into(), ..Default::default() - } + }, + ..Default::default() })) } }; @@ -409,21 +374,6 @@ struct CosmicAppList { output_list: FxHashMap, locales: Vec, hovered_toplevel: Option, - /// Yoda: which dock icon the pointer is currently over (for hover - /// magnification). None = no dock icon hovered. - hovered_dock_item: Option, - /// Yoda: animated "virtual cursor" center used by the fisheye - /// formula — lerps toward the real hovered icon's center on each - /// AnimTick, so the bell curve slides smoothly from one icon to the - /// next instead of snapping. - anim_hover_center: Option<(f32, f32)>, - /// Yoda: fade-in/out intensity of the magnification effect - /// (0.0 = icons flat, 1.0 = full fisheye). Targets 1.0 while the - /// pointer is over any dock icon, 0.0 otherwise. Lerped on AnimTick. - anim_hover_intensity: f32, - /// Yoda: timestamp of the last AnimTick, for dt-based exponential - /// smoothing. `None` on first tick. - anim_last_tick: Option, overflow_favorites_popup: Option, overflow_active_popup: Option, } @@ -439,13 +389,6 @@ enum Message { Wayland(WaylandUpdate), PinApp(u32), UnpinApp(u32), - /// Yoda: pointer entered (Some) or left (None) a dock icon — drives - /// the macOS Tahoe-style hover magnification effect. - DockItemHover(Option), - /// Yoda: ticked at ~60fps by the animation subscription. Advances - /// anim_hover_center + anim_hover_intensity toward their targets so - /// the fisheye effect transitions smoothly instead of snapping. - AnimTick(std::time::Instant), Popup(u32, window::Id), Pressed(window::Id), ToplevelListPopup(u32, window::Id), @@ -704,79 +647,6 @@ impl CosmicAppList { .collect::>(); } - /// Yoda: macOS-Tahoe fisheye-style magnification. Returns the - /// per-icon size multiplier based on the distance (in pixels) from - /// the currently hovered icon's center to this icon's center. - /// - /// Uses a gaussian bell curve so the hovered icon peaks at - /// 1.0 + PEAK, immediate neighbors still bulge noticeably, and icons - /// further away relax back to 1.0× — that's the smooth neighbour - /// deformation people associate with the macOS Dock. - /// - /// Falls back to binary 1.3×/1.0× when the rectangle tracker hasn't - /// populated yet (first render, or just after layout changes). - /// - /// Uses the animated hover center (anim_hover_center) and intensity - /// (anim_hover_intensity) so inter-icon transitions slide smoothly - /// and the whole effect fades in/out at the dock's edges. - fn icon_scale_for(&self, id: &DockItemId) -> f32 { - const PEAK: f32 = 0.35; - // sigma expressed in multiples of the hovered icon's size — - // 1.4 means the ±1 neighbors sit ~0.7σ away and still bulge - // visibly, while ±3+ has collapsed to ~1.0× (fisheye footprint - // close to 5 icons wide, Tahoe-ish). - const SIGMA_FACTOR: f32 = 1.4; - - // No intensity at all → skip the rest. - if self.anim_hover_intensity < 0.001 { - return 1.0; - } - - // Prefer the animated center (smooth); fall back to the real - // hovered icon's center (first render, before tick fires). - let hover_center = self.anim_hover_center.or_else(|| { - let hovered_id = self.hovered_dock_item.as_ref()?; - let r = self.rectangles.get(hovered_id)?; - Some((r.x + r.width / 2.0, r.y + r.height / 2.0)) - }); - let Some(hover_center) = hover_center else { - // No coords yet — visibly peak on the exact hovered id so - // the very first frame still responds. - return if self.hovered_dock_item.as_ref() == Some(id) { - 1.0 + PEAK * self.anim_hover_intensity - } else { - 1.0 - }; - }; - - let this_rect = match self.rectangles.get(id) { - Some(r) => r, - None => return 1.0, - }; - - let is_horizontal = matches!( - self.core.applet.anchor, - PanelAnchor::Top | PanelAnchor::Bottom - ); - let this_center = if is_horizontal { - this_rect.x + this_rect.width / 2.0 - } else { - this_rect.y + this_rect.height / 2.0 - }; - let hover_axis = if is_horizontal { hover_center.0 } else { hover_center.1 }; - let distance = (this_center - hover_axis).abs(); - let icon_extent = if is_horizontal { - this_rect.width - } else { - this_rect.height - } - .max(1.0); - let sigma = icon_extent * SIGMA_FACTOR; - // exp(-t²) bell curve, t = distance / sigma - let t = distance / sigma; - 1.0 + PEAK * self.anim_hover_intensity * (-t * t).exp() - } - fn is_on_current_monitor_and_workspace(&self, toplevel_info: &ToplevelInfo) -> bool { use cosmic_app_list_config::ToplevelFilter; @@ -1712,52 +1582,6 @@ impl cosmic::Application for CosmicAppList { Message::GpuRequest(gpus) => { self.gpus = gpus; } - Message::DockItemHover(id) => { - self.hovered_dock_item = id; - // Seed the animated center on the very first hover so - // the bell doesn't "fly in" from (0,0). - if self.anim_hover_center.is_none() - && let Some(hovered_id) = self.hovered_dock_item.as_ref() - && let Some(r) = self.rectangles.get(hovered_id) - { - self.anim_hover_center = Some(( - r.x + r.width / 2.0, - r.y + r.height / 2.0, - )); - } - } - Message::AnimTick(now) => { - // dt-based exponential smoothing: reach ~99% of the - // target in ~120ms at 60fps (about 7 ticks). - let dt = self - .anim_last_tick - .map(|prev| now.saturating_duration_since(prev).as_secs_f32()) - .unwrap_or(0.016) - .min(0.1); // clamp so a long pause doesn't snap - self.anim_last_tick = Some(now); - - // Intensity: target 1.0 when any icon is hovered, 0.0 else. - let intensity_target = if self.hovered_dock_item.is_some() { 1.0 } else { 0.0 }; - let tau = 0.060_f32; // time-constant (s); smaller = snappier - let alpha = 1.0 - (-dt / tau).exp(); - self.anim_hover_intensity += (intensity_target - self.anim_hover_intensity) * alpha; - - // Hovered-center smoothing: chase the real rect's center. - if let Some(hovered_id) = self.hovered_dock_item.as_ref() - && let Some(r) = self.rectangles.get(hovered_id) - { - let target = (r.x + r.width / 2.0, r.y + r.height / 2.0); - let current = self.anim_hover_center.unwrap_or(target); - self.anim_hover_center = Some(( - current.0 + (target.0 - current.0) * alpha, - current.1 + (target.1 - current.1) * alpha, - )); - } else if self.anim_hover_intensity < 0.01 { - // Nothing hovered + intensity faded out → forget the - // animated center so the next hover seeds fresh. - self.anim_hover_center = None; - } - } Message::OpenActive => { let create_new = self.overflow_active_popup.is_none(); let mut cmds = vec![self.close_popups()]; @@ -1939,9 +1763,7 @@ impl cosmic::Application for CosmicAppList { .filter(|(info, _)| self.is_on_current_monitor_and_workspace(info)) .any(|y| focused_item.contains(&y.0.foreign_toplevel)); - let dock_id = dock_item.id; - let icon_scale = self.icon_scale_for(&DockItemId::from(dock_id)); - let tooltip = self.core + self.core .applet .applet_tooltip::( dock_item.as_icon( @@ -1954,7 +1776,6 @@ impl cosmic::Application for CosmicAppList { dot_radius, self.core.main_window_id().unwrap(), Some(&|info| self.is_on_current_monitor_and_workspace(info)), - icon_scale, ), dock_item .desktop_info @@ -1964,10 +1785,7 @@ impl cosmic::Application for CosmicAppList { self.popup.is_some(), Message::Surface, None, - ); - cosmic::widget::mouse_area(tooltip) - .on_enter(Message::DockItemHover(Some(DockItemId::from(dock_id)))) - .on_exit(Message::DockItemHover(None)) + ) .into() }) .collect(); @@ -2018,10 +1836,6 @@ impl cosmic::Application for CosmicAppList { dot_radius, self.core.main_window_id().unwrap(), Some(&|info| self.is_on_current_monitor_and_workspace(info)), - // Yoda: no magnification on DnD-preview icons — these - // float around as the user drags, so static 1.0 is - // less visually confusing. - 1.0, ), ); } else if self.is_listening_for_dnd && self.pinned_list.is_empty() { @@ -2061,10 +1875,8 @@ impl cosmic::Application for CosmicAppList { .iter() .filter(|(info, _)| self.is_on_current_monitor_and_workspace(info)) .any(|y| focused_item.contains(&y.0.foreign_toplevel)); - let dock_id = dock_item.id; - let icon_scale = self.icon_scale_for(&DockItemId::from(dock_id)); - let tooltip = self.core + self.core .applet .applet_tooltip( dock_item.as_icon( @@ -2077,7 +1889,6 @@ impl cosmic::Application for CosmicAppList { dot_radius, self.core.main_window_id().unwrap(), Some(&|info| self.is_on_current_monitor_and_workspace(info)), - icon_scale, ), dock_item .desktop_info @@ -2087,10 +1898,7 @@ impl cosmic::Application for CosmicAppList { self.popup.is_some(), Message::Surface, None, - ); - cosmic::widget::mouse_area(tooltip) - .on_enter(Message::DockItemHover(Some(DockItemId::from(dock_id)))) - .on_exit(Message::DockItemHover(None)) + ) .into() }) .collect(); @@ -2505,10 +2313,6 @@ impl cosmic::Application for CosmicAppList { dot_radius, id, Some(&|info| self.is_on_current_monitor_and_workspace(info)), - // Yoda: icons in the overflow popup are - // already smaller-grid — keep them at 1.0 - // so the popup doesn't reshuffle on hover. - 1.0, ), dock_item .desktop_info @@ -2617,10 +2421,6 @@ impl cosmic::Application for CosmicAppList { dot_radius, id, Some(&|info| self.is_on_current_monitor_and_workspace(info)), - // Yoda: popup icons stay at 1.0 (hover - // magnification is applied to the main - // dock row only). - 1.0, ), dock_item .desktop_info @@ -2679,21 +2479,7 @@ impl cosmic::Application for CosmicAppList { } fn subscription(&self) -> Subscription { - // Yoda: ~60fps animation ticks for the fisheye magnification. - // Only emitted when an animation is actually in progress (hover - // intensity >0 OR still fading out) — keeps the panel idle when - // the pointer is nowhere near the dock. - let anim_active = self.hovered_dock_item.is_some() - || self.anim_hover_intensity > 0.001; - let anim_subscription = if anim_active { - cosmic::iced::time::every(std::time::Duration::from_millis(16)) - .map(Message::AnimTick) - } else { - Subscription::none() - }; - Subscription::batch([ - anim_subscription, wayland_subscription().map(Message::Wayland), listen_with(|e, _, id| match e { cosmic::iced::core::Event::PlatformSpecific(event::PlatformSpecific::Wayland( diff --git a/cosmic-app-list/src/wayland_handler.rs b/cosmic-app-list/src/wayland_handler.rs index d27409dc..03c16973 100644 --- a/cosmic-app-list/src/wayland_handler.rs +++ b/cosmic-app-list/src/wayland_handler.rs @@ -388,10 +388,7 @@ impl CaptureData { }, ) .unwrap(); - if let Err(err) = self.conn.flush() { - tracing::error!("Wayland flush failed during screencopy session create: {err}"); - return None; - } + self.conn.flush().unwrap(); let formats = session .wait_while(|data| data.formats.is_none()) @@ -440,10 +437,7 @@ impl CaptureData { session: capture_session.clone(), }, ); - if let Err(err) = self.conn.flush() { - tracing::error!("Wayland flush failed during screencopy capture: {err}"); - return None; - } + self.conn.flush().unwrap(); // TODO: wait for server to release buffer? let res = session @@ -715,10 +709,7 @@ pub(crate) fn wayland_handler( if app_data.exit { break; } - if let Err(err) = event_loop.dispatch(None, &mut app_data) { - tracing::error!("Wayland event loop terminated: {err}"); - break; - } + event_loop.dispatch(None, &mut app_data).unwrap(); } } diff --git a/cosmic-applet-audio/src/lib.rs b/cosmic-applet-audio/src/lib.rs index 78667f5f..5f49d0f9 100644 --- a/cosmic-applet-audio/src/lib.rs +++ b/cosmic-applet-audio/src/lib.rs @@ -487,31 +487,11 @@ impl cosmic::Application for Audio { .icon_button(self.output_icon_name()) .on_press_down(Message::TogglePopup); - const WHEEL_STEP: f32 = 5.0; // 5% par cran logique - // Wayland axis_v120 envoie un cran physique en rafale de plusieurs - // ScrollDelta::Pixels (5–8 events ~15–20px), pour ~120px par cran. On - // accumule ces sub-events dans un thread_local et on n'émet qu'au - // passage d'un seuil — sinon `signum()` faisait croire que chaque - // sub-event = un cran, et un seul cran physique faisait chuter le - // volume jusqu'à 0 ("coupe le son"). - const PIXEL_THRESHOLD: f32 = 15.0; // px par cran logique - std::thread_local! { - static PIXEL_ACC: std::cell::Cell = const { std::cell::Cell::new(0.0) }; - } + const WHEEL_STEP: f32 = 5.0; // 5% per wheel event let btn = crate::mouse_area::MouseArea::new(btn).on_mouse_wheel(|delta| { let scroll_vector = match delta { - iced::mouse::ScrollDelta::Lines { y, .. } => { - PIXEL_ACC.with(|a| a.set(0.0)); - y * WHEEL_STEP - } - iced::mouse::ScrollDelta::Pixels { y, .. } => { - PIXEL_ACC.with(|acc_cell| { - let acc = acc_cell.get() + y; - let steps = (acc / PIXEL_THRESHOLD).trunc(); - acc_cell.set(acc - steps * PIXEL_THRESHOLD); - steps * WHEEL_STEP - }) - } + iced::mouse::ScrollDelta::Lines { y, .. } => y.signum() * WHEEL_STEP, // -1/0/1 + iced::mouse::ScrollDelta::Pixels { y, .. } => y.signum(), // -1/0/1 }; if scroll_vector == 0.0 { return Message::Ignore; @@ -519,7 +499,7 @@ impl cosmic::Application for Audio { let new_volume = (self.model.sink_volume as f64 + (scroll_vector as f64)) .clamp(0.0, self.max_sink_volume as f64); - Message::SetSinkVolume(new_volume.round() as u32) + Message::SetSinkVolume(new_volume as u32) }); let playback_buttons = (!self.core.applet.suggested_bounds.as_ref().is_some_and(|c| { diff --git a/cosmic-applet-notifications/src/lib.rs b/cosmic-applet-notifications/src/lib.rs index f759fd88..438696b9 100644 --- a/cosmic-applet-notifications/src/lib.rs +++ b/cosmic-applet-notifications/src/lib.rs @@ -16,7 +16,7 @@ use cosmic::{ Alignment, Length, Subscription, advanced::text::{Ellipsize, EllipsizeHeightLimit}, platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup}, - widget::{self, column, row}, + widget::{self, column, rich_text, row}, window, }, surface, theme, @@ -26,7 +26,7 @@ use cosmic::{ use cosmic::iced::futures::executor::block_on; use cosmic_notifications_config::NotificationsConfig; -use cosmic_notifications_util::{ActionId, Image, Notification}; +use cosmic_notifications_util::{ActionId, Image, Notification, markup}; use std::{borrow::Cow, collections::HashMap, path::PathBuf, sync::LazyLock}; use subscriptions::notifications::{self, NotificationsAppletProxy}; use tokio::sync::mpsc::Sender; @@ -456,8 +456,11 @@ impl cosmic::Application for Notifications { column![ text::body(n.summary.lines().next().unwrap_or_default()) .width(Length::Fill), - text::caption(n.body.lines().next().unwrap_or_default()) - .width(Length::Fill) + Element::from( + rich_text(markup::html_to_spans(&n.body)) + .size(12.0) + .width(Length::Fill) + ) ] ) .width(Length::Fill), diff --git a/cosmic-applets/src/main.rs b/cosmic-applets/src/main.rs index dfd541d1..f62bbf21 100644 --- a/cosmic-applets/src/main.rs +++ b/cosmic-applets/src/main.rs @@ -12,44 +12,26 @@ fn main() -> cosmic::iced::Result { }; let start = applet.rfind('/').map_or(0, |v| v + 1); - let cmd = applet.as_str()[start..].to_string(); + let cmd = &applet.as_str()[start..]; tracing::info!("Starting `{cmd}` with version {VERSION}"); - let cmd_for_run = cmd.clone(); - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || { - match cmd_for_run.as_str() { - "cosmic-app-list" => cosmic_app_list::run(), - "cosmic-applet-a11y" => cosmic_applet_a11y::run(), - "cosmic-applet-audio" => cosmic_applet_audio::run(), - "cosmic-applet-battery" => cosmic_applet_battery::run(), - "cosmic-applet-bluetooth" => cosmic_applet_bluetooth::run(), - "cosmic-applet-minimize" => cosmic_applet_minimize::run(), - "cosmic-applet-network" => cosmic_applet_network::run(), - "cosmic-applet-notifications" => cosmic_applet_notifications::run(), - "cosmic-applet-power" => cosmic_applet_power::run(), - "cosmic-applet-status-area" => cosmic_applet_status_area::run(), - "cosmic-applet-tiling" => cosmic_applet_tiling::run(), - "cosmic-applet-time" => cosmic_applet_time::run(), - "cosmic-applet-workspaces" => cosmic_applet_workspaces::run(), - "cosmic-applet-input-sources" => cosmic_applet_input_sources::run(), - "cosmic-panel-button" => cosmic_panel_button::run(), - _ => Ok(()), - } - })); - - match result { - Ok(r) => r, - Err(payload) => { - let msg = payload - .downcast_ref::<&str>() - .map(|s| s.to_string()) - .or_else(|| payload.downcast_ref::().cloned()) - .unwrap_or_else(|| "".to_string()); - tracing::error!( - "`{cmd}` panicked (likely compositor disconnect), exiting cleanly: {msg}" - ); - Ok(()) - } + match cmd { + "cosmic-app-list" => cosmic_app_list::run(), + "cosmic-applet-a11y" => cosmic_applet_a11y::run(), + "cosmic-applet-audio" => cosmic_applet_audio::run(), + "cosmic-applet-battery" => cosmic_applet_battery::run(), + "cosmic-applet-bluetooth" => cosmic_applet_bluetooth::run(), + "cosmic-applet-minimize" => cosmic_applet_minimize::run(), + "cosmic-applet-network" => cosmic_applet_network::run(), + "cosmic-applet-notifications" => cosmic_applet_notifications::run(), + "cosmic-applet-power" => cosmic_applet_power::run(), + "cosmic-applet-status-area" => cosmic_applet_status_area::run(), + "cosmic-applet-tiling" => cosmic_applet_tiling::run(), + "cosmic-applet-time" => cosmic_applet_time::run(), + "cosmic-applet-workspaces" => cosmic_applet_workspaces::run(), + "cosmic-applet-input-sources" => cosmic_applet_input_sources::run(), + "cosmic-panel-button" => cosmic_panel_button::run(), + _ => Ok(()), } } diff --git a/redeploy.sh b/redeploy.sh deleted file mode 100755 index 7138a806..00000000 --- a/redeploy.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash -# Recompile et déploie le fork local cosmic-applets (multiplexor cosmic-applets + -# binaire séparé cosmic-app-list). -# -# Cibles : /usr/local/bin/cosmic-applets et /usr/local/bin/cosmic-app-list -# (binaires compilés manuellement, hors pacman, qui priment sur ceux du paquet -# via $PATH /usr/local/bin avant /usr/bin). -# -# Branche de déploiement : yoda-dock-magnification (contient les commits -# magnification + le fix Wayland error handling). - -set -euo pipefail - -REPO="/home/lionel/Devels/cosmic-applets" -TARGETS=( - "cosmic-applets" - "cosmic-app-list" - "cosmic-panel-button" -) -BIN_DIR="/usr/local/bin" - -cd "$REPO" - -echo "==> Branche actuelle : $(git branch --show-current)" -echo "==> Build release (workspace)..." -cargo build --release --workspace - -STAMP=$(date +%Y%m%d-%H%M%S) -for bin in "${TARGETS[@]}"; do - src="$REPO/target/release/$bin" - dst="$BIN_DIR/$bin" - - if [[ ! -f "$src" ]]; then - echo "!! Binaire absent après build : $src" >&2 - exit 1 - fi - - if [[ -f "$dst" ]]; then - echo "==> Backup $dst" - sudo cp -a "$dst" "${dst}.bak.${STAMP}" - fi - - echo "==> Install $src -> $dst" - sudo install -m755 "$src" "$dst" -done - -echo "==> Kill des process applet en cours (cosmic-panel relancera à la demande)" -pkill -u "$USER" -f "/usr/local/bin/cosmic-applet" || true -pkill -u "$USER" -f "/usr/local/bin/cosmic-app-list" || true -pkill -u "$USER" -f "/usr/local/bin/cosmic-panel-button" || true - -echo "==> Vérif" -for bin in "${TARGETS[@]}"; do - file "$BIN_DIR/$bin" -done -echo "OK — relogin recommandé pour repartir sur des process applet propres."