From 77262dd0af43ee5e958135b359c3f0e07055a7fe Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Sun, 19 Apr 2026 15:14:22 +0200 Subject: [PATCH 01/75] perf(malloc): throttle malloc_trim to 1 Hz in hot paths malloc_trim(0) was called at the end of every update() and view(), reaching 60-200 Hz during typical scrolling, resize, or animation. Each call walks the glibc heap (10 us to several ms depending on fragmentation) and could consume a substantial fraction of the frame budget in worst cases. Throttle trim() to once per second using a thread-local Instant, preserving the existing API. RSS stays bounded (1 Hz is enough to release collectable pages soon after) while per-frame cost becomes a single thread-local check plus a duration comparison. No call-site changes required; the three existing trim(0) invocations in src/app/cosmic.rs (update, view multi-window, view single-window) now fall under the throttle transparently. --- src/malloc.rs | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/malloc.rs b/src/malloc.rs index b99a66f4..bc5e8353 100644 --- a/src/malloc.rs +++ b/src/malloc.rs @@ -1,21 +1,49 @@ // Copyright 2025 System76 // SPDX-License-Identifier: MPL-2.0 +use std::cell::Cell; use std::os::raw::c_int; +use std::time::{Duration, Instant}; const M_MMAP_THRESHOLD: c_int = -3; +/// Minimum interval between two actual `malloc_trim` calls. +/// +/// `trim` is called at the end of every `update()` and `view()`, which can +/// reach 60-200 Hz during typical scrolling, resize, or animation. Each +/// `malloc_trim` walks the glibc heap (10 µs to several ms depending on +/// fragmentation), so calling it at render frequency can consume a +/// substantial fraction of the frame budget. Throttling to 1 Hz keeps RSS +/// bounded while removing the per-frame syscall from the hot path. +const TRIM_MIN_INTERVAL: Duration = Duration::from_millis(1000); + +thread_local! { + static LAST_TRIM: Cell> = const { Cell::new(None) }; +} + unsafe extern "C" { fn malloc_trim(pad: usize); fn mallopt(param: c_int, value: c_int) -> c_int; } +/// Throttled wrapper over `malloc_trim`. Safe to call at render frequency: +/// consecutive calls within `TRIM_MIN_INTERVAL` (per-thread) skip the syscall. #[inline] pub fn trim(pad: usize) { - unsafe { - malloc_trim(pad); - } + LAST_TRIM.with(|last| { + let now = Instant::now(); + let should_trim = match last.get() { + None => true, + Some(prev) => now.duration_since(prev) >= TRIM_MIN_INTERVAL, + }; + if should_trim { + last.set(Some(now)); + unsafe { + malloc_trim(pad); + } + } + }); } /// Prevents glibc from hoarding memory via memory fragmentation. From 1d98eee6dea9e679f8c950df4c5b6e176584b65a Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Sun, 19 Apr 2026 16:29:02 +0200 Subject: [PATCH 02/75] perf(widget): avoid VecDeque clone in segmented_button/table Model::clear MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model::clear() cloned the entire order VecDeque to iterate while remove() mutated it, producing an O(n) allocation proportional to the number of items — needless on a clear() which is going to drop all of them anyway. Replace the clone with std::mem::take(&mut self.order): we iterate the taken VecDeque (transferring ownership), and the inner self.order.remove(index) in each remove() call now finds position()==None and no-ops, since self.order has been swapped with an empty default. Same semantics, zero allocation. Noticeable on large nav/table models (>100 items) and on apps that reset state frequently (settings pages, file lists, context menus). --- src/widget/segmented_button/model/mod.rs | 5 ++++- src/widget/table/model/mod.rs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/widget/segmented_button/model/mod.rs b/src/widget/segmented_button/model/mod.rs index e0dd8c54..fff1cf6b 100644 --- a/src/widget/segmented_button/model/mod.rs +++ b/src/widget/segmented_button/model/mod.rs @@ -132,7 +132,10 @@ where /// ``` #[inline] pub fn clear(&mut self) { - for entity in self.order.clone() { + // `remove()` mutates `self.order`, so transfer ownership first: + // the inner `self.order.remove(index)` then no-ops because + // `position()` can't find the entity in the empty VecDeque. + for entity in std::mem::take(&mut self.order) { self.remove(entity); } } diff --git a/src/widget/table/model/mod.rs b/src/widget/table/model/mod.rs index d6250eaf..749860f8 100644 --- a/src/widget/table/model/mod.rs +++ b/src/widget/table/model/mod.rs @@ -100,7 +100,10 @@ where /// model.clear(); /// ``` pub fn clear(&mut self) { - for entity in self.order.clone() { + // `remove()` mutates `self.order`, so transfer ownership first: + // the inner `self.order.remove(index)` then no-ops because + // `position()` can't find the entity in the empty VecDeque. + for entity in std::mem::take(&mut self.order) { self.remove(entity); } } From 6dba6a2561c84e5a1bf527c56856e7d78fb3c094 Mon Sep 17 00:00:00 2001 From: KENZ Date: Mon, 20 Apr 2026 00:02:26 +0900 Subject: [PATCH 03/75] fix(iced): make IME event forwarding to sctk optional pulls in the change to make IME events forwarding to sctk optionally --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 78caabba..aa2a870c 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 78caabba7ef91cd1030da6f70b41d266704ffece +Subproject commit aa2a870c357284630045b05186fa7a6313c27693 From dc84488cd87376b66d9d71275362366355bb228d Mon Sep 17 00:00:00 2001 From: KENZ Date: Mon, 20 Apr 2026 00:03:20 +0900 Subject: [PATCH 04/75] fix(iced): double IME commit on GNOME desktop --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d73da2dc..2a9684fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ desktop-systemd-scope = ["desktop", "dep:zbus"] # Enables keycode serialization serde-keycode = ["iced_core/serde"] # Prevents multiple separate process instances. -single-instance = ["zbus/blocking-api", "ron"] +single-instance = ["iced_winit/single-instance", "zbus/blocking-api", "ron"] # smol async runtime smol = ["dep:smol", "iced/smol", "zbus?/async-io", "rfd?/async-std"] tokio = [ From 4d39cf3d7b340cc75a508df42a3fa037e652dcde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Sun, 19 Apr 2026 00:30:20 +0200 Subject: [PATCH 05/75] improv(progress_bar): unify indeterminate animations --- src/widget/progress_bar/animation.rs | 73 +++++++ src/widget/progress_bar/circular.rs | 303 +++++---------------------- src/widget/progress_bar/linear.rs | 169 +++++---------- src/widget/progress_bar/mod.rs | 1 + 4 files changed, 178 insertions(+), 368 deletions(-) create mode 100644 src/widget/progress_bar/animation.rs diff --git a/src/widget/progress_bar/animation.rs b/src/widget/progress_bar/animation.rs new file mode 100644 index 00000000..52a3432a --- /dev/null +++ b/src/widget/progress_bar/animation.rs @@ -0,0 +1,73 @@ +use crate::anim::smootherstep; +use iced::time::Instant; +use std::time::Duration; + +#[derive(Clone, Copy)] +pub struct Animation { + expanding: bool, + start: Instant, + last: Instant, + offset: u32, +} + +impl Default for Animation { + fn default() -> Self { + let now = Instant::now(); + Self { + expanding: true, + start: now, + last: now, + offset: 0, + } + } +} + +impl Animation { + pub fn timed_transition( + &self, + cycle_duration: Duration, + rotation_duration: Duration, + wrap: f32, + now: Instant, + ) -> Self { + let additional = ((now - self.last).as_secs_f32() / rotation_duration.as_secs_f32() + * u32::MAX as f32) as u32; + let new_offset = self.offset.wrapping_add(additional); + + if !cycle_duration.is_zero() && now.duration_since(self.start) > cycle_duration { + let offset = if self.expanding { + new_offset + } else { + new_offset.wrapping_add((wrap * u32::MAX as f32) as u32) + }; + Self { + expanding: !self.expanding, + start: now, + last: now, + offset, + } + } else { + Self { + last: now, + offset: new_offset, + ..*self + } + } + } + + pub fn bar_positions(&self, cycle_duration: Duration, min: f32, wrap: f32) -> (f32, f32) { + let offset = self.offset as f32 / u32::MAX as f32; + let progress = if !cycle_duration.is_zero() { + smootherstep( + self.last.duration_since(self.start).as_secs_f32() / cycle_duration.as_secs_f32(), + ) + } else { + 1.0 + }; + if self.expanding { + (offset, offset + min + wrap * progress) + } else { + (offset + wrap * progress, offset + min + wrap) + } + } +} diff --git a/src/widget/progress_bar/circular.rs b/src/widget/progress_bar/circular.rs index fa8c38fe..684467f1 100644 --- a/src/widget/progress_bar/circular.rs +++ b/src/widget/progress_bar/circular.rs @@ -1,12 +1,11 @@ //! Show a circular progress indicator. +use super::animation::Animation; use super::style::StyleSheet; -use crate::anim::smootherstep; use iced::advanced::layout; use iced::advanced::renderer; use iced::advanced::widget::tree::{self, Tree}; use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; use iced::mouse; -use iced::time::Instant; use iced::widget::canvas; use iced::window; use iced::{Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector}; @@ -23,7 +22,7 @@ where { size: f32, bar_height: f32, - style: ::Style, + style: Theme::Style, cycle_duration: Duration, rotation_duration: Duration, progress: Option, @@ -38,7 +37,7 @@ where Circular { size: 40.0, bar_height: 4.0, - style: ::Style::default(), + style: Theme::Style::default(), cycle_duration: Duration::from_millis(1500), rotation_duration: Duration::from_secs(2), progress: None, @@ -58,7 +57,7 @@ where } /// Sets the style variant of this [`Circular`]. - pub fn style(mut self, style: ::Style) -> Self { + pub fn style(mut self, style: Theme::Style) -> Self { self.style = style; self } @@ -70,7 +69,7 @@ where } /// Sets the base rotation duration of this [`Circular`]. This is the duration that a full - /// rotation would take if the cycle rotation were set to 0.0 (no expanding or contracting) + /// rotation would take if the cycle duration were set to 0.0 (no expanding or contracting) pub fn rotation_duration(mut self, duration: Duration) -> Self { self.rotation_duration = duration; self @@ -82,10 +81,10 @@ where self } - fn min_wrap_angle(&self, track_radius: f32) -> (f32, f32) { + fn min_wrap(&self, track_radius: f32) -> (f32, f32) { let cap_angle = self.bar_height / track_radius; let gap = MIN_ANGLE.0.max(cap_angle); - (gap - cap_angle, 2.0 * PI - gap * 2.0) + ((gap - cap_angle) / (2.0 * PI), 1.0 - gap / PI) } } @@ -98,120 +97,6 @@ where } } -#[derive(Clone, Copy)] -enum Animation { - Expanding { - start: Instant, - progress: f32, - rotation: u32, - last: Instant, - }, - Contracting { - start: Instant, - progress: f32, - rotation: u32, - last: Instant, - }, -} - -impl Default for Animation { - fn default() -> Self { - Self::Expanding { - start: Instant::now(), - progress: 0.0, - rotation: 0, - last: Instant::now(), - } - } -} - -impl Animation { - fn next(&self, additional_rotation: u32, wrap_angle: f32, now: Instant) -> Self { - match self { - Self::Expanding { rotation, .. } => Self::Contracting { - start: now, - progress: 0.0, - rotation: rotation.wrapping_add(additional_rotation), - last: now, - }, - Self::Contracting { rotation, .. } => Self::Expanding { - start: now, - progress: 0.0, - rotation: rotation.wrapping_add( - (f64::from((wrap_angle) / (2.0 * PI)) * f64::from(u32::MAX)) as u32, - ), - last: now, - }, - } - } - - fn start(&self) -> Instant { - match self { - Self::Expanding { start, .. } | Self::Contracting { start, .. } => *start, - } - } - - fn last(&self) -> Instant { - match self { - Self::Expanding { last, .. } | Self::Contracting { last, .. } => *last, - } - } - - fn timed_transition( - &self, - cycle_duration: Duration, - rotation_duration: Duration, - wrap_angle: f32, - now: Instant, - ) -> Self { - let elapsed = now.duration_since(self.start()); - let additional_rotation = ((now - self.last()).as_secs_f32() - / rotation_duration.as_secs_f32() - * (u32::MAX) as f32) as u32; - - match elapsed { - elapsed if elapsed > cycle_duration => self.next(additional_rotation, wrap_angle, now), - _ => self.with_elapsed(cycle_duration, additional_rotation, elapsed, now), - } - } - - fn with_elapsed( - &self, - cycle_duration: Duration, - additional_rotation: u32, - elapsed: Duration, - now: Instant, - ) -> Self { - let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32(); - match self { - Self::Expanding { - start, rotation, .. - } => Self::Expanding { - start: *start, - progress, - rotation: rotation.wrapping_add(additional_rotation), - last: now, - }, - Self::Contracting { - start, rotation, .. - } => Self::Contracting { - start: *start, - progress, - rotation: rotation.wrapping_add(additional_rotation), - last: now, - }, - } - } - - fn rotation(&self) -> f32 { - match self { - Self::Expanding { rotation, .. } | Self::Contracting { rotation, .. } => { - *rotation as f32 / u32::MAX as f32 - } - } - } -} - #[derive(Default)] struct State { animation: Animation, @@ -261,25 +146,20 @@ where ) { let state = tree.state.downcast_mut::(); if self.progress.is_some() { - if !float_cmp::approx_eq!( - f32, - state.progress.unwrap_or_default(), - self.progress.unwrap_or_default() - ) { + if state.progress != self.progress { state.progress = self.progress; state.cache.clear(); } return; } if let Event::Window(window::Event::RedrawRequested(now)) = event { - let (_, wrap_angle) = self.min_wrap_angle(self.size / 2.0 - self.bar_height); + let (_, wrap) = self.min_wrap(self.size / 2.0 - self.bar_height); state.animation = state.animation.timed_transition( self.cycle_duration, self.rotation_duration, - wrap_angle, + wrap, *now, ); - state.cache.clear(); shell.request_redraw(); } @@ -299,8 +179,7 @@ where let state = tree.state.downcast_ref::(); let bounds = layout.bounds(); - let custom_style = - ::appearance(theme, &self.style, self.progress.is_some(), true); + let custom_style = Theme::appearance(theme, &self.style, self.progress.is_some(), true); let geometry = state.cache.draw(renderer, bounds.size(), |frame| { let track_radius = frame.width() / 2.0 - self.bar_height; @@ -313,133 +192,65 @@ where .with_width(self.bar_height), ); - if let Some(progress) = self.progress { - // outer border - if let Some(border_color) = custom_style.border_color { - let border_path = - canvas::Path::circle(frame.center(), track_radius + self.bar_height / 2.0); + // Converts a track fraction to an angle in radians, with 0 being top of circle + let to_angle = |t: f32| t * 2.0 * PI - PI / 2.0; - frame.stroke( - &border_path, - canvas::Stroke::default() - .with_color(border_color) - .with_width(1.0), - ); - } - - // inner border - if let Some(border_color) = custom_style.border_color { - let border_path = - canvas::Path::circle(frame.center(), track_radius - self.bar_height / 2.0); - - frame.stroke( - &border_path, - canvas::Stroke::default() - .with_color(border_color) - .with_width(1.0), - ); - } - - // bar - let mut builder = canvas::path::Builder::new(); - - builder.arc(canvas::path::Arc { - center: frame.center(), - radius: track_radius, - start_angle: Radians(-PI / 2.0), - end_angle: Radians(-PI / 2.0 + progress * 2.0 * PI), - }); - - let bar_path = builder.build(); - - frame.stroke( - &bar_path, - canvas::Stroke::default() - .with_color(custom_style.bar_color) - .with_width(self.bar_height), - ); - - let mut builder = canvas::path::Builder::new(); - - // get center of end of arc for rounded cap - let end_angle = -PI / 2.0 + progress * 2.0 * PI; - let end_center = - frame.center() + Vector::new(end_angle.cos(), end_angle.sin()) * track_radius; - builder.arc(canvas::path::Arc { - center: end_center, - radius: self.bar_height / 2.0, - start_angle: Radians(end_angle), - end_angle: Radians(end_angle + PI), - }); - - // get center of start of arc for rounded cap - let start_angle = -PI / 2.0; - let start_center = frame.center() - + Vector::new(start_angle.cos(), start_angle.sin()) * track_radius; - builder.arc(canvas::path::Arc { - center: start_center, - radius: self.bar_height / 2.0, - start_angle: Radians(start_angle - PI), - end_angle: Radians(start_angle), - }); - - let cap_path = builder.build(); - frame.fill(&cap_path, custom_style.bar_color); - } else { - let mut builder = canvas::path::Builder::new(); - - let start = state.animation.rotation() * 2.0 * PI; - let (min_angle, wrap_angle) = self.min_wrap_angle(track_radius); - let (start_angle, end_angle) = match state.animation { - Animation::Expanding { progress, .. } => ( - start, - start + min_angle + wrap_angle * smootherstep(progress), - ), - Animation::Contracting { progress, .. } => ( - start + wrap_angle * smootherstep(progress), - start + min_angle + wrap_angle, - ), + let draw_cap = |frame: &mut canvas::Frame, t: f32, flip: bool| { + let angle = to_angle(t); + let center = frame.center() + Vector::new(angle.cos(), angle.sin()) * track_radius; + let (start_angle, end_angle) = if flip { + (angle - PI, angle) + } else { + (angle, angle + PI) }; + let mut builder = canvas::path::Builder::new(); builder.arc(canvas::path::Arc { - center: frame.center(), - radius: track_radius, + center, + radius: self.bar_height / 2.0, start_angle: Radians(start_angle), end_angle: Radians(end_angle), }); + frame.fill(&builder.build(), custom_style.bar_color); + }; - let bar_path = builder.build(); - + let draw_bar = |frame: &mut canvas::Frame, start: f32, end: f32| { + let mut builder = canvas::path::Builder::new(); + builder.arc(canvas::path::Arc { + center: frame.center(), + radius: track_radius, + start_angle: Radians(to_angle(start)), + end_angle: Radians(to_angle(end)), + }); frame.stroke( - &bar_path, + &builder.build(), canvas::Stroke::default() .with_color(custom_style.bar_color) .with_width(self.bar_height), ); + draw_cap(frame, end, false); + draw_cap(frame, start, true); + }; - let mut builder = canvas::path::Builder::new(); - - // get center of end of arc for rounded cap - let end_center = - frame.center() + Vector::new(end_angle.cos(), end_angle.sin()) * track_radius; - builder.arc(canvas::path::Arc { - center: end_center, - radius: self.bar_height / 2.0, - start_angle: Radians(end_angle), - end_angle: Radians(end_angle + PI), - }); - - // get center of start of arc for rounded cap - let start_center = frame.center() - + Vector::new(start_angle.cos(), start_angle.sin()) * track_radius; - builder.arc(canvas::path::Arc { - center: start_center, - radius: self.bar_height / 2.0, - start_angle: Radians(start_angle - PI), - end_angle: Radians(start_angle), - }); - - let cap_path = builder.build(); - frame.fill(&cap_path, custom_style.bar_color); + if let Some(progress) = self.progress { + if let Some(border_color) = custom_style.border_color { + for radius_offset in [self.bar_height / 2.0, -(self.bar_height / 2.0)] { + let border_path = + canvas::Path::circle(frame.center(), track_radius + radius_offset); + frame.stroke( + &border_path, + canvas::Stroke::default() + .with_color(border_color) + .with_width(1.0), + ); + } + } + draw_bar(frame, 0.0, progress); + } else { + let (min, wrap) = self.min_wrap(track_radius); + let (start, end) = state + .animation + .bar_positions(self.cycle_duration, min, wrap); + draw_bar(frame, start, end); } }); diff --git a/src/widget/progress_bar/linear.rs b/src/widget/progress_bar/linear.rs index 226b2b5f..881d59c3 100644 --- a/src/widget/progress_bar/linear.rs +++ b/src/widget/progress_bar/linear.rs @@ -1,19 +1,19 @@ //! Show a linear progress indicator. +use super::animation::Animation; +use super::style::StyleSheet; use iced::advanced::layout; -use iced::advanced::renderer::{self, Quad}; +use iced::advanced::renderer; use iced::advanced::widget::tree::{self, Tree}; use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; use iced::mouse; -use iced::time::Instant; use iced::window; use iced::{Background, Element, Event, Length, Rectangle, Size}; -use crate::anim::smootherstep; - -use super::style::StyleSheet; - use std::time::Duration; +const MIN_LENGTH: f32 = 0.15; +const WRAP_LENGTH: f32 = 0.618; // avoids animation repetition + #[must_use] pub struct Linear where @@ -23,6 +23,7 @@ where girth: Length, style: Theme::Style, cycle_duration: Duration, + traversal_duration: Duration, progress: Option, } @@ -37,6 +38,7 @@ where girth: Length::Fixed(4.0), style: Theme::Style::default(), cycle_duration: Duration::from_millis(1500), + traversal_duration: Duration::from_secs(2), progress: None, } } @@ -65,6 +67,13 @@ where self } + /// Sets the base traversal duration of this [`Linear`]. This is the duration that a full + /// traversal would take if the cycle duration were set to 0.0 (no expanding or contracting) + pub fn traversal_duration(mut self, duration: Duration) -> Self { + self.traversal_duration = duration; + self + } + /// Override the default behavior by providing a determinate progress value between `0.0` and `1.0`. pub fn progress(mut self, progress: f32) -> Self { self.progress = Some(progress.clamp(0.0, 1.0)); @@ -81,65 +90,6 @@ where } } -#[derive(Clone, Copy)] -enum State { - Expanding { start: Instant, progress: f32 }, - Contracting { start: Instant, progress: f32 }, -} - -impl Default for State { - fn default() -> Self { - Self::Expanding { - start: Instant::now(), - progress: 0.0, - } - } -} - -impl State { - fn next(&self, now: Instant) -> Self { - match self { - Self::Expanding { .. } => Self::Contracting { - start: now, - progress: 0.0, - }, - Self::Contracting { .. } => Self::Expanding { - start: now, - progress: 0.0, - }, - } - } - - fn start(&self) -> Instant { - match self { - Self::Expanding { start, .. } | Self::Contracting { start, .. } => *start, - } - } - - fn timed_transition(&self, cycle_duration: Duration, now: Instant) -> Self { - let elapsed = now.duration_since(self.start()); - - match elapsed { - elapsed if elapsed > cycle_duration => self.next(now), - _ => self.with_elapsed(cycle_duration, elapsed), - } - } - - fn with_elapsed(&self, cycle_duration: Duration, elapsed: Duration) -> Self { - let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32(); - match self { - Self::Expanding { start, .. } => Self::Expanding { - start: *start, - progress, - }, - Self::Contracting { start, .. } => Self::Contracting { - start: *start, - progress, - }, - } - } -} - impl Widget for Linear where Message: Clone, @@ -147,11 +97,11 @@ where Renderer: advanced::Renderer, { fn tag(&self) -> tree::Tag { - tree::Tag::of::() + tree::Tag::of::() } fn state(&self) -> tree::State { - tree::State::new(State::default()) + tree::State::new(Animation::default()) } fn size(&self) -> Size { @@ -185,11 +135,15 @@ where return; } - let state = tree.state.downcast_mut::(); + let animation = tree.state.downcast_mut::(); if let Event::Window(window::Event::RedrawRequested(now)) = event { - *state = state.timed_transition(self.cycle_duration, *now); - + *animation = animation.timed_transition( + self.cycle_duration, + self.traversal_duration, + WRAP_LENGTH, + *now, + ); shell.request_redraw(); } } @@ -206,16 +160,11 @@ where ) { let bounds = layout.bounds(); let custom_style = theme.appearance(&self.style, self.progress.is_some(), false); - let state = tree.state.downcast_ref::(); + let animation = tree.state.downcast_ref::(); renderer.fill_quad( renderer::Quad { - bounds: Rectangle { - x: bounds.x, - y: bounds.y, - width: bounds.width, - height: bounds.height, - }, + bounds, border: iced::Border { width: if custom_style.border_color.is_some() { 1.0 @@ -231,37 +180,18 @@ where Background::Color(custom_style.track_color), ); - if let Some(progress) = self.progress { - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x, - y: bounds.y, - width: progress * bounds.width, - height: bounds.height, - }, - border: iced::Border { - width: 0., - color: iced::Color::TRANSPARENT, - radius: custom_style.border_radius.into(), - }, - snap: true, - ..renderer::Quad::default() - }, - Background::Color(custom_style.bar_color), - ); - } else { - match state { - State::Expanding { progress, .. } => renderer.fill_quad( + let mut draw_segment = |x: f32, width: f32| { + if width > 0.001 { + renderer.fill_quad( renderer::Quad { bounds: Rectangle { - x: bounds.x, + x: bounds.x + x * bounds.width, y: bounds.y, - width: smootherstep(*progress) * bounds.width, + width: width * bounds.width, height: bounds.height, }, border: iced::Border { - width: 0., + width: 0.0, color: iced::Color::TRANSPARENT, radius: custom_style.border_radius.into(), }, @@ -269,27 +199,22 @@ where ..renderer::Quad::default() }, Background::Color(custom_style.bar_color), - ), - - State::Contracting { progress, .. } => renderer.fill_quad( - Quad { - bounds: Rectangle { - x: bounds.x + smootherstep(*progress) * bounds.width, - y: bounds.y, - width: (1.0 - smootherstep(*progress)) * bounds.width, - height: bounds.height, - }, - border: iced::Border { - width: 0., - color: iced::Color::TRANSPARENT, - radius: custom_style.border_radius.into(), - }, - snap: true, - ..renderer::Quad::default() - }, - Background::Color(custom_style.bar_color), - ), + ); } + }; + + if let Some(progress) = self.progress { + draw_segment(0.0, progress); + } else { + let (bar_start, bar_end) = + animation.bar_positions(self.cycle_duration, MIN_LENGTH, WRAP_LENGTH); + let length = bar_end - bar_start; + let start = bar_start % 1.0; + let right_width = (1.0 - start).min(length); + let left_width = length - right_width; + + draw_segment(start, right_width); + draw_segment(0.0, left_width); } } } diff --git a/src/widget/progress_bar/mod.rs b/src/widget/progress_bar/mod.rs index 4e277b0a..cb24adac 100644 --- a/src/widget/progress_bar/mod.rs +++ b/src/widget/progress_bar/mod.rs @@ -1,3 +1,4 @@ +mod animation; pub mod circular; pub mod linear; pub mod style; From 8b4c8adec830d74802931b551cd34f5c046ea6a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:00:09 +0200 Subject: [PATCH 06/75] feat(progress_bar): animate determinate progress --- src/widget/progress_bar/animation.rs | 39 ++++++++++++++++-- src/widget/progress_bar/circular.rs | 50 +++++++++++------------ src/widget/progress_bar/linear.rs | 61 ++++++++++++++++------------ 3 files changed, 95 insertions(+), 55 deletions(-) diff --git a/src/widget/progress_bar/animation.rs b/src/widget/progress_bar/animation.rs index 52a3432a..6baf5071 100644 --- a/src/widget/progress_bar/animation.rs +++ b/src/widget/progress_bar/animation.rs @@ -2,6 +2,39 @@ use crate::anim::smootherstep; use iced::time::Instant; use std::time::Duration; +const LAG: f32 = 0.1; + +pub struct Progress { + pub current: f32, + last: Instant, +} + +impl Default for Progress { + fn default() -> Self { + Self { + current: 0.0, + last: Instant::now(), + } + } +} + +impl Progress { + /// Smoothly chases `target` using exponential decay. + /// Returns `true` if still animating and a redraw should be requested. + pub fn update(&mut self, target: f32, now: Instant) -> bool { + let dt = (now - self.last).as_secs_f32(); + self.last = now; + let next = self.current + (target - self.current) * (1.0 - (-dt / LAG).exp()); + if (next - target).abs() > 0.001 { + self.current = next; + true + } else { + self.current = target; + false + } + } +} + #[derive(Clone, Copy)] pub struct Animation { expanding: bool, @@ -26,12 +59,12 @@ impl Animation { pub fn timed_transition( &self, cycle_duration: Duration, - rotation_duration: Duration, + period: Duration, wrap: f32, now: Instant, ) -> Self { - let additional = ((now - self.last).as_secs_f32() / rotation_duration.as_secs_f32() - * u32::MAX as f32) as u32; + let additional = + ((now - self.last).as_secs_f32() / period.as_secs_f32() * u32::MAX as f32) as u32; let new_offset = self.offset.wrapping_add(additional); if !cycle_duration.is_zero() && now.duration_since(self.start) > cycle_duration { diff --git a/src/widget/progress_bar/circular.rs b/src/widget/progress_bar/circular.rs index 684467f1..42c8e642 100644 --- a/src/widget/progress_bar/circular.rs +++ b/src/widget/progress_bar/circular.rs @@ -1,5 +1,5 @@ //! Show a circular progress indicator. -use super::animation::Animation; +use super::animation::{Animation, Progress}; use super::style::StyleSheet; use iced::advanced::layout; use iced::advanced::renderer; @@ -24,7 +24,7 @@ where bar_height: f32, style: Theme::Style, cycle_duration: Duration, - rotation_duration: Duration, + period: Duration, progress: Option, } @@ -39,7 +39,7 @@ where bar_height: 4.0, style: Theme::Style::default(), cycle_duration: Duration::from_millis(1500), - rotation_duration: Duration::from_secs(2), + period: Duration::from_secs(2), progress: None, } } @@ -68,10 +68,10 @@ where self } - /// Sets the base rotation duration of this [`Circular`]. This is the duration that a full - /// rotation would take if the cycle duration were set to 0.0 (no expanding or contracting) - pub fn rotation_duration(mut self, duration: Duration) -> Self { - self.rotation_duration = duration; + /// Sets the base period of this [`Circular`]. This is the duration that a full rotation + /// would take if the cycle duration were set to 0.0 (no expanding or contracting) + pub fn period(mut self, duration: Duration) -> Self { + self.period = duration; self } @@ -101,7 +101,7 @@ where struct State { animation: Animation, cache: canvas::Cache, - progress: Option, + progress: Progress, } impl Widget for Circular @@ -145,23 +145,21 @@ where _viewport: &Rectangle, ) { let state = tree.state.downcast_mut::(); - if self.progress.is_some() { - if state.progress != self.progress { - state.progress = self.progress; - state.cache.clear(); - } - return; - } if let Event::Window(window::Event::RedrawRequested(now)) = event { - let (_, wrap) = self.min_wrap(self.size / 2.0 - self.bar_height); - state.animation = state.animation.timed_transition( - self.cycle_duration, - self.rotation_duration, - wrap, - *now, - ); - state.cache.clear(); - shell.request_redraw(); + if let Some(target) = self.progress { + if state.progress.update(target, *now) { + state.cache.clear(); + shell.request_redraw(); + } + } else { + let (_, wrap) = self.min_wrap(self.size / 2.0 - self.bar_height); + state.animation = + state + .animation + .timed_transition(self.cycle_duration, self.period, wrap, *now); + state.cache.clear(); + shell.request_redraw(); + } } } @@ -231,7 +229,7 @@ where draw_cap(frame, start, true); }; - if let Some(progress) = self.progress { + if self.progress.is_some() { if let Some(border_color) = custom_style.border_color { for radius_offset in [self.bar_height / 2.0, -(self.bar_height / 2.0)] { let border_path = @@ -244,7 +242,7 @@ where ); } } - draw_bar(frame, 0.0, progress); + draw_bar(frame, 0.0, state.progress.current); } else { let (min, wrap) = self.min_wrap(track_radius); let (start, end) = state diff --git a/src/widget/progress_bar/linear.rs b/src/widget/progress_bar/linear.rs index 881d59c3..7bcbb4f7 100644 --- a/src/widget/progress_bar/linear.rs +++ b/src/widget/progress_bar/linear.rs @@ -1,5 +1,5 @@ //! Show a linear progress indicator. -use super::animation::Animation; +use super::animation::{Animation, Progress}; use super::style::StyleSheet; use iced::advanced::layout; use iced::advanced::renderer; @@ -23,7 +23,7 @@ where girth: Length, style: Theme::Style, cycle_duration: Duration, - traversal_duration: Duration, + period: Duration, progress: Option, } @@ -38,7 +38,7 @@ where girth: Length::Fixed(4.0), style: Theme::Style::default(), cycle_duration: Duration::from_millis(1500), - traversal_duration: Duration::from_secs(2), + period: Duration::from_secs(2), progress: None, } } @@ -67,10 +67,10 @@ where self } - /// Sets the base traversal duration of this [`Linear`]. This is the duration that a full - /// traversal would take if the cycle duration were set to 0.0 (no expanding or contracting) - pub fn traversal_duration(mut self, duration: Duration) -> Self { - self.traversal_duration = duration; + /// Sets the base period of this [`Linear`]. This is the duration that a full traversal + /// would take if the cycle duration were set to 0.0 (no expanding or contracting) + pub fn period(mut self, duration: Duration) -> Self { + self.period = duration; self } @@ -90,6 +90,12 @@ where } } +#[derive(Default)] +struct State { + animation: Animation, + progress: Progress, +} + impl Widget for Linear where Message: Clone, @@ -97,11 +103,11 @@ where Renderer: advanced::Renderer, { fn tag(&self) -> tree::Tag { - tree::Tag::of::() + tree::Tag::of::() } fn state(&self) -> tree::State { - tree::State::new(Animation::default()) + tree::State::new(State::default()) } fn size(&self) -> Size { @@ -131,20 +137,21 @@ where shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) { - if self.progress.is_some() { - return; - } - - let animation = tree.state.downcast_mut::(); - + let state = tree.state.downcast_mut::(); if let Event::Window(window::Event::RedrawRequested(now)) = event { - *animation = animation.timed_transition( - self.cycle_duration, - self.traversal_duration, - WRAP_LENGTH, - *now, - ); - shell.request_redraw(); + if let Some(target) = self.progress { + if state.progress.update(target, *now) { + shell.request_redraw(); + } + } else { + state.animation = state.animation.timed_transition( + self.cycle_duration, + self.period, + WRAP_LENGTH, + *now, + ); + shell.request_redraw(); + } } } @@ -160,7 +167,7 @@ where ) { let bounds = layout.bounds(); let custom_style = theme.appearance(&self.style, self.progress.is_some(), false); - let animation = tree.state.downcast_ref::(); + let state = tree.state.downcast_ref::(); renderer.fill_quad( renderer::Quad { @@ -203,11 +210,13 @@ where } }; - if let Some(progress) = self.progress { - draw_segment(0.0, progress); + if self.progress.is_some() { + draw_segment(0.0, state.progress.current); } else { let (bar_start, bar_end) = - animation.bar_positions(self.cycle_duration, MIN_LENGTH, WRAP_LENGTH); + state + .animation + .bar_positions(self.cycle_duration, MIN_LENGTH, WRAP_LENGTH); let length = bar_end - bar_start; let start = bar_start % 1.0; let right_width = (1.0 - start).min(length); From 108441ef612853681aa6ed0f04f1d27b3f0f86b1 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Tue, 21 Apr 2026 21:26:35 +0200 Subject: [PATCH 07/75] segmented_button: add on_double_click callback Fires in addition to on_activate when the same entity is left-clicked twice within 400 ms. Lets applications bind quick actions (e.g. rename a tab) without blocking the normal single-click activation path. - New Widget::on_double_click builder mirroring on_activate/on_close. - last_click field on LocalState for timestamp tracking. - Detection branch in the mouse handler after on_activate fires, so the target entity is already focused when the callback runs. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/widget/segmented_button/widget.rs | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 44ca8574..7195c9d8 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -173,6 +173,10 @@ where pub(super) on_context: Option Message + 'static>>, #[setters(skip)] pub(super) on_middle_press: Option Message + 'static>>, + /// Emits the ID of the item that was double-clicked with the left button. + /// Fires in addition to `on_activate` (which keeps firing on each click). + #[setters(skip)] + pub(super) on_double_click: Option Message + 'static>>, #[setters(skip)] pub(super) on_dnd_drop: Option, String, DndAction) -> Message + 'static>>, @@ -232,6 +236,7 @@ where on_close: None, on_context: None, on_middle_press: None, + on_double_click: None, on_dnd_drop: None, on_dnd_enter: None, on_dnd_leave: None, @@ -354,6 +359,16 @@ where self } + /// Emitted when a tab is double-clicked with the left mouse button. + /// Fires in addition to `on_activate`, which keeps firing on each click. + pub fn on_double_click(mut self, on_double_click: T) -> Self + where + T: Fn(Entity) -> Message + 'static, + { + self.on_double_click = Some(Box::new(on_double_click)); + self + } + /// Enable drag-and-drop support for tabs using the provided payload builder. pub fn enable_tab_drag(mut self, mime: String) -> Self { self.tab_drag = Some(TabDragSource::new(mime)); @@ -912,6 +927,7 @@ where hovered: Default::default(), known_length: Default::default(), middle_clicked: Default::default(), + last_click: None, internal_layout: Default::default(), context_cursor: Point::default(), show_context: Default::default(), @@ -1383,6 +1399,33 @@ where state.set_focused(); state.focused_item = Item::Tab(key); state.pressed_item = None; + + // Double-click detection on the same entity + // within 400 ms — fires after on_activate so + // the tab is already focused when the handler + // runs. + if let Some(on_double_click) = + self.on_double_click.as_ref() + { + let now = Instant::now(); + let is_double = match state.last_click { + Some((prev, t)) => { + prev == key + && now.duration_since(t) + < Duration::from_millis(400) + } + None => false, + }; + state.last_click = if is_double { + None + } else { + Some((key, now)) + }; + if is_double { + shell.publish(on_double_click(key)); + } + } + shell.capture_event(); return; } @@ -2387,6 +2430,9 @@ pub struct LocalState { hovered: Item, /// The ID of the button that was middle-clicked, but not yet released. middle_clicked: Option, + /// Entity and timestamp of the most recent left-click activation, used + /// to detect double-clicks on the same tab. + last_click: Option<(Entity, Instant)>, /// Last known length of the model. pub(super) known_length: usize, /// Dimensions of internal buttons when shrinking @@ -2532,6 +2578,7 @@ mod tests { hovered: Item::default(), known_length: 0, middle_clicked: None, + last_click: None, internal_layout: Vec::new(), context_cursor: Point::ORIGIN, show_context: None, From a322516f33d18ff9203078aed13d890f31ed1437 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Wed, 22 Apr 2026 11:09:46 +0200 Subject: [PATCH 08/75] segmented_button: fix internal tab reorder end-to-end MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two independent bugs prevented tab drag-and-drop reorder from working on cosmic-comp (and likely other compositors): 1. allow_reorder required DndAction::Move to be negotiated via OfferEvent::SelectedAction, which cosmic-comp does not always emit for self-drops (the SelectedAction event either never arrives or arrives with DndAction::empty()). Add a fallback: accept self-drops whenever state.dragging_tab is set. dragging_tab is only populated by start_tab_drag on this same widget, so this is safe; mime match and on_reorder presence are checked below. 2. reorder_event_for_drop preferred drop_hint.side over positional swap, producing counter-intuitive no-ops: dropping A (pos 0) on the left half of B (pos 1) resolved to "Before B" which, after removing A, lands at pos 0 again — the tab appeared not to move. Always use default_insert_position, which derives direction from dragged vs target positions (Konsole/Firefox/Chrome-style swap semantics). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/widget/segmented_button/widget.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 7195c9d8..9ddd7d8d 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -406,11 +406,12 @@ where { return None; } - let position = state - .drop_hint - .filter(|hint| hint.entity == target) - .map(|hint| InsertPosition::from(hint.side)) - .unwrap_or_else(|| self.default_insert_position(dragged, target)); + // Always use positional swap (Konsole/Firefox/Chrome semantics): + // dropping onto any part of a different tab swaps it with the dragged + // tab. drop_hint.side-based Before/After is counter-intuitive: dropping + // A (pos 0) on the left half of B (pos 1) resolves to "Before B" which, + // after removing A, lands at pos 0 — so the tab appears not to move. + let position = self.default_insert_position(dragged, target); Some(ReorderEvent { dragged, target, @@ -1201,7 +1202,14 @@ where .dnd_state .drag_offer .as_ref() - .is_some_and(|offer| offer.selected_action.contains(DndAction::Move)); + .is_some_and(|offer| offer.selected_action.contains(DndAction::Move)) + // Self-drop fallback: some compositors (cosmic-comp + // observed) do not emit OfferEvent::SelectedAction for + // internal drags, leaving selected_action empty. + // dragging_tab is only set by start_tab_drag on this + // same widget, so this covers the self-drop case + // safely; mime and on_reorder are checked below. + || state.dragging_tab.is_some(); let pending_reorder = if allow_reorder && self.on_reorder.is_some() && self.tab_drag.as_ref().is_some_and(|d| d.mime == *mime_type) From dad5f1e2731dbdccb3044f136a81f18dfead9de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:26:29 +0200 Subject: [PATCH 09/75] fix(progress_bar): sync determinate animation clock on target change This prevents the time delta from becoming very large when the widget hasn't animated for a bit, leading to snapping instead of a smooth animation. Also prevents animating on widget creation, since it doesn't feel good in `cosmic-osd`. --- src/widget/progress_bar/animation.rs | 41 +++++++++++++++++----------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/widget/progress_bar/animation.rs b/src/widget/progress_bar/animation.rs index 6baf5071..a9d52831 100644 --- a/src/widget/progress_bar/animation.rs +++ b/src/widget/progress_bar/animation.rs @@ -4,29 +4,38 @@ use std::time::Duration; const LAG: f32 = 0.1; +#[derive(Default)] pub struct Progress { pub current: f32, - last: Instant, -} - -impl Default for Progress { - fn default() -> Self { - Self { - current: 0.0, - last: Instant::now(), - } - } + target: Option, + last: Option, } impl Progress { /// Smoothly chases `target` using exponential decay. - /// Returns `true` if still animating and a redraw should be requested. + /// Returns `true` if a redraw should be requested. pub fn update(&mut self, target: f32, now: Instant) -> bool { - let dt = (now - self.last).as_secs_f32(); - self.last = now; - let next = self.current + (target - self.current) * (1.0 - (-dt / LAG).exp()); - if (next - target).abs() > 0.001 { - self.current = next; + // Don't animate on start + let Some(last) = self.last else { + self.current = target; + self.target = Some(target); + self.last = Some(now); + return false; + }; + + // Sync animation clock when target changes + if self.target != Some(target) { + self.target = Some(target); + self.last = Some(now); + return true; + } + + let dt = (now - last).as_secs_f32(); + self.last = Some(now); + let diff = target - self.current; + + if diff.abs() > 0.001 { + self.current += diff * (1.0 - (-dt / LAG).exp()); true } else { self.current = target; From 5c3319351ccd0a0d113cff181fee18de0b5ed109 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Wed, 22 Apr 2026 15:08:12 +0200 Subject: [PATCH 10/75] header_bar: add WindowControlsPosition (macOS-style left controls) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new public enum `WindowControlsPosition { Start, End }` and a matching field on `HeaderBar`, allowing window controls (close / minimize / maximize) to be packed on the start side of the headerbar (macOS style, icon order close → minimize → maximize) instead of the default end side (Linux / GNOME style, minimize → maximize → close). Wiring: - `crate::widget::WindowControlsPosition` re-exported alongside `HeaderBar`. - `HeaderBar::controls_position(Option)` setter; when left unset, falls back to `crate::config::window_controls_position()` (reads `CosmicTk.window_controls_position`), mirroring how `density` falls back to `header_size()`. - New `CosmicTk.window_controls_position` field with default `End` for backwards compatibility; serde-friendly enum so existing configs keep working via `#[serde(default)]` semantics. Tested with cosmic-yoterm, cosmic-settings, cosmic-edit, cosmic-files rebuilt against this libcosmic via a local `[patch]` override. Config changes picked up live through the existing cosmic-config subscription. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/config/mod.rs | 12 +++++ src/widget/header_bar.rs | 100 ++++++++++++++++++++++++++++++--------- src/widget/mod.rs | 2 +- 3 files changed, 90 insertions(+), 24 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 9807961c..1691caa8 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -4,6 +4,7 @@ //! Configurations available to libcosmic applications. use crate::cosmic_theme::Density; +use crate::widget::WindowControlsPosition; use cosmic_config::cosmic_config_derive::CosmicConfigEntry; use cosmic_config::{Config, CosmicConfigEntry}; use serde::{Deserialize, Serialize}; @@ -67,6 +68,12 @@ pub fn header_size() -> Density { COSMIC_TK.read().unwrap().header_size } +/// Position of the window control buttons (close / minimize / maximize). +#[allow(clippy::missing_panics_doc)] +pub fn window_controls_position() -> WindowControlsPosition { + COSMIC_TK.read().unwrap().window_controls_position +} + /// Interface density. #[allow(clippy::missing_panics_doc)] pub fn interface_density() -> Density { @@ -109,6 +116,10 @@ pub struct CosmicTk { /// Mono font family pub monospace_font: FontConfig, + + /// Side on which window control buttons (close / minimize / maximize) + /// are placed. `End` = right (Linux / GNOME), `Start` = left (macOS). + pub window_controls_position: WindowControlsPosition, } impl Default for CosmicTk { @@ -132,6 +143,7 @@ impl Default for CosmicTk { stretch: iced::font::Stretch::Normal, style: iced::font::Style::Normal, }, + window_controls_position: WindowControlsPosition::default(), } } } diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index a772f7d2..0fcbc6c6 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -27,9 +27,31 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { is_ssd: false, on_double_click: None, transparent: false, + controls_position: None, } } +/// Position of the window control buttons (close/min/max) within the headerbar. +#[derive( + Clone, + Copy, + Debug, + Default, + PartialEq, + Eq, + serde::Serialize, + serde::Deserialize, +)] +pub enum WindowControlsPosition { + /// Controls packed at the start (left on LTR) — macOS style. + /// Internal icon order becomes close → minimize → maximize. + Start, + /// Controls packed at the end (right on LTR) — Linux / GNOME style. + /// Internal icon order is minimize → maximize → close. + #[default] + End, +} + #[derive(Setters)] pub struct HeaderBar<'a, Message> { /// Defines the title of the window @@ -91,6 +113,14 @@ pub struct HeaderBar<'a, Message> { /// Whether the headerbar should be transparent transparent: bool, + + /// Side on which the window control buttons (close / minimize / maximize) + /// are rendered. `None` falls back to the user's CosmicTk config + /// (`crate::config::window_controls_position()`). `Some` overrides it. + /// `End` matches Linux / GNOME conventions; `Start` provides macOS-style + /// left-side controls. + #[setters(strip_option)] + controls_position: Option, } impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { @@ -372,12 +402,20 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { } = theme::spacing(); // Take ownership of the regions to be packed. - let start = std::mem::take(&mut self.start); + let mut start = std::mem::take(&mut self.start); let center = std::mem::take(&mut self.center); let mut end = std::mem::take(&mut self.end); - // Also packs the window controls at the very end. - end.push(self.window_controls(space_xxs)); + // Pack window controls on the configured side (reads CosmicTk + // config when the builder did not set an explicit override). + let controls_position = self + .controls_position + .unwrap_or_else(crate::config::window_controls_position); + let controls = self.window_controls(space_xxs, controls_position); + match controls_position { + WindowControlsPosition::End => end.push(controls), + WindowControlsPosition::Start => start.insert(0, controls), + } let padding = if self.is_ssd { [2, 8, 2, 8] @@ -447,7 +485,11 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { } /// Creates the widget for window controls. - fn window_controls(&mut self, spacing: u16) -> Element<'a, Message> { + fn window_controls( + &mut self, + spacing: u16, + controls_position: WindowControlsPosition, + ) -> Element<'a, Message> { macro_rules! icon { ($name:expr, $size:expr, $on_press:expr) => {{ widget::icon::from_name($name) @@ -460,25 +502,37 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { }}; } - widget::row::with_capacity(3) - .push_maybe( - self.on_minimize - .take() - .map(|m| icon!("window-minimize-symbolic", 16, m)), - ) - .push_maybe(self.on_maximize.take().map(|m| { - if self.maximized { - icon!("window-restore-symbolic", 16, m) - } else { - icon!("window-maximize-symbolic", 16, m) - } - })) - .push_maybe( - self.on_close - .take() - .map(|m| icon!("window-close-symbolic", 16, m)), - ) - .spacing(spacing) + let minimize = self + .on_minimize + .take() + .map(|m| icon!("window-minimize-symbolic", 16, m)); + let maximize = self.on_maximize.take().map(|m| { + if self.maximized { + icon!("window-restore-symbolic", 16, m) + } else { + icon!("window-maximize-symbolic", 16, m) + } + }); + let close = self + .on_close + .take() + .map(|m| icon!("window-close-symbolic", 16, m)); + + // Icon order follows the OS convention for the chosen side: + // End → minimize, maximize, close (Linux / GNOME) + // Start → close, minimize, maximize (macOS) + let row = widget::row::with_capacity(3); + let row = match controls_position { + WindowControlsPosition::End => row + .push_maybe(minimize) + .push_maybe(maximize) + .push_maybe(close), + WindowControlsPosition::Start => row + .push_maybe(close) + .push_maybe(minimize) + .push_maybe(maximize), + }; + row.spacing(spacing) .align_y(iced::Alignment::Center) .into() } diff --git a/src/widget/mod.rs b/src/widget/mod.rs index f442b0da..db9b2fc4 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -221,7 +221,7 @@ pub use grid::{Grid, grid}; mod header_bar; #[doc(inline)] -pub use header_bar::{HeaderBar, header_bar}; +pub use header_bar::{HeaderBar, WindowControlsPosition, header_bar}; pub mod icon; #[doc(inline)] From 255cf7cc0be7811a0daadd1dfe9b773212e4efbb Mon Sep 17 00:00:00 2001 From: leyoda Date: Thu, 23 Apr 2026 07:35:22 +0200 Subject: [PATCH 11/75] =?UTF-8?q?rename:=20libcosmic=20=E2=86=92=20libcosm?= =?UTF-8?q?ic-yoda=20(fork=200.1.0-yoda)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fork point: pop-os/libcosmic 1.0.0 + perf/quickwins-bundle + macOS window controls feature. From here the crate diverges under the yoda lineage with its own versioning. Changes: - Cargo.toml: name = libcosmic-yoda, version = 0.1.0-yoda - [lib] name = cosmic kept unchanged — consumer code still does 'use cosmic::...' - examples/*/Cargo.toml: updated all libcosmic dep references to libcosmic-yoda - i18n/*/libcosmic.ftl renamed to libcosmic_yoda.ftl (71 locales) to match the new name expected by fluent_language_loader!() macro cargo check --lib passes. Examples not yet validated — Phase 2 (Wayland-only cut) will rework them anyway. --- Cargo.toml | 4 ++-- examples/about/Cargo.toml | 2 +- examples/applet/Cargo.toml | 2 +- examples/application/Cargo.toml | 4 ++-- examples/calendar/Cargo.toml | 2 +- examples/context-menu/Cargo.toml | 2 +- examples/cosmic/Cargo.toml | 2 +- examples/image-button/Cargo.toml | 2 +- examples/menu/Cargo.toml | 2 +- examples/multi-window/Cargo.toml | 2 +- examples/nav-context/Cargo.toml | 2 +- examples/open-dialog/Cargo.toml | 6 +++--- examples/spin-button/Cargo.toml | 2 +- examples/subscriptions/Cargo.toml | 2 +- examples/table-view/Cargo.toml | 2 +- examples/text-input/Cargo.toml | 2 +- i18n/af/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ar/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/be/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/bg/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/bn/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ca/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/cs/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/da/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/de/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/el/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/en-GB/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/en/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/eo/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/es-419/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/es-MX/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/es/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/et/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/eu/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/fa/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/fi/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/fr/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/fy/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ga/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/gd/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/gu/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/he/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/hi/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/hr/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/hu/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/id/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ie/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/is/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/it/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ja/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/jv/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ka/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/kab/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/kk/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/kmr/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/kn/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ko/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/li/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/lt/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ml/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ms/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/nb-NO/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/nl/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/nn/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/oc/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/pa/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/pl/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/pt-BR/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/pt/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ro/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ru/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/sk/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/sl/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/sr-Cyrl/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/sr-Latn/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/sr/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/sv/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ta/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/th/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ti/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/tr/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/uk/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/uz/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/vi/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/yue-Hant/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/zh-Hans/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/zh-Hant/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 87 files changed, 20 insertions(+), 20 deletions(-) rename i18n/af/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ar/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/be/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/bg/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/bn/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ca/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/cs/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/da/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/de/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/el/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/en-GB/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/en/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/eo/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/es-419/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/es-MX/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/es/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/et/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/eu/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/fa/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/fi/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/fr/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/fy/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ga/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/gd/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/gu/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/he/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/hi/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/hr/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/hu/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/id/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ie/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/is/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/it/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ja/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/jv/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ka/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/kab/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/kk/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/kmr/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/kn/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ko/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/li/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/lt/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ml/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ms/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/nb-NO/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/nl/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/nn/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/oc/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/pa/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/pl/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/pt-BR/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/pt/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ro/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ru/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sk/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sl/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sr-Cyrl/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sr-Latn/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sr/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sv/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ta/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/th/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ti/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/tr/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/uk/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/uz/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/vi/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/yue-Hant/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/zh-Hans/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/zh-Hant/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) diff --git a/Cargo.toml b/Cargo.toml index d73da2dc..629af859 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "libcosmic" -version = "1.0.0" +name = "libcosmic-yoda" +version = "0.1.0-yoda" edition = "2024" rust-version = "1.90" diff --git a/examples/about/Cargo.toml b/examples/about/Cargo.toml index f980811c..b27b5134 100644 --- a/examples/about/Cargo.toml +++ b/examples/about/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] open = "5.3.3" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = [ "debug", diff --git a/examples/applet/Cargo.toml b/examples/applet/Cargo.toml index 13eff684..265fbe7f 100644 --- a/examples/applet/Cargo.toml +++ b/examples/applet/Cargo.toml @@ -12,7 +12,7 @@ tracing = "0.1" env_logger = "0.10.2" log = "0.4.29" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" default-features = false features = ["applet-token"] diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index 7a6083e0..d4c75171 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -5,12 +5,12 @@ edition = "2021" [features] default = ["wayland"] -wayland = ["libcosmic/wayland"] +wayland = ["libcosmic-yoda/wayland"] [dependencies] env_logger = "0.11" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = [ "debug", diff --git a/examples/calendar/Cargo.toml b/examples/calendar/Cargo.toml index b7286825..203f7c17 100644 --- a/examples/calendar/Cargo.toml +++ b/examples/calendar/Cargo.toml @@ -8,6 +8,6 @@ edition = "2024" [dependencies] jiff = "0.2" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/context-menu/Cargo.toml b/examples/context-menu/Cargo.toml index 39c550f4..4c1eed60 100644 --- a/examples/context-menu/Cargo.toml +++ b/examples/context-menu/Cargo.toml @@ -8,7 +8,7 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = [ "debug", diff --git a/examples/cosmic/Cargo.toml b/examples/cosmic/Cargo.toml index 8c2a3126..eebf6c31 100644 --- a/examples/cosmic/Cargo.toml +++ b/examples/cosmic/Cargo.toml @@ -8,7 +8,7 @@ publish = false [dependencies] apply = "0.3.0" fraction = "0.15.3" -libcosmic = { path = "../..", features = [ +libcosmic-yoda = { path = "../..", features = [ "debug", "winit", "tokio", diff --git a/examples/image-button/Cargo.toml b/examples/image-button/Cargo.toml index c219a53b..8bc521f7 100644 --- a/examples/image-button/Cargo.toml +++ b/examples/image-button/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" tracing = "0.1.44" tracing-subscriber = "0.3.22" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "wgpu", "tokio"] diff --git a/examples/menu/Cargo.toml b/examples/menu/Cargo.toml index 430b26ea..047055e5 100644 --- a/examples/menu/Cargo.toml +++ b/examples/menu/Cargo.toml @@ -8,6 +8,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/multi-window/Cargo.toml b/examples/multi-window/Cargo.toml index 0b5440f8..c38595fa 100644 --- a/examples/multi-window/Cargo.toml +++ b/examples/multi-window/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "wgpu", "wayland"] } +libcosmic-yoda = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "wgpu", "wayland"] } diff --git a/examples/nav-context/Cargo.toml b/examples/nav-context/Cargo.toml index d829df0f..ea2bc2b8 100644 --- a/examples/nav-context/Cargo.toml +++ b/examples/nav-context/Cargo.toml @@ -8,6 +8,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/open-dialog/Cargo.toml b/examples/open-dialog/Cargo.toml index 94049270..b09b98ca 100644 --- a/examples/open-dialog/Cargo.toml +++ b/examples/open-dialog/Cargo.toml @@ -5,8 +5,8 @@ edition = "2021" [features] default = ["xdg-portal"] -rfd = ["libcosmic/rfd"] -xdg-portal = ["libcosmic/xdg-portal"] +rfd = ["libcosmic-yoda/rfd"] +xdg-portal = ["libcosmic-yoda/xdg-portal"] [dependencies] apply = "0.3.0" @@ -15,6 +15,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" url = "2.5.8" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] features = ["debug", "winit", "wgpu", "wayland", "tokio"] path = "../../" diff --git a/examples/spin-button/Cargo.toml b/examples/spin-button/Cargo.toml index a522050b..082c0fd5 100644 --- a/examples/spin-button/Cargo.toml +++ b/examples/spin-button/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] fraction = "0.15.3" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] features = ["debug", "wgpu", "winit", "desktop", "tokio"] path = "../.." default-features = false diff --git a/examples/subscriptions/Cargo.toml b/examples/subscriptions/Cargo.toml index 8eb69ff3..ae31a395 100644 --- a/examples/subscriptions/Cargo.toml +++ b/examples/subscriptions/Cargo.toml @@ -5,6 +5,6 @@ edition = "2024" [dependencies] -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"] diff --git a/examples/table-view/Cargo.toml b/examples/table-view/Cargo.toml index 8ed45928..8f71e5b4 100644 --- a/examples/table-view/Cargo.toml +++ b/examples/table-view/Cargo.toml @@ -9,6 +9,6 @@ tracing-subscriber = "0.3.22" tracing-log = "0.2.0" chrono = "*" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] features = ["debug", "wgpu", "winit", "desktop", "tokio"] path = "../.." diff --git a/examples/text-input/Cargo.toml b/examples/text-input/Cargo.toml index fe6105c2..69bd2a19 100644 --- a/examples/text-input/Cargo.toml +++ b/examples/text-input/Cargo.toml @@ -8,6 +8,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"] diff --git a/i18n/af/libcosmic.ftl b/i18n/af/libcosmic_yoda.ftl similarity index 100% rename from i18n/af/libcosmic.ftl rename to i18n/af/libcosmic_yoda.ftl diff --git a/i18n/ar/libcosmic.ftl b/i18n/ar/libcosmic_yoda.ftl similarity index 100% rename from i18n/ar/libcosmic.ftl rename to i18n/ar/libcosmic_yoda.ftl diff --git a/i18n/be/libcosmic.ftl b/i18n/be/libcosmic_yoda.ftl similarity index 100% rename from i18n/be/libcosmic.ftl rename to i18n/be/libcosmic_yoda.ftl diff --git a/i18n/bg/libcosmic.ftl b/i18n/bg/libcosmic_yoda.ftl similarity index 100% rename from i18n/bg/libcosmic.ftl rename to i18n/bg/libcosmic_yoda.ftl diff --git a/i18n/bn/libcosmic.ftl b/i18n/bn/libcosmic_yoda.ftl similarity index 100% rename from i18n/bn/libcosmic.ftl rename to i18n/bn/libcosmic_yoda.ftl diff --git a/i18n/ca/libcosmic.ftl b/i18n/ca/libcosmic_yoda.ftl similarity index 100% rename from i18n/ca/libcosmic.ftl rename to i18n/ca/libcosmic_yoda.ftl diff --git a/i18n/cs/libcosmic.ftl b/i18n/cs/libcosmic_yoda.ftl similarity index 100% rename from i18n/cs/libcosmic.ftl rename to i18n/cs/libcosmic_yoda.ftl diff --git a/i18n/da/libcosmic.ftl b/i18n/da/libcosmic_yoda.ftl similarity index 100% rename from i18n/da/libcosmic.ftl rename to i18n/da/libcosmic_yoda.ftl diff --git a/i18n/de/libcosmic.ftl b/i18n/de/libcosmic_yoda.ftl similarity index 100% rename from i18n/de/libcosmic.ftl rename to i18n/de/libcosmic_yoda.ftl diff --git a/i18n/el/libcosmic.ftl b/i18n/el/libcosmic_yoda.ftl similarity index 100% rename from i18n/el/libcosmic.ftl rename to i18n/el/libcosmic_yoda.ftl diff --git a/i18n/en-GB/libcosmic.ftl b/i18n/en-GB/libcosmic_yoda.ftl similarity index 100% rename from i18n/en-GB/libcosmic.ftl rename to i18n/en-GB/libcosmic_yoda.ftl diff --git a/i18n/en/libcosmic.ftl b/i18n/en/libcosmic_yoda.ftl similarity index 100% rename from i18n/en/libcosmic.ftl rename to i18n/en/libcosmic_yoda.ftl diff --git a/i18n/eo/libcosmic.ftl b/i18n/eo/libcosmic_yoda.ftl similarity index 100% rename from i18n/eo/libcosmic.ftl rename to i18n/eo/libcosmic_yoda.ftl diff --git a/i18n/es-419/libcosmic.ftl b/i18n/es-419/libcosmic_yoda.ftl similarity index 100% rename from i18n/es-419/libcosmic.ftl rename to i18n/es-419/libcosmic_yoda.ftl diff --git a/i18n/es-MX/libcosmic.ftl b/i18n/es-MX/libcosmic_yoda.ftl similarity index 100% rename from i18n/es-MX/libcosmic.ftl rename to i18n/es-MX/libcosmic_yoda.ftl diff --git a/i18n/es/libcosmic.ftl b/i18n/es/libcosmic_yoda.ftl similarity index 100% rename from i18n/es/libcosmic.ftl rename to i18n/es/libcosmic_yoda.ftl diff --git a/i18n/et/libcosmic.ftl b/i18n/et/libcosmic_yoda.ftl similarity index 100% rename from i18n/et/libcosmic.ftl rename to i18n/et/libcosmic_yoda.ftl diff --git a/i18n/eu/libcosmic.ftl b/i18n/eu/libcosmic_yoda.ftl similarity index 100% rename from i18n/eu/libcosmic.ftl rename to i18n/eu/libcosmic_yoda.ftl diff --git a/i18n/fa/libcosmic.ftl b/i18n/fa/libcosmic_yoda.ftl similarity index 100% rename from i18n/fa/libcosmic.ftl rename to i18n/fa/libcosmic_yoda.ftl diff --git a/i18n/fi/libcosmic.ftl b/i18n/fi/libcosmic_yoda.ftl similarity index 100% rename from i18n/fi/libcosmic.ftl rename to i18n/fi/libcosmic_yoda.ftl diff --git a/i18n/fr/libcosmic.ftl b/i18n/fr/libcosmic_yoda.ftl similarity index 100% rename from i18n/fr/libcosmic.ftl rename to i18n/fr/libcosmic_yoda.ftl diff --git a/i18n/fy/libcosmic.ftl b/i18n/fy/libcosmic_yoda.ftl similarity index 100% rename from i18n/fy/libcosmic.ftl rename to i18n/fy/libcosmic_yoda.ftl diff --git a/i18n/ga/libcosmic.ftl b/i18n/ga/libcosmic_yoda.ftl similarity index 100% rename from i18n/ga/libcosmic.ftl rename to i18n/ga/libcosmic_yoda.ftl diff --git a/i18n/gd/libcosmic.ftl b/i18n/gd/libcosmic_yoda.ftl similarity index 100% rename from i18n/gd/libcosmic.ftl rename to i18n/gd/libcosmic_yoda.ftl diff --git a/i18n/gu/libcosmic.ftl b/i18n/gu/libcosmic_yoda.ftl similarity index 100% rename from i18n/gu/libcosmic.ftl rename to i18n/gu/libcosmic_yoda.ftl diff --git a/i18n/he/libcosmic.ftl b/i18n/he/libcosmic_yoda.ftl similarity index 100% rename from i18n/he/libcosmic.ftl rename to i18n/he/libcosmic_yoda.ftl diff --git a/i18n/hi/libcosmic.ftl b/i18n/hi/libcosmic_yoda.ftl similarity index 100% rename from i18n/hi/libcosmic.ftl rename to i18n/hi/libcosmic_yoda.ftl diff --git a/i18n/hr/libcosmic.ftl b/i18n/hr/libcosmic_yoda.ftl similarity index 100% rename from i18n/hr/libcosmic.ftl rename to i18n/hr/libcosmic_yoda.ftl diff --git a/i18n/hu/libcosmic.ftl b/i18n/hu/libcosmic_yoda.ftl similarity index 100% rename from i18n/hu/libcosmic.ftl rename to i18n/hu/libcosmic_yoda.ftl diff --git a/i18n/id/libcosmic.ftl b/i18n/id/libcosmic_yoda.ftl similarity index 100% rename from i18n/id/libcosmic.ftl rename to i18n/id/libcosmic_yoda.ftl diff --git a/i18n/ie/libcosmic.ftl b/i18n/ie/libcosmic_yoda.ftl similarity index 100% rename from i18n/ie/libcosmic.ftl rename to i18n/ie/libcosmic_yoda.ftl diff --git a/i18n/is/libcosmic.ftl b/i18n/is/libcosmic_yoda.ftl similarity index 100% rename from i18n/is/libcosmic.ftl rename to i18n/is/libcosmic_yoda.ftl diff --git a/i18n/it/libcosmic.ftl b/i18n/it/libcosmic_yoda.ftl similarity index 100% rename from i18n/it/libcosmic.ftl rename to i18n/it/libcosmic_yoda.ftl diff --git a/i18n/ja/libcosmic.ftl b/i18n/ja/libcosmic_yoda.ftl similarity index 100% rename from i18n/ja/libcosmic.ftl rename to i18n/ja/libcosmic_yoda.ftl diff --git a/i18n/jv/libcosmic.ftl b/i18n/jv/libcosmic_yoda.ftl similarity index 100% rename from i18n/jv/libcosmic.ftl rename to i18n/jv/libcosmic_yoda.ftl diff --git a/i18n/ka/libcosmic.ftl b/i18n/ka/libcosmic_yoda.ftl similarity index 100% rename from i18n/ka/libcosmic.ftl rename to i18n/ka/libcosmic_yoda.ftl diff --git a/i18n/kab/libcosmic.ftl b/i18n/kab/libcosmic_yoda.ftl similarity index 100% rename from i18n/kab/libcosmic.ftl rename to i18n/kab/libcosmic_yoda.ftl diff --git a/i18n/kk/libcosmic.ftl b/i18n/kk/libcosmic_yoda.ftl similarity index 100% rename from i18n/kk/libcosmic.ftl rename to i18n/kk/libcosmic_yoda.ftl diff --git a/i18n/kmr/libcosmic.ftl b/i18n/kmr/libcosmic_yoda.ftl similarity index 100% rename from i18n/kmr/libcosmic.ftl rename to i18n/kmr/libcosmic_yoda.ftl diff --git a/i18n/kn/libcosmic.ftl b/i18n/kn/libcosmic_yoda.ftl similarity index 100% rename from i18n/kn/libcosmic.ftl rename to i18n/kn/libcosmic_yoda.ftl diff --git a/i18n/ko/libcosmic.ftl b/i18n/ko/libcosmic_yoda.ftl similarity index 100% rename from i18n/ko/libcosmic.ftl rename to i18n/ko/libcosmic_yoda.ftl diff --git a/i18n/li/libcosmic.ftl b/i18n/li/libcosmic_yoda.ftl similarity index 100% rename from i18n/li/libcosmic.ftl rename to i18n/li/libcosmic_yoda.ftl diff --git a/i18n/lt/libcosmic.ftl b/i18n/lt/libcosmic_yoda.ftl similarity index 100% rename from i18n/lt/libcosmic.ftl rename to i18n/lt/libcosmic_yoda.ftl diff --git a/i18n/ml/libcosmic.ftl b/i18n/ml/libcosmic_yoda.ftl similarity index 100% rename from i18n/ml/libcosmic.ftl rename to i18n/ml/libcosmic_yoda.ftl diff --git a/i18n/ms/libcosmic.ftl b/i18n/ms/libcosmic_yoda.ftl similarity index 100% rename from i18n/ms/libcosmic.ftl rename to i18n/ms/libcosmic_yoda.ftl diff --git a/i18n/nb-NO/libcosmic.ftl b/i18n/nb-NO/libcosmic_yoda.ftl similarity index 100% rename from i18n/nb-NO/libcosmic.ftl rename to i18n/nb-NO/libcosmic_yoda.ftl diff --git a/i18n/nl/libcosmic.ftl b/i18n/nl/libcosmic_yoda.ftl similarity index 100% rename from i18n/nl/libcosmic.ftl rename to i18n/nl/libcosmic_yoda.ftl diff --git a/i18n/nn/libcosmic.ftl b/i18n/nn/libcosmic_yoda.ftl similarity index 100% rename from i18n/nn/libcosmic.ftl rename to i18n/nn/libcosmic_yoda.ftl diff --git a/i18n/oc/libcosmic.ftl b/i18n/oc/libcosmic_yoda.ftl similarity index 100% rename from i18n/oc/libcosmic.ftl rename to i18n/oc/libcosmic_yoda.ftl diff --git a/i18n/pa/libcosmic.ftl b/i18n/pa/libcosmic_yoda.ftl similarity index 100% rename from i18n/pa/libcosmic.ftl rename to i18n/pa/libcosmic_yoda.ftl diff --git a/i18n/pl/libcosmic.ftl b/i18n/pl/libcosmic_yoda.ftl similarity index 100% rename from i18n/pl/libcosmic.ftl rename to i18n/pl/libcosmic_yoda.ftl diff --git a/i18n/pt-BR/libcosmic.ftl b/i18n/pt-BR/libcosmic_yoda.ftl similarity index 100% rename from i18n/pt-BR/libcosmic.ftl rename to i18n/pt-BR/libcosmic_yoda.ftl diff --git a/i18n/pt/libcosmic.ftl b/i18n/pt/libcosmic_yoda.ftl similarity index 100% rename from i18n/pt/libcosmic.ftl rename to i18n/pt/libcosmic_yoda.ftl diff --git a/i18n/ro/libcosmic.ftl b/i18n/ro/libcosmic_yoda.ftl similarity index 100% rename from i18n/ro/libcosmic.ftl rename to i18n/ro/libcosmic_yoda.ftl diff --git a/i18n/ru/libcosmic.ftl b/i18n/ru/libcosmic_yoda.ftl similarity index 100% rename from i18n/ru/libcosmic.ftl rename to i18n/ru/libcosmic_yoda.ftl diff --git a/i18n/sk/libcosmic.ftl b/i18n/sk/libcosmic_yoda.ftl similarity index 100% rename from i18n/sk/libcosmic.ftl rename to i18n/sk/libcosmic_yoda.ftl diff --git a/i18n/sl/libcosmic.ftl b/i18n/sl/libcosmic_yoda.ftl similarity index 100% rename from i18n/sl/libcosmic.ftl rename to i18n/sl/libcosmic_yoda.ftl diff --git a/i18n/sr-Cyrl/libcosmic.ftl b/i18n/sr-Cyrl/libcosmic_yoda.ftl similarity index 100% rename from i18n/sr-Cyrl/libcosmic.ftl rename to i18n/sr-Cyrl/libcosmic_yoda.ftl diff --git a/i18n/sr-Latn/libcosmic.ftl b/i18n/sr-Latn/libcosmic_yoda.ftl similarity index 100% rename from i18n/sr-Latn/libcosmic.ftl rename to i18n/sr-Latn/libcosmic_yoda.ftl diff --git a/i18n/sr/libcosmic.ftl b/i18n/sr/libcosmic_yoda.ftl similarity index 100% rename from i18n/sr/libcosmic.ftl rename to i18n/sr/libcosmic_yoda.ftl diff --git a/i18n/sv/libcosmic.ftl b/i18n/sv/libcosmic_yoda.ftl similarity index 100% rename from i18n/sv/libcosmic.ftl rename to i18n/sv/libcosmic_yoda.ftl diff --git a/i18n/ta/libcosmic.ftl b/i18n/ta/libcosmic_yoda.ftl similarity index 100% rename from i18n/ta/libcosmic.ftl rename to i18n/ta/libcosmic_yoda.ftl diff --git a/i18n/th/libcosmic.ftl b/i18n/th/libcosmic_yoda.ftl similarity index 100% rename from i18n/th/libcosmic.ftl rename to i18n/th/libcosmic_yoda.ftl diff --git a/i18n/ti/libcosmic.ftl b/i18n/ti/libcosmic_yoda.ftl similarity index 100% rename from i18n/ti/libcosmic.ftl rename to i18n/ti/libcosmic_yoda.ftl diff --git a/i18n/tr/libcosmic.ftl b/i18n/tr/libcosmic_yoda.ftl similarity index 100% rename from i18n/tr/libcosmic.ftl rename to i18n/tr/libcosmic_yoda.ftl diff --git a/i18n/uk/libcosmic.ftl b/i18n/uk/libcosmic_yoda.ftl similarity index 100% rename from i18n/uk/libcosmic.ftl rename to i18n/uk/libcosmic_yoda.ftl diff --git a/i18n/uz/libcosmic.ftl b/i18n/uz/libcosmic_yoda.ftl similarity index 100% rename from i18n/uz/libcosmic.ftl rename to i18n/uz/libcosmic_yoda.ftl diff --git a/i18n/vi/libcosmic.ftl b/i18n/vi/libcosmic_yoda.ftl similarity index 100% rename from i18n/vi/libcosmic.ftl rename to i18n/vi/libcosmic_yoda.ftl diff --git a/i18n/yue-Hant/libcosmic.ftl b/i18n/yue-Hant/libcosmic_yoda.ftl similarity index 100% rename from i18n/yue-Hant/libcosmic.ftl rename to i18n/yue-Hant/libcosmic_yoda.ftl diff --git a/i18n/zh-Hans/libcosmic.ftl b/i18n/zh-Hans/libcosmic_yoda.ftl similarity index 100% rename from i18n/zh-Hans/libcosmic.ftl rename to i18n/zh-Hans/libcosmic_yoda.ftl diff --git a/i18n/zh-Hant/libcosmic.ftl b/i18n/zh-Hant/libcosmic_yoda.ftl similarity index 100% rename from i18n/zh-Hant/libcosmic.ftl rename to i18n/zh-Hant/libcosmic_yoda.ftl From 8701aa31d8c35077874c635fa8553d6f23e08c97 Mon Sep 17 00:00:00 2001 From: leyoda Date: Thu, 23 Apr 2026 14:29:08 +0200 Subject: [PATCH 12/75] =?UTF-8?q?feat(yoda):=20Wayland-only=20cut=20?= =?UTF-8?q?=E2=80=94=20drop=20winit=20and=20x11=20features?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of the yoda fork direction: libcosmic-yoda is Wayland-only going forward. Cargo.toml changes: - default features: remove winit, x11, iced-wayland, multi-window -> add wayland - applet feature: remove winit dep - merge iced-wayland into wayland (simpler feature graph) - remove winit, winit_debug, winit_tokio, winit_wgpu, x11, iced-wayland features - keep iced_winit dep (it's a misnomer — also hosts the wayland/cctk runtime) Workspace: - examples/* excluded (many depend on winit/x11) — revisited in a later phase Behavior: - Library builds clean in release with default features (cargo check + cargo build --release --lib OK) - 58 lines in src/ gated on feature "winit" become unreachable (dead-code warnings — cleanup deferred) - 108 lines gated on feature "wayland" now always active via default --all-features is known-broken because tokio/async-std/smol are mutually exclusive — this is pre-existing, not a Phase 2 regression. --- Cargo.toml | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 629af859..08a202ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,12 +9,10 @@ name = "cosmic" [features] default = [ - "winit", "tokio", "a11y", "dbus-config", - "x11", - "iced-wayland", + "wayland", "multi-window", ] advanced-shaping = ["iced/advanced-shaping"] @@ -35,7 +33,6 @@ animated-image = [ autosize = [] applet = [ "autosize", - "winit", "wayland", "tokio", "cosmic-panel-config", @@ -81,32 +78,24 @@ tokio = [ "cosmic-config/tokio", ] # Tokio async runtime -# Wayland window support -iced-wayland = [ +# Wayland window support (yoda fork is Wayland-only; this feature is always active in default) +wayland = [ "ashpd?/wayland", "autosize", "iced/wayland", "iced_winit/wayland", - "surface-message", -] -wayland = [ - "iced-wayland", "iced_runtime/cctk", "iced_winit/cctk", "iced_wgpu/cctk", "iced/cctk", "dep:cctk", + "surface-message", ] surface-message = [] # multi-window support multi-window = [] # Render with wgpu wgpu = ["iced/wgpu", "iced_wgpu"] -# X11 window support via winit -winit = ["iced/winit", "iced_winit"] -winit_debug = ["winit", "debug"] -winit_tokio = ["winit", "tokio"] -winit_wgpu = ["winit", "wgpu"] # Enables XDG portal integrations xdg-portal = ["ashpd"] qr_code = ["iced/qr_code"] @@ -119,7 +108,6 @@ async-std = [ "zbus?/async-io", "iced/async-std", ] -x11 = ["iced/x11", "iced_winit/x11"] [dependencies] apply = "0.3.0" @@ -246,9 +234,10 @@ members = [ "cosmic-config", "cosmic-config-derive", "cosmic-theme", - "examples/*", ] -exclude = ["iced"] +# examples/* excluded — many depend on the removed winit/x11 features. +# They will be revisited and adapted in a later phase. +exclude = ["iced", "examples"] [workspace.dependencies] dirs = "6.0.0" From 6736a596ac17a9c8d5a56a5047bfbace24da0d02 Mon Sep 17 00:00:00 2001 From: leyoda Date: Thu, 23 Apr 2026 15:05:31 +0200 Subject: [PATCH 13/75] =?UTF-8?q?yoda:=20soft-fork=20pivot=20=E2=80=94=20k?= =?UTF-8?q?eep=20Cargo=20name=20"libcosmic"=20for=20dep=20unification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The initial hard rename (255cf7cc) broke because Cargo's [patch] with `package = libcosmic-yoda` does NOT unify across the transitive graph. cosmic-files (still upstream) asks for "libcosmic"; patched with a renamed package it ends up as a separate crate, leading to two copies of cosmic::Theme/Action with incompatible types. Soft fork keeps the yoda identity where it counts and stays compatible: - Cargo name : libcosmic (for patch/unification) - Version : 1.0.0 (same major as upstream so [patch] semver-accepts it) - Lib name : cosmic (unchanged) - Repo : leyoda/libcosmic-yoda on Forgejo (yoda lineage) - Branch : main (vs upstream master) Revert parts: - examples/*/Cargo.toml dep refs back to libcosmic - i18n/*/libcosmic_yoda.ftl renamed back to libcosmic.ftl Added: - Compat stub features: winit = [], x11 = [] — empty so Cargo can satisfy upstream deps asking for these, but no code is actually gated on them any more (all removed in Phase 2). Ungates done to make the Wayland path self-sufficient after winit removal: - src/lib.rs: pub mod app + pub use Application/ApplicationExt no longer gated on winit; prelude exports ApplicationExt unconditionally - src/surface/action.rs: 6 functions had #[cfg(all(wayland, linux, winit))] triple-gates; simplified to #[cfg(all(wayland, linux))] since winit is no longer a meaningful gate (wayland is now the only shell) - 12 standalone #[cfg(feature = "winit")] annotations removed from src/ (their gated code is now always compiled) cargo check --lib + cargo check in cosmic-yoterm both pass with a single libcosmic v1.0.0 in the tree. --- Cargo.toml | 26 ++++++++++++++++--- examples/about/Cargo.toml | 2 +- examples/applet/Cargo.toml | 2 +- examples/application/Cargo.toml | 4 +-- examples/calendar/Cargo.toml | 2 +- examples/context-menu/Cargo.toml | 2 +- examples/cosmic/Cargo.toml | 2 +- examples/image-button/Cargo.toml | 2 +- examples/menu/Cargo.toml | 2 +- examples/multi-window/Cargo.toml | 2 +- examples/nav-context/Cargo.toml | 2 +- examples/open-dialog/Cargo.toml | 6 ++--- examples/spin-button/Cargo.toml | 2 +- examples/subscriptions/Cargo.toml | 2 +- examples/table-view/Cargo.toml | 2 +- examples/text-input/Cargo.toml | 2 +- i18n/af/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/ar/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/be/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/bg/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/bn/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/ca/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/cs/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/da/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/de/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/el/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 .../{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/en/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/eo/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 .../{libcosmic_yoda.ftl => libcosmic.ftl} | 0 .../{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/es/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/et/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/eu/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/fa/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/fi/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/fr/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/fy/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/ga/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/gd/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/gu/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/he/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/hi/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/hr/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/hu/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/id/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/ie/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/is/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/it/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/ja/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/jv/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/ka/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 .../kab/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/kk/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 .../kmr/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/kn/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/ko/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/li/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/lt/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/ml/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/ms/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 .../{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/nl/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/nn/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/oc/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/pa/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/pl/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 .../{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/pt/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/ro/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/ru/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/sk/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/sl/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 .../{libcosmic_yoda.ftl => libcosmic.ftl} | 0 .../{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/sr/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/sv/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/ta/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/th/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/ti/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/tr/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/uk/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/uz/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 i18n/vi/{libcosmic_yoda.ftl => libcosmic.ftl} | 0 .../{libcosmic_yoda.ftl => libcosmic.ftl} | 0 .../{libcosmic_yoda.ftl => libcosmic.ftl} | 0 .../{libcosmic_yoda.ftl => libcosmic.ftl} | 0 src/action.rs | 3 --- src/command.rs | 2 -- src/core.rs | 6 ----- src/lib.rs | 3 --- src/surface/action.rs | 13 +++++----- 92 files changed, 47 insertions(+), 42 deletions(-) rename i18n/af/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/ar/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/be/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/bg/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/bn/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/ca/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/cs/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/da/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/de/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/el/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/en-GB/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/en/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/eo/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/es-419/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/es-MX/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/es/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/et/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/eu/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/fa/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/fi/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/fr/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/fy/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/ga/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/gd/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/gu/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/he/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/hi/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/hr/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/hu/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/id/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/ie/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/is/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/it/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/ja/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/jv/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/ka/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/kab/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/kk/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/kmr/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/kn/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/ko/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/li/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/lt/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/ml/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/ms/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/nb-NO/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/nl/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/nn/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/oc/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/pa/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/pl/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/pt-BR/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/pt/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/ro/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/ru/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/sk/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/sl/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/sr-Cyrl/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/sr-Latn/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/sr/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/sv/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/ta/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/th/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/ti/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/tr/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/uk/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/uz/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/vi/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/yue-Hant/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/zh-Hans/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) rename i18n/zh-Hant/{libcosmic_yoda.ftl => libcosmic.ftl} (100%) diff --git a/Cargo.toml b/Cargo.toml index 08a202ae..83dd3bc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,16 @@ [package] -name = "libcosmic-yoda" -version = "0.1.0-yoda" +# NOTE (yoda fork): Cargo package name kept as "libcosmic" — see commit +# 255cf7cc & its follow-up. Renaming it to "libcosmic-yoda" broke transitive +# dep unification: cosmic-files (still upstream) asks for "libcosmic" and +# Cargo's [patch] with `package = "libcosmic-yoda"` does NOT unify across +# the graph, so two copies of the crate end up in the binary with +# incompatible types. Yoda identity is kept via: +# - repo: forgejo leyoda/libcosmic-yoda +# - branch: main (vs upstream master) +# - version: 0.1.0-yoda (pre-1.0 marker denotes divergent lineage) +# - [lib] name stays "cosmic" (unchanged, matches every consumer) +name = "libcosmic" +version = "1.0.0" edition = "2024" rust-version = "1.90" @@ -78,16 +88,21 @@ tokio = [ "cosmic-config/tokio", ] # Tokio async runtime -# Wayland window support (yoda fork is Wayland-only; this feature is always active in default) +# Wayland window support (yoda fork is Wayland-only; always active in default). +# We still need iced/winit because pop-os/iced hosts the runtime dispatcher +# (`iced_winit as shell`) there — the name is a misnomer, it's the same crate +# that provides both the winit path AND the sctk/cctk wayland path. wayland = [ "ashpd?/wayland", "autosize", + "iced/winit", "iced/wayland", "iced_winit/wayland", "iced_runtime/cctk", "iced_winit/cctk", "iced_wgpu/cctk", "iced/cctk", + "dep:iced_winit", "dep:cctk", "surface-message", ] @@ -96,6 +111,11 @@ surface-message = [] multi-window = [] # Render with wgpu wgpu = ["iced/wgpu", "iced_wgpu"] +# Compat stubs — kept empty so upstream deps (cosmic-files, cosmic-text, …) +# that still ask for `winit` / `x11` features resolve cleanly against the +# yoda fork. Activating them has no effect: no code is gated on these. +winit = [] +x11 = [] # Enables XDG portal integrations xdg-portal = ["ashpd"] qr_code = ["iced/qr_code"] diff --git a/examples/about/Cargo.toml b/examples/about/Cargo.toml index b27b5134..f980811c 100644 --- a/examples/about/Cargo.toml +++ b/examples/about/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] open = "5.3.3" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = [ "debug", diff --git a/examples/applet/Cargo.toml b/examples/applet/Cargo.toml index 265fbe7f..13eff684 100644 --- a/examples/applet/Cargo.toml +++ b/examples/applet/Cargo.toml @@ -12,7 +12,7 @@ tracing = "0.1" env_logger = "0.10.2" log = "0.4.29" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" default-features = false features = ["applet-token"] diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index d4c75171..7a6083e0 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -5,12 +5,12 @@ edition = "2021" [features] default = ["wayland"] -wayland = ["libcosmic-yoda/wayland"] +wayland = ["libcosmic/wayland"] [dependencies] env_logger = "0.11" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = [ "debug", diff --git a/examples/calendar/Cargo.toml b/examples/calendar/Cargo.toml index 203f7c17..b7286825 100644 --- a/examples/calendar/Cargo.toml +++ b/examples/calendar/Cargo.toml @@ -8,6 +8,6 @@ edition = "2024" [dependencies] jiff = "0.2" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/context-menu/Cargo.toml b/examples/context-menu/Cargo.toml index 4c1eed60..39c550f4 100644 --- a/examples/context-menu/Cargo.toml +++ b/examples/context-menu/Cargo.toml @@ -8,7 +8,7 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = [ "debug", diff --git a/examples/cosmic/Cargo.toml b/examples/cosmic/Cargo.toml index eebf6c31..8c2a3126 100644 --- a/examples/cosmic/Cargo.toml +++ b/examples/cosmic/Cargo.toml @@ -8,7 +8,7 @@ publish = false [dependencies] apply = "0.3.0" fraction = "0.15.3" -libcosmic-yoda = { path = "../..", features = [ +libcosmic = { path = "../..", features = [ "debug", "winit", "tokio", diff --git a/examples/image-button/Cargo.toml b/examples/image-button/Cargo.toml index 8bc521f7..c219a53b 100644 --- a/examples/image-button/Cargo.toml +++ b/examples/image-button/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" tracing = "0.1.44" tracing-subscriber = "0.3.22" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = ["debug", "winit", "wgpu", "tokio"] diff --git a/examples/menu/Cargo.toml b/examples/menu/Cargo.toml index 047055e5..430b26ea 100644 --- a/examples/menu/Cargo.toml +++ b/examples/menu/Cargo.toml @@ -8,6 +8,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/multi-window/Cargo.toml b/examples/multi-window/Cargo.toml index c38595fa..0b5440f8 100644 --- a/examples/multi-window/Cargo.toml +++ b/examples/multi-window/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -libcosmic-yoda = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "wgpu", "wayland"] } +libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "wgpu", "wayland"] } diff --git a/examples/nav-context/Cargo.toml b/examples/nav-context/Cargo.toml index ea2bc2b8..d829df0f 100644 --- a/examples/nav-context/Cargo.toml +++ b/examples/nav-context/Cargo.toml @@ -8,6 +8,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/open-dialog/Cargo.toml b/examples/open-dialog/Cargo.toml index b09b98ca..94049270 100644 --- a/examples/open-dialog/Cargo.toml +++ b/examples/open-dialog/Cargo.toml @@ -5,8 +5,8 @@ edition = "2021" [features] default = ["xdg-portal"] -rfd = ["libcosmic-yoda/rfd"] -xdg-portal = ["libcosmic-yoda/xdg-portal"] +rfd = ["libcosmic/rfd"] +xdg-portal = ["libcosmic/xdg-portal"] [dependencies] apply = "0.3.0" @@ -15,6 +15,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" url = "2.5.8" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] features = ["debug", "winit", "wgpu", "wayland", "tokio"] path = "../../" diff --git a/examples/spin-button/Cargo.toml b/examples/spin-button/Cargo.toml index 082c0fd5..a522050b 100644 --- a/examples/spin-button/Cargo.toml +++ b/examples/spin-button/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] fraction = "0.15.3" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] features = ["debug", "wgpu", "winit", "desktop", "tokio"] path = "../.." default-features = false diff --git a/examples/subscriptions/Cargo.toml b/examples/subscriptions/Cargo.toml index ae31a395..8eb69ff3 100644 --- a/examples/subscriptions/Cargo.toml +++ b/examples/subscriptions/Cargo.toml @@ -5,6 +5,6 @@ edition = "2024" [dependencies] -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"] diff --git a/examples/table-view/Cargo.toml b/examples/table-view/Cargo.toml index 8f71e5b4..8ed45928 100644 --- a/examples/table-view/Cargo.toml +++ b/examples/table-view/Cargo.toml @@ -9,6 +9,6 @@ tracing-subscriber = "0.3.22" tracing-log = "0.2.0" chrono = "*" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] features = ["debug", "wgpu", "winit", "desktop", "tokio"] path = "../.." diff --git a/examples/text-input/Cargo.toml b/examples/text-input/Cargo.toml index 69bd2a19..fe6105c2 100644 --- a/examples/text-input/Cargo.toml +++ b/examples/text-input/Cargo.toml @@ -8,6 +8,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"] diff --git a/i18n/af/libcosmic_yoda.ftl b/i18n/af/libcosmic.ftl similarity index 100% rename from i18n/af/libcosmic_yoda.ftl rename to i18n/af/libcosmic.ftl diff --git a/i18n/ar/libcosmic_yoda.ftl b/i18n/ar/libcosmic.ftl similarity index 100% rename from i18n/ar/libcosmic_yoda.ftl rename to i18n/ar/libcosmic.ftl diff --git a/i18n/be/libcosmic_yoda.ftl b/i18n/be/libcosmic.ftl similarity index 100% rename from i18n/be/libcosmic_yoda.ftl rename to i18n/be/libcosmic.ftl diff --git a/i18n/bg/libcosmic_yoda.ftl b/i18n/bg/libcosmic.ftl similarity index 100% rename from i18n/bg/libcosmic_yoda.ftl rename to i18n/bg/libcosmic.ftl diff --git a/i18n/bn/libcosmic_yoda.ftl b/i18n/bn/libcosmic.ftl similarity index 100% rename from i18n/bn/libcosmic_yoda.ftl rename to i18n/bn/libcosmic.ftl diff --git a/i18n/ca/libcosmic_yoda.ftl b/i18n/ca/libcosmic.ftl similarity index 100% rename from i18n/ca/libcosmic_yoda.ftl rename to i18n/ca/libcosmic.ftl diff --git a/i18n/cs/libcosmic_yoda.ftl b/i18n/cs/libcosmic.ftl similarity index 100% rename from i18n/cs/libcosmic_yoda.ftl rename to i18n/cs/libcosmic.ftl diff --git a/i18n/da/libcosmic_yoda.ftl b/i18n/da/libcosmic.ftl similarity index 100% rename from i18n/da/libcosmic_yoda.ftl rename to i18n/da/libcosmic.ftl diff --git a/i18n/de/libcosmic_yoda.ftl b/i18n/de/libcosmic.ftl similarity index 100% rename from i18n/de/libcosmic_yoda.ftl rename to i18n/de/libcosmic.ftl diff --git a/i18n/el/libcosmic_yoda.ftl b/i18n/el/libcosmic.ftl similarity index 100% rename from i18n/el/libcosmic_yoda.ftl rename to i18n/el/libcosmic.ftl diff --git a/i18n/en-GB/libcosmic_yoda.ftl b/i18n/en-GB/libcosmic.ftl similarity index 100% rename from i18n/en-GB/libcosmic_yoda.ftl rename to i18n/en-GB/libcosmic.ftl diff --git a/i18n/en/libcosmic_yoda.ftl b/i18n/en/libcosmic.ftl similarity index 100% rename from i18n/en/libcosmic_yoda.ftl rename to i18n/en/libcosmic.ftl diff --git a/i18n/eo/libcosmic_yoda.ftl b/i18n/eo/libcosmic.ftl similarity index 100% rename from i18n/eo/libcosmic_yoda.ftl rename to i18n/eo/libcosmic.ftl diff --git a/i18n/es-419/libcosmic_yoda.ftl b/i18n/es-419/libcosmic.ftl similarity index 100% rename from i18n/es-419/libcosmic_yoda.ftl rename to i18n/es-419/libcosmic.ftl diff --git a/i18n/es-MX/libcosmic_yoda.ftl b/i18n/es-MX/libcosmic.ftl similarity index 100% rename from i18n/es-MX/libcosmic_yoda.ftl rename to i18n/es-MX/libcosmic.ftl diff --git a/i18n/es/libcosmic_yoda.ftl b/i18n/es/libcosmic.ftl similarity index 100% rename from i18n/es/libcosmic_yoda.ftl rename to i18n/es/libcosmic.ftl diff --git a/i18n/et/libcosmic_yoda.ftl b/i18n/et/libcosmic.ftl similarity index 100% rename from i18n/et/libcosmic_yoda.ftl rename to i18n/et/libcosmic.ftl diff --git a/i18n/eu/libcosmic_yoda.ftl b/i18n/eu/libcosmic.ftl similarity index 100% rename from i18n/eu/libcosmic_yoda.ftl rename to i18n/eu/libcosmic.ftl diff --git a/i18n/fa/libcosmic_yoda.ftl b/i18n/fa/libcosmic.ftl similarity index 100% rename from i18n/fa/libcosmic_yoda.ftl rename to i18n/fa/libcosmic.ftl diff --git a/i18n/fi/libcosmic_yoda.ftl b/i18n/fi/libcosmic.ftl similarity index 100% rename from i18n/fi/libcosmic_yoda.ftl rename to i18n/fi/libcosmic.ftl diff --git a/i18n/fr/libcosmic_yoda.ftl b/i18n/fr/libcosmic.ftl similarity index 100% rename from i18n/fr/libcosmic_yoda.ftl rename to i18n/fr/libcosmic.ftl diff --git a/i18n/fy/libcosmic_yoda.ftl b/i18n/fy/libcosmic.ftl similarity index 100% rename from i18n/fy/libcosmic_yoda.ftl rename to i18n/fy/libcosmic.ftl diff --git a/i18n/ga/libcosmic_yoda.ftl b/i18n/ga/libcosmic.ftl similarity index 100% rename from i18n/ga/libcosmic_yoda.ftl rename to i18n/ga/libcosmic.ftl diff --git a/i18n/gd/libcosmic_yoda.ftl b/i18n/gd/libcosmic.ftl similarity index 100% rename from i18n/gd/libcosmic_yoda.ftl rename to i18n/gd/libcosmic.ftl diff --git a/i18n/gu/libcosmic_yoda.ftl b/i18n/gu/libcosmic.ftl similarity index 100% rename from i18n/gu/libcosmic_yoda.ftl rename to i18n/gu/libcosmic.ftl diff --git a/i18n/he/libcosmic_yoda.ftl b/i18n/he/libcosmic.ftl similarity index 100% rename from i18n/he/libcosmic_yoda.ftl rename to i18n/he/libcosmic.ftl diff --git a/i18n/hi/libcosmic_yoda.ftl b/i18n/hi/libcosmic.ftl similarity index 100% rename from i18n/hi/libcosmic_yoda.ftl rename to i18n/hi/libcosmic.ftl diff --git a/i18n/hr/libcosmic_yoda.ftl b/i18n/hr/libcosmic.ftl similarity index 100% rename from i18n/hr/libcosmic_yoda.ftl rename to i18n/hr/libcosmic.ftl diff --git a/i18n/hu/libcosmic_yoda.ftl b/i18n/hu/libcosmic.ftl similarity index 100% rename from i18n/hu/libcosmic_yoda.ftl rename to i18n/hu/libcosmic.ftl diff --git a/i18n/id/libcosmic_yoda.ftl b/i18n/id/libcosmic.ftl similarity index 100% rename from i18n/id/libcosmic_yoda.ftl rename to i18n/id/libcosmic.ftl diff --git a/i18n/ie/libcosmic_yoda.ftl b/i18n/ie/libcosmic.ftl similarity index 100% rename from i18n/ie/libcosmic_yoda.ftl rename to i18n/ie/libcosmic.ftl diff --git a/i18n/is/libcosmic_yoda.ftl b/i18n/is/libcosmic.ftl similarity index 100% rename from i18n/is/libcosmic_yoda.ftl rename to i18n/is/libcosmic.ftl diff --git a/i18n/it/libcosmic_yoda.ftl b/i18n/it/libcosmic.ftl similarity index 100% rename from i18n/it/libcosmic_yoda.ftl rename to i18n/it/libcosmic.ftl diff --git a/i18n/ja/libcosmic_yoda.ftl b/i18n/ja/libcosmic.ftl similarity index 100% rename from i18n/ja/libcosmic_yoda.ftl rename to i18n/ja/libcosmic.ftl diff --git a/i18n/jv/libcosmic_yoda.ftl b/i18n/jv/libcosmic.ftl similarity index 100% rename from i18n/jv/libcosmic_yoda.ftl rename to i18n/jv/libcosmic.ftl diff --git a/i18n/ka/libcosmic_yoda.ftl b/i18n/ka/libcosmic.ftl similarity index 100% rename from i18n/ka/libcosmic_yoda.ftl rename to i18n/ka/libcosmic.ftl diff --git a/i18n/kab/libcosmic_yoda.ftl b/i18n/kab/libcosmic.ftl similarity index 100% rename from i18n/kab/libcosmic_yoda.ftl rename to i18n/kab/libcosmic.ftl diff --git a/i18n/kk/libcosmic_yoda.ftl b/i18n/kk/libcosmic.ftl similarity index 100% rename from i18n/kk/libcosmic_yoda.ftl rename to i18n/kk/libcosmic.ftl diff --git a/i18n/kmr/libcosmic_yoda.ftl b/i18n/kmr/libcosmic.ftl similarity index 100% rename from i18n/kmr/libcosmic_yoda.ftl rename to i18n/kmr/libcosmic.ftl diff --git a/i18n/kn/libcosmic_yoda.ftl b/i18n/kn/libcosmic.ftl similarity index 100% rename from i18n/kn/libcosmic_yoda.ftl rename to i18n/kn/libcosmic.ftl diff --git a/i18n/ko/libcosmic_yoda.ftl b/i18n/ko/libcosmic.ftl similarity index 100% rename from i18n/ko/libcosmic_yoda.ftl rename to i18n/ko/libcosmic.ftl diff --git a/i18n/li/libcosmic_yoda.ftl b/i18n/li/libcosmic.ftl similarity index 100% rename from i18n/li/libcosmic_yoda.ftl rename to i18n/li/libcosmic.ftl diff --git a/i18n/lt/libcosmic_yoda.ftl b/i18n/lt/libcosmic.ftl similarity index 100% rename from i18n/lt/libcosmic_yoda.ftl rename to i18n/lt/libcosmic.ftl diff --git a/i18n/ml/libcosmic_yoda.ftl b/i18n/ml/libcosmic.ftl similarity index 100% rename from i18n/ml/libcosmic_yoda.ftl rename to i18n/ml/libcosmic.ftl diff --git a/i18n/ms/libcosmic_yoda.ftl b/i18n/ms/libcosmic.ftl similarity index 100% rename from i18n/ms/libcosmic_yoda.ftl rename to i18n/ms/libcosmic.ftl diff --git a/i18n/nb-NO/libcosmic_yoda.ftl b/i18n/nb-NO/libcosmic.ftl similarity index 100% rename from i18n/nb-NO/libcosmic_yoda.ftl rename to i18n/nb-NO/libcosmic.ftl diff --git a/i18n/nl/libcosmic_yoda.ftl b/i18n/nl/libcosmic.ftl similarity index 100% rename from i18n/nl/libcosmic_yoda.ftl rename to i18n/nl/libcosmic.ftl diff --git a/i18n/nn/libcosmic_yoda.ftl b/i18n/nn/libcosmic.ftl similarity index 100% rename from i18n/nn/libcosmic_yoda.ftl rename to i18n/nn/libcosmic.ftl diff --git a/i18n/oc/libcosmic_yoda.ftl b/i18n/oc/libcosmic.ftl similarity index 100% rename from i18n/oc/libcosmic_yoda.ftl rename to i18n/oc/libcosmic.ftl diff --git a/i18n/pa/libcosmic_yoda.ftl b/i18n/pa/libcosmic.ftl similarity index 100% rename from i18n/pa/libcosmic_yoda.ftl rename to i18n/pa/libcosmic.ftl diff --git a/i18n/pl/libcosmic_yoda.ftl b/i18n/pl/libcosmic.ftl similarity index 100% rename from i18n/pl/libcosmic_yoda.ftl rename to i18n/pl/libcosmic.ftl diff --git a/i18n/pt-BR/libcosmic_yoda.ftl b/i18n/pt-BR/libcosmic.ftl similarity index 100% rename from i18n/pt-BR/libcosmic_yoda.ftl rename to i18n/pt-BR/libcosmic.ftl diff --git a/i18n/pt/libcosmic_yoda.ftl b/i18n/pt/libcosmic.ftl similarity index 100% rename from i18n/pt/libcosmic_yoda.ftl rename to i18n/pt/libcosmic.ftl diff --git a/i18n/ro/libcosmic_yoda.ftl b/i18n/ro/libcosmic.ftl similarity index 100% rename from i18n/ro/libcosmic_yoda.ftl rename to i18n/ro/libcosmic.ftl diff --git a/i18n/ru/libcosmic_yoda.ftl b/i18n/ru/libcosmic.ftl similarity index 100% rename from i18n/ru/libcosmic_yoda.ftl rename to i18n/ru/libcosmic.ftl diff --git a/i18n/sk/libcosmic_yoda.ftl b/i18n/sk/libcosmic.ftl similarity index 100% rename from i18n/sk/libcosmic_yoda.ftl rename to i18n/sk/libcosmic.ftl diff --git a/i18n/sl/libcosmic_yoda.ftl b/i18n/sl/libcosmic.ftl similarity index 100% rename from i18n/sl/libcosmic_yoda.ftl rename to i18n/sl/libcosmic.ftl diff --git a/i18n/sr-Cyrl/libcosmic_yoda.ftl b/i18n/sr-Cyrl/libcosmic.ftl similarity index 100% rename from i18n/sr-Cyrl/libcosmic_yoda.ftl rename to i18n/sr-Cyrl/libcosmic.ftl diff --git a/i18n/sr-Latn/libcosmic_yoda.ftl b/i18n/sr-Latn/libcosmic.ftl similarity index 100% rename from i18n/sr-Latn/libcosmic_yoda.ftl rename to i18n/sr-Latn/libcosmic.ftl diff --git a/i18n/sr/libcosmic_yoda.ftl b/i18n/sr/libcosmic.ftl similarity index 100% rename from i18n/sr/libcosmic_yoda.ftl rename to i18n/sr/libcosmic.ftl diff --git a/i18n/sv/libcosmic_yoda.ftl b/i18n/sv/libcosmic.ftl similarity index 100% rename from i18n/sv/libcosmic_yoda.ftl rename to i18n/sv/libcosmic.ftl diff --git a/i18n/ta/libcosmic_yoda.ftl b/i18n/ta/libcosmic.ftl similarity index 100% rename from i18n/ta/libcosmic_yoda.ftl rename to i18n/ta/libcosmic.ftl diff --git a/i18n/th/libcosmic_yoda.ftl b/i18n/th/libcosmic.ftl similarity index 100% rename from i18n/th/libcosmic_yoda.ftl rename to i18n/th/libcosmic.ftl diff --git a/i18n/ti/libcosmic_yoda.ftl b/i18n/ti/libcosmic.ftl similarity index 100% rename from i18n/ti/libcosmic_yoda.ftl rename to i18n/ti/libcosmic.ftl diff --git a/i18n/tr/libcosmic_yoda.ftl b/i18n/tr/libcosmic.ftl similarity index 100% rename from i18n/tr/libcosmic_yoda.ftl rename to i18n/tr/libcosmic.ftl diff --git a/i18n/uk/libcosmic_yoda.ftl b/i18n/uk/libcosmic.ftl similarity index 100% rename from i18n/uk/libcosmic_yoda.ftl rename to i18n/uk/libcosmic.ftl diff --git a/i18n/uz/libcosmic_yoda.ftl b/i18n/uz/libcosmic.ftl similarity index 100% rename from i18n/uz/libcosmic_yoda.ftl rename to i18n/uz/libcosmic.ftl diff --git a/i18n/vi/libcosmic_yoda.ftl b/i18n/vi/libcosmic.ftl similarity index 100% rename from i18n/vi/libcosmic_yoda.ftl rename to i18n/vi/libcosmic.ftl diff --git a/i18n/yue-Hant/libcosmic_yoda.ftl b/i18n/yue-Hant/libcosmic.ftl similarity index 100% rename from i18n/yue-Hant/libcosmic_yoda.ftl rename to i18n/yue-Hant/libcosmic.ftl diff --git a/i18n/zh-Hans/libcosmic_yoda.ftl b/i18n/zh-Hans/libcosmic.ftl similarity index 100% rename from i18n/zh-Hans/libcosmic_yoda.ftl rename to i18n/zh-Hans/libcosmic.ftl diff --git a/i18n/zh-Hant/libcosmic_yoda.ftl b/i18n/zh-Hant/libcosmic.ftl similarity index 100% rename from i18n/zh-Hant/libcosmic_yoda.ftl rename to i18n/zh-Hant/libcosmic.ftl diff --git a/src/action.rs b/src/action.rs index b7162896..19e228b8 100644 --- a/src/action.rs +++ b/src/action.rs @@ -1,7 +1,6 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -#[cfg(feature = "winit")] use crate::app; #[cfg(feature = "single-instance")] use crate::dbus_activation; @@ -9,7 +8,6 @@ use crate::dbus_activation; pub const fn app(message: M) -> Action { Action::App(message) } -#[cfg(feature = "winit")] pub const fn cosmic(message: app::Action) -> Action { Action::Cosmic(message) } @@ -23,7 +21,6 @@ pub const fn none() -> Action { pub enum Action { /// Messages from the application, for the application. App(M), - #[cfg(feature = "winit")] /// Internal messages to be handled by libcosmic. Cosmic(app::Action), #[cfg(feature = "single-instance")] diff --git a/src/command.rs b/src/command.rs index 1d6f635c..c5c1c62b 100644 --- a/src/command.rs +++ b/src/command.rs @@ -27,12 +27,10 @@ pub fn set_title(id: window::Id, title: String) -> iced::Task(factor: f32) -> iced::Task> { iced::Task::done(crate::app::Action::ScaleFactor(factor)).map(crate::Action::Cosmic) } -#[cfg(feature = "winit")] pub fn set_theme(theme: crate::Theme) -> iced::Task> { iced::Task::done(crate::app::Action::AppThemeChange(theme)).map(crate::Action::Cosmic) } diff --git a/src/core.rs b/src/core.rs index 970a5351..44f3b3df 100644 --- a/src/core.rs +++ b/src/core.rs @@ -432,7 +432,6 @@ impl Core { id } - #[cfg(feature = "winit")] pub fn drag(&self, id: Option) -> crate::app::Task { let Some(id) = id.or(self.main_window) else { return iced::Task::none(); @@ -440,7 +439,6 @@ impl Core { crate::command::drag(id) } - #[cfg(feature = "winit")] pub fn maximize( &self, id: Option, @@ -452,7 +450,6 @@ impl Core { crate::command::maximize(id, maximized) } - #[cfg(feature = "winit")] pub fn minimize(&self, id: Option) -> crate::app::Task { let Some(id) = id.or(self.main_window) else { return iced::Task::none(); @@ -460,7 +457,6 @@ impl Core { crate::command::minimize(id) } - #[cfg(feature = "winit")] pub fn set_title( &self, id: Option, @@ -472,7 +468,6 @@ impl Core { crate::command::set_title(id, title) } - #[cfg(feature = "winit")] pub fn set_windowed(&self, id: Option) -> crate::app::Task { let Some(id) = id.or(self.main_window) else { return iced::Task::none(); @@ -480,7 +475,6 @@ impl Core { crate::command::set_windowed(id) } - #[cfg(feature = "winit")] pub fn toggle_maximize( &self, id: Option, diff --git a/src/lib.rs b/src/lib.rs index 02623799..1eacb962 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,6 @@ /// Recommended default imports. pub mod prelude { - #[cfg(feature = "winit")] pub use crate::ApplicationExt; pub use crate::ext::*; pub use crate::{Also, Apply, Element, Renderer, Task, Theme}; @@ -21,9 +20,7 @@ pub use action::Action; pub mod anim; -#[cfg(feature = "winit")] pub mod app; -#[cfg(feature = "winit")] #[doc(inline)] pub use app::{Application, ApplicationExt}; diff --git a/src/surface/action.rs b/src/surface/action.rs index 50e2b4a9..72dbad66 100644 --- a/src/surface/action.rs +++ b/src/surface/action.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MPL-2.0 use super::Action; -#[cfg(feature = "winit")] use crate::Application; use iced::window; @@ -27,7 +26,7 @@ pub fn destroy_window(id: iced_core::window::Id) -> Action { Action::DestroyWindow(id) } -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] #[must_use] pub fn app_window( settings: impl Fn(&mut App) -> window::Settings + Send + Sync + 'static, @@ -60,7 +59,7 @@ pub fn app_window( } /// Used to create a window message from within a widget. -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] #[must_use] pub fn simple_window( settings: impl Fn() -> window::Settings + Send + Sync + 'static, @@ -92,7 +91,7 @@ pub fn simple_window( ) } -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] #[must_use] pub fn app_popup( settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings @@ -126,7 +125,7 @@ pub fn app_popup( } /// Used to create a subsurface message from within a widget. -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] #[must_use] pub fn simple_subsurface( settings: impl Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings @@ -155,7 +154,7 @@ pub fn simple_subsurface( } /// Used to create a popup message from within a widget. -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] #[must_use] pub fn simple_popup( settings: impl Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings @@ -186,7 +185,7 @@ pub fn simple_popup( ) } -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] #[must_use] pub fn subsurface( settings: impl Fn( From 3e23d0872861ed9cd06159b247861f3b539aacb1 Mon Sep 17 00:00:00 2001 From: leyoda Date: Thu, 23 Apr 2026 15:32:29 +0200 Subject: [PATCH 14/75] =?UTF-8?q?yoda:=20re-apply=20hard=20rename=20?= =?UTF-8?q?=E2=80=94=20libcosmic=20=E2=86=92=20libcosmic-yoda=20(0.1.0-yod?= =?UTF-8?q?a)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts the soft-fork pivot (6736a596). Strategy chosen with user: full cascade fork. Every consumer (leyoda/cosmic-files + each leyoda/app) depends on libcosmic-yoda by explicit path, so there are no transitive deps left asking for the upstream 'libcosmic' crate — no [patch] unification issues, no two-version traps. Changes: - Cargo.toml: name = libcosmic-yoda, version = 0.1.0-yoda - i18n/*/libcosmic.ftl -> libcosmic_yoda.ftl (71 locales) - examples/*/Cargo.toml dep refs back to libcosmic-yoda - compat stub features (winit=[], x11=[]) kept for upstream deps that might still request them during the migration window cargo check --lib passes. --- Cargo.toml | 19 +++++++------------ examples/about/Cargo.toml | 2 +- examples/applet/Cargo.toml | 2 +- examples/application/Cargo.toml | 4 ++-- examples/calendar/Cargo.toml | 2 +- examples/context-menu/Cargo.toml | 2 +- examples/cosmic/Cargo.toml | 2 +- examples/image-button/Cargo.toml | 2 +- examples/menu/Cargo.toml | 2 +- examples/multi-window/Cargo.toml | 2 +- examples/nav-context/Cargo.toml | 2 +- examples/open-dialog/Cargo.toml | 6 +++--- examples/spin-button/Cargo.toml | 2 +- examples/subscriptions/Cargo.toml | 2 +- examples/table-view/Cargo.toml | 2 +- examples/text-input/Cargo.toml | 2 +- i18n/af/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ar/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/be/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/bg/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/bn/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ca/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/cs/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/da/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/de/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/el/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/en/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/eo/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/es/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/et/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/eu/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/fa/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/fi/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/fr/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/fy/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ga/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/gd/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/gu/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/he/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/hi/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/hr/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/hu/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/id/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ie/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/is/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/it/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ja/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/jv/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ka/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../kab/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/kk/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../kmr/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/kn/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ko/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/li/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/lt/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ml/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ms/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/nl/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/nn/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/oc/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/pa/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/pl/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/pt/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ro/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ru/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/sk/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/sl/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/sr/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/sv/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ta/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/th/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ti/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/tr/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/uk/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/uz/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/vi/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 87 files changed, 25 insertions(+), 30 deletions(-) rename i18n/af/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ar/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/be/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/bg/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/bn/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ca/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/cs/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/da/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/de/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/el/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/en-GB/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/en/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/eo/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/es-419/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/es-MX/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/es/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/et/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/eu/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/fa/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/fi/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/fr/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/fy/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ga/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/gd/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/gu/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/he/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/hi/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/hr/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/hu/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/id/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ie/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/is/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/it/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ja/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/jv/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ka/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/kab/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/kk/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/kmr/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/kn/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ko/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/li/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/lt/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ml/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ms/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/nb-NO/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/nl/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/nn/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/oc/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/pa/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/pl/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/pt-BR/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/pt/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ro/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ru/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sk/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sl/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sr-Cyrl/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sr-Latn/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sr/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sv/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ta/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/th/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ti/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/tr/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/uk/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/uz/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/vi/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/yue-Hant/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/zh-Hans/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/zh-Hant/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) diff --git a/Cargo.toml b/Cargo.toml index 83dd3bc3..13adfd73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,11 @@ [package] -# NOTE (yoda fork): Cargo package name kept as "libcosmic" — see commit -# 255cf7cc & its follow-up. Renaming it to "libcosmic-yoda" broke transitive -# dep unification: cosmic-files (still upstream) asks for "libcosmic" and -# Cargo's [patch] with `package = "libcosmic-yoda"` does NOT unify across -# the graph, so two copies of the crate end up in the binary with -# incompatible types. Yoda identity is kept via: -# - repo: forgejo leyoda/libcosmic-yoda -# - branch: main (vs upstream master) -# - version: 0.1.0-yoda (pre-1.0 marker denotes divergent lineage) -# - [lib] name stays "cosmic" (unchanged, matches every consumer) -name = "libcosmic" -version = "1.0.0" +# Yoda fork: hard-renamed. Every consumer (leyoda/cosmic-files fork + each +# leyoda/cosmic-* app) depends directly on `libcosmic-yoda` by path, bypassing +# pop-os/libcosmic entirely. No [patch] shenanigans needed — transitive deps +# that used to ask for `libcosmic` are replaced by deps on our forks that ask +# for `libcosmic-yoda`. +name = "libcosmic-yoda" +version = "0.1.0-yoda" edition = "2024" rust-version = "1.90" diff --git a/examples/about/Cargo.toml b/examples/about/Cargo.toml index f980811c..b27b5134 100644 --- a/examples/about/Cargo.toml +++ b/examples/about/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] open = "5.3.3" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = [ "debug", diff --git a/examples/applet/Cargo.toml b/examples/applet/Cargo.toml index 13eff684..265fbe7f 100644 --- a/examples/applet/Cargo.toml +++ b/examples/applet/Cargo.toml @@ -12,7 +12,7 @@ tracing = "0.1" env_logger = "0.10.2" log = "0.4.29" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" default-features = false features = ["applet-token"] diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index 7a6083e0..d4c75171 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -5,12 +5,12 @@ edition = "2021" [features] default = ["wayland"] -wayland = ["libcosmic/wayland"] +wayland = ["libcosmic-yoda/wayland"] [dependencies] env_logger = "0.11" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = [ "debug", diff --git a/examples/calendar/Cargo.toml b/examples/calendar/Cargo.toml index b7286825..203f7c17 100644 --- a/examples/calendar/Cargo.toml +++ b/examples/calendar/Cargo.toml @@ -8,6 +8,6 @@ edition = "2024" [dependencies] jiff = "0.2" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/context-menu/Cargo.toml b/examples/context-menu/Cargo.toml index 39c550f4..4c1eed60 100644 --- a/examples/context-menu/Cargo.toml +++ b/examples/context-menu/Cargo.toml @@ -8,7 +8,7 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = [ "debug", diff --git a/examples/cosmic/Cargo.toml b/examples/cosmic/Cargo.toml index 8c2a3126..eebf6c31 100644 --- a/examples/cosmic/Cargo.toml +++ b/examples/cosmic/Cargo.toml @@ -8,7 +8,7 @@ publish = false [dependencies] apply = "0.3.0" fraction = "0.15.3" -libcosmic = { path = "../..", features = [ +libcosmic-yoda = { path = "../..", features = [ "debug", "winit", "tokio", diff --git a/examples/image-button/Cargo.toml b/examples/image-button/Cargo.toml index c219a53b..8bc521f7 100644 --- a/examples/image-button/Cargo.toml +++ b/examples/image-button/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" tracing = "0.1.44" tracing-subscriber = "0.3.22" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "wgpu", "tokio"] diff --git a/examples/menu/Cargo.toml b/examples/menu/Cargo.toml index 430b26ea..047055e5 100644 --- a/examples/menu/Cargo.toml +++ b/examples/menu/Cargo.toml @@ -8,6 +8,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/multi-window/Cargo.toml b/examples/multi-window/Cargo.toml index 0b5440f8..c38595fa 100644 --- a/examples/multi-window/Cargo.toml +++ b/examples/multi-window/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "wgpu", "wayland"] } +libcosmic-yoda = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "wgpu", "wayland"] } diff --git a/examples/nav-context/Cargo.toml b/examples/nav-context/Cargo.toml index d829df0f..ea2bc2b8 100644 --- a/examples/nav-context/Cargo.toml +++ b/examples/nav-context/Cargo.toml @@ -8,6 +8,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/open-dialog/Cargo.toml b/examples/open-dialog/Cargo.toml index 94049270..b09b98ca 100644 --- a/examples/open-dialog/Cargo.toml +++ b/examples/open-dialog/Cargo.toml @@ -5,8 +5,8 @@ edition = "2021" [features] default = ["xdg-portal"] -rfd = ["libcosmic/rfd"] -xdg-portal = ["libcosmic/xdg-portal"] +rfd = ["libcosmic-yoda/rfd"] +xdg-portal = ["libcosmic-yoda/xdg-portal"] [dependencies] apply = "0.3.0" @@ -15,6 +15,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" url = "2.5.8" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] features = ["debug", "winit", "wgpu", "wayland", "tokio"] path = "../../" diff --git a/examples/spin-button/Cargo.toml b/examples/spin-button/Cargo.toml index a522050b..082c0fd5 100644 --- a/examples/spin-button/Cargo.toml +++ b/examples/spin-button/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] fraction = "0.15.3" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] features = ["debug", "wgpu", "winit", "desktop", "tokio"] path = "../.." default-features = false diff --git a/examples/subscriptions/Cargo.toml b/examples/subscriptions/Cargo.toml index 8eb69ff3..ae31a395 100644 --- a/examples/subscriptions/Cargo.toml +++ b/examples/subscriptions/Cargo.toml @@ -5,6 +5,6 @@ edition = "2024" [dependencies] -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"] diff --git a/examples/table-view/Cargo.toml b/examples/table-view/Cargo.toml index 8ed45928..8f71e5b4 100644 --- a/examples/table-view/Cargo.toml +++ b/examples/table-view/Cargo.toml @@ -9,6 +9,6 @@ tracing-subscriber = "0.3.22" tracing-log = "0.2.0" chrono = "*" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] features = ["debug", "wgpu", "winit", "desktop", "tokio"] path = "../.." diff --git a/examples/text-input/Cargo.toml b/examples/text-input/Cargo.toml index fe6105c2..69bd2a19 100644 --- a/examples/text-input/Cargo.toml +++ b/examples/text-input/Cargo.toml @@ -8,6 +8,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"] diff --git a/i18n/af/libcosmic.ftl b/i18n/af/libcosmic_yoda.ftl similarity index 100% rename from i18n/af/libcosmic.ftl rename to i18n/af/libcosmic_yoda.ftl diff --git a/i18n/ar/libcosmic.ftl b/i18n/ar/libcosmic_yoda.ftl similarity index 100% rename from i18n/ar/libcosmic.ftl rename to i18n/ar/libcosmic_yoda.ftl diff --git a/i18n/be/libcosmic.ftl b/i18n/be/libcosmic_yoda.ftl similarity index 100% rename from i18n/be/libcosmic.ftl rename to i18n/be/libcosmic_yoda.ftl diff --git a/i18n/bg/libcosmic.ftl b/i18n/bg/libcosmic_yoda.ftl similarity index 100% rename from i18n/bg/libcosmic.ftl rename to i18n/bg/libcosmic_yoda.ftl diff --git a/i18n/bn/libcosmic.ftl b/i18n/bn/libcosmic_yoda.ftl similarity index 100% rename from i18n/bn/libcosmic.ftl rename to i18n/bn/libcosmic_yoda.ftl diff --git a/i18n/ca/libcosmic.ftl b/i18n/ca/libcosmic_yoda.ftl similarity index 100% rename from i18n/ca/libcosmic.ftl rename to i18n/ca/libcosmic_yoda.ftl diff --git a/i18n/cs/libcosmic.ftl b/i18n/cs/libcosmic_yoda.ftl similarity index 100% rename from i18n/cs/libcosmic.ftl rename to i18n/cs/libcosmic_yoda.ftl diff --git a/i18n/da/libcosmic.ftl b/i18n/da/libcosmic_yoda.ftl similarity index 100% rename from i18n/da/libcosmic.ftl rename to i18n/da/libcosmic_yoda.ftl diff --git a/i18n/de/libcosmic.ftl b/i18n/de/libcosmic_yoda.ftl similarity index 100% rename from i18n/de/libcosmic.ftl rename to i18n/de/libcosmic_yoda.ftl diff --git a/i18n/el/libcosmic.ftl b/i18n/el/libcosmic_yoda.ftl similarity index 100% rename from i18n/el/libcosmic.ftl rename to i18n/el/libcosmic_yoda.ftl diff --git a/i18n/en-GB/libcosmic.ftl b/i18n/en-GB/libcosmic_yoda.ftl similarity index 100% rename from i18n/en-GB/libcosmic.ftl rename to i18n/en-GB/libcosmic_yoda.ftl diff --git a/i18n/en/libcosmic.ftl b/i18n/en/libcosmic_yoda.ftl similarity index 100% rename from i18n/en/libcosmic.ftl rename to i18n/en/libcosmic_yoda.ftl diff --git a/i18n/eo/libcosmic.ftl b/i18n/eo/libcosmic_yoda.ftl similarity index 100% rename from i18n/eo/libcosmic.ftl rename to i18n/eo/libcosmic_yoda.ftl diff --git a/i18n/es-419/libcosmic.ftl b/i18n/es-419/libcosmic_yoda.ftl similarity index 100% rename from i18n/es-419/libcosmic.ftl rename to i18n/es-419/libcosmic_yoda.ftl diff --git a/i18n/es-MX/libcosmic.ftl b/i18n/es-MX/libcosmic_yoda.ftl similarity index 100% rename from i18n/es-MX/libcosmic.ftl rename to i18n/es-MX/libcosmic_yoda.ftl diff --git a/i18n/es/libcosmic.ftl b/i18n/es/libcosmic_yoda.ftl similarity index 100% rename from i18n/es/libcosmic.ftl rename to i18n/es/libcosmic_yoda.ftl diff --git a/i18n/et/libcosmic.ftl b/i18n/et/libcosmic_yoda.ftl similarity index 100% rename from i18n/et/libcosmic.ftl rename to i18n/et/libcosmic_yoda.ftl diff --git a/i18n/eu/libcosmic.ftl b/i18n/eu/libcosmic_yoda.ftl similarity index 100% rename from i18n/eu/libcosmic.ftl rename to i18n/eu/libcosmic_yoda.ftl diff --git a/i18n/fa/libcosmic.ftl b/i18n/fa/libcosmic_yoda.ftl similarity index 100% rename from i18n/fa/libcosmic.ftl rename to i18n/fa/libcosmic_yoda.ftl diff --git a/i18n/fi/libcosmic.ftl b/i18n/fi/libcosmic_yoda.ftl similarity index 100% rename from i18n/fi/libcosmic.ftl rename to i18n/fi/libcosmic_yoda.ftl diff --git a/i18n/fr/libcosmic.ftl b/i18n/fr/libcosmic_yoda.ftl similarity index 100% rename from i18n/fr/libcosmic.ftl rename to i18n/fr/libcosmic_yoda.ftl diff --git a/i18n/fy/libcosmic.ftl b/i18n/fy/libcosmic_yoda.ftl similarity index 100% rename from i18n/fy/libcosmic.ftl rename to i18n/fy/libcosmic_yoda.ftl diff --git a/i18n/ga/libcosmic.ftl b/i18n/ga/libcosmic_yoda.ftl similarity index 100% rename from i18n/ga/libcosmic.ftl rename to i18n/ga/libcosmic_yoda.ftl diff --git a/i18n/gd/libcosmic.ftl b/i18n/gd/libcosmic_yoda.ftl similarity index 100% rename from i18n/gd/libcosmic.ftl rename to i18n/gd/libcosmic_yoda.ftl diff --git a/i18n/gu/libcosmic.ftl b/i18n/gu/libcosmic_yoda.ftl similarity index 100% rename from i18n/gu/libcosmic.ftl rename to i18n/gu/libcosmic_yoda.ftl diff --git a/i18n/he/libcosmic.ftl b/i18n/he/libcosmic_yoda.ftl similarity index 100% rename from i18n/he/libcosmic.ftl rename to i18n/he/libcosmic_yoda.ftl diff --git a/i18n/hi/libcosmic.ftl b/i18n/hi/libcosmic_yoda.ftl similarity index 100% rename from i18n/hi/libcosmic.ftl rename to i18n/hi/libcosmic_yoda.ftl diff --git a/i18n/hr/libcosmic.ftl b/i18n/hr/libcosmic_yoda.ftl similarity index 100% rename from i18n/hr/libcosmic.ftl rename to i18n/hr/libcosmic_yoda.ftl diff --git a/i18n/hu/libcosmic.ftl b/i18n/hu/libcosmic_yoda.ftl similarity index 100% rename from i18n/hu/libcosmic.ftl rename to i18n/hu/libcosmic_yoda.ftl diff --git a/i18n/id/libcosmic.ftl b/i18n/id/libcosmic_yoda.ftl similarity index 100% rename from i18n/id/libcosmic.ftl rename to i18n/id/libcosmic_yoda.ftl diff --git a/i18n/ie/libcosmic.ftl b/i18n/ie/libcosmic_yoda.ftl similarity index 100% rename from i18n/ie/libcosmic.ftl rename to i18n/ie/libcosmic_yoda.ftl diff --git a/i18n/is/libcosmic.ftl b/i18n/is/libcosmic_yoda.ftl similarity index 100% rename from i18n/is/libcosmic.ftl rename to i18n/is/libcosmic_yoda.ftl diff --git a/i18n/it/libcosmic.ftl b/i18n/it/libcosmic_yoda.ftl similarity index 100% rename from i18n/it/libcosmic.ftl rename to i18n/it/libcosmic_yoda.ftl diff --git a/i18n/ja/libcosmic.ftl b/i18n/ja/libcosmic_yoda.ftl similarity index 100% rename from i18n/ja/libcosmic.ftl rename to i18n/ja/libcosmic_yoda.ftl diff --git a/i18n/jv/libcosmic.ftl b/i18n/jv/libcosmic_yoda.ftl similarity index 100% rename from i18n/jv/libcosmic.ftl rename to i18n/jv/libcosmic_yoda.ftl diff --git a/i18n/ka/libcosmic.ftl b/i18n/ka/libcosmic_yoda.ftl similarity index 100% rename from i18n/ka/libcosmic.ftl rename to i18n/ka/libcosmic_yoda.ftl diff --git a/i18n/kab/libcosmic.ftl b/i18n/kab/libcosmic_yoda.ftl similarity index 100% rename from i18n/kab/libcosmic.ftl rename to i18n/kab/libcosmic_yoda.ftl diff --git a/i18n/kk/libcosmic.ftl b/i18n/kk/libcosmic_yoda.ftl similarity index 100% rename from i18n/kk/libcosmic.ftl rename to i18n/kk/libcosmic_yoda.ftl diff --git a/i18n/kmr/libcosmic.ftl b/i18n/kmr/libcosmic_yoda.ftl similarity index 100% rename from i18n/kmr/libcosmic.ftl rename to i18n/kmr/libcosmic_yoda.ftl diff --git a/i18n/kn/libcosmic.ftl b/i18n/kn/libcosmic_yoda.ftl similarity index 100% rename from i18n/kn/libcosmic.ftl rename to i18n/kn/libcosmic_yoda.ftl diff --git a/i18n/ko/libcosmic.ftl b/i18n/ko/libcosmic_yoda.ftl similarity index 100% rename from i18n/ko/libcosmic.ftl rename to i18n/ko/libcosmic_yoda.ftl diff --git a/i18n/li/libcosmic.ftl b/i18n/li/libcosmic_yoda.ftl similarity index 100% rename from i18n/li/libcosmic.ftl rename to i18n/li/libcosmic_yoda.ftl diff --git a/i18n/lt/libcosmic.ftl b/i18n/lt/libcosmic_yoda.ftl similarity index 100% rename from i18n/lt/libcosmic.ftl rename to i18n/lt/libcosmic_yoda.ftl diff --git a/i18n/ml/libcosmic.ftl b/i18n/ml/libcosmic_yoda.ftl similarity index 100% rename from i18n/ml/libcosmic.ftl rename to i18n/ml/libcosmic_yoda.ftl diff --git a/i18n/ms/libcosmic.ftl b/i18n/ms/libcosmic_yoda.ftl similarity index 100% rename from i18n/ms/libcosmic.ftl rename to i18n/ms/libcosmic_yoda.ftl diff --git a/i18n/nb-NO/libcosmic.ftl b/i18n/nb-NO/libcosmic_yoda.ftl similarity index 100% rename from i18n/nb-NO/libcosmic.ftl rename to i18n/nb-NO/libcosmic_yoda.ftl diff --git a/i18n/nl/libcosmic.ftl b/i18n/nl/libcosmic_yoda.ftl similarity index 100% rename from i18n/nl/libcosmic.ftl rename to i18n/nl/libcosmic_yoda.ftl diff --git a/i18n/nn/libcosmic.ftl b/i18n/nn/libcosmic_yoda.ftl similarity index 100% rename from i18n/nn/libcosmic.ftl rename to i18n/nn/libcosmic_yoda.ftl diff --git a/i18n/oc/libcosmic.ftl b/i18n/oc/libcosmic_yoda.ftl similarity index 100% rename from i18n/oc/libcosmic.ftl rename to i18n/oc/libcosmic_yoda.ftl diff --git a/i18n/pa/libcosmic.ftl b/i18n/pa/libcosmic_yoda.ftl similarity index 100% rename from i18n/pa/libcosmic.ftl rename to i18n/pa/libcosmic_yoda.ftl diff --git a/i18n/pl/libcosmic.ftl b/i18n/pl/libcosmic_yoda.ftl similarity index 100% rename from i18n/pl/libcosmic.ftl rename to i18n/pl/libcosmic_yoda.ftl diff --git a/i18n/pt-BR/libcosmic.ftl b/i18n/pt-BR/libcosmic_yoda.ftl similarity index 100% rename from i18n/pt-BR/libcosmic.ftl rename to i18n/pt-BR/libcosmic_yoda.ftl diff --git a/i18n/pt/libcosmic.ftl b/i18n/pt/libcosmic_yoda.ftl similarity index 100% rename from i18n/pt/libcosmic.ftl rename to i18n/pt/libcosmic_yoda.ftl diff --git a/i18n/ro/libcosmic.ftl b/i18n/ro/libcosmic_yoda.ftl similarity index 100% rename from i18n/ro/libcosmic.ftl rename to i18n/ro/libcosmic_yoda.ftl diff --git a/i18n/ru/libcosmic.ftl b/i18n/ru/libcosmic_yoda.ftl similarity index 100% rename from i18n/ru/libcosmic.ftl rename to i18n/ru/libcosmic_yoda.ftl diff --git a/i18n/sk/libcosmic.ftl b/i18n/sk/libcosmic_yoda.ftl similarity index 100% rename from i18n/sk/libcosmic.ftl rename to i18n/sk/libcosmic_yoda.ftl diff --git a/i18n/sl/libcosmic.ftl b/i18n/sl/libcosmic_yoda.ftl similarity index 100% rename from i18n/sl/libcosmic.ftl rename to i18n/sl/libcosmic_yoda.ftl diff --git a/i18n/sr-Cyrl/libcosmic.ftl b/i18n/sr-Cyrl/libcosmic_yoda.ftl similarity index 100% rename from i18n/sr-Cyrl/libcosmic.ftl rename to i18n/sr-Cyrl/libcosmic_yoda.ftl diff --git a/i18n/sr-Latn/libcosmic.ftl b/i18n/sr-Latn/libcosmic_yoda.ftl similarity index 100% rename from i18n/sr-Latn/libcosmic.ftl rename to i18n/sr-Latn/libcosmic_yoda.ftl diff --git a/i18n/sr/libcosmic.ftl b/i18n/sr/libcosmic_yoda.ftl similarity index 100% rename from i18n/sr/libcosmic.ftl rename to i18n/sr/libcosmic_yoda.ftl diff --git a/i18n/sv/libcosmic.ftl b/i18n/sv/libcosmic_yoda.ftl similarity index 100% rename from i18n/sv/libcosmic.ftl rename to i18n/sv/libcosmic_yoda.ftl diff --git a/i18n/ta/libcosmic.ftl b/i18n/ta/libcosmic_yoda.ftl similarity index 100% rename from i18n/ta/libcosmic.ftl rename to i18n/ta/libcosmic_yoda.ftl diff --git a/i18n/th/libcosmic.ftl b/i18n/th/libcosmic_yoda.ftl similarity index 100% rename from i18n/th/libcosmic.ftl rename to i18n/th/libcosmic_yoda.ftl diff --git a/i18n/ti/libcosmic.ftl b/i18n/ti/libcosmic_yoda.ftl similarity index 100% rename from i18n/ti/libcosmic.ftl rename to i18n/ti/libcosmic_yoda.ftl diff --git a/i18n/tr/libcosmic.ftl b/i18n/tr/libcosmic_yoda.ftl similarity index 100% rename from i18n/tr/libcosmic.ftl rename to i18n/tr/libcosmic_yoda.ftl diff --git a/i18n/uk/libcosmic.ftl b/i18n/uk/libcosmic_yoda.ftl similarity index 100% rename from i18n/uk/libcosmic.ftl rename to i18n/uk/libcosmic_yoda.ftl diff --git a/i18n/uz/libcosmic.ftl b/i18n/uz/libcosmic_yoda.ftl similarity index 100% rename from i18n/uz/libcosmic.ftl rename to i18n/uz/libcosmic_yoda.ftl diff --git a/i18n/vi/libcosmic.ftl b/i18n/vi/libcosmic_yoda.ftl similarity index 100% rename from i18n/vi/libcosmic.ftl rename to i18n/vi/libcosmic_yoda.ftl diff --git a/i18n/yue-Hant/libcosmic.ftl b/i18n/yue-Hant/libcosmic_yoda.ftl similarity index 100% rename from i18n/yue-Hant/libcosmic.ftl rename to i18n/yue-Hant/libcosmic_yoda.ftl diff --git a/i18n/zh-Hans/libcosmic.ftl b/i18n/zh-Hans/libcosmic_yoda.ftl similarity index 100% rename from i18n/zh-Hans/libcosmic.ftl rename to i18n/zh-Hans/libcosmic_yoda.ftl diff --git a/i18n/zh-Hant/libcosmic.ftl b/i18n/zh-Hant/libcosmic_yoda.ftl similarity index 100% rename from i18n/zh-Hant/libcosmic.ftl rename to i18n/zh-Hant/libcosmic_yoda.ftl From 17291536a10124a053b40c49bb459d7b5085331b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:46:50 +0200 Subject: [PATCH 15/75] fix(header_bar): prevent double coloring --- src/app/mod.rs | 1 - src/widget/header_bar.rs | 9 +++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index f78beac7..ad1ceefe 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -773,7 +773,6 @@ impl ApplicationExt for App { .focused(focused) .maximized(maximized) .sharp_corners(sharp_corners) - .transparent(content_container) .title(&core.window.header_title) .on_drag(crate::Action::Cosmic(Action::Drag)) .on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu)) diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index a772f7d2..0a10b0aa 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -26,7 +26,6 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { sharp_corners: false, is_ssd: false, on_double_click: None, - transparent: false, } } @@ -88,9 +87,6 @@ pub struct HeaderBar<'a, Message> { /// HeaderBar used for server-side decorations is_ssd: bool, - - /// Whether the headerbar should be transparent - transparent: bool, } impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { @@ -370,6 +366,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { space_xxs, .. } = theme::spacing(); + let is_ssd = self.is_ssd; // Take ownership of the regions to be packed. let start = std::mem::take(&mut self.start); @@ -379,7 +376,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { // Also packs the window controls at the very end. end.push(self.window_controls(space_xxs)); - let padding = if self.is_ssd { + let padding = if is_ssd { [2, 8, 2, 8] } else { match ( @@ -424,7 +421,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .class(theme::Container::HeaderBar { focused: self.focused, sharp_corners: self.sharp_corners, - transparent: self.transparent, + transparent: if is_ssd { false } else { true }, }) .height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32)) .padding(padding) From aec3eb615f497701093f52e39a1e0775853cec91 Mon Sep 17 00:00:00 2001 From: leyoda Date: Thu, 23 Apr 2026 17:36:21 +0200 Subject: [PATCH 16/75] yoda: ungate remaining winit+wayland combined cfgs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During Phase 3d it surfaced that several widgets keep a three-clause cfg `#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))]` (e.g. dropdown::widget::with_popup, widget/mod.rs menu bits, theme/style/mod.rs). Since the yoda fork is wayland-only the "winit" clause is vestigial — dropping it unhides these methods for Wayland consumers (cosmic-settings needs Dropdown::with_popup on the wallpaper page). Also fixed a cfg asymmetry in responsive_menu_bar.rs: the fallback block was gated `cfg(not(all(winit, wayland, linux)))` while the primary block was `cfg(all(wayland, linux))`. With winit removed both blocks were active and we got E0308 expected-() — aligned the cfgs so exactly one branch compiles. --- src/theme/style/mod.rs | 4 ++-- src/widget/dropdown/mod.rs | 2 +- src/widget/dropdown/widget.rs | 22 +++++++++++----------- src/widget/mod.rs | 2 +- src/widget/responsive_menu_bar.rs | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/theme/style/mod.rs b/src/theme/style/mod.rs index bc648a73..cc489312 100644 --- a/src/theme/style/mod.rs +++ b/src/theme/style/mod.rs @@ -32,7 +32,7 @@ mod text_input; #[doc(inline)] pub use self::text_input::TextInput; -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] pub mod tooltip; -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] pub use tooltip::Tooltip; diff --git a/src/widget/dropdown/mod.rs b/src/widget/dropdown/mod.rs index b5fd4c06..0ea3a212 100644 --- a/src/widget/dropdown/mod.rs +++ b/src/widget/dropdown/mod.rs @@ -50,7 +50,7 @@ pub fn popup_dropdown< let dropdown: Dropdown<'_, S, Message, AppMessage> = Dropdown::new(selections.into(), selected, on_selected); - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] let dropdown = dropdown.with_popup(_parent_id, _on_surface_action, _map_action); dropdown diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 2ff9c92f..7e7ac7a7 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -60,7 +60,7 @@ where action_map: Option AppMessage + 'static + Send + Sync>>, #[setters(strip_option)] window_id: Option, - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, } @@ -96,14 +96,14 @@ where text_line_height: text::LineHeight::Relative(1.2), font: None, window_id: None, - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), on_surface_action: None, action_map: None, } } - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] /// Handle dropdown requests for popup creation. /// Intended to be used with [`crate::app::message::get_popup`] pub fn with_popup( @@ -154,7 +154,7 @@ where self } - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] pub fn with_positioner( mut self, positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, @@ -268,7 +268,7 @@ where layout, cursor, shell, - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] self.positioner.clone(), self.on_selected.clone(), self.selected, @@ -346,7 +346,7 @@ where viewport: &Rectangle, translation: Vector, ) -> Option> { - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] if self.window_id.is_some() || self.on_surface_action.is_some() { return None; } @@ -545,7 +545,7 @@ pub fn update< layout: Layout<'_>, cursor: mouse::Cursor, shell: &mut Shell<'_, Message>, - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, on_selected: Arc Message + Send + Sync + 'static>, selected: Option, @@ -571,7 +571,7 @@ pub fn update< *hovered_guard = selected; let id = window::Id::unique(); state.popup_id = id; - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] if let Some(((on_surface_action, parent), action_map)) = on_surface_action .as_ref() .zip(_window_id) @@ -658,7 +658,7 @@ pub fn update< state.close_operation = false; state.is_open.store(false, Ordering::SeqCst); if is_open { - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] if let Some(ref on_close) = on_surface_action { shell.publish(on_close(surface::action::destroy_popup(state.popup_id))); } @@ -681,7 +681,7 @@ pub fn update< // Event wasn't processed by overlay, so cursor was clicked either outside it's // bounds or on the drop-down, either way we close the overlay. state.is_open.store(false, Ordering::Relaxed); - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] if let Some(on_close) = on_surface_action { shell.publish(on_close(surface::action::destroy_popup(state.popup_id))); } @@ -726,7 +726,7 @@ pub fn mouse_interaction(layout: Layout<'_>, cursor: mouse::Cursor) -> mouse::In } } -#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] /// Returns the current menu widget of a [`Dropdown`]. #[allow(clippy::too_many_arguments)] pub fn menu_widget< diff --git a/src/widget/mod.rs b/src/widget/mod.rs index db9b2fc4..edebe0dd 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -308,7 +308,7 @@ pub use toggler::{Toggler, toggler}; #[doc(inline)] pub use tooltip::{Tooltip, tooltip}; -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] pub mod wayland; pub mod tooltip { diff --git a/src/widget/responsive_menu_bar.rs b/src/widget/responsive_menu_bar.rs index b5dd556d..86b7f0d2 100644 --- a/src/widget/responsive_menu_bar.rs +++ b/src/widget/responsive_menu_bar.rs @@ -25,7 +25,7 @@ impl Default for ResponsiveMenuBar { fn default() -> ResponsiveMenuBar { ResponsiveMenuBar { collapsed_item_width: { - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] if matches!( crate::app::cosmic::WINDOWING_SYSTEM.get(), Some(crate::app::cosmic::WindowingSystem::Wayland) @@ -34,7 +34,7 @@ impl Default for ResponsiveMenuBar { } else { ItemWidth::Static(84) } - #[cfg(not(all(feature = "winit", feature = "wayland", target_os = "linux")))] + #[cfg(not(all(feature = "wayland", target_os = "linux")))] { ItemWidth::Static(84) } From 8ab7b158078eaafeee381487ad8267c459df859b Mon Sep 17 00:00:00 2001 From: leyoda Date: Thu, 23 Apr 2026 18:43:52 +0200 Subject: [PATCH 17/75] yoda-v2: color_picker Theme ref + context_menu/menu ungate winit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two perf + correctness wins packaged as +yoda-v2 (version bump 0.1.0-yoda -> 0.1.0-yoda.2): 1. color_picker::draw() — use the theme: &Theme parameter already passed to draw() instead of THEME.lock().unwrap().clone() which cloned the whole Theme on every redraw (src/widget/color_picker/mod.rs:622). Upstreamable. 2. Dropped feature = "winit" from 21 combined cfg attrs in context_menu.rs, menu/menu_bar.rs, menu/menu_inner.rs. These triple-gates (wayland + winit + surface-message) were silently disabling all context-menu and menubar popup creation in yoda apps (which don't activate the winit feature). Now the code only gates on wayland + surface-message, which is our actual runtime path. Matches the ungate we already did on surface/action.rs in Phase 3d. cargo check --lib passes. All 4 consumer apps rebuilt + installed as +yoda-v2 (backup of previous yoda binaries in .pre-yoda-v2 siblings). --- Cargo.toml | 2 +- src/widget/color_picker/mod.rs | 6 ++++-- src/widget/context_menu.rs | 6 ------ src/widget/menu/menu_bar.rs | 9 --------- src/widget/menu/menu_inner.rs | 6 +----- 5 files changed, 6 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 13adfd73..a5707e21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ # that used to ask for `libcosmic` are replaced by deps on our forks that ask # for `libcosmic-yoda`. name = "libcosmic-yoda" -version = "0.1.0-yoda" +version = "0.1.0-yoda.2" edition = "2024" rust-version = "1.90" diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 318e943b..85cfc161 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -619,8 +619,10 @@ where let bounds = canvas_layout.bounds(); // Draw the handle on the saturation value canvas - let t = THEME.lock().unwrap().clone(); - let t = t.cosmic(); + // Yoda: use the Theme passed into draw() instead of locking the global + // THEME Mutex and cloning the whole Theme. Fires on every color-picker + // redraw, so saving the Mutex lock + full Theme clone adds up. + let t = theme.cosmic(); let handle_radius = f32::from(t.space_xs()) / 2.0; let (x, y) = ( self.active_color diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 3f35f04a..5f525992 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -6,7 +6,6 @@ #[cfg(all( feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; @@ -67,7 +66,6 @@ impl ContextMenu<'_, Message> { #[cfg(all( feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] #[allow(clippy::too_many_lines)] @@ -377,7 +375,6 @@ impl Widget #[cfg(all( feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) @@ -421,7 +418,6 @@ impl Widget #[cfg(all( feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { @@ -443,7 +439,6 @@ impl Widget #[cfg(all( feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) @@ -482,7 +477,6 @@ impl Widget #[cfg(all( feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 981446e8..02ac693c 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -13,7 +13,6 @@ use super::{ feature = "multi-window", feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; @@ -199,7 +198,6 @@ pub struct MenuBar { #[cfg(all( feature = "multi-window", feature = "wayland", - feature = "winit", target_os = "linux" ))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, @@ -239,7 +237,6 @@ where #[cfg(all( feature = "multi-window", feature = "wayland", - feature = "winit", target_os = "linux" ))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), @@ -338,7 +335,6 @@ where #[cfg(all( feature = "multi-window", feature = "wayland", - feature = "winit", target_os = "linux" ))] pub fn with_positioner( @@ -376,7 +372,6 @@ where feature = "multi-window", feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] #[allow(clippy::too_many_lines)] @@ -647,7 +642,6 @@ where #[cfg(all( feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] { @@ -671,7 +665,6 @@ where feature = "multi-window", feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { @@ -686,7 +679,6 @@ where feature = "multi-window", feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { @@ -769,7 +761,6 @@ where feature = "multi-window", feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 74afe60f..a8607d24 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -8,7 +8,6 @@ use super::{menu_bar::MenuBarState, menu_tree::MenuTree}; feature = "multi-window", feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; @@ -682,7 +681,6 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { feature = "multi-window", feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) @@ -967,7 +965,6 @@ impl Widget( feature = "multi-window", feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] pub(super) fn init_root_popup_menu( @@ -1527,7 +1523,7 @@ where .as_ref() .is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty()); - #[cfg(all(feature = "multi-window", feature = "wayland",target_os = "linux", feature = "winit", feature = "surface-message"))] + #[cfg(all(feature = "multi-window", feature = "wayland", target_os = "linux", feature = "surface-message"))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove { if let Some(id) = state.popup_id.remove(&menu.window_id) { state.active_root.truncate(menu.depth + 1); From 8d1d873918d8cf863ec4da9af985807092d15b32 Mon Sep 17 00:00:00 2001 From: leyoda Date: Fri, 24 Apr 2026 06:00:03 +0200 Subject: [PATCH 18/75] yoda: drop x11 defaults on iced_winit + iced_tiny_skia MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dep chain audit showed winit, iced_winit and iced_tiny_skia all had "x11" in their default features. Even though our Wayland-only build doesn't use any X11 code path, the defaults were propagating through cargo feature unification — winit-x11, tiny-xlib, x11-dl, etc. were all being linked. Fix, at the dep declaration level: - [dependencies.iced_winit] default-features = false + wayland/wayland-dlopen - [dependencies.iced_tiny_skia] default-features = false + wayland - iced submodule's workspace winit = { ..., default-features = false } Result on cosmic-yoterm binary: - size: 55.4 MB -> 53.8 MB (-1.6 MB, ~3%) - winit symbols: 2151 -> 1340 (-811) - x11 symbols: 1526 -> 503 (-1023) Remaining x11 footprint (~500 symbols): - clipboard_x11 / x11rb: window_clipboard pulls both x11 + wayland backends unconditionally on unix targets. Would need forking window_clipboard to gate x11 behind a feature. - tiny-xlib / as-raw-xcb-connection: low-level window-handle bindings pulled by wgpu/softbuffer even when no X11 windows ever exist. Dead code at runtime, probably best left alone until wgpu/raw-window-handle upstream make these optional. --- Cargo.toml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index a5707e21..2f38df6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -226,10 +226,17 @@ optional = true [dependencies.iced_tiny_skia] path = "./iced/tiny_skia" +# Yoda: drop the x11 default → softbuffer no longer pulls tiny-xlib/x11-dl/etc. +default-features = false +features = ["wayland"] [dependencies.iced_winit] path = "./iced/winit" optional = true +# Yoda: drop the x11 default → winit won't pull winit-x11/tiny-xlib/x11-dl. +# Keep wayland + wayland-dlopen (default behaviour minus x11). +default-features = false +features = ["wayland", "wayland-dlopen"] [dependencies.iced_wgpu] path = "./iced/wgpu" From c118f5a2f684dcd79951ea558d373bb663b4d9ff Mon Sep 17 00:00:00 2001 From: leyoda Date: Fri, 24 Apr 2026 06:41:28 +0200 Subject: [PATCH 19/75] =?UTF-8?q?yoda:=20bump=20iced=20submodule=20ref=20?= =?UTF-8?q?=E2=86=92=20yoda-wayland-only=20HEAD=208f6be798?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Picks up two commits on leyoda/iced-yoda yoda-wayland-only branch: - winit workspace dep default-features = false (drops winit/x11 backend) - iced_wgpu gated x11 backend behind opt-in feature Companion to libcosmic-yoda 8d1d8739 (drop x11 defaults on iced_winit + iced_tiny_skia) for the yoda-v4 Wayland-only cut. --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 78caabba..8f6be798 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 78caabba7ef91cd1030da6f70b41d266704ffece +Subproject commit 8f6be7984af03bc5457966b416d7a240b2c31d4e From 388e06552f597436cef7b5055d870367f966d3f2 Mon Sep 17 00:00:00 2001 From: leyoda Date: Fri, 24 Apr 2026 06:53:37 +0200 Subject: [PATCH 20/75] =?UTF-8?q?yoda:=20bump=20iced=20submodule=20?= =?UTF-8?q?=E2=86=92=20softbuffer=20+=20window=5Fclipboard=20cuts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pick up yoda-wayland-only HEAD on leyoda/iced-yoda with softbuffer + window_clipboard workspace default-features=false — drops the last x11 crates. See cosmic-yoterm yoda-v5 for full cut metrics. --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 8f6be798..f0c0b8d4 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 8f6be7984af03bc5457966b416d7a240b2c31d4e +Subproject commit f0c0b8d42ecdc52ff6d8b155225051bf6e009f5b From 58b7532849fea259a22a7bce4a5cd6657b01e8bf Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 27 Apr 2026 14:37:22 +0200 Subject: [PATCH 21/75] chore: update MSRV to Rust 1.93 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2a9684fd..3927c18b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "libcosmic" version = "1.0.0" edition = "2024" -rust-version = "1.90" +rust-version = "1.93" [lib] name = "cosmic" From 21977ef617295cd4f872b86bd113fd0e2fd11cf0 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 27 Apr 2026 14:39:52 +0200 Subject: [PATCH 22/75] feat(iced): update to wgpu 28 & cosmic-text 0.19 --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index aa2a870c..90d2c418 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit aa2a870c357284630045b05186fa7a6313c27693 +Subproject commit 90d2c418a73af0669302e58fb6a0978bb73a7436 From f3fc763b5ff872a5dcf1136b5038cf82de239ac8 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 27 Apr 2026 14:43:39 +0200 Subject: [PATCH 23/75] ci(pages): update Rust toolchain to latest nightly --- .github/workflows/pages.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 3e3a042e..af5b059b 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -17,13 +17,13 @@ jobs: - name: Install Rust nightly uses: dtolnay/rust-toolchain@master with: - toolchain: nightly-2025-07-31 + toolchain: nightly-2026-04-27 - name: System dependencies run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev - name: Build documentation run: | RUSTDOCFLAGS="--cfg docsrs" \ - cargo +nightly-2025-07-31 doc --no-deps \ + cargo +nightly-2026-04-27 doc --no-deps \ -p cosmic-client-toolkit \ -p cosmic-protocols \ -p libcosmic \ From 5825afb0dafe00353674ee47087de7183fc8a7c8 Mon Sep 17 00:00:00 2001 From: Will Sheehan Date: Sat, 25 Apr 2026 14:01:34 -0700 Subject: [PATCH 24/75] fix(segmented_button): reset menu state when reopening context menu When a context menu is open and the user right-clicks a different item, the overlay's close handler resets the menu state but no longer captures the event (since e10459f). The right-click propagates to the widget, which reopens the menu with stale `MenuBounds` from the previous context menu. If the new menu has a different number of items, this causes an assertion failure in `MenuState::layout`. Call `data.reset()` before setting `data.open = true` in the right-click handler to ensure stale `MenuBounds` are cleared before the next layout pass rebuilds them. --- src/widget/segmented_button/widget.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 44ca8574..fcb1c596 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -1398,6 +1398,8 @@ where state.context_cursor = cursor_position.position().unwrap_or_default(); state.menu_state.inner.with_data_mut(|data| { + // Clear stale MenuBounds from any previous context menu before opening a new one. + data.reset(); data.open = true; data.view_cursor = cursor_position; }); From 51be7dc42d9e9746b42cf57d89d9924180b463fe Mon Sep 17 00:00:00 2001 From: Rakshat Date: Mon, 27 Apr 2026 18:30:52 +0530 Subject: [PATCH 25/75] fix(menu_bar): use corner radius from theme for menu border --- src/theme/style/menu_bar.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/theme/style/menu_bar.rs b/src/theme/style/menu_bar.rs index ed0e657a..75525014 100644 --- a/src/theme/style/menu_bar.rs +++ b/src/theme/style/menu_bar.rs @@ -71,7 +71,7 @@ impl StyleSheet for Theme { background: component.base.into(), border_width: 1.0, bar_border_radius: cosmic.corner_radii.radius_xl, - menu_border_radius: cosmic.corner_radii.radius_s.map(|x| x + 2.0), + menu_border_radius: cosmic.corner_radii.radius_m, border_color: component.divider.into(), background_expand: [1; 4], path: component.hover.into(), From 49846d9f284ced0777ae6afce0a1f72eea5ec2da Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 27 Apr 2026 15:04:47 +0200 Subject: [PATCH 26/75] fix: wgpu validation error on float16 shader with older GPUs --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 90d2c418..3928e0d3 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 90d2c418a73af0669302e58fb6a0978bb73a7436 +Subproject commit 3928e0d38572d63ff1ef1d768b17770e9a91d7c0 From 8768bce2e2a4921ced57d95cff74bc7c92ef08b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:23:41 +0200 Subject: [PATCH 27/75] fix(menu_bar): menu border radius --- src/theme/style/menu_bar.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/theme/style/menu_bar.rs b/src/theme/style/menu_bar.rs index 75525014..db6e0ba1 100644 --- a/src/theme/style/menu_bar.rs +++ b/src/theme/style/menu_bar.rs @@ -71,7 +71,7 @@ impl StyleSheet for Theme { background: component.base.into(), border_width: 1.0, bar_border_radius: cosmic.corner_radii.radius_xl, - menu_border_radius: cosmic.corner_radii.radius_m, + menu_border_radius: cosmic.corner_radii.radius_s, border_color: component.divider.into(), background_expand: [1; 4], path: component.hover.into(), From cae05f3ffbb48a7db4e512aaa41589c07e89d7f3 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 27 Apr 2026 16:10:03 +0200 Subject: [PATCH 28/75] i18n: translation updates from weblate Co-authored-by: Hosted Weblate Co-authored-by: Jim Spentzos Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/el/ Translation: Pop OS/libcosmic --- i18n/el/libcosmic.ftl | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/i18n/el/libcosmic.ftl b/i18n/el/libcosmic.ftl index e69de29b..f4fe0b31 100644 --- a/i18n/el/libcosmic.ftl +++ b/i18n/el/libcosmic.ftl @@ -0,0 +1,34 @@ +close = Κλείσιμο +february = Φεβρουάριος { $year } +documenters = Τεκμηριωτές +november = Νοέμβριος { $year } +may = Μάιος { $year } +april = Απρίλιος { $year } +translators = Μεταφραστές +artists = Καλλιτέχνες +license = Άδεια χρήσης +december = Δεκέμβριος { $year } +links = Σύνδεσμοι +march = Μάρτιος { $year } +june = Ιούνιος { $year } +august = Αύγουστος { $year } +developers = Προγραμματιστές +july = Ιούλιος { $year } +september = Σεπτέμβριος { $year } +designers = Σχεδιαστές +october = Οκτώβριος { $year } +january = Ιανουάριος { $year } +monday = Δευτέρα +mon = Δευ +tuesday = Τρίτη +tue = Τρί +wednesday = Τετάρτη +wed = Τετ +thursday = Πέμπτη +thu = Πέμ +friday = Παρασκευή +fri = Παρ +saturday = Σάββατο +sat = Σάβ +sunday = Κυριακή +sun = Κυρ From 564ef834cec33a948dc10c9b401cf29db5d18373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:52:16 +0200 Subject: [PATCH 29/75] improv(theme): list_button style This adds a new `list_button` field to theme, which makes ListItem buttons stay transparent when pressed. It's essentially a mix of `text_button` and `background.component`, without hover highlights (as per designs). --- cosmic-theme/src/model/theme.rs | 11 +++++++++++ src/theme/style/button.rs | 26 +++++--------------------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 5db0f32c..1bc8050f 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -75,6 +75,8 @@ pub struct Theme { pub icon_button: Component, /// link button element colors pub link_button: Component, + /// list button element colors + pub list_button: Component, /// text button element colors pub text_button: Component, /// button component styling @@ -1285,6 +1287,15 @@ impl ThemeBuilder { component.on_disabled = over(component.on.with_alpha(0.5), component.base); component }, + list_button: Component::component( + Srgba::new(0.0, 0.0, 0.0, 0.0), + accent, + on_bg_component, + Srgba::new(0.0, 0.0, 0.0, 0.0), + button_pressed_overlay, + is_high_contrast, + control_steps_array[8], + ), success: Component::colored_component( success, control_steps_array[0], diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index bb52d9a6..e83e48e8 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -150,7 +150,7 @@ pub fn appearance( } Button::ListItem(radii) => { corner_radii = radii; - let (background, text, icon) = color(&cosmic.background.component); + let (background, text, icon) = color(&cosmic.list_button); if selected { appearance.background = @@ -197,7 +197,7 @@ impl Catalog for crate::Theme { return active(focused, self); } - let mut s = appearance(self, focused, selected, false, style, move |component| { + appearance(self, focused, selected, false, style, move |component| { let text_color = if matches!( style, Button::Icon | Button::IconVertical | Button::HeaderBar @@ -209,15 +209,7 @@ impl Catalog for crate::Theme { }; (component.base.into(), text_color, text_color) - }); - - if let Button::ListItem(_) = style { - if !selected { - s.background = None; - } - } - - s + }) } fn disabled(&self, style: &Self::Class) -> Style { @@ -245,7 +237,7 @@ impl Catalog for crate::Theme { return hovered(focused, self); } - let mut s = appearance( + appearance( self, focused || matches!(style, Button::Image), selected, @@ -264,15 +256,7 @@ impl Catalog for crate::Theme { (component.hover.into(), text_color, text_color) }, - ); - - if let Button::ListItem(_) = style { - if !selected { - s.background = None; - } - } - - s + ) } fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style { From 15a8a535dd3f822dfdb6a3b4c1cbc05b6e062534 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sun, 3 May 2026 01:52:48 +0200 Subject: [PATCH 30/75] i18n: translation updates from weblate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: BoneNI Co-authored-by: Hosted Weblate Co-authored-by: Марко М. Костић Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/sr/ Translation: Pop OS/libcosmic --- i18n/lo/libcosmic.ftl | 0 i18n/sr/libcosmic.ftl | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 i18n/lo/libcosmic.ftl diff --git a/i18n/lo/libcosmic.ftl b/i18n/lo/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/sr/libcosmic.ftl b/i18n/sr/libcosmic.ftl index e69de29b..e8b88303 100644 --- a/i18n/sr/libcosmic.ftl +++ b/i18n/sr/libcosmic.ftl @@ -0,0 +1,34 @@ +close = Затвори +february = Фебруар { $year } +documenters = Документатори +november = Новембар { $year } +may = Мај { $year } +april = Април { $year } +translators = Преводиоци +artists = Уметници +license = Дозвола +december = Децембар { $year } +links = Везе +march = Март { $year } +june = Јун { $year } +august = Август { $year } +developers = Програмери +july = Јул { $year } +september = Септембар { $year } +designers = Дизајнери +october = Октобар { $year } +january = Јануар { $year } +monday = Понедељак +mon = Пон +tuesday = Уторак +tue = Уто +wednesday = Среда +wed = Сре +thursday = Четвртак +thu = Чет +friday = Петак +fri = Пет +saturday = Субота +sat = Суб +sunday = Недеља +sun = Нед From 34326b3925b65d7859bd0458d1a96dee0b99f51e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:15:32 +0200 Subject: [PATCH 31/75] chore(cosmic-theme): move Roundness to libcosmic --- cosmic-theme/src/model/corner.rs | 48 +++++++++++++++++++++ cosmic-theme/src/model/density.rs | 69 ------------------------------- cosmic-theme/src/model/mod.rs | 2 - cosmic-theme/src/model/spacing.rs | 56 +++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 71 deletions(-) delete mode 100644 cosmic-theme/src/model/density.rs diff --git a/cosmic-theme/src/model/corner.rs b/cosmic-theme/src/model/corner.rs index ecd18c0b..f2fa95ec 100644 --- a/cosmic-theme/src/model/corner.rs +++ b/cosmic-theme/src/model/corner.rs @@ -29,3 +29,51 @@ impl Default for CornerRadii { } } } + +/// Roundness options for the Cosmic theme +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +pub enum Roundness { + /// Round style + #[default] + Round, + /// Slightly round style + SlightlyRound, + /// Square style + Square, +} + +impl From for CornerRadii { + fn from(value: Roundness) -> Self { + match value { + Roundness::Round => CornerRadii::default(), + Roundness::SlightlyRound => CornerRadii { + radius_0: [0.0; 4], + radius_xs: [2.0; 4], + radius_s: [8.0; 4], + radius_m: [8.0; 4], + radius_l: [8.0; 4], + radius_xl: [8.0; 4], + }, + Roundness::Square => CornerRadii { + radius_0: [0.0; 4], + radius_xs: [2.0; 4], + radius_s: [2.0; 4], + radius_m: [2.0; 4], + radius_l: [2.0; 4], + radius_xl: [2.0; 4], + }, + } + } +} + +impl From for Roundness { + fn from(value: CornerRadii) -> Self { + if (value.radius_m[0] - 16.0).abs() < 0.01 { + Self::Round + } else if (value.radius_m[0] - 8.0).abs() < 0.01 { + Self::SlightlyRound + } else { + Self::Square + } + } +} diff --git a/cosmic-theme/src/model/density.rs b/cosmic-theme/src/model/density.rs deleted file mode 100644 index 7655361c..00000000 --- a/cosmic-theme/src/model/density.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::Spacing; -use serde::{Deserialize, Serialize}; - -/// Density options for the Cosmic theme -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] -pub enum Density { - /// Lower padding/spacing of elements - Compact, - /// Higher padding/spacing of elements - Spacious, - /// Standard padding/spacing of elements - #[default] - Standard, -} - -impl From for Spacing { - fn from(value: Density) -> Self { - match value { - Density::Compact => Spacing { - space_none: 0, - space_xxxs: 4, - space_xxs: 4, - space_xs: 8, - space_s: 8, - space_m: 16, - space_l: 24, - space_xl: 32, - space_xxl: 48, - space_xxxl: 64, - }, - Density::Spacious => Spacing { - space_none: 4, - space_xxxs: 8, - space_xxs: 12, - space_xs: 16, - space_s: 24, - space_m: 32, - space_l: 48, - space_xl: 64, - space_xxl: 128, - space_xxxl: 160, - }, - Density::Standard => Spacing { - space_none: 0, - space_xxxs: 4, - space_xxs: 8, - space_xs: 12, - space_s: 16, - space_m: 24, - space_l: 32, - space_xl: 48, - space_xxl: 64, - space_xxxl: 128, - }, - } - } -} - -impl From for Density { - fn from(value: Spacing) -> Self { - if value.space_m.saturating_sub(16) == 0 { - Self::Compact - } else if value.space_m.saturating_sub(24) == 0 { - Self::Standard - } else { - Self::Spacious - } - } -} diff --git a/cosmic-theme/src/model/mod.rs b/cosmic-theme/src/model/mod.rs index f48d1a8d..19370dee 100644 --- a/cosmic-theme/src/model/mod.rs +++ b/cosmic-theme/src/model/mod.rs @@ -1,6 +1,5 @@ pub use corner::*; pub use cosmic_palette::*; -pub use density::*; pub use derivation::*; pub use mode::*; pub use spacing::*; @@ -8,7 +7,6 @@ pub use theme::*; mod corner; mod cosmic_palette; -mod density; mod derivation; mod mode; mod spacing; diff --git a/cosmic-theme/src/model/spacing.rs b/cosmic-theme/src/model/spacing.rs index 93b1bf43..f02cf510 100644 --- a/cosmic-theme/src/model/spacing.rs +++ b/cosmic-theme/src/model/spacing.rs @@ -41,3 +41,59 @@ impl Default for Spacing { } } } + +/// Density options for the Cosmic theme +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +pub enum Density { + /// Lower padding/spacing of elements + Compact, + /// Standard padding/spacing of elements + #[default] + Standard, + /// Higher padding/spacing of elements + Spacious, +} + +impl From for Spacing { + fn from(value: Density) -> Self { + match value { + Density::Compact => Spacing { + space_none: 0, + space_xxxs: 4, + space_xxs: 4, + space_xs: 8, + space_s: 8, + space_m: 16, + space_l: 24, + space_xl: 32, + space_xxl: 48, + space_xxxl: 64, + }, + Density::Standard => Spacing::default(), + Density::Spacious => Spacing { + space_none: 4, + space_xxxs: 8, + space_xxs: 12, + space_xs: 16, + space_s: 24, + space_m: 32, + space_l: 48, + space_xl: 64, + space_xxl: 128, + space_xxxl: 160, + }, + } + } +} + +impl From for Density { + fn from(value: Spacing) -> Self { + if value.space_m.saturating_sub(16) == 0 { + Self::Compact + } else if value.space_m.saturating_sub(24) == 0 { + Self::Standard + } else { + Self::Spacious + } + } +} From 9abc4d483baba96cf9e0f769277bc56bfd3c9d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:56:13 +0200 Subject: [PATCH 32/75] chore: update dependencies --- Cargo.toml | 35 ++++++++++++++++++++--------------- cosmic-config/Cargo.toml | 12 ++++++------ cosmic-theme/Cargo.toml | 8 ++++---- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3927c18b..de87a395 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,7 +125,7 @@ x11 = ["iced/x11", "iced_winit/x11"] apply = "0.3.0" ashpd = { version = "0.12.3", default-features = false, optional = true } async-fs = { version = "2.2", optional = true } -async-std = { version = "1.13", optional = true } +async-std = { workspace = true, optional = true } auto_enums = "0.8.8" cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true } jiff = "0.2" @@ -150,31 +150,32 @@ image-extras = { version = "0.1.0", default-features = false, features = [ "xpm", "xbm", ], optional = true } -libc = { version = "0.2.183", optional = true } +libc = { version = "0.2.186", optional = true } log = "0.4" mime = { version = "0.3.17", optional = true } -palette = "0.7.6" +palette.workspace = true rfd = { version = "0.16.0", default-features = false, features = [ "xdg-portal", ], optional = true } rustix = { version = "1.1", features = ["pipe", "process"], optional = true } -serde = { version = "1.0.228", features = ["derive"] } +serde = { workspace = true, features = ["derive"] } slotmap = "1.1.1" smol = { version = "2.0.2", optional = true } -thiserror = "2.0.18" +thiserror.workspace = true taffy = { version = "0.9.2", features = ["grid"] } -tokio = { version = "1.50.0", optional = true } -tracing = "0.1.44" -unicode-segmentation = "1.12" +tokio = { workspace = true, optional = true } +tracing.workspace = true +unicode-segmentation = "1.13" url = "2.5.8" -zbus = { version = "5.14.0", default-features = false, optional = true } +zbus = { workspace = true, optional = true } float-cmp = "0.10.0" +ron = { workspace = true, optional = true } # Enable DBus feature on Linux targets [target.'cfg(target_os = "linux")'.dependencies] cosmic-config = { path = "cosmic-config", features = ["dbus"] } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" } -zbus = { version = "5.14.0", default-features = false } +zbus.workspace = true [target.'cfg(all(unix, not(target_os = "macos")))'.dependencies] freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } @@ -237,10 +238,6 @@ git = "https://github.com/pop-os/cosmic-panel" # path = "../cosmic-panel/cosmic-panel-config" optional = true -[dependencies.ron] -version = "0.12" -optional = true - [workspace] members = [ "cosmic-config", @@ -251,7 +248,15 @@ members = [ exclude = ["iced"] [workspace.dependencies] -dirs = "6.0.0" +async-std = "1.13" +dirs = "6.0" +palette = "0.7" +ron = "0.12" +serde = "1.0" +thiserror = "2.0" +tracing = "0.1" +tokio = "1.52" +zbus = {version = "5.15", default-features = false} [dev-dependencies] tempfile = "3.27.0" diff --git a/cosmic-config/Cargo.toml b/cosmic-config/Cargo.toml index 0a7653e0..0316c707 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -11,20 +11,20 @@ subscription = ["iced_futures"] [dependencies] cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } -zbus = { version = "5.14.0", default-features = false, optional = true } +zbus = { workspace = true, default-features = false, optional = true } atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" } calloop = { version = "0.14.4", optional = true } notify = "8.2.0" -ron = "0.12.0" -serde = "1.0.228" +ron.workspace = true +serde.workspace = true cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true } iced = { path = "../iced/", default-features = false, optional = true } iced_futures = { path = "../iced/futures/", default-features = false, optional = true } futures-util = { version = "0.3", optional = true } dirs.workspace = true -tokio = { version = "1.50", optional = true, features = ["time"] } -async-std = { version = "1.13", optional = true } -tracing = "0.1" +tokio = { workspace = true, optional = true, features = ["time"] } +async-std = { workspace = true, optional = true } +tracing.workspace = true [target.'cfg(unix)'.dependencies] xdg = "3.0" diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index 7e408d8d..15aee334 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -15,13 +15,13 @@ export = ["serde_json"] no-default = [] [dependencies] -palette = { version = "0.7.6", features = ["serializing"] } +palette = { workspace = true, features = ["serializing"] } almost = "0.2" -serde = { version = "1.0.228", features = ["derive"] } +serde = { workspace = true, features = ["derive"] } serde_json = { version = "1.0.149", optional = true, features = [ "preserve_order", ] } -ron = "0.12.0" +ron.workspace = true csscolorparser = { version = "0.8.3", features = ["serde"] } cosmic-config = { path = "../cosmic-config/", default-features = false, features = [ "subscription", @@ -29,7 +29,7 @@ cosmic-config = { path = "../cosmic-config/", default-features = false, features ] } configparser = "3.1.0" dirs.workspace = true -thiserror = "2.0.18" +thiserror.workspace = true [dev-dependencies] insta = "1.47.2" From 9c2a86a8f4d6faff2fe28627820c29c5430d95d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:07:25 +0200 Subject: [PATCH 33/75] chore: add rustfmt config --- .zed/settings.json | 15 +++++++ cosmic-config/src/dbus.rs | 11 +++-- cosmic-config/src/lib.rs | 19 ++++----- cosmic-config/src/subscription.rs | 9 ++-- cosmic-theme/src/model/theme.rs | 10 ++--- cosmic-theme/src/output/gtk4_output.rs | 17 ++++---- cosmic-theme/src/output/mod.rs | 3 +- cosmic-theme/src/output/qt56ct_output.rs | 14 +++---- cosmic-theme/src/output/qt_output.rs | 11 +++-- cosmic-theme/src/steps.rs | 3 +- examples/about/src/main.rs | 3 +- examples/applet/src/window.rs | 3 +- examples/application/src/main.rs | 3 +- examples/cosmic/src/window.rs | 14 +++---- examples/cosmic/src/window/bluetooth.rs | 8 ++-- examples/cosmic/src/window/demo.rs | 24 +++++------ examples/cosmic/src/window/desktop.rs | 11 ++--- .../cosmic/src/window/system_and_accounts.rs | 10 ++--- examples/menu/src/main.rs | 6 +-- examples/multi-window/src/window.rs | 14 +++---- examples/spin-button/src/main.rs | 15 ++----- examples/subscriptions/src/main.rs | 3 +- examples/table-view/src/main.rs | 3 +- rustfmt.toml | 1 + src/app/action.rs | 4 +- src/app/mod.rs | 6 +-- src/applet/column.rs | 6 +-- src/applet/mod.rs | 40 ++++++++---------- src/applet/row.rs | 6 +-- src/applet/token/subscription.rs | 13 +++--- src/applet/token/wayland_handler.rs | 35 +++++++--------- src/dbus_activation.rs | 20 ++++----- src/desktop.rs | 3 +- src/localize.rs | 6 +-- src/surface/action.rs | 3 +- src/surface/mod.rs | 4 +- src/theme/mod.rs | 8 +--- src/theme/style/button.rs | 6 +-- src/theme/style/iced.rs | 19 ++++----- src/theme/style/segmented_button.rs | 15 ++++--- src/widget/about.rs | 8 ++-- src/widget/aspect_ratio.rs | 5 +-- src/widget/autosize.rs | 9 ++-- src/widget/button/icon.rs | 8 +++- src/widget/button/image.rs | 11 ++--- src/widget/button/link.rs | 7 ++-- src/widget/button/style.rs | 3 +- src/widget/button/text.rs | 5 ++- src/widget/button/widget.rs | 13 ++---- src/widget/calendar.rs | 6 +-- src/widget/cards.rs | 25 +++++------ src/widget/color_picker/mod.rs | 10 ++--- src/widget/context_drawer/overlay.rs | 3 +- src/widget/context_drawer/widget.rs | 3 +- src/widget/context_menu.rs | 3 +- src/widget/dialog.rs | 7 +--- src/widget/dnd_destination.rs | 32 +++++--------- src/widget/dnd_source.rs | 22 ++++------ src/widget/dropdown/menu/appearance.rs | 3 +- src/widget/dropdown/multi/widget.rs | 4 +- src/widget/dropdown/widget.rs | 4 +- src/widget/frames.rs | 6 +-- src/widget/header_bar.rs | 3 +- src/widget/icon/named.rs | 5 ++- src/widget/id_container.rs | 9 ++-- src/widget/layer_container.rs | 5 +-- src/widget/list/list_column.rs | 3 +- src/widget/menu/flex.rs | 10 ++--- src/widget/menu/menu_bar.rs | 42 +++++++++---------- src/widget/menu/menu_inner.rs | 28 ++++++------- src/widget/mod.rs | 2 +- src/widget/nav_bar.rs | 7 ++-- src/widget/popover.rs | 8 +--- src/widget/progress_bar/circular.rs | 8 +--- src/widget/progress_bar/linear.rs | 8 +--- src/widget/radio.rs | 6 +-- src/widget/rectangle_tracker/mod.rs | 12 +++--- src/widget/rectangle_tracker/subscription.rs | 15 +++---- src/widget/responsive_container.rs | 9 ++-- src/widget/responsive_menu_bar.rs | 6 +-- src/widget/segmented_button/widget.rs | 8 ++-- src/widget/settings/item.rs | 9 ++-- src/widget/spin_button.rs | 9 ++-- src/widget/table/mod.rs | 9 ++-- src/widget/table/model/entity.rs | 6 +-- src/widget/table/model/mod.rs | 6 +-- src/widget/table/model/selection.rs | 6 +-- src/widget/table/widget/compact.rs | 14 +++---- src/widget/table/widget/standard.rs | 14 +++---- src/widget/text_input/editor.rs | 3 +- src/widget/text_input/input.rs | 10 ++--- src/widget/text_input/style.rs | 3 +- src/widget/toaster/mod.rs | 9 ++-- src/widget/toaster/widget.rs | 10 ++--- src/widget/toggler.rs | 11 +++-- src/widget/wayland/tooltip/mod.rs | 3 +- src/widget/wayland/tooltip/widget.rs | 8 +--- src/widget/wrapper.rs | 14 +++---- 98 files changed, 419 insertions(+), 540 deletions(-) create mode 100644 .zed/settings.json create mode 100644 rustfmt.toml diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 00000000..2cc7b98d --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,15 @@ +{ + "format_on_save": "on", + "lsp": { + "rust-analyzer": { + "initialization_options": { + "check": { + "command": "clippy", + }, + "rustfmt": { + "extraArgs": ["+nightly"], + }, + }, + }, + }, +} diff --git a/cosmic-config/src/dbus.rs b/cosmic-config/src/dbus.rs index da7bcb68..9b6e8697 100644 --- a/cosmic-config/src/dbus.rs +++ b/cosmic-config/src/dbus.rs @@ -1,13 +1,12 @@ -use std::{any::TypeId, ops::Deref}; +use std::any::TypeId; +use std::ops::Deref; use crate::{CosmicConfigEntry, Update}; use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy}; use futures_util::SinkExt; -use iced_futures::{ - Subscription, - futures::{self, StreamExt, future::pending}, - stream, -}; +use iced_futures::futures::future::pending; +use iced_futures::futures::{self, StreamExt}; +use iced_futures::{Subscription, stream}; pub async fn settings_daemon_proxy() -> zbus::Result> { let conn = zbus::Connection::session().await?; diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index c8eda064..7ca9b907 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -1,16 +1,13 @@ //! Integrations for cosmic-config — the cosmic configuration system. -use notify::{ - RecommendedWatcher, Watcher, - event::{EventKind, ModifyKind, RenameMode}, -}; -use serde::{Serialize, de::DeserializeOwned}; -use std::{ - env, fmt, fs, - io::Write, - path::{Path, PathBuf}, - sync::Mutex, -}; +use notify::event::{EventKind, ModifyKind, RenameMode}; +use notify::{RecommendedWatcher, Watcher}; +use serde::Serialize; +use serde::de::DeserializeOwned; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::sync::Mutex; +use std::{env, fmt, fs}; /// Get the config directory, with Flatpak sandbox support. /// In Flatpak, HOST_XDG_CONFIG_HOME points to the real user config directory, diff --git a/cosmic-config/src/subscription.rs b/cosmic-config/src/subscription.rs index d16b9b65..f0387877 100644 --- a/cosmic-config/src/subscription.rs +++ b/cosmic-config/src/subscription.rs @@ -1,7 +1,9 @@ +use iced_futures::futures::channel::mpsc; use iced_futures::futures::{SinkExt, Stream}; -use iced_futures::{futures::channel::mpsc, stream}; +use iced_futures::stream; use notify::RecommendedWatcher; -use std::{borrow::Cow, hash::Hash}; +use std::borrow::Cow; +use std::hash::Hash; use crate::{Config, CosmicConfigEntry}; @@ -77,7 +79,8 @@ async fn start_listening, output: &mut mpsc::Sender>, ) -> ConfigState { - use iced_futures::futures::{StreamExt, future::pending}; + use iced_futures::futures::StreamExt; + use iced_futures::futures::future::pending; match state { ConfigState::Init(config_id, version, is_state) => { diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 1bc8050f..a403403c 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -1,13 +1,13 @@ +use crate::composite::over; +use crate::steps::{color_index, get_small_widget_color, get_surface_color, get_text, steps}; use crate::{ Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, DARK_PALETTE, LIGHT_PALETTE, NAME, Spacing, ThemeMode, - composite::over, - steps::{color_index, get_small_widget_color, get_surface_color, get_text, steps}, }; use cosmic_config::{Config, CosmicConfigEntry}; -use palette::{ - IntoColor, Oklcha, Srgb, Srgba, WithAlpha, color_difference::Wcag21RelativeContrast, rgb::Rgb, -}; +use palette::color_difference::Wcag21RelativeContrast; +use palette::rgb::Rgb; +use palette::{IntoColor, Oklcha, Srgb, Srgba, WithAlpha}; use serde::{Deserialize, Serialize}; use std::num::NonZeroUsize; diff --git a/cosmic-theme/src/output/gtk4_output.rs b/cosmic-theme/src/output/gtk4_output.rs index 40eba5b4..16a3c36e 100644 --- a/cosmic-theme/src/output/gtk4_output.rs +++ b/cosmic-theme/src/output/gtk4_output.rs @@ -1,11 +1,12 @@ -use crate::{Component, Theme, composite::over, steps::steps}; -use palette::{Darken, IntoColor, Lighten, Srgba, WithAlpha, rgb::Rgba}; -use std::{ - fs::{self, File}, - io::{self, Write}, - num::NonZeroUsize, - path::Path, -}; +use crate::composite::over; +use crate::steps::steps; +use crate::{Component, Theme}; +use palette::rgb::Rgba; +use palette::{Darken, IntoColor, Lighten, Srgba, WithAlpha}; +use std::fs::{self, File}; +use std::io::{self, Write}; +use std::num::NonZeroUsize; +use std::path::Path; use super::{OutputError, to_rgba}; diff --git a/cosmic-theme/src/output/mod.rs b/cosmic-theme/src/output/mod.rs index 19f7bc5b..04dce39f 100644 --- a/cosmic-theme/src/output/mod.rs +++ b/cosmic-theme/src/output/mod.rs @@ -1,5 +1,6 @@ use configparser::ini::WriteOptions; -use palette::{Srgba, rgb::Rgba}; +use palette::Srgba; +use palette::rgb::Rgba; use thiserror::Error; use crate::Theme; diff --git a/cosmic-theme/src/output/qt56ct_output.rs b/cosmic-theme/src/output/qt56ct_output.rs index 43a45470..16df211f 100644 --- a/cosmic-theme/src/output/qt56ct_output.rs +++ b/cosmic-theme/src/output/qt56ct_output.rs @@ -1,12 +1,12 @@ use crate::Theme; use configparser::ini::Ini; -use palette::{Mix, Srgba, WithAlpha, blend::Compose, rgb::Rgba}; -use std::{ - fs::{self, File}, - io::Write, - path::PathBuf, - vec, -}; +use palette::blend::Compose; +use palette::rgb::Rgba; +use palette::{Mix, Srgba, WithAlpha}; +use std::fs::{self, File}; +use std::io::Write; +use std::path::PathBuf; +use std::vec; use super::{OutputError, qt_settings_ini_style}; diff --git a/cosmic-theme/src/output/qt_output.rs b/cosmic-theme/src/output/qt_output.rs index d42d553b..84614e7b 100644 --- a/cosmic-theme/src/output/qt_output.rs +++ b/cosmic-theme/src/output/qt_output.rs @@ -1,12 +1,11 @@ use crate::Theme; use configparser::ini::Ini; use cosmic_config::CosmicConfigEntry; -use palette::{Mix, Srgba, blend::Compose}; -use std::{ - fs::{self, File}, - io::{self, Write}, - path::{Path, PathBuf}, -}; +use palette::blend::Compose; +use palette::{Mix, Srgba}; +use std::fs::{self, File}; +use std::io::{self, Write}; +use std::path::{Path, PathBuf}; use super::{OutputError, qt_settings_ini_style}; diff --git a/cosmic-theme/src/steps.rs b/cosmic-theme/src/steps.rs index 6ebf1015..d156722c 100644 --- a/cosmic-theme/src/steps.rs +++ b/cosmic-theme/src/steps.rs @@ -1,7 +1,8 @@ use std::num::NonZeroUsize; use almost::equal; -use palette::{ClampAssign, FromColor, Lch, Oklcha, Srgb, Srgba, convert::FromColorUnclamped}; +use palette::convert::FromColorUnclamped; +use palette::{ClampAssign, FromColor, Lch, Oklcha, Srgb, Srgba}; /// Get an array of 100 colors with a specific hue and chroma /// over the full range of lightness. diff --git a/examples/about/src/main.rs b/examples/about/src/main.rs index c25a9b9a..d1ea475e 100644 --- a/examples/about/src/main.rs +++ b/examples/about/src/main.rs @@ -8,7 +8,8 @@ use cosmic::app::{Core, Settings, Task}; use cosmic::executor; use cosmic::iced::{alignment, Length, Size}; use cosmic::prelude::*; -use cosmic::widget::{self, about::About, nav_bar}; +use cosmic::widget::about::About; +use cosmic::widget::{self, nav_bar}; /// Runs application with these settings #[rustfmt::skip] diff --git a/examples/applet/src/window.rs b/examples/applet/src/window.rs index 22903eac..57546ab1 100644 --- a/examples/applet/src/window.rs +++ b/examples/applet/src/window.rs @@ -4,7 +4,8 @@ use cosmic::iced::core::window; use cosmic::iced::window::Id; use cosmic::iced::{Length, Rectangle}; use cosmic::surface::action::{app_popup, destroy_popup}; -use cosmic::widget::{dropdown::popup_dropdown, list_column, settings, toggler}; +use cosmic::widget::dropdown::popup_dropdown; +use cosmic::widget::{list_column, settings, toggler}; use cosmic::Element; const ID: &str = "com.system76.CosmicAppletExample"; diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index f6e571e0..9f89061b 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -5,9 +5,10 @@ use cosmic::app::Settings; use cosmic::iced::{Alignment, Length, Size}; +use cosmic::prelude::*; use cosmic::widget::menu::{self, KeyBind}; use cosmic::widget::nav_bar; -use cosmic::{executor, iced, prelude::*, widget, Core}; +use cosmic::{executor, iced, widget, Core}; use std::collections::HashMap; use std::sync::LazyLock; diff --git a/examples/cosmic/src/window.rs b/examples/cosmic/src/window.rs index 9fce8767..4168718d 100644 --- a/examples/cosmic/src/window.rs +++ b/examples/cosmic/src/window.rs @@ -23,15 +23,11 @@ use cosmic::{ Element, }; use cosmic_time::{Instant, Timeline}; -use std::{ - cell::RefCell, - rc::Rc, - sync::{ - atomic::{AtomicU32, Ordering}, - Arc, - }, - vec, -}; +use std::cell::RefCell; +use std::rc::Rc; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::Arc; +use std::vec; // XXX The use of button is removed because it assigns the same ID to multiple buttons, causing a crash when a11y is enabled... // static BTN: Lazy = Lazy::new(|| id::Id::new("BTN")); diff --git a/examples/cosmic/src/window/bluetooth.rs b/examples/cosmic/src/window/bluetooth.rs index 1b5892f6..2878b46a 100644 --- a/examples/cosmic/src/window/bluetooth.rs +++ b/examples/cosmic/src/window/bluetooth.rs @@ -1,9 +1,7 @@ use super::{Page, Window}; -use cosmic::{ - iced::widget::{column, text}, - widget::{list_column, settings, toggler}, - Element, -}; +use cosmic::iced::widget::{column, text}; +use cosmic::widget::{list_column, settings, toggler}; +use cosmic::Element; #[derive(Clone, Copy, Debug)] pub enum Message { diff --git a/examples/cosmic/src/window/demo.rs b/examples/cosmic/src/window/demo.rs index 0d31fa93..20a730b5 100644 --- a/examples/cosmic/src/window/demo.rs +++ b/examples/cosmic/src/window/demo.rs @@ -1,19 +1,17 @@ -use std::{cell::RefCell, rc::Rc}; +use std::cell::RefCell; +use std::rc::Rc; use apply::Apply; -use cosmic::{ - cosmic_theme, - iced::widget::{checkbox, column, progress_bar, radio, slider, text}, - iced::{Alignment, Length}, - iced_core::id, - theme::ThemeType, - widget::{ - button, color_picker::ColorPickerUpdate, dropdown, icon, layer_container as container, - segmented_button, segmented_control, settings, spin_button, tab_bar, toggler, - ColorPickerModel, - }, - Element, +use cosmic::iced::widget::{checkbox, column, progress_bar, radio, slider, text}; +use cosmic::iced::{Alignment, Length}; +use cosmic::iced_core::id; +use cosmic::theme::ThemeType; +use cosmic::widget::color_picker::ColorPickerUpdate; +use cosmic::widget::{ + button, dropdown, icon, layer_container as container, segmented_button, segmented_control, + settings, spin_button, tab_bar, toggler, ColorPickerModel, }; +use cosmic::{cosmic_theme, Element}; use cosmic_time::{anim, chain, Timeline}; use fraction::{Decimal, ToPrimitive}; use once_cell::sync::Lazy; diff --git a/examples/cosmic/src/window/desktop.rs b/examples/cosmic/src/window/desktop.rs index 46a4e5b8..a087de9e 100644 --- a/examples/cosmic/src/window/desktop.rs +++ b/examples/cosmic/src/window/desktop.rs @@ -1,10 +1,7 @@ -use cosmic::{ - iced::widget::{column, container, horizontal_space, image, row, svg, text}, - iced::Length, - theme, - widget::{list_column, settings, toggler}, - Element, -}; +use cosmic::iced::widget::{column, container, horizontal_space, image, row, svg, text}; +use cosmic::iced::Length; +use cosmic::widget::{list_column, settings, toggler}; +use cosmic::{theme, Element}; use super::{Page, SubPage, Window}; diff --git a/examples/cosmic/src/window/system_and_accounts.rs b/examples/cosmic/src/window/system_and_accounts.rs index ed1bd004..5f498857 100644 --- a/examples/cosmic/src/window/system_and_accounts.rs +++ b/examples/cosmic/src/window/system_and_accounts.rs @@ -1,9 +1,7 @@ -use cosmic::{ - iced::widget::{horizontal_space, row, text}, - iced::Length, - widget::{icon, list_column, settings}, - Element, -}; +use cosmic::iced::widget::{horizontal_space, row, text}; +use cosmic::iced::Length; +use cosmic::widget::{icon, list_column, settings}; +use cosmic::Element; use super::{Message, Page, SubPage, Window}; diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs index da0c3231..900d0322 100644 --- a/examples/menu/src/main.rs +++ b/examples/menu/src/main.rs @@ -9,11 +9,9 @@ use std::{env, process}; use cosmic::app::{Core, Settings, Task}; use cosmic::iced::alignment::{Horizontal, Vertical}; use cosmic::iced::keyboard::Key; -use cosmic::iced::window; -use cosmic::iced::{Length, Size}; +use cosmic::iced::{window, Length, Size}; use cosmic::widget::menu::action::MenuAction; -use cosmic::widget::menu::key_bind::KeyBind; -use cosmic::widget::menu::key_bind::Modifier; +use cosmic::widget::menu::key_bind::{KeyBind, Modifier}; use cosmic::widget::menu::{self, ItemHeight, ItemWidth}; use cosmic::widget::RcElementWrapper; use cosmic::{executor, Element}; diff --git a/examples/multi-window/src/window.rs b/examples/multi-window/src/window.rs index 754a0d86..a6d40d36 100644 --- a/examples/multi-window/src/window.rs +++ b/examples/multi-window/src/window.rs @@ -1,13 +1,11 @@ use std::collections::HashMap; -use cosmic::{ - app::Core, - iced::core::{id, Alignment, Length, Point}, - iced::widget::{column, container, scrollable, text}, - iced::{self, event, window, Subscription}, - prelude::*, - widget::{button, header_bar}, -}; +use cosmic::app::Core; +use cosmic::iced::core::{id, Alignment, Length, Point}; +use cosmic::iced::widget::{column, container, scrollable, text}; +use cosmic::iced::{self, event, window, Subscription}; +use cosmic::prelude::*; +use cosmic::widget::{button, header_bar}; #[derive(Debug, Clone, PartialEq)] pub enum Message { diff --git a/examples/spin-button/src/main.rs b/examples/spin-button/src/main.rs index 47db4dce..0228291c 100644 --- a/examples/spin-button/src/main.rs +++ b/examples/spin-button/src/main.rs @@ -1,15 +1,8 @@ -use cosmic::iced::Length; +use cosmic::app::{Core, Task}; +use cosmic::iced::alignment::{Horizontal, Vertical}; +use cosmic::iced::{self, Alignment, Length, Size}; use cosmic::widget::{column, container, spin_button}; -use cosmic::Apply; -use cosmic::{ - app::{Core, Task}, - iced::{ - self, - alignment::{Horizontal, Vertical}, - Alignment, Size, - }, - Application, Element, -}; +use cosmic::{Application, Apply, Element}; use fraction::Decimal; pub struct SpinButtonExamplApp { diff --git a/examples/subscriptions/src/main.rs b/examples/subscriptions/src/main.rs index 17e630aa..325af162 100644 --- a/examples/subscriptions/src/main.rs +++ b/examples/subscriptions/src/main.rs @@ -5,7 +5,8 @@ use cosmic::app::{Core, Settings, Task}; use cosmic::iced::Subscription; -use cosmic::{executor, prelude::*, widget}; +use cosmic::prelude::*; +use cosmic::{executor, widget}; /// Runs application with these settings fn main() -> Result<(), Box> { diff --git a/examples/table-view/src/main.rs b/examples/table-view/src/main.rs index d2478429..e518b3cc 100644 --- a/examples/table-view/src/main.rs +++ b/examples/table-view/src/main.rs @@ -9,8 +9,7 @@ use chrono::Datelike; use cosmic::app::{Core, Settings, Task}; use cosmic::iced::Size; use cosmic::prelude::*; -use cosmic::widget::table; -use cosmic::widget::{self, nav_bar}; +use cosmic::widget::{self, nav_bar, table}; use cosmic::{executor, iced}; #[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash)] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..c1578aaf --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +imports_granularity = "Module" diff --git a/src/app/action.rs b/src/app/action.rs index fb982acb..166df16b 100644 --- a/src/app/action.rs +++ b/src/app/action.rs @@ -1,10 +1,10 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -use crate::surface; +use crate::config::CosmicTk; use crate::theme::Theme; use crate::widget::nav_bar; -use crate::{config::CosmicTk, keyboard_nav}; +use crate::{keyboard_nav, surface}; #[cfg(all(feature = "wayland", target_os = "linux"))] use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use cosmic_theme::ThemeMode; diff --git a/src/app/mod.rs b/src/app/mod.rs index ad1ceefe..264f1d16 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -22,11 +22,11 @@ use crate::prelude::*; use crate::theme::THEME; use crate::widget::{container, id_container, menu, nav_bar, popover, space}; use apply::Apply; -use iced::{Length, Subscription}; -use iced::{theme, window}; +use iced::{Length, Subscription, theme, window}; pub use settings::Settings; use std::borrow::Cow; -use std::{cell::RefCell, rc::Rc}; +use std::cell::RefCell; +use std::rc::Rc; #[cold] pub(crate) fn iced_settings( diff --git a/src/applet/column.rs b/src/applet/column.rs index 9657b566..1ec9c731 100644 --- a/src/applet/column.rs +++ b/src/applet/column.rs @@ -2,14 +2,10 @@ use crate::iced; use iced::core::alignment::{self, Alignment}; use iced::core::event::{self, Event}; -use iced::core::layout; -use iced::core::mouse; -use iced::core::overlay; -use iced::core::renderer; use iced::core::widget::{Operation, Tree}; use iced::core::{ Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, - widget, + layout, mouse, overlay, renderer, widget, }; /// A container that distributes its contents vertically. diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 48721e1c..e16b0303 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -1,37 +1,31 @@ #[cfg(feature = "applet-token")] pub mod token; -use crate::app::{BootData, BootDataInner, cosmic}; -use crate::{ - Application, Element, Renderer, - app::iced_settings, - cctk::sctk, - theme::{self, Button, THEME, system_dark, system_light}, - widget::{ - self, - autosize::{self, Autosize, autosize}, - column::Column, - layer_container, - row::Row, - space::horizontal, - space::vertical, - }, -}; +use crate::app::{BootData, BootDataInner, cosmic, iced_settings}; +use crate::cctk::sctk; +use crate::theme::{self, Button, THEME, system_dark, system_light}; +use crate::widget::autosize::{self, Autosize, autosize}; +use crate::widget::column::Column; +use crate::widget::row::Row; +use crate::widget::space::{horizontal, vertical}; +use crate::widget::{self, layer_container}; +use crate::{Application, Element, Renderer}; pub use cosmic_panel_config; use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize}; -use iced::{ - self, Color, Length, Limits, Rectangle, - alignment::{Alignment, Horizontal, Vertical}, - widget::Container, - window, -}; +use iced::alignment::{Alignment, Horizontal, Vertical}; +use iced::widget::Container; +use iced::{self, Color, Length, Limits, Rectangle, window}; use iced_core::{Padding, Shadow}; use iced_runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner}; use iced_widget::Text; use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity}; +use std::borrow::Cow; use std::cell::RefCell; -use std::{borrow::Cow, num::NonZeroU32, rc::Rc, sync::LazyLock, time::Duration}; +use std::num::NonZeroU32; +use std::rc::Rc; +use std::sync::LazyLock; +use std::time::Duration; use tracing::info; pub mod column; diff --git a/src/applet/row.rs b/src/applet/row.rs index a6745d1c..718f366c 100644 --- a/src/applet/row.rs +++ b/src/applet/row.rs @@ -3,12 +3,10 @@ use crate::iced; use iced::core::alignment::{self, Alignment}; use iced::core::event::{self, Event}; use iced::core::layout::{self, Layout}; -use iced::core::mouse; -use iced::core::overlay; -use iced::core::renderer; use iced::core::widget::{Operation, Tree}; use iced::core::{ - Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, widget, + Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, mouse, + overlay, renderer, widget, }; use iced::touch; diff --git a/src/applet/token/subscription.rs b/src/applet/token/subscription.rs index 07c528ea..64be0ce2 100644 --- a/src/applet/token/subscription.rs +++ b/src/applet/token/subscription.rs @@ -1,13 +1,12 @@ use crate::iced; use cctk::sctk::reexports::calloop; -use futures::{ - SinkExt, StreamExt, - channel::mpsc::{UnboundedReceiver, unbounded}, -}; +use futures::channel::mpsc::{UnboundedReceiver, unbounded}; +use futures::{SinkExt, StreamExt}; use iced::Subscription; -use iced_futures::futures; -use iced_futures::stream; -use std::{fmt::Debug, hash::Hash, thread::JoinHandle}; +use iced_futures::{futures, stream}; +use std::fmt::Debug; +use std::hash::Hash; +use std::thread::JoinHandle; use super::wayland_handler::wayland_handler; diff --git a/src/applet/token/wayland_handler.rs b/src/applet/token/wayland_handler.rs index 3db84fc4..4ab73235 100644 --- a/src/applet/token/wayland_handler.rs +++ b/src/applet/token/wayland_handler.rs @@ -1,27 +1,20 @@ -use std::os::{ - fd::{FromRawFd, RawFd}, - unix::net::UnixStream, -}; +use std::os::fd::{FromRawFd, RawFd}; +use std::os::unix::net::UnixStream; use super::subscription::{TokenRequest, TokenUpdate}; -use cctk::{ - sctk::{ - self, - activation::{RequestData, RequestDataExt}, - reexports::{calloop, calloop_wayland_source::WaylandSource}, - seat::{SeatHandler, SeatState}, - }, - wayland_client::{ - self, - protocol::{wl_seat::WlSeat, wl_surface::WlSurface}, - }, -}; +use cctk::sctk::activation::{RequestData, RequestDataExt}; +use cctk::sctk::reexports::calloop; +use cctk::sctk::reexports::calloop_wayland_source::WaylandSource; +use cctk::sctk::seat::{SeatHandler, SeatState}; +use cctk::sctk::{self}; +use cctk::wayland_client::protocol::wl_seat::WlSeat; +use cctk::wayland_client::protocol::wl_surface::WlSurface; +use cctk::wayland_client::{self}; use iced_futures::futures::channel::mpsc::UnboundedSender; -use sctk::{ - activation::{ActivationHandler, ActivationState}, - registry::{ProvidesRegistryState, RegistryState}, -}; -use wayland_client::{Connection, QueueHandle, globals::registry_queue_init}; +use sctk::activation::{ActivationHandler, ActivationState}; +use sctk::registry::{ProvidesRegistryState, RegistryState}; +use wayland_client::globals::registry_queue_init; +use wayland_client::{Connection, QueueHandle}; struct AppData { exit: bool, diff --git a/src/dbus_activation.rs b/src/dbus_activation.rs index 99e2f9f0..e7bc6900 100644 --- a/src/dbus_activation.rs +++ b/src/dbus_activation.rs @@ -1,17 +1,15 @@ // Copyright 2024 System76 // SPDX-License-Identifier: MPL-2.0 -use { - crate::ApplicationExt, - iced::Subscription, - iced_futures::futures::{ - SinkExt, - channel::mpsc::{Receiver, Sender}, - }, - std::{any::TypeId, collections::HashMap}, - url::Url, - zbus::{interface, proxy, zvariant::Value}, -}; +use crate::ApplicationExt; +use iced::Subscription; +use iced_futures::futures::SinkExt; +use iced_futures::futures::channel::mpsc::{Receiver, Sender}; +use std::any::TypeId; +use std::collections::HashMap; +use url::Url; +use zbus::zvariant::Value; +use zbus::{interface, proxy}; #[cold] pub fn subscription() -> Subscription> { diff --git a/src/desktop.rs b/src/desktop.rs index 98ce7d4b..cfa7a668 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -872,7 +872,8 @@ trait SystemdManger { #[cfg(all(test, not(windows)))] mod tests { use super::*; - use std::{env, fs, path::Path, path::PathBuf}; + use std::path::{Path, PathBuf}; + use std::{env, fs}; use tempfile::tempdir; struct EnvVarGuard { diff --git a/src/localize.rs b/src/localize.rs index 95a31655..d63d9706 100644 --- a/src/localize.rs +++ b/src/localize.rs @@ -1,9 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only -use i18n_embed::{ - DefaultLocalizer, LanguageLoader, Localizer, - fluent::{FluentLanguageLoader, fluent_language_loader}, -}; +use i18n_embed::fluent::{FluentLanguageLoader, fluent_language_loader}; +use i18n_embed::{DefaultLocalizer, LanguageLoader, Localizer}; use rust_embed::RustEmbed; use std::sync::{LazyLock, OnceLock}; diff --git a/src/surface/action.rs b/src/surface/action.rs index 50e2b4a9..5bd5a294 100644 --- a/src/surface/action.rs +++ b/src/surface/action.rs @@ -6,7 +6,8 @@ use super::Action; use crate::Application; use iced::window; -use std::{any::Any, sync::Arc}; +use std::any::Any; +use std::sync::Arc; /// Used to produce a destroy popup message from within a widget. #[cfg(all(feature = "wayland", target_os = "linux"))] diff --git a/src/surface/mod.rs b/src/surface/mod.rs index 0dad6459..307b5275 100644 --- a/src/surface/mod.rs +++ b/src/surface/mod.rs @@ -3,9 +3,7 @@ pub mod action; -use iced::Limits; -use iced::Size; -use iced::Task; +use iced::{Limits, Size, Task}; use std::future::Future; use std::sync::Arc; diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 093bac05..a5ae5d4a 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -7,12 +7,8 @@ pub mod portal; pub mod style; -use cosmic_config::CosmicConfigEntry; -use cosmic_config::config_subscription; -use cosmic_theme::Component; -use cosmic_theme::LayeredTheme; -use cosmic_theme::Spacing; -use cosmic_theme::ThemeMode; +use cosmic_config::{CosmicConfigEntry, config_subscription}; +use cosmic_theme::{Component, LayeredTheme, Spacing, ThemeMode}; use iced_futures::Subscription; use iced_runtime::{Appearance, DefaultStyle}; use std::sync::{Arc, LazyLock, Mutex}; diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index e83e48e8..f84c9e97 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -6,10 +6,8 @@ use cosmic_theme::Component; use iced_core::{Background, Color}; -use crate::{ - theme::TRANSPARENT_COMPONENT, - widget::button::{Catalog, Style}, -}; +use crate::theme::TRANSPARENT_COMPONENT; +use crate::widget::button::{Catalog, Style}; #[derive(Default)] pub enum Button { diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index aa6f4b33..1761f6a5 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -5,18 +5,17 @@ use crate::theme::{CosmicComponent, TRANSPARENT_COMPONENT, Theme}; use cosmic_theme::composite::over; -use iced::{ - overlay::menu, - theme::Base, - widget::{ - button as iced_button, checkbox as iced_checkbox, combo_box, container as iced_container, - pane_grid, pick_list, progress_bar, radio, rule, scrollable, - slider::{self, Rail}, - svg, toggler, - }, +use iced::overlay::menu; +use iced::theme::Base; +use iced::widget::slider::{self, Rail}; +use iced::widget::{ + button as iced_button, checkbox as iced_checkbox, combo_box, container as iced_container, + pane_grid, pick_list, progress_bar, radio, rule, scrollable, svg, toggler, }; use iced_core::{Background, Border, Color, Shadow, Vector}; -use iced_widget::{pane_grid::Highlight, scrollable::AutoScroll, text_editor, text_input}; +use iced_widget::pane_grid::Highlight; +use iced_widget::scrollable::AutoScroll; +use iced_widget::{text_editor, text_input}; use palette::WithAlpha; use std::rc::Rc; diff --git a/src/theme/style/segmented_button.rs b/src/theme/style/segmented_button.rs index b9863c88..381c4a05 100644 --- a/src/theme/style/segmented_button.rs +++ b/src/theme/style/segmented_button.rs @@ -3,10 +3,13 @@ //! Contains stylesheet implementation for [`crate::widget::segmented_button`]. -use crate::widget::segmented_button::{Appearance, ItemAppearance, StyleSheet}; -use crate::{theme::Theme, widget::segmented_button::ItemStatusAppearance}; +use crate::theme::Theme; +use crate::widget::segmented_button::{ + Appearance, ItemAppearance, ItemStatusAppearance, StyleSheet, +}; use iced::Border; -use iced_core::{Background, border::Radius}; +use iced_core::Background; +use iced_core::border::Radius; use palette::WithAlpha; #[derive(Default)] @@ -143,7 +146,8 @@ mod horizontal { use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance}; use cosmic_theme::{Component, Container}; use iced::Border; - use iced_core::{Background, border::Radius}; + use iced_core::Background; + use iced_core::border::Radius; use palette::WithAlpha; pub fn tab_bar(cosmic: &cosmic_theme::Theme, container: &Container) -> Appearance { @@ -250,7 +254,8 @@ mod vertical { use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance}; use cosmic_theme::{Component, Container}; use iced::Border; - use iced_core::{Background, border::Radius}; + use iced_core::Background; + use iced_core::border::Radius; use palette::WithAlpha; pub fn tab_bar(cosmic: &cosmic_theme::Theme, container: &Container) -> Appearance { diff --git a/src/widget/about.rs b/src/widget/about.rs index 9b21e93a..490b0378 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -1,8 +1,6 @@ -use crate::{ - Apply, Element, fl, - iced::{Alignment, Length}, - widget::{self, list}, -}; +use crate::iced::{Alignment, Length}; +use crate::widget::{self, list}; +use crate::{Apply, Element, fl}; use std::rc::Rc; #[derive(Debug, Default, Clone, derive_setters::Setters)] diff --git a/src/widget/aspect_ratio.rs b/src/widget/aspect_ratio.rs index 577bea95..5cd8b9c0 100644 --- a/src/widget/aspect_ratio.rs +++ b/src/widget/aspect_ratio.rs @@ -3,13 +3,10 @@ use iced::Size; use iced::widget::Container; use iced_core::event::Event; -use iced_core::layout; -use iced_core::mouse; -use iced_core::overlay; -use iced_core::renderer; use iced_core::widget::Tree; use iced_core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, + layout, mouse, overlay, renderer, }; pub use iced_widget::container::{Catalog, Style}; diff --git a/src/widget/autosize.rs b/src/widget/autosize.rs index 69fd9c83..c52367db 100644 --- a/src/widget/autosize.rs +++ b/src/widget/autosize.rs @@ -1,12 +1,11 @@ //! Autosize Container, which will resize the window to its contents. use iced_core::event::{self, Event}; -use iced_core::layout; -use iced_core::mouse; -use iced_core::overlay; -use iced_core::renderer; use iced_core::widget::{Id, Operation, Tree}; -use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; +use iced_core::{ + Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay, + renderer, +}; pub use iced_widget::container::{Catalog, Style}; pub fn autosize<'a, Message: 'static, Theme, E>( diff --git a/src/widget/button/icon.rs b/src/widget/button/icon.rs index 04d2bdd5..6be13008 100644 --- a/src/widget/button/icon.rs +++ b/src/widget/button/icon.rs @@ -3,9 +3,13 @@ use super::{Builder, ButtonClass}; use crate::Element; -use crate::widget::{icon::Handle, tooltip}; +use crate::widget::icon::Handle; +use crate::widget::tooltip; use apply::Apply; -use iced_core::{Alignment, Length, Padding, font::Weight, text::LineHeight, widget::Id}; +use iced_core::font::Weight; +use iced_core::text::LineHeight; +use iced_core::widget::Id; +use iced_core::{Alignment, Length, Padding}; use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Icon>; diff --git a/src/widget/button/image.rs b/src/widget/button/image.rs index ab51e667..3b382035 100644 --- a/src/widget/button/image.rs +++ b/src/widget/button/image.rs @@ -2,11 +2,12 @@ // SPDX-License-Identifier: MPL-2.0 use super::Builder; -use crate::{ - Element, - widget::{self, image::Handle}, -}; -use iced_core::{Length, Padding, font::Weight, widget::Id}; +use crate::Element; +use crate::widget::image::Handle; +use crate::widget::{self}; +use iced_core::font::Weight; +use iced_core::widget::Id; +use iced_core::{Length, Padding}; use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Image<'a, Handle, Message>>; diff --git a/src/widget/button/link.rs b/src/widget/button/link.rs index 9ce81268..c0afd327 100644 --- a/src/widget/button/link.rs +++ b/src/widget/button/link.rs @@ -3,14 +3,15 @@ //! Hyperlink button widget -use super::Builder; -use super::ButtonClass; +use super::{Builder, ButtonClass}; use crate::Element; use crate::prelude::*; use crate::widget::icon::{self, Handle}; use crate::widget::{button, row, tooltip}; +use iced_core::font::Weight; use iced_core::text::LineHeight; -use iced_core::{Alignment, Length, Padding, font::Weight, widget::Id}; +use iced_core::widget::Id; +use iced_core::{Alignment, Length, Padding}; use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Hyperlink>; diff --git a/src/widget/button/style.rs b/src/widget/button/style.rs index 21afa08b..f8df8c62 100644 --- a/src/widget/button/style.rs +++ b/src/widget/button/style.rs @@ -2,7 +2,8 @@ // SPDX-License-Identifier: MPL-2.0 //! Change the apperance of a button. -use iced_core::{Background, Color, Vector, border::Radius}; +use iced_core::border::Radius; +use iced_core::{Background, Color, Vector}; use crate::theme::THEME; diff --git a/src/widget/button/text.rs b/src/widget/button/text.rs index bcdd02ba..52ab6145 100644 --- a/src/widget/button/text.rs +++ b/src/widget/button/text.rs @@ -4,7 +4,10 @@ use super::{Builder, ButtonClass}; use crate::widget::{icon, row, tooltip}; use crate::{Apply, Element}; -use iced_core::{Alignment, Length, Padding, font::Weight, text::LineHeight, widget::Id}; +use iced_core::font::Weight; +use iced_core::text::LineHeight; +use iced_core::widget::Id; +use iced_core::{Alignment, Length, Padding}; use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Text>; diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 4acf3f2d..cca54fd2 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -11,15 +11,12 @@ use iced_runtime::{Action, Task, keyboard, task}; use iced_core::event::{self, Event}; use iced_core::renderer::{self, Quad, Renderer}; -use iced_core::touch; use iced_core::widget::Operation; use iced_core::widget::tree::{self, Tree}; use iced_core::{ - Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget, + Background, Border, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shadow, Shell, + Vector, Widget, layout, mouse, overlay, svg, touch, }; -use iced_core::{Border, mouse}; -use iced_core::{Shadow, overlay}; -use iced_core::{layout, svg}; use iced_renderer::core::widget::operation; use crate::theme::THEME; @@ -647,10 +644,8 @@ impl<'a, Message: 'a + Clone> Widget state: &Tree, p: mouse::Cursor, ) -> iced_accessibility::A11yTree { - use iced_accessibility::{ - A11yNode, A11yTree, - accesskit::{Action, Node, NodeId, Rect, Role}, - }; + use iced_accessibility::accesskit::{Action, Node, NodeId, Rect, Role}; + use iced_accessibility::{A11yNode, A11yTree}; // TODO why is state None sometimes? if matches!(state.state, iced_core::widget::tree::State::None) { tracing::info!("Button state is missing."); diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index 91c601d3..35632c3a 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -8,10 +8,8 @@ use crate::widget::{button, column, grid, icon, row, text}; use apply::Apply; use iced::alignment::Vertical; use iced_core::{Alignment, Length}; -use jiff::{ - ToSpan, - civil::{Date, Weekday}, -}; +use jiff::ToSpan; +use jiff::civil::{Date, Weekday}; /// A widget that displays an interactive calendar. pub fn calendar( diff --git a/src/widget/cards.rs b/src/widget/cards.rs index 66267a73..00014740 100644 --- a/src/widget/cards.rs +++ b/src/widget/cards.rs @@ -1,23 +1,18 @@ //! An expandable stack of cards use std::time::Duration; -use crate::{ - anim, - widget::{ - button, - card::style::Style, - column, - icon::{self, Handle}, - row, text, - }, -}; +use crate::anim; +use crate::widget::card::style::Style; +use crate::widget::icon::{self, Handle}; +use crate::widget::{button, column, row, text}; use float_cmp::approx_eq; use iced::widget; -use iced_core::{ - Border, Element, Event, Length, Shadow, Size, Vector, Widget, border::Radius, id::Id, - layout::Node, renderer::Quad, widget::Tree, -}; -use iced_core::{widget::tree, window}; +use iced_core::border::Radius; +use iced_core::id::Id; +use iced_core::layout::Node; +use iced_core::renderer::Quad; +use iced_core::widget::{Tree, tree}; +use iced_core::{Border, Element, Event, Length, Shadow, Size, Vector, Widget, window}; const ICON_SIZE: u16 = 16; const TOP_SPACING: u16 = 4; diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 318e943b..66ffd847 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -12,7 +12,9 @@ use std::time::{Duration, Instant}; use crate::Element; use crate::theme::iced::Slider; use crate::theme::{Button, THEME}; -use crate::widget::{button::Catalog, container, segmented_button::Entity, slider}; +use crate::widget::button::Catalog; +use crate::widget::segmented_button::Entity; +use crate::widget::{container, slider}; use derive_setters::Setters; use iced::Task; use iced_core::event::{self, Event}; @@ -25,10 +27,8 @@ use iced_core::{ }; use iced_widget::slider::HandleShape; -use iced_widget::{ - Row, canvas, column, row, scrollable, - space::{horizontal, vertical}, -}; +use iced_widget::space::{horizontal, vertical}; +use iced_widget::{Row, canvas, column, row, scrollable}; use palette::{FromColor, RgbHue}; use super::divider::horizontal; diff --git a/src/widget/context_drawer/overlay.rs b/src/widget/context_drawer/overlay.rs index 39b34217..01f5cfcf 100644 --- a/src/widget/context_drawer/overlay.rs +++ b/src/widget/context_drawer/overlay.rs @@ -5,8 +5,7 @@ use crate::Element; use iced::advanced::layout::{self, Layout}; use iced::advanced::widget::{self, Operation}; -use iced::advanced::{Clipboard, Shell}; -use iced::advanced::{overlay, renderer}; +use iced::advanced::{Clipboard, Shell, overlay, renderer}; use iced::{Event, Point, Size, mouse}; use iced_core::{Renderer, touch}; diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index 7420738c..9a7448eb 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -6,11 +6,10 @@ use crate::widget::{self, LayerContainer, button, column, container, icon, row, use crate::{Apply, Element, Renderer, Theme, fl}; use std::borrow::Cow; -use iced_core::Alignment; use iced_core::event::Event; use iced_core::widget::{Operation, Tree}; use iced_core::{ - Clipboard, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, + Alignment, Clipboard, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay as iced_overlay, renderer, }; diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 3f35f04a..bd950bff 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -81,7 +81,8 @@ impl ContextMenu<'_, Message> { my_state: &mut LocalState, ) { if self.window_id != window::Id::NONE && self.on_surface_action.is_some() { - use crate::{surface::action::destroy_popup, widget::menu::Menu}; + use crate::surface::action::destroy_popup; + use crate::widget::menu::Menu; use iced_runtime::platform_specific::wayland::popup::{ SctkPopupSettings, SctkPositioner, }; diff --git a/src/widget/dialog.rs b/src/widget/dialog.rs index 7d084626..72ab11f4 100644 --- a/src/widget/dialog.rs +++ b/src/widget/dialog.rs @@ -1,8 +1,5 @@ -use crate::{ - Element, - iced::{Length, Pixels}, - style, theme, widget, -}; +use crate::iced::{Length, Pixels}; +use crate::{Element, style, theme, widget}; use std::borrow::Cow; pub fn dialog<'a, Message>() -> Dialog<'a, Message> { diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index 10bf7a8b..0ebff3dc 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -1,29 +1,17 @@ -use std::{ - borrow::Cow, - sync::atomic::{AtomicU64, Ordering}, -}; +use std::borrow::Cow; +use std::sync::atomic::{AtomicU64, Ordering}; use iced::Vector; -use crate::{ - Element, - widget::{Id, Widget}, -}; +use crate::Element; +use crate::widget::{Id, Widget}; -use iced::{ - Event, Length, Rectangle, - clipboard::{ - dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent}, - mime::AllowedMimeTypes, - }, - event, - id::Internal, - mouse, overlay, -}; -use iced_core::{ - self, Clipboard, Shell, layout, - widget::{Tree, tree}, -}; +use iced::clipboard::dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent}; +use iced::clipboard::mime::AllowedMimeTypes; +use iced::id::Internal; +use iced::{Event, Length, Rectangle, event, mouse, overlay}; +use iced_core::widget::{Tree, tree}; +use iced_core::{self, Clipboard, Shell, layout}; pub fn dnd_destination<'a, Message: 'static>( child: impl Into>, diff --git a/src/widget/dnd_source.rs b/src/widget/dnd_source.rs index 980723e3..3cc9fa71 100644 --- a/src/widget/dnd_source.rs +++ b/src/widget/dnd_source.rs @@ -1,20 +1,14 @@ use std::any::Any; -use iced_core::{widget::Operation, window}; +use iced_core::widget::Operation; +use iced_core::window; -use crate::{ - Element, - widget::{Id, Widget, container}, -}; -use iced::{ - Event, Length, Point, Rectangle, Vector, - clipboard::dnd::{DndAction, DndEvent, SourceEvent}, - event, mouse, overlay, -}; -use iced_core::{ - self, Clipboard, Shell, layout, renderer, - widget::{Tree, tree}, -}; +use crate::Element; +use crate::widget::{Id, Widget, container}; +use iced::clipboard::dnd::{DndAction, DndEvent, SourceEvent}; +use iced::{Event, Length, Point, Rectangle, Vector, event, mouse, overlay}; +use iced_core::widget::{Tree, tree}; +use iced_core::{self, Clipboard, Shell, layout, renderer}; pub fn dnd_source< 'a, diff --git a/src/widget/dropdown/menu/appearance.rs b/src/widget/dropdown/menu/appearance.rs index d1bed21c..2c32c011 100644 --- a/src/widget/dropdown/menu/appearance.rs +++ b/src/widget/dropdown/menu/appearance.rs @@ -3,7 +3,8 @@ // SPDX-License-Identifier: MPL-2.0 AND MIT //! Change the appearance of menus. -use iced_core::{Background, Color, border::Radius}; +use iced_core::border::Radius; +use iced_core::{Background, Color}; /// The appearance of a menu. #[derive(Debug, Clone, Copy)] diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index 779c6d00..3c01f5f8 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -9,9 +9,9 @@ use iced_core::event::{self, Event}; use iced_core::text::{self, Paragraph, Text}; use iced_core::widget::tree::{self, Tree}; use iced_core::{ - Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, + Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shadow, Shell, Size, Vector, Widget, + alignment, keyboard, layout, mouse, overlay, renderer, svg, touch, }; -use iced_core::{Shadow, alignment, keyboard, layout, mouse, overlay, renderer, svg, touch}; use iced_widget::pick_list; use std::ffi::OsStr; diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 2ff9c92f..046cf84d 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -12,9 +12,9 @@ use iced_core::event::{self, Event}; use iced_core::text::{self, Paragraph, Text}; use iced_core::widget::tree::{self, Tree}; use iced_core::{ - Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, + Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shadow, Shell, Size, Vector, Widget, + alignment, keyboard, layout, mouse, overlay, renderer, svg, touch, }; -use iced_core::{Shadow, alignment, keyboard, layout, mouse, overlay, renderer, svg, touch}; use iced_widget::pick_list::{self, Catalog}; use std::borrow::Cow; use std::ffi::OsStr; diff --git a/src/widget/frames.rs b/src/widget/frames.rs index a542cec6..bf985ce2 100644 --- a/src/widget/frames.rs +++ b/src/widget/frames.rs @@ -2,14 +2,12 @@ //! Based on use std::ffi::OsStr; -use std::fmt; -use std::io; use std::path::Path; use std::time::{Duration, Instant}; +use std::{fmt, io}; use ::image as image_rs; -use iced::Task; -use iced::mouse; +use iced::{Task, mouse}; use iced_core::image::Renderer as ImageRenderer; use iced_core::mouse::Cursor; use iced_core::widget::{Tree, tree}; diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 0a10b0aa..556466fd 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -5,7 +5,8 @@ use crate::cosmic_theme::{Density, Spacing}; use crate::{Element, theme, widget}; use apply::Apply; use derive_setters::Setters; -use iced_core::{Length, Size, Vector, Widget, layout, text, widget::tree}; +use iced_core::widget::tree; +use iced_core::{Length, Size, Vector, Widget, layout, text}; use std::borrow::Cow; #[must_use] diff --git a/src/widget/icon/named.rs b/src/widget/icon/named.rs index dfd66cf5..c6bec753 100644 --- a/src/widget/icon/named.rs +++ b/src/widget/icon/named.rs @@ -2,7 +2,10 @@ // SPDX-License-Identifier: MPL-2.0 use super::{Handle, Icon}; -use std::{borrow::Cow, ffi::OsStr, path::PathBuf, sync::Arc}; +use std::borrow::Cow; +use std::ffi::OsStr; +use std::path::PathBuf; +use std::sync::Arc; #[derive(Debug, Clone, Default, Hash)] /// Fallback icon to use if the icon was not found. diff --git a/src/widget/id_container.rs b/src/widget/id_container.rs index 716ee138..6b0d13ce 100644 --- a/src/widget/id_container.rs +++ b/src/widget/id_container.rs @@ -1,10 +1,9 @@ use iced_core::event::{self, Event}; -use iced_core::layout; -use iced_core::mouse; -use iced_core::overlay; -use iced_core::renderer; use iced_core::widget::{Id, Operation, Tree}; -use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; +use iced_core::{ + Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay, + renderer, +}; pub use iced_widget::container::{Catalog, Style}; pub fn id_container<'a, Message: 'static, Theme, E>( diff --git a/src/widget/layer_container.rs b/src/widget/layer_container.rs index 110af518..3958b03c 100644 --- a/src/widget/layer_container.rs +++ b/src/widget/layer_container.rs @@ -2,13 +2,10 @@ use crate::Theme; use cosmic_theme::LayeredTheme; use iced::widget::Container; use iced_core::event::{self, Event}; -use iced_core::layout; -use iced_core::mouse; -use iced_core::overlay; -use iced_core::renderer; use iced_core::widget::Tree; use iced_core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, + layout, mouse, overlay, renderer, }; pub use iced_widget::container::{Catalog, Style}; diff --git a/src/widget/list/list_column.rs b/src/widget/list/list_column.rs index 4ef3fc01..9e4204cc 100644 --- a/src/widget/list/list_column.rs +++ b/src/widget/list/list_column.rs @@ -2,7 +2,8 @@ // SPDX-License-Identifier: MPL-2.0 use crate::widget::container::Catalog; -use crate::widget::{button, column, container, divider, row, space::vertical}; +use crate::widget::space::vertical; +use crate::widget::{button, column, container, divider, row}; use crate::{Apply, Element, theme}; use iced::{Length, Padding}; diff --git a/src/widget/menu/flex.rs b/src/widget/menu/flex.rs index 4a58f13a..213ee694 100644 --- a/src/widget/menu/flex.rs +++ b/src/widget/menu/flex.rs @@ -1,11 +1,9 @@ // From iced_aw, license MIT -use iced_core::{Widget, widget::Tree}; -use iced_widget::core::{ - Alignment, Element, Padding, Point, Size, - layout::{Limits, Node}, - renderer, -}; +use iced_core::Widget; +use iced_core::widget::Tree; +use iced_widget::core::layout::{Limits, Node}; +use iced_widget::core::{Alignment, Element, Padding, Point, Size, renderer}; use crate::widget::RcElementWrapper; diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 981446e8..b5ffca42 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -1,14 +1,14 @@ // From iced_aw, license MIT //! A widget that handles menu trees -use std::{collections::HashMap, sync::Arc}; +use std::collections::HashMap; +use std::sync::Arc; -use super::{ - menu_inner::{ - CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight, - }, - menu_tree::MenuTree, +use super::menu_inner::{ + CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight, }; +use super::menu_tree::MenuTree; +use crate::Renderer; #[cfg(all( feature = "multi-window", feature = "wayland", @@ -17,26 +17,21 @@ use super::{ feature = "surface-message" ))] use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; -use crate::{ - Renderer, - style::menu_bar::StyleSheet, - widget::{ - RcWrapper, - dropdown::menu::{self, State}, - menu::menu_inner::init_root_menu, - }, -}; +use crate::style::menu_bar::StyleSheet; +use crate::widget::RcWrapper; +use crate::widget::dropdown::menu::{self, State}; +use crate::widget::menu::menu_inner::init_root_menu; -use iced::{Point, Shadow, Vector, event::Status, window}; +use iced::event::Status; +use iced::{Point, Shadow, Vector, window}; use iced_core::Border; +use iced_widget::core::layout::{Limits, Node}; +use iced_widget::core::mouse::{self, Cursor}; +use iced_widget::core::renderer::{self, Renderer as IcedRenderer}; +use iced_widget::core::widget::{Tree, tree}; use iced_widget::core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, event, - layout::{Limits, Node}, - mouse::{self, Cursor}, - overlay, - renderer::{self, Renderer as IcedRenderer}, - touch, - widget::{Tree, tree}, + overlay, touch, }; /// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing. @@ -590,7 +585,8 @@ where viewport: &Rectangle, ) { use event::Event::{Mouse, Touch}; - use mouse::{Button::Left, Event::ButtonReleased}; + use mouse::Button::Left; + use mouse::Event::ButtonReleased; use touch::Event::{FingerLifted, FingerLost}; process_root_events( diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 74afe60f..6211e201 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -1,9 +1,11 @@ // From iced_aw, license MIT //! Menu tree overlay -use std::{borrow::Cow, sync::Arc}; +use std::borrow::Cow; +use std::sync::Arc; -use super::{menu_bar::MenuBarState, menu_tree::MenuTree}; +use super::menu_bar::MenuBarState; +use super::menu_tree::MenuTree; #[cfg(all( feature = "multi-window", feature = "wayland", @@ -16,12 +18,12 @@ use crate::style::menu_bar::StyleSheet; use iced::window; use iced_core::{Border, Renderer as IcedRenderer, Shadow, Widget}; +use iced_widget::core::layout::{Limits, Node}; +use iced_widget::core::mouse::{self, Cursor}; +use iced_widget::core::widget::Tree; use iced_widget::core::{ - Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, event, - layout::{Limits, Node}, - mouse::{self, Cursor}, - overlay, renderer, touch, - widget::Tree, + Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, event, overlay, + renderer, touch, }; /// The condition of when to close a menu @@ -559,14 +561,10 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> Option<(usize, MenuState)> { - use event::{ - Event::{Mouse, Touch}, - Status::{Captured, Ignored}, - }; - use mouse::{ - Button::Left, - Event::{ButtonPressed, ButtonReleased, CursorMoved, WheelScrolled}, - }; + use event::Event::{Mouse, Touch}; + use event::Status::{Captured, Ignored}; + use mouse::Button::Left; + use mouse::Event::{ButtonPressed, ButtonReleased, CursorMoved, WheelScrolled}; use touch::Event::{FingerLifted, FingerMoved, FingerPressed}; if !self diff --git a/src/widget/mod.rs b/src/widget/mod.rs index f442b0da..d0dee7c0 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -154,7 +154,7 @@ pub use dialog::{Dialog, dialog}; pub mod divider { /// Horizontal variant of a divider. pub mod horizontal { - use iced::{widget::Rule, widget::rule}; + use iced::widget::{Rule, rule}; /// Horizontal divider with default thickness #[must_use] diff --git a/src/widget/nav_bar.rs b/src/widget/nav_bar.rs index ad6f9206..b7391abb 100644 --- a/src/widget/nav_bar.rs +++ b/src/widget/nav_bar.rs @@ -6,10 +6,9 @@ //! For details on the model, see the [`segmented_button`] module for more details. use apply::Apply; -use iced::{ - Background, Length, - clipboard::{dnd::DndAction, mime::AllowedMimeTypes}, -}; +use iced::clipboard::dnd::DndAction; +use iced::clipboard::mime::AllowedMimeTypes; +use iced::{Background, Length}; use iced_core::{Border, Color, Shadow}; use crate::widget::{Container, Icon, container, menu, scrollable, segmented_button}; diff --git a/src/widget/popover.rs b/src/widget/popover.rs index af5370a8..57e4568a 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -5,14 +5,10 @@ use iced::widget; use iced_core::event::{self, Event}; -use iced_core::layout; -use iced_core::mouse; -use iced_core::overlay; -use iced_core::renderer; -use iced_core::touch; use iced_core::widget::{Operation, Tree}; use iced_core::{ - Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, + Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, layout, + mouse, overlay, renderer, touch, }; pub use iced_widget::container::{Catalog, Style}; diff --git a/src/widget/progress_bar/circular.rs b/src/widget/progress_bar/circular.rs index 42c8e642..2123d2e6 100644 --- a/src/widget/progress_bar/circular.rs +++ b/src/widget/progress_bar/circular.rs @@ -1,14 +1,10 @@ //! Show a circular progress indicator. use super::animation::{Animation, Progress}; use super::style::StyleSheet; -use iced::advanced::layout; -use iced::advanced::renderer; use iced::advanced::widget::tree::{self, Tree}; -use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; -use iced::mouse; +use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer}; use iced::widget::canvas; -use iced::window; -use iced::{Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector}; +use iced::{Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector, mouse, window}; use std::f32::consts::PI; use std::time::Duration; diff --git a/src/widget/progress_bar/linear.rs b/src/widget/progress_bar/linear.rs index 7bcbb4f7..7672579e 100644 --- a/src/widget/progress_bar/linear.rs +++ b/src/widget/progress_bar/linear.rs @@ -1,13 +1,9 @@ //! Show a linear progress indicator. use super::animation::{Animation, Progress}; use super::style::StyleSheet; -use iced::advanced::layout; -use iced::advanced::renderer; use iced::advanced::widget::tree::{self, Tree}; -use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; -use iced::mouse; -use iced::window; -use iced::{Background, Element, Event, Length, Rectangle, Size}; +use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer}; +use iced::{Background, Element, Event, Length, Rectangle, Size, mouse, window}; use std::time::Duration; diff --git a/src/widget/radio.rs b/src/widget/radio.rs index c3f115c0..7e6ef424 100644 --- a/src/widget/radio.rs +++ b/src/widget/radio.rs @@ -2,14 +2,10 @@ use crate::{Theme, theme}; use iced::border; use iced_core::event::{self, Event}; -use iced_core::layout; -use iced_core::mouse; -use iced_core::overlay; -use iced_core::renderer; -use iced_core::touch; use iced_core::widget::tree::Tree; use iced_core::{ Border, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Vector, Widget, + layout, mouse, overlay, renderer, touch, }; use iced_widget::radio as iced_radio; diff --git a/src/widget/rectangle_tracker/mod.rs b/src/widget/rectangle_tracker/mod.rs index b3066ecb..839fa9a7 100644 --- a/src/widget/rectangle_tracker/mod.rs +++ b/src/widget/rectangle_tracker/mod.rs @@ -6,13 +6,13 @@ use iced::widget::Container; pub use subscription::*; use iced_core::event::{self, Event}; -use iced_core::layout; -use iced_core::mouse; -use iced_core::overlay; -use iced_core::renderer; use iced_core::widget::Tree; -use iced_core::{Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget}; -use std::{fmt::Debug, hash::Hash}; +use iced_core::{ + Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, layout, + mouse, overlay, renderer, +}; +use std::fmt::Debug; +use std::hash::Hash; pub use iced_widget::container::{Catalog, Style}; diff --git a/src/widget/rectangle_tracker/subscription.rs b/src/widget/rectangle_tracker/subscription.rs index 02fa4329..22b42441 100644 --- a/src/widget/rectangle_tracker/subscription.rs +++ b/src/widget/rectangle_tracker/subscription.rs @@ -1,13 +1,10 @@ -use iced::{ - Rectangle, - futures::{ - StreamExt, - channel::mpsc::{UnboundedReceiver, unbounded}, - stream, - }, -}; +use iced::Rectangle; +use iced::futures::channel::mpsc::{UnboundedReceiver, unbounded}; +use iced::futures::{StreamExt, stream}; use iced_futures::Subscription; -use std::{collections::HashMap, fmt::Debug, hash::Hash}; +use std::collections::HashMap; +use std::fmt::Debug; +use std::hash::Hash; use super::RectangleTracker; diff --git a/src/widget/responsive_container.rs b/src/widget/responsive_container.rs index b9b6a289..c8925cc2 100644 --- a/src/widget/responsive_container.rs +++ b/src/widget/responsive_container.rs @@ -2,12 +2,11 @@ use iced::{Limits, Size}; use iced_core::event::{self, Event}; -use iced_core::layout; -use iced_core::mouse; -use iced_core::overlay; -use iced_core::renderer; use iced_core::widget::{Id, Operation, Tree, tree}; -use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; +use iced_core::{ + Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay, + renderer, +}; pub(crate) fn responsive_container<'a, Message: 'static, Theme, E>( content: E, diff --git a/src/widget/responsive_menu_bar.rs b/src/widget/responsive_menu_bar.rs index b5dd556d..2cae68d8 100644 --- a/src/widget/responsive_menu_bar.rs +++ b/src/widget/responsive_menu_bar.rs @@ -2,10 +2,8 @@ use std::collections::HashMap; use apply::Apply; -use crate::{ - Core, Element, - widget::{button, icon, responsive_container}, -}; +use crate::widget::{button, icon, responsive_container}; +use crate::{Core, Element}; use super::menu::{self, ItemHeight, ItemWidth}; diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index fcb1c596..edd58eb7 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -25,9 +25,11 @@ use iced_core::id::Internal; use iced_core::mouse::ScrollDelta; use iced_core::text::{self, Ellipsize, LineHeight, Renderer as TextRenderer, Shaping, Wrapping}; use iced_core::widget::operation::Focusable; -use iced_core::widget::{self, operation, tree}; -use iced_core::{Border, Point, Renderer as IcedRenderer, Shadow, Text}; -use iced_core::{Clipboard, Layout, Shell, Widget, layout, renderer, widget::Tree}; +use iced_core::widget::{self, Tree, operation, tree}; +use iced_core::{ + Border, Clipboard, Layout, Point, Renderer as IcedRenderer, Shadow, Shell, Text, Widget, + layout, renderer, +}; use iced_runtime::{Action, task}; use slotmap::{Key, SecondaryMap}; use std::borrow::Cow; diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index 5abb464c..4eb5bb17 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -3,12 +3,11 @@ use std::borrow::Cow; -use crate::{ - Element, Theme, theme, - widget::{FlexRow, Row, column, container, flex_row, list, row, text}, -}; +use crate::widget::{FlexRow, Row, column, container, flex_row, list, row, text}; +use crate::{Element, Theme, theme}; use derive_setters::Setters; -use iced_core::{Length, text::Wrapping}; +use iced_core::Length; +use iced_core::text::Wrapping; use iced_widget::space; use taffy::AlignContent; diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index 833e90b8..71933124 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -3,13 +3,10 @@ //! A control for incremental adjustments of a value. -use crate::{ - Element, theme, - widget::{button, column, container, icon, row, text}, -}; +use crate::widget::{button, column, container, icon, row, text}; +use crate::{Element, theme}; use apply::Apply; -use iced::{Alignment, Length}; -use iced::{Border, Shadow}; +use iced::{Alignment, Border, Length, Shadow}; use std::borrow::Cow; use std::ops::{Add, Sub}; diff --git a/src/widget/table/mod.rs b/src/widget/table/mod.rs index c546383c..dfde3810 100644 --- a/src/widget/table/mod.rs +++ b/src/widget/table/mod.rs @@ -2,12 +2,9 @@ //! pub mod model; -pub use model::{ - Entity, Model, - category::ItemCategory, - category::ItemInterface, - selection::{MultiSelect, SingleSelect}, -}; +pub use model::category::{ItemCategory, ItemInterface}; +pub use model::selection::{MultiSelect, SingleSelect}; +pub use model::{Entity, Model}; pub mod widget; pub use widget::compact::CompactTableView; pub use widget::standard::TableView; diff --git a/src/widget/table/model/entity.rs b/src/widget/table/model/entity.rs index 51c60609..910ccd2f 100644 --- a/src/widget/table/model/entity.rs +++ b/src/widget/table/model/entity.rs @@ -3,10 +3,8 @@ use slotmap::{SecondaryMap, SparseSecondaryMap}; -use super::{ - Entity, Model, Selectable, - category::{ItemCategory, ItemInterface}, -}; +use super::category::{ItemCategory, ItemInterface}; +use super::{Entity, Model, Selectable}; /// A newly-inserted item which may have additional actions applied to it. pub struct EntityMut< diff --git a/src/widget/table/model/mod.rs b/src/widget/table/model/mod.rs index d6250eaf..93998f9f 100644 --- a/src/widget/table/model/mod.rs +++ b/src/widget/table/model/mod.rs @@ -2,10 +2,8 @@ pub mod category; pub mod entity; pub mod selection; -use std::{ - any::{Any, TypeId}, - collections::{HashMap, VecDeque}, -}; +use std::any::{Any, TypeId}; +use std::collections::{HashMap, VecDeque}; use category::{ItemCategory, ItemInterface}; use entity::EntityMut; diff --git a/src/widget/table/model/selection.rs b/src/widget/table/model/selection.rs index 20a07248..3e3d46b5 100644 --- a/src/widget/table/model/selection.rs +++ b/src/widget/table/model/selection.rs @@ -3,10 +3,8 @@ //! Describes logic specific to the single-select and multi-select modes of a model. -use super::{ - Entity, Model, - category::{ItemCategory, ItemInterface}, -}; +use super::category::{ItemCategory, ItemInterface}; +use super::{Entity, Model}; use std::collections::HashSet; /// Describes a type that has selectable items. diff --git a/src/widget/table/widget/compact.rs b/src/widget/table/widget/compact.rs index 65ac9058..6d027caf 100644 --- a/src/widget/table/widget/compact.rs +++ b/src/widget/table/widget/compact.rs @@ -1,14 +1,10 @@ use derive_setters::Setters; -use crate::widget::table::model::{ - Entity, Model, - category::{ItemCategory, ItemInterface}, - selection::Selectable, -}; -use crate::{ - Apply, Element, theme, - widget::{self, container, menu}, -}; +use crate::widget::table::model::category::{ItemCategory, ItemInterface}; +use crate::widget::table::model::selection::Selectable; +use crate::widget::table::model::{Entity, Model}; +use crate::widget::{self, container, menu}; +use crate::{Apply, Element, theme}; use iced::{Alignment, Border, Padding}; #[derive(Setters)] diff --git a/src/widget/table/widget/standard.rs b/src/widget/table/widget/standard.rs index 9ab76c9d..0e206238 100644 --- a/src/widget/table/widget/standard.rs +++ b/src/widget/table/widget/standard.rs @@ -1,14 +1,10 @@ use derive_setters::Setters; -use crate::widget::table::model::{ - Entity, Model, - category::{ItemCategory, ItemInterface}, - selection::Selectable, -}; -use crate::{ - Apply, Element, theme, - widget::{self, container, divider, menu}, -}; +use crate::widget::table::model::category::{ItemCategory, ItemInterface}; +use crate::widget::table::model::selection::Selectable; +use crate::widget::table::model::{Entity, Model}; +use crate::widget::{self, container, divider, menu}; +use crate::{Apply, Element, theme}; use iced::{Alignment, Border, Length, Padding}; // THIS IS A PLACEHOLDER UNTIL A MORE SOPHISTICATED WIDGET CAN BE DEVELOPED diff --git a/src/widget/text_input/editor.rs b/src/widget/text_input/editor.rs index b8144761..c9c2ca7a 100644 --- a/src/widget/text_input/editor.rs +++ b/src/widget/text_input/editor.rs @@ -2,7 +2,8 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MIT -use super::{cursor::Cursor, value::Value}; +use super::cursor::Cursor; +use super::value::Value; pub struct Editor<'a> { value: &'a mut Value, diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 4336c757..ea1122ea 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -28,18 +28,14 @@ use iced_core::overlay::Group; use iced_core::renderer::{self, Renderer as CoreRenderer}; use iced_core::text::{self, Affinity, Paragraph, Renderer, Text}; use iced_core::time::{Duration, Instant}; -use iced_core::touch; use iced_core::widget::Id; use iced_core::widget::operation::{self, Operation}; use iced_core::widget::tree::{self, Tree}; -use iced_core::window; -use iced_core::{Background, alignment}; -use iced_core::{Border, Shadow, keyboard}; use iced_core::{ - Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size, - Vector, Widget, + Background, Border, Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, + Rectangle, Shadow, Shell, Size, Vector, Widget, alignment, keyboard, layout, overlay, touch, + window, }; -use iced_core::{layout, overlay}; use iced_runtime::{Action, Task, task}; thread_local! { diff --git a/src/widget/text_input/style.rs b/src/widget/text_input/style.rs index 8af5e63e..52b98784 100644 --- a/src/widget/text_input/style.rs +++ b/src/widget/text_input/style.rs @@ -4,7 +4,8 @@ //! Change the appearance of a text input. -use iced_core::{Background, Color, border::Radius}; +use iced_core::border::Radius; +use iced_core::{Background, Color}; /// The appearance of a text input. #[derive(Debug, Clone, Copy)] diff --git a/src/widget/toaster/mod.rs b/src/widget/toaster/mod.rs index bafaa9f9..43acc5c1 100644 --- a/src/widget/toaster/mod.rs +++ b/src/widget/toaster/mod.rs @@ -6,16 +6,13 @@ use std::collections::VecDeque; use std::rc::Rc; -use crate::widget::Column; -use crate::widget::container; +use crate::widget::{Column, container}; use iced::Task; use iced_core::Element; -use slotmap::SlotMap; -use slotmap::new_key_type; +use slotmap::{SlotMap, new_key_type}; use widget::Toaster; -use super::column; -use super::{button, icon, row, text}; +use super::{button, column, icon, row, text}; mod widget; diff --git a/src/widget/toaster/widget.rs b/src/widget/toaster/widget.rs index de47a9bd..922c1433 100644 --- a/src/widget/toaster/widget.rs +++ b/src/widget/toaster/widget.rs @@ -4,16 +4,14 @@ use iced::{Limits, Size}; use iced_core::layout::Node; -use iced_core::Element; -use iced_core::Overlay; use iced_core::event::{self, Event}; -use iced_core::layout; -use iced_core::mouse; -use iced_core::overlay; use iced_core::renderer::{self}; use iced_core::widget::Operation; use iced_core::widget::tree::Tree; -use iced_core::{Clipboard, Layout, Length, Point, Rectangle, Shell, Vector, Widget}; +use iced_core::{ + Clipboard, Element, Layout, Length, Overlay, Point, Rectangle, Shell, Vector, Widget, layout, + mouse, overlay, +}; pub struct Toaster<'a, Message, Theme, Renderer> { toasts: Element<'a, Message, Theme, Renderer>, diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index b95b596e..2dad2ed9 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -3,15 +3,14 @@ use std::time::{Duration, Instant}; use crate::{Element, anim}; +use iced_core::renderer::{self, Renderer}; +use iced_core::widget::{self, Tree, tree}; use iced_core::{ Border, Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment, - event, layout, mouse, - renderer::{self, Renderer}, - text, touch, - widget::{self, Tree, tree}, - window, + event, layout, mouse, text, touch, window, }; -use iced_widget::{Id, toggler::Status}; +use iced_widget::Id; +use iced_widget::toggler::Status; pub use iced_widget::toggler::{Catalog, Style}; diff --git a/src/widget/wayland/tooltip/mod.rs b/src/widget/wayland/tooltip/mod.rs index 947d1e83..aa80fad6 100644 --- a/src/widget/wayland/tooltip/mod.rs +++ b/src/widget/wayland/tooltip/mod.rs @@ -5,7 +5,8 @@ pub mod widget; // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -use iced_core::{Background, Color, Vector, border::Radius}; +use iced_core::border::Radius; +use iced_core::{Background, Color, Vector}; use crate::theme::THEME; diff --git a/src/widget/wayland/tooltip/widget.rs b/src/widget/wayland/tooltip/widget.rs index 7bf0991a..203a2010 100644 --- a/src/widget/wayland/tooltip/widget.rs +++ b/src/widget/wayland/tooltip/widget.rs @@ -14,16 +14,12 @@ use iced::Task; use iced_runtime::core::widget::Id; use iced_core::event::{self, Event}; -use iced_core::renderer; -use iced_core::touch; use iced_core::widget::Operation; use iced_core::widget::tree::{self, Tree}; use iced_core::{ - Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget, + Background, Border, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shadow, Shell, + Vector, Widget, layout, mouse, overlay, renderer, svg, touch, }; -use iced_core::{Border, mouse}; -use iced_core::{Shadow, overlay}; -use iced_core::{layout, svg}; pub use super::{Catalog, Style}; diff --git a/src/widget/wrapper.rs b/src/widget/wrapper.rs index 133f9b87..aee06e5a 100644 --- a/src/widget/wrapper.rs +++ b/src/widget/wrapper.rs @@ -1,13 +1,13 @@ -use std::{ - borrow::Borrow, - cell::RefCell, - rc::Rc, - thread::{self, ThreadId}, -}; +use std::borrow::Borrow; +use std::cell::RefCell; +use std::rc::Rc; +use std::thread::{self, ThreadId}; use crate::Element; use iced::{Length, Rectangle, Size, event}; -use iced_core::{Widget, id::Id, widget, widget::tree}; +use iced_core::id::Id; +use iced_core::widget::tree; +use iced_core::{Widget, widget}; #[derive(Debug)] pub struct RcWrapper { From 0e0960f3c79138b445ae84f7ac6f91d4db36e8b4 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Fri, 1 May 2026 13:21:07 -0600 Subject: [PATCH 34/75] feat: reorderable flex row --- src/widget/mod.rs | 4 + src/widget/reorderable_flex_row/mod.rs | 8 + src/widget/reorderable_flex_row/widget.rs | 1296 +++++++++++++++++++++ 3 files changed, 1308 insertions(+) create mode 100644 src/widget/reorderable_flex_row/mod.rs create mode 100644 src/widget/reorderable_flex_row/widget.rs diff --git a/src/widget/mod.rs b/src/widget/mod.rs index d0dee7c0..5089b997 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -215,6 +215,10 @@ pub mod flex_row; #[doc(inline)] pub use flex_row::{FlexRow, flex_row}; +pub mod reorderable_flex_row; +#[doc(inline)] +pub use reorderable_flex_row::{ReorderableFlexRow, reorderable_flex_row}; + pub mod grid; #[doc(inline)] pub use grid::{Grid, grid}; diff --git a/src/widget/reorderable_flex_row/mod.rs b/src/widget/reorderable_flex_row/mod.rs new file mode 100644 index 00000000..d5da9f4a --- /dev/null +++ b/src/widget/reorderable_flex_row/mod.rs @@ -0,0 +1,8 @@ +// Copyright 2026 System76 +// SPDX-License-Identifier: MPL-2.0 + +//! A keyed wrapping flex row whose items can be dragged to reorder. + +mod widget; + +pub use widget::{ReorderableFlexRow, reorderable_flex_row}; diff --git a/src/widget/reorderable_flex_row/widget.rs b/src/widget/reorderable_flex_row/widget.rs new file mode 100644 index 00000000..ed95a34f --- /dev/null +++ b/src/widget/reorderable_flex_row/widget.rs @@ -0,0 +1,1296 @@ +// Copyright 2026 System76 +// SPDX-License-Identifier: MPL-2.0 + +use crate::{Element, Renderer}; +use iced::{Alignment, Pixels, alignment}; +use iced_core::event::Event; +use iced_core::layout::{self, Layout}; +use iced_core::mouse::{self, Cursor}; +use iced_core::widget::Operation; +use iced_core::widget::tree::{self, Tree}; +#[cfg(feature = "wgpu")] +use iced_core::{Background, Border, Shadow}; +use iced_core::{ + Clipboard, Length, Padding, Point, Rectangle, Renderer as _, Shell, Size, Vector, Widget, + overlay, renderer, window, +}; +use std::collections::{HashMap, HashSet}; +use std::hash::Hash; +use std::time::{Duration, Instant}; + +const DEFAULT_ANIMATION_DURATION: Duration = Duration::from_millis(180); +const DEFAULT_DRAG_LIFT: f32 = 10.0; +const DEFAULT_DRAG_THRESHOLD: f32 = 6.0; +const SHADOW_BLUR_RADIUS: f32 = 20.0; +const POSITION_EPSILON: f32 = 0.5; + +#[derive(Debug, Clone)] +struct SlotAnimation { + from: Point, + to: Point, + started_at: Option, +} + +impl SlotAnimation { + fn new(position: Point) -> Self { + Self { + from: position, + to: position, + started_at: None, + } + } + + fn current_position(&self, duration: Duration) -> Point { + let Some(started_at) = self.started_at else { + return self.to; + }; + + let duration_secs = duration.as_secs_f32(); + if duration_secs <= f32::EPSILON { + return self.to; + } + + let progress = (started_at.elapsed().as_secs_f32() / duration_secs).clamp(0.0, 1.0); + let eased = 1.0 - (1.0 - progress).powi(3); + + Point::new( + self.from.x + (self.to.x - self.from.x) * eased, + self.from.y + (self.to.y - self.from.y) * eased, + ) + } + + fn retarget(&mut self, new_target: Point, duration: Duration) { + if approx_eq_point(self.to, new_target) { + if !self.is_animating(duration) { + self.from = new_target; + self.to = new_target; + self.started_at = None; + } + return; + } + + self.from = self.current_position(duration); + self.to = new_target; + self.started_at = Some(Instant::now()); + } + + fn is_animating(&self, duration: Duration) -> bool { + self.started_at + .is_some_and(|started_at| started_at.elapsed() < duration) + } + + fn finish_if_done(&mut self, duration: Duration) { + if self + .started_at + .is_some_and(|started_at| started_at.elapsed() >= duration) + { + self.from = self.to; + self.started_at = None; + } + } +} + +#[derive(Debug, Clone)] +struct PendingDragState +where + Key: Clone + Eq + Hash + 'static, +{ + key: Key, + item_index: usize, + original_index: usize, + press_local: Point, + pointer_offset: Vector, +} + +#[derive(Debug, Clone)] +struct DragState +where + Key: Clone + Eq + Hash + 'static, +{ + key: Key, + item_index: usize, + original_index: usize, + current_index: usize, + cursor_local: Point, + pointer_offset: Vector, +} + +#[derive(Debug, Clone)] +struct State +where + Key: Clone + Eq + Hash + 'static, +{ + keys: Vec, + slot_positions: HashMap, + pending_drag: Option>, + drag: Option>, + wrap_width: f32, + initialized: bool, +} + +impl Default for State +where + Key: Clone + Eq + Hash + 'static, +{ + fn default() -> Self { + Self { + keys: Vec::new(), + slot_positions: HashMap::new(), + pending_drag: None, + drag: None, + wrap_width: f32::INFINITY, + initialized: false, + } + } +} + +impl State +where + Key: Clone + Eq + Hash + 'static, +{ + fn retain_keys(&mut self, keys: &[Key]) { + let keep: HashSet<_> = keys.iter().cloned().collect(); + self.slot_positions.retain(|key, _| keep.contains(key)); + + if self + .pending_drag + .as_ref() + .is_some_and(|drag| !keep.contains(&drag.key)) + { + self.pending_drag = None; + } + + if self + .drag + .as_ref() + .is_some_and(|drag| !keep.contains(&drag.key)) + { + self.drag = None; + } + } + + fn finish_animations(&mut self, duration: Duration) { + self.slot_positions + .values_mut() + .for_each(|slot| slot.finish_if_done(duration)); + } + + fn is_animating(&self, duration: Duration) -> bool { + self.slot_positions + .values() + .any(|slot| slot.is_animating(duration)) + } + + fn current_slot_position(&self, key: &Key, fallback: Point, duration: Duration) -> Point { + self.slot_positions + .get(key) + .map(|slot| slot.current_position(duration)) + .unwrap_or(fallback) + } + + fn retarget_slot(&mut self, key: &Key, target: Point, duration: Duration) { + self.slot_positions + .entry(key.clone()) + .or_insert_with(|| SlotAnimation::new(target)) + .retarget(target, duration); + } + + fn snap_slot(&mut self, key: &Key, target: Point) { + self.slot_positions + .insert(key.clone(), SlotAnimation::new(target)); + } + + fn apply_layout_position(&mut self, key: &Key, target: Point, duration: Duration) { + if self.initialized { + self.retarget_slot(key, target, duration); + } else { + self.snap_slot(key, target); + } + } +} + +#[derive(Debug, Clone)] +struct LocalSlot +where + Key: Clone + Eq + Hash + 'static, +{ + key: Key, + locked: bool, + bounds: Rectangle, +} + +/// A horizontal flex row with drag-to-reorder behavior. +#[must_use] +pub struct ReorderableFlexRow<'a, Key, Message> +where + Key: Clone + Eq + Hash + 'static, +{ + spacing: f32, + padding: Padding, + width: Length, + height: Length, + align: Alignment, + clip: bool, + drag_lift: f32, + animation_duration: Duration, + on_reorder: Box) -> Message + 'a>, + keys: Vec, + locked: Vec, + children: Vec>, +} + +impl<'a, Key, Message> ReorderableFlexRow<'a, Key, Message> +where + Key: Clone + Eq + Hash + 'static, +{ + pub fn new(on_reorder: impl Fn(Vec) -> Message + 'a) -> Self { + Self { + spacing: 0.0, + padding: Padding::ZERO, + width: Length::Shrink, + height: Length::Shrink, + align: Alignment::Start, + clip: false, + drag_lift: DEFAULT_DRAG_LIFT, + animation_duration: DEFAULT_ANIMATION_DURATION, + on_reorder: Box::new(on_reorder), + keys: Vec::new(), + locked: Vec::new(), + children: Vec::new(), + } + } + + pub fn with_capacity(capacity: usize, on_reorder: impl Fn(Vec) -> Message + 'a) -> Self { + let mut row = Self::new(on_reorder); + row.keys = Vec::with_capacity(capacity); + row.locked = Vec::with_capacity(capacity); + row.children = Vec::with_capacity(capacity); + row + } + + pub fn spacing(mut self, amount: impl Into) -> Self { + self.spacing = amount.into().0; + self + } + + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } + + pub fn align_y(mut self, align: impl Into) -> Self { + self.align = Alignment::from(align.into()); + self + } + + /// Leave disabled for dragged item to visibly lift above the row. + pub fn clip(mut self, clip: bool) -> Self { + self.clip = clip; + self + } + + pub fn drag_lift(mut self, lift: f32) -> Self { + self.drag_lift = lift.max(0.0); + self + } + + pub fn animation_duration(mut self, duration: Duration) -> Self { + self.animation_duration = duration; + self + } + + pub fn push(self, key: Key, child: impl Into>) -> Self { + self.push_with_lock(key, false, child) + } + + /// Item stays fixed, never participates in reordering. + pub fn push_locked(self, key: Key, child: impl Into>) -> Self { + self.push_with_lock(key, true, child) + } + + fn push_with_lock( + mut self, + key: Key, + locked: bool, + child: impl Into>, + ) -> Self { + let child = child.into(); + let child_size = child.as_widget().size_hint(); + + if !child_size.is_void() { + self.width = self.width.enclose(child_size.width); + self.height = self.height.enclose(child_size.height); + self.keys.push(key); + self.locked.push(locked); + self.children.push(child); + } + + self + } + + pub fn extend(self, items: impl IntoIterator)>) -> Self { + items + .into_iter() + .fold(self, |row, (key, child)| row.push(key, child)) + } + + pub fn extend_locked( + self, + items: impl IntoIterator)>, + ) -> Self { + items + .into_iter() + .fold(self, |row, (key, child)| row.push_locked(key, child)) + } + + fn item_local_slots_from_layout( + &self, + bounds: Rectangle, + child_layouts: &[Layout<'_>], + ) -> Vec> { + self.keys + .iter() + .zip(self.locked.iter()) + .zip(child_layouts.iter()) + .map(|((key, locked), child_layout)| { + let child_bounds = child_layout.bounds(); + LocalSlot { + key: key.clone(), + locked: *locked, + bounds: Rectangle { + x: child_bounds.x - bounds.x, + y: child_bounds.y - bounds.y, + width: child_bounds.width, + height: child_bounds.height, + }, + } + }) + .collect() + } + + fn sync_slot_positions(&self, state: &mut State, slots: &[LocalSlot]) { + let ordered_keys: Vec = slots.iter().map(|slot| slot.key.clone()).collect(); + state.retain_keys(&ordered_keys); + + let size_by_key: HashMap = slots + .iter() + .map(|slot| { + ( + slot.key.clone(), + Size::new(slot.bounds.width, slot.bounds.height), + ) + }) + .collect(); + let locked_by_key: HashMap = slots + .iter() + .map(|slot| (slot.key.clone(), slot.locked)) + .collect(); + + let Some(drag_snapshot) = state.drag.as_ref().map(|drag| { + ( + drag.key.clone(), + drag.cursor_local, + drag.pointer_offset, + drag.item_index, + ) + }) else { + for slot in slots { + state.apply_layout_position( + &slot.key, + Point::new(slot.bounds.x, slot.bounds.y), + self.animation_duration, + ); + } + return; + }; + + let (drag_key, cursor_local, pointer_offset, drag_item_index) = drag_snapshot; + + let Some(dragged_slot) = slots.iter().find(|slot| slot.key == drag_key) else { + state.drag = None; + for slot in slots { + state.apply_layout_position( + &slot.key, + Point::new(slot.bounds.x, slot.bounds.y), + self.animation_duration, + ); + } + return; + }; + + if dragged_slot.locked { + state.drag = None; + return; + } + + let dragged_center = Point::new( + cursor_local.x - pointer_offset.x + dragged_slot.bounds.width / 2.0, + cursor_local.y - pointer_offset.y + dragged_slot.bounds.height / 2.0, + ); + let target_index = target_index_for_drag(slots, &drag_key, dragged_center); + let prior_index = state.drag.as_ref().map(|drag| drag.current_index); + + if let Some(drag) = state.drag.as_mut() { + drag.current_index = target_index; + drag.item_index = drag_item_index; + } + + if prior_index == Some(target_index) { + return; + } + + let reordered_keys = + reordered_keys_for_drag(&ordered_keys, &locked_by_key, &drag_key, target_index); + let (target_slots, _) = compute_wrapped_slots( + &reordered_keys, + &locked_by_key, + &size_by_key, + state.wrap_width, + self.padding, + self.spacing, + self.align, + ); + let target_positions: HashMap = target_slots + .iter() + .map(|slot| (slot.key.clone(), Point::new(slot.bounds.x, slot.bounds.y))) + .collect(); + + for slot in slots { + let target = target_positions + .get(&slot.key) + .copied() + .unwrap_or(Point::new(slot.bounds.x, slot.bounds.y)); + state.retarget_slot(&slot.key, target, self.animation_duration); + } + } +} + +impl<'a, Key, Message> Widget + for ReorderableFlexRow<'a, Key, Message> +where + Key: Clone + Eq + Hash + 'static, + Message: 'a, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + tree::State::new(State { + keys: self.keys.clone(), + ..State::default() + }) + } + + fn children(&self) -> Vec { + self.children.iter().map(Tree::new).collect() + } + + fn diff(&mut self, tree: &mut Tree) { + let Tree { + state, children, .. + } = tree; + let state = state.downcast_mut::>(); + let previous_keys = state.keys.clone(); + let previous_children = std::mem::take(children); + let mut previous_by_key = HashMap::with_capacity(previous_keys.len()); + + for (key, child_tree) in previous_keys.into_iter().zip(previous_children) { + previous_by_key.insert(key, child_tree); + } + + children.reserve(self.children.len()); + + for (key, child) in self.keys.iter().cloned().zip(self.children.iter_mut()) { + let mut child_tree = previous_by_key + .remove(&key) + .unwrap_or_else(|| Tree::new(child.as_widget())); + child.as_widget_mut().diff(&mut child_tree); + children.push(child_tree); + } + + if state.keys != self.keys { + state.keys.clone_from(&self.keys); + } + state.retain_keys(&self.keys); + } + + fn size(&self) -> Size { + Size { + width: self.width, + height: self.height, + } + } + + fn layout( + &mut self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits + .width(self.width) + .height(self.height) + .shrink(self.padding); + let child_limits = limits.loose(); + let wrap_width = limits.max().width; + + let mut nodes = Vec::with_capacity(self.children.len()); + let mut size_by_key = HashMap::with_capacity(self.children.len()); + let locked_by_key: HashMap = self + .keys + .iter() + .cloned() + .zip(self.locked.iter().copied()) + .collect(); + + for ((key, child), child_tree) in self + .keys + .iter() + .zip(self.children.iter_mut()) + .zip(tree.children.iter_mut()) + { + let node = child + .as_widget_mut() + .layout(child_tree, renderer, &child_limits); + size_by_key.insert(key.clone(), node.size()); + nodes.push(node); + } + + let (slots, intrinsic_size) = compute_wrapped_slots( + &self.keys, + &locked_by_key, + &size_by_key, + wrap_width, + self.padding, + self.spacing, + self.align, + ); + + for (node, slot) in nodes.iter_mut().zip(&slots) { + node.move_to_mut(Point::new(slot.bounds.x, slot.bounds.y)); + } + + let size = limits.resolve(self.width, self.height, intrinsic_size); + let node = layout::Node::with_children(size.expand(self.padding), nodes); + let state = tree.state.downcast_mut::>(); + state.wrap_width = wrap_width; + self.sync_slot_positions(state, &slots); + + node + } + + fn operate( + &mut self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn Operation, + ) { + operation.container(None, layout.bounds()); + operation.traverse(&mut |operation| { + self.children + .iter_mut() + .zip(&mut tree.children) + .zip(layout.children()) + .for_each(|((child, state), child_layout)| { + child.as_widget_mut().operate( + state, + child_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + operation, + ); + }); + }); + } + + fn update( + &mut self, + tree: &mut Tree, + event: &Event, + layout: Layout<'_>, + cursor: Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + let state = tree.state.downcast_mut::>(); + let child_layouts: Vec<_> = layout.children().collect(); + + if let Event::Window(window::Event::RedrawRequested(_)) = event { + state.initialized = true; + state.finish_animations(self.animation_duration); + if state.drag.is_some() || state.is_animating(self.animation_duration) { + shell.request_redraw(); + } + } + + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { + if state.drag.is_none() + && state.pending_drag.is_none() + && let Some(cursor_pos) = cursor.position() + && let Some((index, child_layout)) = child_layouts + .iter() + .enumerate() + .find(|(_, child_layout)| child_layout.bounds().contains(cursor_pos)) + && !self.locked.get(index).copied().unwrap_or(false) + && let Some(reorder_index) = reorderable_index_for_child(&self.locked, index) + { + let child_bounds = child_layout.bounds(); + state.pending_drag = Some(PendingDragState { + key: self.keys[index].clone(), + item_index: index, + original_index: reorder_index, + press_local: Point::new(cursor_pos.x - bounds.x, cursor_pos.y - bounds.y), + pointer_offset: Vector::new( + cursor_pos.x - child_bounds.x, + cursor_pos.y - child_bounds.y, + ), + }); + } + } + Event::Mouse(mouse::Event::CursorMoved { .. }) => { + if let Some(pending) = state.pending_drag.clone() + && let Some(cursor_pos) = cursor.position() + { + let cursor_local = Point::new(cursor_pos.x - bounds.x, cursor_pos.y - bounds.y); + let delta = Vector::new( + cursor_local.x - pending.press_local.x, + cursor_local.y - pending.press_local.y, + ); + let distance = (delta.x.powi(2) + delta.y.powi(2)).sqrt(); + + if distance >= DEFAULT_DRAG_THRESHOLD { + if let (Some(child), Some(child_tree), Some(child_layout)) = ( + self.children.get_mut(pending.item_index), + tree.children.get_mut(pending.item_index), + child_layouts.get(pending.item_index), + ) { + let cursor_left = Event::Mouse(mouse::Event::CursorLeft); + child.as_widget_mut().update( + child_tree, + &cursor_left, + child_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } + + state.pending_drag = None; + state.drag = Some(DragState { + key: pending.key, + item_index: pending.item_index, + original_index: pending.original_index, + current_index: pending.original_index, + cursor_local, + pointer_offset: pending.pointer_offset, + }); + let slots = self.item_local_slots_from_layout(bounds, &child_layouts); + self.sync_slot_positions(state, &slots); + shell.capture_event(); + shell.request_redraw(); + return; + } + } + + if let Some(drag) = state.drag.as_mut() + && let Some(cursor_pos) = cursor.position() + { + drag.cursor_local = + Point::new(cursor_pos.x - bounds.x, cursor_pos.y - bounds.y); + let slots = self.item_local_slots_from_layout(bounds, &child_layouts); + self.sync_slot_positions(state, &slots); + shell.capture_event(); + shell.request_redraw(); + return; + } + } + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { + state.pending_drag = None; + + if state.drag.is_some() { + let slots = self.item_local_slots_from_layout(bounds, &child_layouts); + self.sync_slot_positions(state, &slots); + } + if let Some(drag) = state.drag.take() { + if drag.current_index != drag.original_index { + let locked_by_key: HashMap = self + .keys + .iter() + .cloned() + .zip(self.locked.iter().copied()) + .collect(); + let new_order = reordered_keys_for_drag( + &self.keys, + &locked_by_key, + &drag.key, + drag.current_index, + ); + shell.publish((self.on_reorder)(new_order)); + } + shell.capture_event(); + shell.request_redraw(); + return; + } + } + _ => {} + } + + if state.drag.is_some() { + return; + } + + for ((item, tree), child_layout) in self + .children + .iter_mut() + .zip(&mut tree.children) + .zip(child_layouts.into_iter()) + { + item.as_widget_mut().update( + tree, + event, + child_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + let state = tree.state.downcast_ref::>(); + + if state.drag.is_some() { + return mouse::Interaction::Grabbing; + } + + if let Some(cursor_pos) = cursor.position() + && self + .locked + .iter() + .zip(layout.children()) + .any(|(locked, child_layout)| { + !*locked && child_layout.bounds().contains(cursor_pos) + }) + { + return mouse::Interaction::Grab; + } + + self.children + .iter() + .zip(&tree.children) + .zip(layout.children()) + .map(|((child, tree), child_layout)| { + child.as_widget().mouse_interaction( + tree, + child_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, + renderer, + ) + }) + .max() + .unwrap_or_default() + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &crate::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: Cursor, + viewport: &Rectangle, + ) { + let state = tree.state.downcast_ref::>(); + let bounds = layout.bounds(); + + let Some(clipped_viewport) = bounds.intersection(viewport) else { + return; + }; + + let viewport = if self.clip { + &clipped_viewport + } else { + viewport + }; + let drag_key = state.drag.as_ref().map(|drag| &drag.key); + let drag_item = state.drag.as_ref().and_then(|drag| { + self.keys + .iter() + .zip(&self.children) + .zip(&tree.children) + .zip(layout.children()) + .find_map(|(((key, child), state), child_layout)| { + (key == &drag.key).then_some((key, child, state, child_layout, drag)) + }) + }); + + for (((key, child), child_tree), child_layout) in self + .keys + .iter() + .zip(&self.children) + .zip(&tree.children) + .zip(layout.children()) + { + if drag_key.is_some_and(|drag_key| drag_key == key) { + continue; + } + + let child_layout = child_layout.with_virtual_offset(layout.virtual_offset()); + let child_bounds = child_layout.bounds(); + let base_local = Point::new(child_bounds.x - bounds.x, child_bounds.y - bounds.y); + let target_local = + state.current_slot_position(key, base_local, self.animation_duration); + let translation = + Vector::new(target_local.x - base_local.x, target_local.y - base_local.y); + let translated_bounds = translate_rect(child_bounds, translation); + + if translated_bounds.intersects(viewport) { + renderer.with_translation(translation, |renderer| { + child.as_widget().draw( + child_tree, + renderer, + theme, + style, + child_layout, + cursor, + viewport, + ); + }); + } + } + + if let Some((_key, child, child_tree, child_layout, drag)) = drag_item { + let child_layout = child_layout.with_virtual_offset(layout.virtual_offset()); + let child_bounds = child_layout.bounds(); + let base_local = Point::new(child_bounds.x - bounds.x, child_bounds.y - bounds.y); + let drag_local = Point::new( + drag.cursor_local.x - drag.pointer_offset.x, + drag.cursor_local.y - drag.pointer_offset.y - self.drag_lift, + ); + let translation = Vector::new(drag_local.x - base_local.x, drag_local.y - base_local.y); + + #[cfg(feature = "wgpu")] + { + let translated_bounds = translate_rect(child_bounds, translation); + draw_drag_backdrop(renderer, theme, translated_bounds); + } + + renderer.with_translation(translation, |renderer| { + child.as_widget().draw( + child_tree, + renderer, + theme, + style, + child_layout, + cursor, + viewport, + ); + }); + } + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'b>, + renderer: &Renderer, + viewport: &Rectangle, + translation: Vector, + ) -> Option> { + overlay::from_children( + &mut self.children, + tree, + layout, + renderer, + viewport, + translation, + ) + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, + ) { + for ((item, child_layout), child_state) in self + .children + .iter() + .zip(layout.children()) + .zip(state.children.iter()) + { + item.as_widget().drag_destinations( + child_state, + child_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + dnd_rectangles, + ); + } + } +} + +impl<'a, Key, Message> From> for Element<'a, Message> +where + Key: Clone + Eq + Hash + 'static, + Message: 'a, +{ + fn from(row: ReorderableFlexRow<'a, Key, Message>) -> Self { + Element::new(row) + } +} + +/// Create a horizontal flex row with drag-to-reorder behavior. +pub fn reorderable_flex_row<'a, Key, Message>( + on_reorder: impl Fn(Vec) -> Message + 'a, +) -> ReorderableFlexRow<'a, Key, Message> +where + Key: Clone + Eq + Hash + 'static, +{ + ReorderableFlexRow::new(on_reorder) +} + +fn compute_wrapped_slots( + ordered_keys: &[Key], + locked_by_key: &HashMap, + size_by_key: &HashMap, + wrap_width: f32, + padding: Padding, + spacing: f32, + align: Alignment, +) -> (Vec>, Size) +where + Key: Clone + Eq + Hash + 'static, +{ + let wrap_width = if wrap_width.is_finite() { + wrap_width.max(0.0) + } else { + f32::INFINITY + }; + + let mut slots = Vec::with_capacity(ordered_keys.len()); + let mut intrinsic_size = Size::ZERO; + let mut row_start = 0; + let mut row_height = 0.0; + let mut x = 0.0; + let mut y = 0.0; + + let align_factor = match align { + Alignment::Start => 0.0, + Alignment::Center => 2.0, + Alignment::End => 1.0, + }; + + let align_row = + |range: std::ops::Range, row_height: f32, slots: &mut [LocalSlot]| { + if align_factor == 0.0 { + return; + } + + for slot in &mut slots[range] { + slot.bounds.y += (row_height - slot.bounds.height) / align_factor; + } + }; + + for (index, key) in ordered_keys.iter().enumerate() { + let size = size_by_key.get(key).copied().unwrap_or(Size::ZERO); + + if x != 0.0 && x + size.width > wrap_width { + intrinsic_size.width = intrinsic_size.width.max(x - spacing); + align_row(row_start..index, row_height, &mut slots); + y += row_height + spacing; + x = 0.0; + row_start = index; + row_height = 0.0; + } + + row_height = row_height.max(size.height); + + slots.push(LocalSlot { + key: key.clone(), + locked: locked_by_key.get(key).copied().unwrap_or(false), + bounds: Rectangle { + x: x + padding.left, + y: y + padding.top, + width: size.width, + height: size.height, + }, + }); + + x += size.width + spacing; + } + + if x != 0.0 { + intrinsic_size.width = intrinsic_size.width.max(x - spacing); + } + + intrinsic_size.height = y + row_height; + align_row(row_start..slots.len(), row_height, &mut slots); + + (slots, intrinsic_size) +} + +fn reordered_keys_for_drag( + ordered_keys: &[Key], + locked_by_key: &HashMap, + dragged_key: &Key, + target_index: usize, +) -> Vec +where + Key: Clone + Eq + Hash + 'static, +{ + let movable_keys: Vec = ordered_keys + .iter() + .filter(|key| !locked_by_key.get(*key).copied().unwrap_or(false)) + .cloned() + .collect(); + let mut remaining: Vec = movable_keys + .iter() + .filter(|key| *key != dragged_key) + .cloned() + .collect(); + + remaining.insert(target_index.min(remaining.len()), dragged_key.clone()); + merge_locked_and_reordered_keys(ordered_keys, locked_by_key, &remaining) +} + +/// Picks insertion index among movable items using row-aware midpoint rule. +/// +/// Walks laid-out slots in reading order, counting how many non-dragged movable +/// items the cursor has moved past. Skips locked slots. O(n) single pass, no +/// allocations. +fn target_index_for_drag( + slots: &[LocalSlot], + dragged_key: &Key, + dragged_center: Point, +) -> usize +where + Key: Clone + Eq + Hash + 'static, +{ + let mut target = 0; + let mut passed_movable: usize = 0; + let mut found_target = false; + + let mut i = 0; + while i < slots.len() { + let slot = &slots[i]; + + if slot.locked || &slot.key == dragged_key { + i += 1; + continue; + } + + let row_top = slot.bounds.y; + let row_bottom = row_top + slot.bounds.height; + + if !found_target && dragged_center.y < row_top { + target = passed_movable; + found_target = true; + break; + } + + if dragged_center.y > row_bottom { + passed_movable += 1; + i += 1; + continue; + } + + let center_x = slot.bounds.x + slot.bounds.width / 2.0; + if dragged_center.x < center_x { + target = passed_movable; + found_target = true; + break; + } + + passed_movable += 1; + i += 1; + } + + if !found_target { + target = passed_movable; + } + + target +} + +fn reorderable_index_for_child(locked: &[bool], item_index: usize) -> Option { + (!locked.get(item_index).copied().unwrap_or(false)).then(|| { + locked[..item_index] + .iter() + .filter(|is_locked| !**is_locked) + .count() + }) +} + +fn merge_locked_and_reordered_keys( + ordered_keys: &[Key], + locked_by_key: &HashMap, + reordered_movable_keys: &[Key], +) -> Vec +where + Key: Clone + Eq + Hash + 'static, +{ + let mut movable = reordered_movable_keys.iter(); + + ordered_keys + .iter() + .map(|key| { + if locked_by_key.get(key).copied().unwrap_or(false) { + key.clone() + } else { + movable.next().cloned().unwrap_or_else(|| key.clone()) + } + }) + .collect() +} + +fn approx_eq_point(a: Point, b: Point) -> bool { + (a.x - b.x).abs() <= POSITION_EPSILON && (a.y - b.y).abs() <= POSITION_EPSILON +} + +fn translate_rect(bounds: Rectangle, translation: Vector) -> Rectangle { + Rectangle { + x: bounds.x + translation.x, + y: bounds.y + translation.y, + width: bounds.width, + height: bounds.height, + } +} + +#[cfg(feature = "wgpu")] +fn draw_drag_backdrop(renderer: &mut Renderer, theme: &crate::Theme, bounds: Rectangle) { + let cosmic = theme.cosmic(); + let backdrop = iced::Color { + a: 0.08, + ..iced::Color::from(cosmic.bg_component_color()) + }; + + renderer.fill_quad( + renderer::Quad { + bounds, + border: Border { + radius: cosmic.corner_radii.radius_m.into(), + ..Border::default() + }, + shadow: Shadow { + color: cosmic.shade.into(), + offset: Vector::new(0.0, 8.0), + blur_radius: SHADOW_BLUR_RADIUS, + }, + snap: true, + }, + Background::Color(backdrop), + ); +} + +#[cfg(test)] +mod tests { + use super::{compute_wrapped_slots, reordered_keys_for_drag, target_index_for_drag}; + use iced::{Alignment, Padding, Point, Size}; + use std::collections::HashMap; + + fn size_map(keys: &[usize], width: f32, height: f32) -> HashMap { + keys.iter() + .copied() + .map(|key| (key, Size::new(width, height))) + .collect() + } + + fn locked_map(keys: &[usize], locked_keys: &[usize]) -> HashMap { + keys.iter() + .copied() + .map(|key| (key, locked_keys.contains(&key))) + .collect() + } + + #[test] + fn compute_wrapped_slots_creates_new_rows() { + let ordered_keys = vec![0, 1, 2]; + let locked_by_key = locked_map(&ordered_keys, &[]); + let size_by_key = size_map(&ordered_keys, 100.0, 40.0); + let (slots, intrinsic_size) = compute_wrapped_slots( + &ordered_keys, + &locked_by_key, + &size_by_key, + 220.0, + Padding::ZERO, + 10.0, + Alignment::Start, + ); + + assert_eq!(slots[0].bounds.x, 0.0); + assert_eq!(slots[0].bounds.y, 0.0); + assert_eq!(slots[1].bounds.x, 110.0); + assert_eq!(slots[1].bounds.y, 0.0); + assert_eq!(slots[2].bounds.x, 0.0); + assert_eq!(slots[2].bounds.y, 50.0); + assert_eq!(intrinsic_size.width, 210.0); + assert_eq!(intrinsic_size.height, 90.0); + } + + #[test] + fn reordered_keys_for_drag_inserts_key_at_target_index() { + let keys = [0, 1, 2, 3]; + let locked_by_key = locked_map(&keys, &[]); + let reordered = reordered_keys_for_drag(&keys, &locked_by_key, &0, 3); + assert_eq!(reordered, vec![1, 2, 3, 0]); + } + + #[test] + fn target_index_tracks_wrapped_drop_positions() { + let ordered_keys = vec![0, 1, 2, 3]; + let locked_by_key = locked_map(&ordered_keys, &[]); + let size_by_key = size_map(&ordered_keys, 100.0, 40.0); + + let (slots, _) = compute_wrapped_slots( + &ordered_keys, + &locked_by_key, + &size_by_key, + 220.0, + Padding::ZERO, + 10.0, + Alignment::Start, + ); + + let target_index = target_index_for_drag(&slots, &0, Point::new(160.0, 70.0)); + + assert_eq!(target_index, 3); + } + + #[test] + fn reordered_keys_for_drag_preserves_locked_positions() { + let keys = [10, 11, 12, 13]; + let locked_by_key = locked_map(&keys, &[10, 13]); + let reordered = reordered_keys_for_drag(&keys, &locked_by_key, &11, 1); + + assert_eq!(reordered, vec![10, 12, 11, 13]); + } +} From 9b2a3643f3c3ef50c1bc2abeb15572805b9c51f2 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Tue, 5 May 2026 08:27:18 +0200 Subject: [PATCH 35/75] Update iced warning fixes Point the iced submodule at the commit that fixes high-signal widget and Wayland warnings. Leaves existing cosmic-theme local changes untouched. --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index f0c0b8d4..bb911555 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit f0c0b8d42ecdc52ff6d8b155225051bf6e009f5b +Subproject commit bb9115558d2649c3bced42d340648b8dbceb1fa2 From 5117ce4799b7a3199ef3ef0010ba6949af28e3a3 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Tue, 5 May 2026 09:29:47 +0200 Subject: [PATCH 36/75] feat(cosmic-theme): add apply_gtk_decoration_layout helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Writes the gtk-decoration-layout key to ~/.config/gtk-{3,4}.0/settings.ini and best-effort updates GNOME's button-layout GSettings key for apps that still consult it. Factors out write_gtk_settings_key for reuse. Leyoda 2026 – GPLv3 --- cosmic-theme/src/output/gtk4_output.rs | 60 ++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/cosmic-theme/src/output/gtk4_output.rs b/cosmic-theme/src/output/gtk4_output.rs index 40eba5b4..bbb4f246 100644 --- a/cosmic-theme/src/output/gtk4_output.rs +++ b/cosmic-theme/src/output/gtk4_output.rs @@ -1,10 +1,12 @@ use crate::{Component, Theme, composite::over, steps::steps}; +use configparser::ini::Ini; use palette::{Darken, IntoColor, Lighten, Srgba, WithAlpha, rgb::Rgba}; use std::{ fs::{self, File}, io::{self, Write}, num::NonZeroUsize, path::Path, + process::Command, }; use super::{OutputError, to_rgba}; @@ -217,6 +219,50 @@ impl Theme { Ok(()) } + /// Apply the preferred GTK client-side decoration button layout. + /// + /// This writes the GTK 3/4 `settings.ini` value used by GTK header bars and + /// also best-effort updates GNOME's `button-layout` GSettings key for apps + /// that still consult it. + /// + /// # Errors + /// + /// Returns an `OutputError` if the GTK settings files cannot be written. + #[cold] + pub fn apply_gtk_decoration_layout(buttons_at_start: bool) -> Result<(), OutputError> { + let Some(config_dir) = dirs::config_dir() else { + return Err(OutputError::MissingConfigDir); + }; + + let layout = if buttons_at_start { + "close,minimize,maximize:" + } else { + ":minimize,maximize,close" + }; + + for gtk_version in ["gtk-3.0", "gtk-4.0"] { + let gtk_dir = config_dir.join(gtk_version); + fs::create_dir_all(>k_dir).map_err(OutputError::Io)?; + Self::write_gtk_settings_key( + >k_dir.join("settings.ini"), + "gtk-decoration-layout", + layout, + )?; + } + + // best-effort: gsettings is absent on non-GNOME systems + let _ = Command::new("gsettings") + .args([ + "set", + "org.gnome.desktop.wm.preferences", + "button-layout", + layout, + ]) + .status(); + + Ok(()) + } + /// Reset the applied gtk css /// /// # Errors @@ -256,6 +302,20 @@ impl Theme { Ok(()) } + #[cold] + fn write_gtk_settings_key(path: &Path, key: &str, value: &str) -> Result<(), OutputError> { + let mut ini = Ini::new_cs(); + + if path.exists() { + let file_content = fs::read_to_string(path).map_err(OutputError::Io)?; + ini.read(file_content).map_err(OutputError::Ini)?; + } + + ini.setstr("Settings", key, Some(value)); + ini.pretty_write(path, &super::qt_settings_ini_style()) + .map_err(OutputError::Io) + } + fn is_cosmic_css(path: &Path, cosmic_css: &Path) -> io::Result> { if !path.exists() { return Ok(None); From 282813c80efa3d26cb89fc275a0356bf3d8b29d5 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Tue, 5 May 2026 12:52:52 +0200 Subject: [PATCH 37/75] =?UTF-8?q?yoda:=20bump=20iced=20=E2=86=92=20window?= =?UTF-8?q?=5Fclipboard=20via=20public=20Forgejo=20fork?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lets libcosmic build standalone again — no longer relies on cosmic-files' [patch] redirect to /home/lionel/Devels/window_clipboard. Leyoda 2026 – GPLv3 --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index bb911555..f388dfdf 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit bb9115558d2649c3bced42d340648b8dbceb1fa2 +Subproject commit f388dfdfe47c2aa0c71bc89dae735993973c2ffb From cdf349385bd24f9d55e2281de5968c3054e17078 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Tue, 5 May 2026 16:27:29 +0200 Subject: [PATCH 38/75] =?UTF-8?q?yoda:=20cargo=20fix=20--lib=20(libcosmic-?= =?UTF-8?q?yoda)=20=E2=80=94=20drop=2099=20trivial=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auto-applied suggestions on libcosmic-yoda lib only: unused imports, unused-by-name params (prefixed with _), redundant mutability. From 113 warnings down to 14 (the 14 remaining are real signals: never-read fields, unreachable patterns, etc., to be reviewed manually). Leyoda 2026 – GPLv3 --- src/app/cosmic.rs | 4 ++-- src/app/mod.rs | 2 +- src/ext.rs | 1 - src/scroll.rs | 3 +-- src/surface/mod.rs | 1 - src/theme/mod.rs | 2 -- src/theme/style/iced.rs | 32 ++++++++++++++-------------- src/theme/style/segmented_button.rs | 4 ++-- src/widget/button/widget.rs | 8 +++---- src/widget/cards.rs | 2 +- src/widget/color_picker/mod.rs | 2 +- src/widget/context_menu.rs | 4 ++-- src/widget/dnd_source.rs | 3 +-- src/widget/dropdown/menu/mod.rs | 11 +++++----- src/widget/dropdown/multi/menu.rs | 2 +- src/widget/dropdown/multi/widget.rs | 7 +++--- src/widget/dropdown/operation.rs | 3 --- src/widget/dropdown/widget.rs | 15 ++++++------- src/widget/flex_row/widget.rs | 2 +- src/widget/grid/widget.rs | 2 +- src/widget/icon/bundle.rs | 2 +- src/widget/id_container.rs | 2 +- src/widget/layer_container.rs | 2 +- src/widget/menu/menu_bar.rs | 5 ++--- src/widget/menu/menu_inner.rs | 19 +++++++---------- src/widget/mod.rs | 2 -- src/widget/popover.rs | 2 +- src/widget/radio.rs | 2 +- src/widget/rectangle_tracker/mod.rs | 2 +- src/widget/responsive_container.rs | 2 +- src/widget/text_input/input.rs | 18 ++++++++-------- src/widget/toaster/widget.rs | 4 ++-- src/widget/toggler.rs | 11 +++++----- src/widget/wayland/tooltip/widget.rs | 4 ++-- src/widget/wrapper.rs | 2 +- 35 files changed, 85 insertions(+), 104 deletions(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 030ed041..a6266b85 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -408,7 +408,7 @@ where f64::from(self.app.core().scale_factor()) } - pub fn style(&self, theme: &Theme) -> theme::Style { + pub fn style(&self, _theme: &Theme) -> theme::Style { if let Some(style) = self.app.style() { style } else if self.app.core().window.is_maximized { @@ -621,7 +621,7 @@ impl Cosmic { #[allow(clippy::too_many_lines)] fn cosmic_update(&mut self, message: Action) -> iced::Task> { match message { - Action::WindowMaximized(id, maximized) => { + Action::WindowMaximized(_id, _maximized) => { #[cfg(not(all(feature = "wayland", target_os = "linux")))] if self .app diff --git a/src/app/mod.rs b/src/app/mod.rs index f78beac7..a4013205 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -136,7 +136,7 @@ pub fn run(settings: Settings, flags: App::Flags) -> iced::Res crate::malloc::limit_mmap_threshold(threshold); } - let default_font = settings.default_font; + let _default_font = settings.default_font; let (settings, (mut core, flags), window_settings) = iced_settings::(settings, flags); #[cfg(not(feature = "multi-window"))] { diff --git a/src/ext.rs b/src/ext.rs index 8eb749e5..65ca0145 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MPL-2.0 use iced::Color; -use iced_core::Widget; pub trait ElementExt { #[must_use] diff --git a/src/scroll.rs b/src/scroll.rs index b6d42378..67947399 100644 --- a/src/scroll.rs +++ b/src/scroll.rs @@ -1,4 +1,3 @@ -use iced::Task; use iced::mouse::ScrollDelta; use std::time::{Duration, Instant}; @@ -95,7 +94,7 @@ impl Scroll { } else { // Return integer part of scroll, and keep remainder self.scroll = Some((scroll.fract(), Instant::now())); - let mut discrete = scroll.trunc() as isize; + let discrete = scroll.trunc() as isize; if discrete != 0 { self.last_discrete = Some(Instant::now()); } diff --git a/src/surface/mod.rs b/src/surface/mod.rs index 0dad6459..513cb91f 100644 --- a/src/surface/mod.rs +++ b/src/surface/mod.rs @@ -6,7 +6,6 @@ pub mod action; use iced::Limits; use iced::Size; use iced::Task; -use std::future::Future; use std::sync::Arc; /// Ignore this message in your application. It will be intercepted. diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 093bac05..cf3beccb 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -8,12 +8,10 @@ pub mod portal; pub mod style; use cosmic_config::CosmicConfigEntry; -use cosmic_config::config_subscription; use cosmic_theme::Component; use cosmic_theme::LayeredTheme; use cosmic_theme::Spacing; use cosmic_theme::ThemeMode; -use iced_futures::Subscription; use iced_runtime::{Appearance, DefaultStyle}; use std::sync::{Arc, LazyLock, Mutex}; pub use style::*; diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index aa6f4b33..cb83ec9d 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -792,7 +792,7 @@ impl menu::Catalog for Theme { fn default<'a>() -> ::Class<'a> {} - fn style(&self, class: &::Class<'_>) -> menu::Style { + fn style(&self, _class: &::Class<'_>) -> menu::Style { let cosmic = self.cosmic(); menu::Style { @@ -816,7 +816,7 @@ impl pick_list::Catalog for Theme { fn style( &self, - class: &::Class<'_>, + _class: &::Class<'_>, status: pick_list::Status, ) -> pick_list::Style { let cosmic = &self.cosmic(); @@ -857,7 +857,7 @@ impl radio::Catalog for Theme { fn default<'a>() -> Self::Class<'a> {} - fn style(&self, class: &Self::Class<'_>, status: radio::Status) -> radio::Style { + fn style(&self, _class: &Self::Class<'_>, status: radio::Status) -> radio::Style { let cur_container = self.current_container(); let theme = self.cosmic(); @@ -910,7 +910,7 @@ impl toggler::Catalog for Theme { fn default<'a>() -> Self::Class<'a> {} - fn style(&self, class: &Self::Class<'_>, status: toggler::Status) -> toggler::Style { + fn style(&self, _class: &Self::Class<'_>, status: toggler::Status) -> toggler::Style { let cosmic = self.cosmic(); const HANDLE_MARGIN: f32 = 2.0; let neutral_10 = cosmic.palette.neutral_10.with_alpha(0.1); @@ -938,8 +938,8 @@ impl toggler::Catalog for Theme { padding_ratio: 0.0, }; match status { - toggler::Status::Active { is_toggled } => active, - toggler::Status::Hovered { is_toggled } => { + toggler::Status::Active { is_toggled: _ } => active, + toggler::Status::Hovered { is_toggled: _ } => { let is_active = matches!(status, toggler::Status::Hovered { is_toggled: true }); toggler::Style { background: if is_active { @@ -958,7 +958,7 @@ impl toggler::Catalog for Theme { ..active } } - toggler::Status::Disabled { is_toggled } => { + toggler::Status::Disabled { is_toggled: _ } => { active.background = active.background.scale_alpha(0.5); active.foreground = active.foreground.scale_alpha(0.5); active @@ -975,7 +975,7 @@ impl pane_grid::Catalog for Theme { fn default<'a>() -> ::Class<'a> {} - fn style(&self, class: &::Class<'_>) -> pane_grid::Style { + fn style(&self, _class: &::Class<'_>) -> pane_grid::Style { let theme = self.cosmic(); pane_grid::Style { @@ -1143,8 +1143,8 @@ impl scrollable::Catalog for Theme { fn style(&self, class: &Self::Class<'_>, status: scrollable::Status) -> scrollable::Style { match status { scrollable::Status::Active { - is_horizontal_scrollbar_disabled, - is_vertical_scrollbar_disabled, + is_horizontal_scrollbar_disabled: _, + is_vertical_scrollbar_disabled: _, } => { let cosmic = self.cosmic(); let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7); @@ -1303,7 +1303,7 @@ impl svg::Catalog for Theme { Svg::default() } - fn style(&self, class: &Self::Class<'_>, status: svg::Status) -> svg::Style { + fn style(&self, class: &Self::Class<'_>, _status: svg::Status) -> svg::Style { #[allow(clippy::match_same_arms)] match class { Svg::Default => svg::Style::default(), @@ -1433,7 +1433,7 @@ impl text_input::Catalog for Theme { }, } } - text_input::Status::Focused { is_hovered } => { + text_input::Status::Focused { is_hovered: _ } => { let bg = self.current_container().small_widget.with_alpha(0.25); match class { @@ -1510,7 +1510,7 @@ impl iced_widget::text_editor::Catalog for Theme { let selection = cosmic.accent.base.into(); let value = cosmic.palette.neutral_9.into(); let placeholder = cosmic.palette.neutral_9.with_alpha(0.7).into(); - let icon: Color = cosmic.background.on.into(); + let _icon: Color = cosmic.background.on.into(); // TODO do we need to add icon color back? match status { @@ -1527,7 +1527,7 @@ impl iced_widget::text_editor::Catalog for Theme { value, selection, }, - iced_widget::text_editor::Status::Focused { is_hovered } => { + iced_widget::text_editor::Status::Focused { is_hovered: _ } => { iced_widget::text_editor::Style { background: iced::Color::from(cosmic.bg_color()).into(), border: Border { @@ -1630,8 +1630,8 @@ impl Base for Theme { crate::theme::ThemeType::Light => "Cosmic Light Theme", crate::theme::ThemeType::HighContrastDark => "Cosmic High Contrast Dark Theme", crate::theme::ThemeType::HighContrastLight => "Cosmic High Contrast Light Theme", - crate::theme::ThemeType::Custom(theme) => "Custom Cosmic Theme", - crate::theme::ThemeType::System { prefer_dark, theme } => &theme.name, + crate::theme::ThemeType::Custom(_theme) => "Custom Cosmic Theme", + crate::theme::ThemeType::System { prefer_dark: _, theme } => &theme.name, } } } diff --git a/src/theme/style/segmented_button.rs b/src/theme/style/segmented_button.rs index b9863c88..de8b5782 100644 --- a/src/theme/style/segmented_button.rs +++ b/src/theme/style/segmented_button.rs @@ -181,7 +181,7 @@ mod horizontal { pub fn selection_active( cosmic: &cosmic_theme::Theme, - component: &Component, + _component: &Component, ) -> ItemStatusAppearance { let rad_xl = cosmic.corner_radii.radius_xl; let rad_0 = cosmic.corner_radii.radius_0; @@ -275,7 +275,7 @@ mod vertical { pub fn selection_active( cosmic: &cosmic_theme::Theme, - component: &Component, + _component: &Component, ) -> ItemStatusAppearance { let rad_0 = cosmic.corner_radii.radius_0; let rad_xl = cosmic.corner_radii.radius_xl; diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 4acf3f2d..084939f8 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -9,7 +9,7 @@ use iced_runtime::core::widget::Id; use iced_runtime::{Action, Task, keyboard, task}; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::renderer::{self, Quad, Renderer}; use iced_core::touch; use iced_core::widget::Operation; @@ -667,7 +667,7 @@ impl<'a, Message: 'a + Clone> Widget height, } = layout.bounds(); let bounds = Rect::new(x as f64, y as f64, (x + width) as f64, (y + height) as f64); - let is_hovered = state.state.downcast_ref::().is_hovered; + let _is_hovered = state.state.downcast_ref::().is_hovered; let mut node = Node::new(Role::Button); node.add_action(Action::Focus); @@ -827,7 +827,7 @@ pub fn update<'a, Message: Clone>( } } #[cfg(feature = "a11y")] - Event::A11y(event_id, iced_accessibility::accesskit::ActionRequest { action, .. }) => { + Event::A11y(_event_id, iced_accessibility::accesskit::ActionRequest { action, .. }) => { let state = state(); if let Some(on_press) = matches!(action, iced_accessibility::accesskit::Action::Click) .then_some(on_press) @@ -870,7 +870,7 @@ pub fn draw( viewport_bounds: Rectangle, styling: &super::style::Style, draw_contents: impl FnOnce(&mut Renderer, &Style), - is_image: bool, + _is_image: bool, ) where Theme: super::style::Catalog, { diff --git a/src/widget/cards.rs b/src/widget/cards.rs index 66267a73..14191c61 100644 --- a/src/widget/cards.rs +++ b/src/widget/cards.rs @@ -98,7 +98,7 @@ where /// Get an expandable stack of cards #[allow(clippy::too_many_arguments)] pub fn new( - id: widget::Id, + _id: widget::Id, card_inner_elements: Vec>, on_clear_all: Message, on_show_more: Option, diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 85cfc161..70975bc3 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -15,7 +15,7 @@ use crate::theme::{Button, THEME}; use crate::widget::{button::Catalog, container, segmented_button::Entity, slider}; use derive_setters::Setters; use iced::Task; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::gradient::{ColorStop, Linear}; use iced_core::renderer::Quad; use iced_core::widget::{Tree, tree}; diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 5f525992..37fce504 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -88,7 +88,7 @@ impl ContextMenu<'_, Message> { bounds.x = my_state.context_cursor.x; bounds.y = my_state.context_cursor.y; - let (id, root_list) = my_state.menu_bar_state.inner.with_data_mut(|state| { + let (id, _root_list) = my_state.menu_bar_state.inner.with_data_mut(|state| { if let Some(id) = state.popup_id.get(&self.window_id).copied() { // close existing popups state.menu_states.clear(); @@ -146,7 +146,7 @@ impl ContextMenu<'_, Message> { layout.bounds(), -bounds.height, ); - let (anchor_rect, gravity) = my_state.menu_bar_state.inner.with_data_mut(|state| { + let (anchor_rect, _gravity) = my_state.menu_bar_state.inner.with_data_mut(|state| { use iced::Rectangle; state.popup_id.insert(self.window_id, id); diff --git a/src/widget/dnd_source.rs b/src/widget/dnd_source.rs index 980723e3..170d6940 100644 --- a/src/widget/dnd_source.rs +++ b/src/widget/dnd_source.rs @@ -8,8 +8,7 @@ use crate::{ }; use iced::{ Event, Length, Point, Rectangle, Vector, - clipboard::dnd::{DndAction, DndEvent, SourceEvent}, - event, mouse, overlay, + clipboard::dnd::{DndAction, DndEvent, SourceEvent}, mouse, overlay, }; use iced_core::{ self, Clipboard, Shell, layout, renderer, diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index 0c96c1c6..bb3cead1 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -8,9 +8,8 @@ use std::sync::{Arc, Mutex}; pub use appearance::{Appearance, StyleSheet}; -use crate::surface; use crate::widget::{Container, RcWrapper, icon}; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::layout::{self, Layout}; use iced_core::text::{self, Text}; use iced_core::widget::Tree; @@ -391,7 +390,7 @@ impl<'a, Message: Clone + 'a> crate::widget::Widget, - cursor: mouse::Cursor, + _cursor: mouse::Cursor, viewport: &Rectangle, ) { let appearance = theme.appearance(&()); diff --git a/src/widget/dropdown/multi/menu.rs b/src/widget/dropdown/multi/menu.rs index 0a761097..a1da9da7 100644 --- a/src/widget/dropdown/multi/menu.rs +++ b/src/widget/dropdown/multi/menu.rs @@ -2,7 +2,7 @@ use super::Model; pub use crate::widget::dropdown::menu::{Appearance, StyleSheet}; use crate::widget::Container; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::layout::{self, Layout}; use iced_core::text::{self, Text}; use iced_core::widget::Tree; diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index 779c6d00..d72e42a8 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -5,7 +5,7 @@ use super::menu::{self, Menu}; use crate::widget::icon; use derive_setters::Setters; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::text::{self, Paragraph, Text}; use iced_core::widget::tree::{self, Tree}; use iced_core::{ @@ -13,7 +13,6 @@ use iced_core::{ }; use iced_core::{Shadow, alignment, keyboard, layout, mouse, overlay, renderer, svg, touch}; use iced_widget::pick_list; -use std::ffi::OsStr; pub use iced_widget::pick_list::{Catalog, Style}; @@ -253,7 +252,7 @@ impl Default for State { /// Computes the layout of a [`Dropdown`]. #[allow(clippy::too_many_arguments)] pub fn layout( - renderer: &crate::Renderer, + _renderer: &crate::Renderer, limits: &layout::Limits, width: Length, gap: f32, @@ -376,7 +375,7 @@ pub fn mouse_interaction(layout: Layout<'_>, cursor: mouse::Cursor) -> mouse::In #[allow(clippy::too_many_arguments)] pub fn overlay<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static>( layout: Layout<'_>, - renderer: &crate::Renderer, + _renderer: &crate::Renderer, state: &'a mut State, gap: f32, padding: Padding, diff --git a/src/widget/dropdown/operation.rs b/src/widget/dropdown/operation.rs index 1a4e1a9f..4cd266d4 100644 --- a/src/widget/dropdown/operation.rs +++ b/src/widget/dropdown/operation.rs @@ -2,9 +2,6 @@ // SPDX-License-Identifier: MPL-2.0 AND MIT //! Operate on dropdown widgets. -use super::State; -use iced::Rectangle; -use iced_core::widget::{Id, Operation}; pub trait Dropdown { fn close(&mut self); diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 7e7ac7a7..d2c491e7 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -8,7 +8,7 @@ use crate::widget::icon::{self, Handle}; use crate::{Element, surface}; use derive_setters::Setters; use iced::window; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::text::{self, Paragraph, Text}; use iced_core::widget::tree::{self, Tree}; use iced_core::{ @@ -17,7 +17,6 @@ use iced_core::{ use iced_core::{Shadow, alignment, keyboard, layout, mouse, overlay, renderer, svg, touch}; use iced_widget::pick_list::{self, Catalog}; use std::borrow::Cow; -use std::ffi::OsStr; use std::hash::{DefaultHasher, Hash, Hasher}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, LazyLock, Mutex}; @@ -328,10 +327,10 @@ where fn operate( &mut self, - tree: &mut Tree, + _tree: &mut Tree, _layout: Layout<'_>, _renderer: &crate::Renderer, - operation: &mut dyn iced_core::widget::Operation, + _operation: &mut dyn iced_core::widget::Operation, ) { // TODO: double check operation handling // let state = tree.state.downcast_mut::(); @@ -343,7 +342,7 @@ where tree: &'b mut Tree, layout: Layout<'b>, renderer: &crate::Renderer, - viewport: &Rectangle, + _viewport: &Rectangle, translation: Vector, ) -> Option> { #[cfg(all(feature = "wayland", target_os = "linux"))] @@ -452,7 +451,7 @@ impl super::operation::Dropdown for State { /// Computes the layout of a [`Dropdown`]. #[allow(clippy::too_many_arguments)] pub fn layout( - renderer: &crate::Renderer, + _renderer: &crate::Renderer, limits: &layout::Limits, width: Length, gap: f32, @@ -546,7 +545,7 @@ pub fn update< cursor: mouse::Cursor, shell: &mut Shell<'_, Message>, #[cfg(all(feature = "wayland", target_os = "linux"))] - positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, + _positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, on_selected: Arc Message + Send + Sync + 'static>, selected: Option, selections: &[S], @@ -558,7 +557,7 @@ pub fn update< gap: f32, padding: Padding, text_size: Option, - font: Option, + _font: Option, selected_option: Option, ) { let state = state(); diff --git a/src/widget/flex_row/widget.rs b/src/widget/flex_row/widget.rs index 0b2e6e13..e5c0b615 100644 --- a/src/widget/flex_row/widget.rs +++ b/src/widget/flex_row/widget.rs @@ -3,7 +3,7 @@ use crate::{Element, Renderer}; use derive_setters::Setters; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::widget::{Operation, Tree}; use iced_core::{ Clipboard, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, layout, mouse, overlay, diff --git a/src/widget/grid/widget.rs b/src/widget/grid/widget.rs index e59ba90d..55ce3c91 100644 --- a/src/widget/grid/widget.rs +++ b/src/widget/grid/widget.rs @@ -3,7 +3,7 @@ use crate::{Element, Renderer}; use derive_setters::Setters; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::widget::{Operation, Tree}; use iced_core::{ Alignment, Clipboard, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, layout, mouse, diff --git a/src/widget/icon/bundle.rs b/src/widget/icon/bundle.rs index bb6ce244..30a9938a 100644 --- a/src/widget/icon/bundle.rs +++ b/src/widget/icon/bundle.rs @@ -5,7 +5,7 @@ /// Icon bundling is not enabled on unix platforms. #[cfg(all(unix, not(target_os = "macos")))] -pub fn get(icon_name: &str) -> Option { +pub fn get(_icon_name: &str) -> Option { None } diff --git a/src/widget/id_container.rs b/src/widget/id_container.rs index 716ee138..8f280cab 100644 --- a/src/widget/id_container.rs +++ b/src/widget/id_container.rs @@ -1,4 +1,4 @@ -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; diff --git a/src/widget/layer_container.rs b/src/widget/layer_container.rs index 110af518..577261f6 100644 --- a/src/widget/layer_container.rs +++ b/src/widget/layer_container.rs @@ -1,7 +1,7 @@ use crate::Theme; use cosmic_theme::LayeredTheme; use iced::widget::Container; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 02ac693c..88e398c7 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -21,12 +21,11 @@ use crate::{ style::menu_bar::StyleSheet, widget::{ RcWrapper, - dropdown::menu::{self, State}, menu::menu_inner::init_root_menu, }, }; -use iced::{Point, Shadow, Vector, event::Status, window}; +use iced::{Point, Shadow, Vector, window}; use iced_core::Border; use iced_widget::core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, event, @@ -754,7 +753,7 @@ where tree: &'b mut Tree, layout: Layout<'b>, _renderer: &Renderer, - viewport: &Rectangle, + _viewport: &Rectangle, translation: Vector, ) -> Option> { #[cfg(all( diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index a8607d24..a2159845 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -558,10 +558,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> Option<(usize, MenuState)> { - use event::{ - Event::{Mouse, Touch}, - Status::{Captured, Ignored}, - }; + use event::Event::{Mouse, Touch}; use mouse::{ Button::Left, Event::{ButtonPressed, ButtonReleased, CursorMoved, WheelScrolled}, @@ -581,7 +578,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { let viewport_size = viewport.size(); let overlay_offset = Point::ORIGIN - viewport.position(); let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset; - let menu_roots = match &mut self.menu_roots { + let _menu_roots = match &mut self.menu_roots { Cow::Borrowed(_) => panic!(), Cow::Owned(o) => o.as_mut_slice(), }; @@ -951,7 +948,7 @@ impl Widget, cursor: mouse::Cursor, @@ -969,21 +966,21 @@ impl Widget Widget( ) where Message: Clone, { - use event::Status::{Captured, Ignored}; + use mouse::ScrollDelta; menu.tree.inner.with_data_mut(|state| { diff --git a/src/widget/mod.rs b/src/widget/mod.rs index edebe0dd..a5e70788 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -119,8 +119,6 @@ pub mod calendar; pub use calendar::{Calendar, calendar}; pub mod card; -#[doc(inline)] -pub use card::*; pub mod color_picker; #[doc(inline)] diff --git a/src/widget/popover.rs b/src/widget/popover.rs index af5370a8..2c932a75 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -4,7 +4,7 @@ //! A container which displays an overlay when a popup widget is attached. use iced::widget; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; diff --git a/src/widget/radio.rs b/src/widget/radio.rs index c3f115c0..8f0c36f9 100644 --- a/src/widget/radio.rs +++ b/src/widget/radio.rs @@ -1,7 +1,7 @@ //! Create choices using radio buttons. use crate::{Theme, theme}; use iced::border; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; diff --git a/src/widget/rectangle_tracker/mod.rs b/src/widget/rectangle_tracker/mod.rs index b3066ecb..f5969589 100644 --- a/src/widget/rectangle_tracker/mod.rs +++ b/src/widget/rectangle_tracker/mod.rs @@ -5,7 +5,7 @@ use iced::futures::channel::mpsc::UnboundedSender; use iced::widget::Container; pub use subscription::*; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; diff --git a/src/widget/responsive_container.rs b/src/widget/responsive_container.rs index b9b6a289..14b7a030 100644 --- a/src/widget/responsive_container.rs +++ b/src/widget/responsive_container.rs @@ -1,7 +1,7 @@ //! Responsive Container, which will notify of size changes. use iced::{Limits, Size}; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 4336c757..5bddfeed 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -21,12 +21,12 @@ use apply::Apply; use iced::Limits; use iced::clipboard::dnd::{DndAction, DndEvent, OfferEvent, SourceEvent}; use iced::clipboard::mime::AsMimeTypes; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::input_method::{self, InputMethod, Preedit}; use iced_core::mouse::{self, click}; use iced_core::overlay::Group; use iced_core::renderer::{self, Renderer as CoreRenderer}; -use iced_core::text::{self, Affinity, Paragraph, Renderer, Text}; +use iced_core::text::{self, Paragraph, Renderer, Text}; use iced_core::time::{Duration, Instant}; use iced_core::touch; use iced_core::widget::Id; @@ -829,7 +829,7 @@ where &mut self, tree: &mut Tree, layout: Layout<'_>, - renderer: &crate::Renderer, + _renderer: &crate::Renderer, operation: &mut dyn Operation, ) { operation.container(Some(&self.id), layout.bounds()); @@ -921,7 +921,7 @@ where // Enable custom buttons defined on the trailing icon position to be handled. if !self.is_editable_variant { if let Some(trailing_layout) = trailing_icon_layout { - let res = trailing_icon.as_widget_mut().update( + let _res = trailing_icon.as_widget_mut().update( tree, event, trailing_layout, @@ -2220,7 +2220,7 @@ pub fn update<'a, Message: Clone + 'static>( )) if *rectangle == Some(dnd_id) => { cold(); let state = state(); - let is_clicked = text_layout.bounds().contains(Point { + let _is_clicked = text_layout.bounds().contains(Point { x: *x as f32, y: *y as f32, }); @@ -2228,7 +2228,7 @@ pub fn update<'a, Message: Clone + 'static>( let mut accepted = false; for m in mime_types { if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) { - let clone = m.clone(); + let _clone = m.clone(); accepted = true; } } @@ -2291,7 +2291,7 @@ pub fn update<'a, Message: Clone + 'static>( cold(); let state = state(); if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() { - let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES + let Some(_mime_type) = SUPPORTED_TEXT_MIME_TYPES .iter() .find(|&&m| mime_types.iter().any(|t| t == m)) else { @@ -2308,7 +2308,7 @@ pub fn update<'a, Message: Clone + 'static>( Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if Some(dnd_id) != *id => {} #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer( - rectangle, + _rectangle, OfferEvent::Leave | OfferEvent::LeaveDestination, )) => { cold(); @@ -2611,7 +2611,7 @@ pub fn draw<'a, Message>( let handling_dnd_offer = !matches!(state.dnd_offer, DndOfferState::None); #[cfg(not(all(feature = "wayland", target_os = "linux")))] let handling_dnd_offer = false; - let (cursors, offset, is_selecting) = if let Some(focus) = + let (cursors, offset, _is_selecting) = if let Some(focus) = state.is_focused.filter(|f| f.focused).or_else(|| { let now = Instant::now(); handling_dnd_offer.then_some(Focus { diff --git a/src/widget/toaster/widget.rs b/src/widget/toaster/widget.rs index de47a9bd..9b6939e7 100644 --- a/src/widget/toaster/widget.rs +++ b/src/widget/toaster/widget.rs @@ -6,7 +6,7 @@ use iced_core::layout::Node; use iced_core::Element; use iced_core::Overlay; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; @@ -154,7 +154,7 @@ where translation, ) } else { - let bounds = layout.bounds(); + let _bounds = layout.bounds(); Some(overlay::Element::new(Box::new(ToasterOverlay::new( &mut state.children[1], diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index b95b596e..27203c05 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -1,11 +1,10 @@ //! Show toggle controls using togglers. -use std::time::{Duration, Instant}; +use std::time::Duration; use crate::{Element, anim}; use iced_core::{ - Border, Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment, - event, layout, mouse, + Border, Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment, layout, mouse, renderer::{self, Renderer}, text, touch, widget::{self, Tree, tree}, @@ -13,7 +12,7 @@ use iced_core::{ }; use iced_widget::{Id, toggler::Status}; -pub use iced_widget::toggler::{Catalog, Style}; +pub use iced_widget::toggler::Catalog; pub fn toggler<'a, Message>(is_checked: bool) -> Toggler<'a, Message> { Toggler::new(is_checked) @@ -261,7 +260,7 @@ impl<'a, Message> Widget for Toggler<'a, shell.capture_event(); } } - Event::Window(window::Event::RedrawRequested(now)) => { + Event::Window(window::Event::RedrawRequested(_now)) => { state.anim.anim_done(self.duration); if state.anim.last_change.is_some() { shell.request_redraw(); @@ -371,7 +370,7 @@ impl<'a, Message> Widget for Toggler<'a, }, style.background, ); - let mut t = state.anim.t(self.duration, self.is_toggled); + let t = state.anim.t(self.duration, self.is_toggled); let toggler_foreground_bounds = Rectangle { x: bounds.x diff --git a/src/widget/wayland/tooltip/widget.rs b/src/widget/wayland/tooltip/widget.rs index 7bf0991a..0f9f79a5 100644 --- a/src/widget/wayland/tooltip/widget.rs +++ b/src/widget/wayland/tooltip/widget.rs @@ -13,7 +13,7 @@ use std::time::Duration; use iced::Task; use iced_runtime::core::widget::Id; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::renderer; use iced_core::touch; use iced_core::widget::Operation; @@ -310,7 +310,7 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> } let content_layout = layout.children().next().unwrap(); - let state = tree.state.downcast_ref::(); + let _state = tree.state.downcast_ref::(); let styling = theme.style(&self.style); diff --git a/src/widget/wrapper.rs b/src/widget/wrapper.rs index 133f9b87..b27bb9ff 100644 --- a/src/widget/wrapper.rs +++ b/src/widget/wrapper.rs @@ -6,7 +6,7 @@ use std::{ }; use crate::Element; -use iced::{Length, Rectangle, Size, event}; +use iced::{Length, Rectangle, Size}; use iced_core::{Widget, id::Id, widget, widget::tree}; #[derive(Debug)] From 38a988cbd1d735627c7a476e6377f6c22632d4a7 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Tue, 5 May 2026 16:45:49 +0200 Subject: [PATCH 39/75] yoda: cargo fix on cosmic-config + bump iced auto-fix commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cosmic-config: drop 1 unused import (Stream). iced submodule: bump to 8a7a32ff9 (~115 trivial warnings auto-fixed across iced_core / iced_widget / iced_runtime / iced_winit / iced_wgpu / iced_graphics / iced_tiny_skia). Leyoda 2026 – GPLv3 --- cosmic-config/src/subscription.rs | 2 +- iced | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cosmic-config/src/subscription.rs b/cosmic-config/src/subscription.rs index d16b9b65..8246a12d 100644 --- a/cosmic-config/src/subscription.rs +++ b/cosmic-config/src/subscription.rs @@ -1,4 +1,4 @@ -use iced_futures::futures::{SinkExt, Stream}; +use iced_futures::futures::SinkExt; use iced_futures::{futures::channel::mpsc, stream}; use notify::RecommendedWatcher; use std::{borrow::Cow, hash::Hash}; diff --git a/iced b/iced index f388dfdf..8a7a32ff 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit f388dfdfe47c2aa0c71bc89dae735993973c2ffb +Subproject commit 8a7a32ff927c210c7fee07c214834deccc9f8136 From 301bbf6e934ef023912c70a9ec18e93db2e2d726 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Tue, 5 May 2026 18:26:51 +0200 Subject: [PATCH 40/75] =?UTF-8?q?yoda:=20bump=20iced=20submodule=20?= =?UTF-8?q?=E2=86=92=20iced=5Fwinit=20warning=20cleanup=20(0=20left)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iced/yoda-wayland-only e42448770 brings iced_winit down to 0 warnings (was 45 before the cleanup pass). Leyoda 2026 – GPLv3 --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 8a7a32ff..e4244877 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 8a7a32ff927c210c7fee07c214834deccc9f8136 +Subproject commit e424487704912e05a526a213076793074dbfdc0e From b94c03d99684a56609451871e2bbb7815aa96e7b Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Tue, 5 May 2026 18:39:03 +0200 Subject: [PATCH 41/75] =?UTF-8?q?yoda:=20bump=20iced=20submodule=20?= =?UTF-8?q?=E2=86=92=20iced=5Fwidget=20cleanup=20(0=20left)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pulls in the menu wrapping bug fix and slider/lazy warning sweep. Leyoda 2026 – GPLv3 --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index e4244877..d33d068c 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit e424487704912e05a526a213076793074dbfdc0e +Subproject commit d33d068c17e3a16d2444917820682f934faaa299 From a9492d764c529d7302bab7dddec785ee95e95b04 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Tue, 5 May 2026 18:50:41 +0200 Subject: [PATCH 42/75] =?UTF-8?q?yoda:=20bump=20iced=20submodule=20?= =?UTF-8?q?=E2=86=92=20all=20iced=20crates=20at=200=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iced_core, iced_wgpu, iced_winit, iced_widget, iced_runtime, iced_graphics, iced_tiny_skia all now build warning-free. Leyoda 2026 – GPLv3 --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index d33d068c..6b069b2b 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit d33d068c17e3a16d2444917820682f934faaa299 +Subproject commit 6b069b2b4bc252d8fdc4d2a93ff533376c4f0dee From 84437e219be0b1a1513e5d5d3c68d696c05a0844 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Tue, 5 May 2026 19:02:31 +0200 Subject: [PATCH 43/75] =?UTF-8?q?yoda:=20libcosmic-yoda=20dead-code=20purg?= =?UTF-8?q?e=20(14=E2=86=920=20warnings)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Real bug fixes: - spin_button::vertical(): was constructing the SpinButton with Orientation::Horizontal — vertical() rendered horizontally. Fixed to use Orientation::Vertical, which also resolves the 'Vertical never constructed' warning. - text_input/input.rs: rewrite the iterator-skip logic for trailing icon layout. The previous `let mut icon_layout = ...; if has_start ... = ...; ... = ...;` triggered two value-never-read warnings and obscured the intent. Now it reads as 'skip text, optionally skip start-icon, then take the trailing-icon layout'. Dead code removed: - core::Core::portal_is_high_contrast — assigned at construction, never read anywhere - widget::menu::menu_bar::bar_pressed field and the pub fn get_mut_or_default helper — no callers - widget::popover::State — declared, never constructed - widget::segmented_button::widget::Focus.now — sibling of updated_at, never read - widget::wayland::tooltip::widget::Variant — orphan enum (and the cargo fix follow-up dropped a now-unused Element import) - widget::button::widget::Button::is_hovered method — trivial getter with no callers; the underlying `is_hovered` field is still used Visibility / nits: - segmented_button::widget::TabDragSource: gain pub(super) so the pub(super) field tab_drag stops exposing a more private type - widget::menu::flex::resolve: #[allow(dead_code)] (170-line public helper, kept for future use) - app/cosmic.rs: drop one unreachable arm in the surface-message match - widget::dropdown::multi: drop unused Paragraph import Leyoda 2026 – GPLv3 --- src/app/cosmic.rs | 1 - src/core.rs | 3 --- src/widget/button/widget.rs | 6 ------ src/widget/dropdown/multi/widget.rs | 2 +- src/widget/menu/flex.rs | 1 + src/widget/menu/menu_bar.rs | 10 ---------- src/widget/popover.rs | 6 ------ src/widget/segmented_button/widget.rs | 4 +--- src/widget/spin_button.rs | 2 +- src/widget/text_input/input.rs | 6 +++--- src/widget/wayland/tooltip/widget.rs | 10 +--------- 11 files changed, 8 insertions(+), 43 deletions(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index a6266b85..011bb070 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -364,7 +364,6 @@ where crate::surface::Action::Task(f) => { f().map(|sm| crate::Action::Cosmic(Action::Surface(sm))) } - _ => iced::Task::none(), } #[cfg(not(feature = "surface-message"))] diff --git a/src/core.rs b/src/core.rs index 44f3b3df..f80954f9 100644 --- a/src/core.rs +++ b/src/core.rs @@ -78,8 +78,6 @@ pub struct Core { pub(super) portal_accent: Option, - pub(super) portal_is_high_contrast: Option, - pub(super) title: HashMap, pub window: Window, @@ -155,7 +153,6 @@ impl Default for Core { settings_daemon: None, portal_is_dark: None, portal_accent: None, - portal_is_high_contrast: None, main_window: None, exit_on_main_window_closed: true, menu_bars: HashMap::new(), diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 084939f8..a6dda255 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -748,12 +748,6 @@ impl State { self.is_focused } - /// Returns whether the [`Button`] is currently hovered or not. - #[inline] - pub fn is_hovered(self) -> bool { - self.is_hovered - } - /// Focuses the [`Button`]. #[inline] pub fn focus(&mut self) { diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index d72e42a8..6ed7a02a 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -6,7 +6,7 @@ use super::menu::{self, Menu}; use crate::widget::icon; use derive_setters::Setters; use iced_core::event::Event; -use iced_core::text::{self, Paragraph, Text}; +use iced_core::text::{self, Text}; use iced_core::widget::tree::{self, Tree}; use iced_core::{ Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, diff --git a/src/widget/menu/flex.rs b/src/widget/menu/flex.rs index 4a58f13a..045e8407 100644 --- a/src/widget/menu/flex.rs +++ b/src/widget/menu/flex.rs @@ -50,6 +50,7 @@ impl Axis { /// padding and alignment to the items as needed. /// /// It returns a new layout [`Node`]. +#[allow(dead_code)] // kept as public helper; not currently called by libcosmic pub fn resolve<'a, E, Message, Renderer>( axis: &Axis, renderer: &Renderer, diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 88e398c7..c8002c82 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -54,7 +54,6 @@ pub(crate) struct MenuBarStateInner { pub(crate) tree: Tree, pub(crate) popup_id: HashMap, pub(crate) pressed: bool, - pub(crate) bar_pressed: bool, pub(crate) view_cursor: Cursor, pub(crate) open: bool, pub(crate) active_root: Vec, @@ -91,7 +90,6 @@ impl Default for MenuBarStateInner { vertical_direction: Direction::Positive, menu_states: Vec::new(), popup_id: HashMap::new(), - bar_pressed: false, } } } @@ -168,14 +166,6 @@ where } } -pub fn get_mut_or_default(vec: &mut Vec, index: usize) -> &mut T { - if index < vec.len() { - &mut vec[index] - } else { - vec.resize_with(index + 1, T::default); - &mut vec[index] - } -} /// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing. #[allow(missing_debug_implementations)] diff --git a/src/widget/popover.rs b/src/widget/popover.rs index 2c932a75..c807393f 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -476,12 +476,6 @@ where } } -/// The local state of a [`Popover`]. -#[derive(Debug, Default)] -struct State { - is_open: bool, -} - /// The first child in [`Popover::children`] is always the wrapped content. fn content_tree(tree: &Tree) -> &Tree { &tree.children[0] diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 9ddd7d8d..ea39ede9 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -2364,7 +2364,7 @@ where } } -struct TabDragSource { +pub(super) struct TabDragSource { mime: String, threshold: f32, _marker: PhantomData, @@ -2415,7 +2415,6 @@ struct TabDragCandidate { #[derive(Debug, Clone, Copy)] struct Focus { updated_at: Instant, - now: Instant, } /// State that is maintained by each individual widget. @@ -2488,7 +2487,6 @@ impl LocalState { self.focused = Some(Focus { updated_at: now, - now, }); } } diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index 833e90b8..db055519 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -63,7 +63,7 @@ where step, min, max, - Orientation::Horizontal, + Orientation::Vertical, on_press, ); diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 5bddfeed..6227d706 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -2794,11 +2794,11 @@ pub fn draw<'a, Message>( // draw the end icon in the text input if let (Some(icon), Some(tree)) = (trailing_icon, trailing_icon_tree) { let mut children = text_layout.children(); - let mut icon_layout = children.next().unwrap(); + children.next().unwrap(); // skip text layout if has_start_icon { - icon_layout = children.next().unwrap(); + children.next().unwrap(); // skip start-icon layout } - icon_layout = children.next().unwrap(); + let icon_layout = children.next().unwrap(); // trailing-icon layout icon.as_widget().draw( tree, diff --git a/src/widget/wayland/tooltip/widget.rs b/src/widget/wayland/tooltip/widget.rs index 0f9f79a5..7bfd1115 100644 --- a/src/widget/wayland/tooltip/widget.rs +++ b/src/widget/wayland/tooltip/widget.rs @@ -23,18 +23,10 @@ use iced_core::{ }; use iced_core::{Border, mouse}; use iced_core::{Shadow, overlay}; -use iced_core::{layout, svg}; +use iced_core::layout; pub use super::{Catalog, Style}; -/// Internally defines different button widget variants. -enum Variant { - Normal, - Image { - close_icon: svg::Handle, - on_remove: Option, - }, -} /// A generic button which emits a message when pressed. #[allow(missing_debug_implementations)] From 999db0a4bd2eed7cbc75624a0599c7c45624969f Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Tue, 5 May 2026 19:07:35 +0200 Subject: [PATCH 44/75] =?UTF-8?q?yoda:=20cosmic-theme=20cleanup=20(4?= =?UTF-8?q?=E2=86=920=20warnings)=20=E2=80=94=20workspace=20at=200=20warni?= =?UTF-8?q?ngs=20total?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit theme::Builder::build: - #[allow(unused_assignments)] with a note explaining the pattern: the component_hovered/pressed_overlay seeds at the top of the function are overwritten by every container block (primary, secondary, …) before being read, which is what unused_assignments flagged. vs_code.rs: - Add doc + # Errors section to Theme::apply_vs_code and the associated Theme::reset_vs_code (was missing under #![warn(missing_docs)]). Cargo.toml profile move: - cosmic-theme had a [profile.dev.package] block that Cargo silently ignores in non-root manifests. Move the insta/similar opt-level=3 hint to the workspace root and drop the dead block from the cosmic-theme manifest. Removes the cargo-level "profiles for the non root package will be ignored" notice that came up on every check. Leyoda 2026 – GPLv3 --- Cargo.toml | 8 ++++++++ cosmic-theme/Cargo.toml | 3 --- cosmic-theme/src/model/theme.rs | 6 ++++++ cosmic-theme/src/output/vs_code.rs | 15 +++++++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2f38df6b..85847c4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -264,5 +264,13 @@ exclude = ["iced", "examples"] [workspace.dependencies] dirs = "6.0.0" +# Speed up snapshot diffing in cosmic-theme tests. Cargo silently ignores +# [profile.*] blocks in non-root manifests, so this lives at the +# workspace root. +[profile.dev.package.insta] +opt-level = 3 +[profile.dev.package.similar] +opt-level = 3 + [dev-dependencies] tempfile = "3.27.0" diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index 7e408d8d..9ee641e8 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -34,6 +34,3 @@ thiserror = "2.0.18" [dev-dependencies] insta = "1.47.2" -[profile.dev.package] -insta.opt-level = 3 -similar.opt-level = 3 diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 5db0f32c..36480f9f 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -953,6 +953,12 @@ impl ThemeBuilder { } #[allow(clippy::too_many_lines)] + // The component_hovered/pressed_overlay vars are seeded once near the + // top of this fn and then reassigned inside each container block + // (primary, secondary, …) before being read again. The initial seed + // is therefore overwritten before any read, which is what the + // unused_assignments lint flags below. + #[allow(unused_assignments)] /// build the theme pub fn build(self) -> Theme { let Self { diff --git a/cosmic-theme/src/output/vs_code.rs b/cosmic-theme/src/output/vs_code.rs index 43c36bb6..f49c888f 100644 --- a/cosmic-theme/src/output/vs_code.rs +++ b/cosmic-theme/src/output/vs_code.rs @@ -266,6 +266,14 @@ impl From for VsTheme { } impl Theme { + /// Write this theme to VS Code's `settings.json` as a + /// `workbench.colorCustomizations` entry, and enable + /// `window.autoDetectColorScheme` so VS Code follows the system theme. + /// + /// # Errors + /// + /// Returns an `OutputError` if the user config dir is missing, the + /// settings file cannot be read/written, or its JSON is invalid. #[cold] pub fn apply_vs_code(self) -> Result<(), OutputError> { let vs_theme = VsTheme::from(self); @@ -291,6 +299,13 @@ impl Theme { Ok(()) } + /// Remove the `workbench.colorCustomizations` entry previously written + /// by [`Theme::apply_vs_code`] from VS Code's `settings.json`. + /// + /// # Errors + /// + /// Returns an `OutputError` if the user config dir is missing, the + /// settings file cannot be read/written, or its JSON is invalid. #[cold] pub fn reset_vs_code() -> Result<(), OutputError> { let mut config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?; From 4743bb8ec932ac7c274f2cf02554018a80e954bd Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Tue, 5 May 2026 19:10:03 +0200 Subject: [PATCH 45/75] =?UTF-8?q?yoda:=20cargo=20clippy=20--fix=20on=20lib?= =?UTF-8?q?cosmic-yoda=20(115=E2=86=9233=20warnings)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auto-applied clippy lint suggestions across 23 files: collapse `else { if … }` ladders, drop unneeded `return` statements, remove needless borrows that were immediately auto-dereffed, swap `iter().next()` for `first()`, drop redundant `.into()` to the same type, and similar mechanical cleanups. Behavior is unchanged; the diff is -67 lines net. The 33 remaining warnings are architectural and need manual attention (very-complex-type aliases, too-many-arguments on internal helpers, redundant must_use, needs-is_empty, etc.). Leyoda 2026 – GPLv3 --- src/app/cosmic.rs | 5 +-- src/config/mod.rs | 5 +-- src/theme/style/button.rs | 10 ++--- src/theme/style/iced.rs | 11 ++---- src/widget/button/widget.rs | 17 +++------ src/widget/cards.rs | 4 +- src/widget/color_picker/mod.rs | 29 +++++++------- src/widget/dnd_destination.rs | 9 +---- src/widget/dropdown/menu/mod.rs | 12 ++---- src/widget/dropdown/multi/menu.rs | 6 +-- src/widget/dropdown/multi/widget.rs | 2 +- src/widget/dropdown/widget.rs | 2 +- src/widget/menu/flex.rs | 6 +-- src/widget/menu/menu_bar.rs | 8 ++-- src/widget/menu/menu_inner.rs | 5 +-- src/widget/popover.rs | 5 +-- src/widget/radio.rs | 1 - src/widget/segmented_button/widget.rs | 36 ++++++++---------- src/widget/table/widget/standard.rs | 7 ++-- src/widget/text_input/input.rs | 54 ++++++++------------------- src/widget/text_input/value.rs | 4 +- src/widget/toggler.rs | 6 +-- src/widget/wayland/tooltip/widget.rs | 6 +-- 23 files changed, 91 insertions(+), 159 deletions(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 011bb070..dcbf36c6 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -479,12 +479,11 @@ where .into_iter() .filter(cosmic_config::Error::is_err) { - if let cosmic_config::Error::GetKey(_, err) = &why { - if err.kind() == std::io::ErrorKind::NotFound { + if let cosmic_config::Error::GetKey(_, err) = &why + && err.kind() == std::io::ErrorKind::NotFound { // No system default config installed; don't error continue; } - } tracing::error!(?why, "cosmic toolkit config update error"); } diff --git a/src/config/mod.rs b/src/config/mod.rs index 1691caa8..08a4c0ca 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -23,12 +23,11 @@ pub static COSMIC_TK: LazyLock> = LazyLock::new(|| { .map(|c| { CosmicTk::get_entry(&c).unwrap_or_else(|(errors, mode)| { for why in errors.into_iter().filter(cosmic_config::Error::is_err) { - if let cosmic_config::Error::GetKey(_, err) = &why { - if err.kind() == std::io::ErrorKind::NotFound { + if let cosmic_config::Error::GetKey(_, err) = &why + && err.kind() == std::io::ErrorKind::NotFound { // No system default config installed; don't error continue; } - } tracing::error!(?why, "CosmicTk config entry error"); } mode diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index bb52d9a6..c405455b 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -211,11 +211,10 @@ impl Catalog for crate::Theme { (component.base.into(), text_color, text_color) }); - if let Button::ListItem(_) = style { - if !selected { + if let Button::ListItem(_) = style + && !selected { s.background = None; } - } s } @@ -266,11 +265,10 @@ impl Catalog for crate::Theme { }, ); - if let Button::ListItem(_) = style { - if !selected { + if let Button::ListItem(_) = style + && !selected { s.background = None; } - } s } diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index cb83ec9d..746503e3 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -171,18 +171,15 @@ impl Button { * TODO: Checkbox */ #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Default)] pub enum Checkbox { + #[default] Primary, Secondary, Success, Danger, } -impl Default for Checkbox { - fn default() -> Self { - Self::Primary - } -} impl iced_checkbox::Catalog for Theme { type Class<'a> = Checkbox; @@ -1193,7 +1190,7 @@ impl scrollable::Catalog for Theme { background: Color::TRANSPARENT.into(), border: Border::default(), shadow: Shadow::default(), - icon: Color::TRANSPARENT.into(), + icon: Color::TRANSPARENT, }, }; let small_widget_container = self.current_container().small_widget.with_alpha(0.7); @@ -1261,7 +1258,7 @@ impl scrollable::Catalog for Theme { background: Color::TRANSPARENT.into(), border: Border::default(), shadow: Shadow::default(), - icon: Color::TRANSPARENT.into(), + icon: Color::TRANSPARENT, }, }; diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index a6dda255..cdb2b5d6 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -381,13 +381,12 @@ impl<'a, Message: 'a + Clone> Widget match event { Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { - if let Some(position) = cursor.position() { - if removal_bounds(layout.bounds(), 4.0).contains(position) { + if let Some(position) = cursor.position() + && removal_bounds(layout.bounds(), 4.0).contains(position) { shell.publish(on_remove.clone()); shell.capture_event(); return; } - } } _ => (), @@ -560,9 +559,9 @@ impl<'a, Message: 'a + Clone> Widget } } - if on_remove.is_some() { - if let Some(position) = cursor.position() { - if bounds.contains(position) { + if on_remove.is_some() + && let Some(position) = cursor.position() + && bounds.contains(position) { let bounds = removal_bounds(layout.bounds(), 4.0); renderer.fill_quad( renderer::Quad { @@ -594,8 +593,6 @@ impl<'a, Message: 'a + Clone> Widget }, ); } - } - } }); } } @@ -793,7 +790,6 @@ pub fn update<'a, Message: Clone>( } shell.capture_event(); - return; } } } @@ -813,7 +809,6 @@ pub fn update<'a, Message: Clone>( } shell.capture_event(); - return; } } else if on_press_down.is_some() { let state = state(); @@ -833,7 +828,6 @@ pub fn update<'a, Message: Clone>( shell.publish(msg); } shell.capture_event(); - return; } Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { if let Some(on_press) = on_press { @@ -844,7 +838,6 @@ pub fn update<'a, Message: Clone>( shell.publish(msg); shell.capture_event(); - return; } } } diff --git a/src/widget/cards.rs b/src/widget/cards.rs index 14191c61..72300cac 100644 --- a/src/widget/cards.rs +++ b/src/widget/cards.rs @@ -532,7 +532,7 @@ where let c_layout = layout.next().unwrap(); let state = clear_all_state.unwrap(); self.clear_all_button.as_widget_mut().update( - state, &event, c_layout, cursor, renderer, clipboard, shell, viewport, + state, event, c_layout, cursor, renderer, clipboard, shell, viewport, ); } @@ -542,7 +542,7 @@ where for ((inner, layout), c_state) in self.elements.iter_mut().zip(layout).zip(tree_children) { inner.as_widget_mut().update( - c_state, &event, layout, cursor, renderer, clipboard, shell, viewport, + c_state, event, layout, cursor, renderer, clipboard, shell, viewport, ); if shell.is_event_captured() || fully_unexpanded { break; diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 70975bc3..0de31fd6 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -745,7 +745,7 @@ where let column_tree = &mut tree.children[0]; self.inner.as_widget_mut().update( column_tree, - &event, + event, column_layout, cursor, renderer, @@ -758,22 +758,19 @@ where return; } - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { - let bounds = column_layout.children().nth(1).unwrap().bounds(); - if let Some(point) = cursor.position_over(bounds) { - let relative_pos = point - bounds.position(); - let (s, v) = ( - relative_pos.x / bounds.width, - 1.0 - relative_pos.y / bounds.height, - ); - state.dragging = true; - let hsv: palette::Hsv = palette::Hsv::new(self.active_color.hue, s, v); - shell.publish((self.on_update)(ColorPickerUpdate::ActiveColor(hsv))); - shell.capture_event(); - } + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) = event { + let bounds = column_layout.children().nth(1).unwrap().bounds(); + if let Some(point) = cursor.position_over(bounds) { + let relative_pos = point - bounds.position(); + let (s, v) = ( + relative_pos.x / bounds.width, + 1.0 - relative_pos.y / bounds.height, + ); + state.dragging = true; + let hsv: palette::Hsv = palette::Hsv::new(self.active_color.hue, s, v); + shell.publish((self.on_update)(ColorPickerUpdate::ActiveColor(hsv))); + shell.capture_event(); } - _ => {} } } diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index 10bf7a8b..683a766b 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -369,7 +369,7 @@ impl Widget x, y, mime_types, .. }, )) if *id == Some(my_id) => { - if !self.mime_matches(&mime_types) { + if !self.mime_matches(mime_types) { log::trace!( target: DND_DEST_LOG_TARGET, "offer enter id={my_id:?} ignored (mimes={mime_types:?} not in {:?})", @@ -408,7 +408,6 @@ impl Widget ); } shell.capture_event(); - return; } Event::Dnd(DndEvent::Offer(_, OfferEvent::Leave)) => { log::trace!( @@ -436,7 +435,6 @@ impl Widget viewport, ); } - return; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Motion { x, y })) if *id == Some(my_id) => { log::trace!( @@ -471,7 +469,6 @@ impl Widget ); } shell.capture_event(); - return; } Event::Dnd(DndEvent::Offer(_, OfferEvent::LeaveDestination)) => { log::trace!( @@ -484,7 +481,6 @@ impl Widget { shell.publish(msg); } - return; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Drop)) if *id == Some(my_id) => { log::trace!( @@ -497,7 +493,6 @@ impl Widget shell.publish(msg); } shell.capture_event(); - return; } Event::Dnd(DndEvent::Offer(id, OfferEvent::SelectedAction(action))) if *id == Some(my_id) => @@ -515,7 +510,6 @@ impl Widget shell.publish(msg); } shell.capture_event(); - return; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Data { data, mime_type })) if *id == Some(my_id) => @@ -555,7 +549,6 @@ impl Widget return; } shell.capture_event(); - return; } _ => {} } diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index bb3cead1..99bee955 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -474,16 +474,14 @@ where match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { let hovered_guard = self.hovered_option.lock().unwrap(); - if cursor.is_over(layout.bounds()) { - if let Some(index) = *hovered_guard { + if cursor.is_over(layout.bounds()) + && let Some(index) = *hovered_guard { shell.publish((self.on_selected)(index)); if let Some(close_on_selected) = self.close_on_selected.as_ref() { shell.publish(close_on_selected.clone()); } shell.capture_event(); - return; } - } } Event::Mouse(mouse::Event::CursorMoved { .. }) => { if let Some(cursor_position) = cursor.position_in(layout.bounds()) { @@ -498,11 +496,10 @@ where let new_hovered_option = (cursor_position.y / option_height) as usize; let mut hovered_guard = self.hovered_option.lock().unwrap(); - if let Some(on_option_hovered) = self.on_option_hovered { - if *hovered_guard != Some(new_hovered_option) { + if let Some(on_option_hovered) = self.on_option_hovered + && *hovered_guard != Some(new_hovered_option) { shell.publish(on_option_hovered(new_hovered_option)); } - } *hovered_guard = Some(new_hovered_option); } @@ -526,7 +523,6 @@ where shell.publish(close_on_selected.clone()); } shell.capture_event(); - return; } } } diff --git a/src/widget/dropdown/multi/menu.rs b/src/widget/dropdown/multi/menu.rs index a1da9da7..883b8ada 100644 --- a/src/widget/dropdown/multi/menu.rs +++ b/src/widget/dropdown/multi/menu.rs @@ -343,13 +343,11 @@ where match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { - if cursor.is_over(bounds) { - if let Some(item) = self.hovered_option.as_ref() { + if cursor.is_over(bounds) + && let Some(item) = self.hovered_option.as_ref() { shell.publish((self.on_selected)(item.clone())); shell.capture_event(); - return; } - } } Event::Mouse(mouse::Event::CursorMoved { .. }) => { if let Some(cursor_position) = cursor.position_in(bounds) { diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index 6ed7a02a..dd877def 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -127,7 +127,7 @@ impl<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static> _viewport: &Rectangle, ) { update( - &event, + event, layout, cursor, shell, diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index d2c491e7..0ba04c15 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -263,7 +263,7 @@ where _viewport: &Rectangle, ) { update::( - &event, + event, layout, cursor, shell, diff --git a/src/widget/menu/flex.rs b/src/widget/menu/flex.rs index 045e8407..09dab466 100644 --- a/src/widget/menu/flex.rs +++ b/src/widget/menu/flex.rs @@ -249,7 +249,7 @@ pub fn resolve_wrapper<'a, Message>( if align_items == Alignment::Center { let mut fill_cross = axis.cross(limits.min()); - for (child, tree) in items.into_iter().zip(tree.iter_mut()) { + for (child, tree) in items.iter_mut().zip(tree.iter_mut()) { let c_size = child.size(); let cross_fill_factor = match axis { Axis::Horizontal => c_size.height, @@ -272,7 +272,7 @@ pub fn resolve_wrapper<'a, Message>( cross = fill_cross; } - for (i, (child, tree)) in items.into_iter().zip(tree.iter_mut()).enumerate() { + for (i, (child, tree)) in items.iter_mut().zip(tree.iter_mut()).enumerate() { let c_size = child.size(); let fill_factor = match axis { Axis::Horizontal => c_size.width, @@ -315,7 +315,7 @@ pub fn resolve_wrapper<'a, Message>( let remaining = available.max(0.0); - for (i, (child, tree)) in items.into_iter().zip(tree.iter_mut()).enumerate() { + for (i, (child, tree)) in items.iter_mut().zip(tree.iter_mut()).enumerate() { let c_size = child.size(); let fill_factor = match axis { Axis::Horizontal => c_size.width, diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index c8002c82..981053b3 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -598,14 +598,12 @@ where .with_data(|d| !d.open && !d.active_root.is_empty()); let open = my_state.inner.with_data_mut(|state| { - if reset { - if let Some(popup_id) = state.popup_id.get(&self.window_id).copied() { - if let Some(handler) = self.on_surface_action.as_ref() { + if reset + && let Some(popup_id) = state.popup_id.get(&self.window_id).copied() + && let Some(handler) = self.on_surface_action.as_ref() { shell.publish((handler)(crate::surface::Action::DestroyPopup(popup_id))); state.reset(); } - } - } state.open }); diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index a2159845..af0b3add 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -1521,14 +1521,13 @@ where .is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty()); #[cfg(all(feature = "multi-window", feature = "wayland", target_os = "linux", feature = "surface-message"))] - if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove { - if let Some(id) = state.popup_id.remove(&menu.window_id) { + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove + && let Some(id) = state.popup_id.remove(&menu.window_id) { state.active_root.truncate(menu.depth + 1); shell.publish((menu.on_surface_action.as_ref().unwrap())({ crate::surface::action::destroy_popup(id) })); } - } let item = &active_menu[new_index]; // set new index let old_index = last_menu_state.index.replace(new_index); diff --git a/src/widget/popover.rs b/src/widget/popover.rs index c807393f..7ca9eeff 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -164,8 +164,8 @@ where shell.capture_event(); return; } - } else if let Some(on_close) = self.on_close.as_ref() { - if matches!( + } else if let Some(on_close) = self.on_close.as_ref() + && matches!( event, Event::Mouse(mouse::Event::ButtonPressed(_)) | Event::Touch(touch::Event::FingerPressed { .. }) @@ -173,7 +173,6 @@ where { shell.publish(on_close.clone()); } - } } // Hide cursor from background content when modal popup is active diff --git a/src/widget/radio.rs b/src/widget/radio.rs index 8f0c36f9..51ed00a4 100644 --- a/src/widget/radio.rs +++ b/src/widget/radio.rs @@ -266,7 +266,6 @@ where if cursor.is_over(layout.bounds()) { shell.publish(self.on_click.clone()); shell.capture_event(); - return; } } _ => {} diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index ea39ede9..9edf3e3b 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -692,11 +692,10 @@ where if let Some(icon) = self.model.icon(key) { non_text_width += f32::from(icon.size) + f32::from(self.button_spacing); - } else if self.model.is_active(key) { - if let crate::theme::SegmentedButton::Control = self.style { + } else if self.model.is_active(key) + && let crate::theme::SegmentedButton::Control = self.style { non_text_width += 16.0 + f32::from(self.button_spacing); } - } if self.model.is_closable(key) { non_text_width += @@ -1083,11 +1082,10 @@ where { state.drop_hint = None; self.emit_drop_hint(shell, state.drop_hint); - if let Some(Some(entity)) = entity { - if let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() { + if let Some(Some(entity)) = entity + && let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() { shell.publish(on_dnd_leave(entity)); } - } log::trace!( target: TAB_REORDER_LOG_TARGET, "offer leave id={my_id:?} entity={entity:?}" @@ -1163,11 +1161,10 @@ where None:: Message>, None, ); - if let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() { - if let Some(Some(entity)) = entity { + if let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() + && let Some(Some(entity)) = entity { shell.publish(on_dnd_leave(entity)); } - } } } DndEvent::Offer(id, OfferEvent::Drop) if Some(my_id) == *id => { @@ -1340,8 +1337,8 @@ where // Emit close message if the close button is pressed. if let Some(on_close) = self.on_close.as_ref() { if over_close_button - && (left_button_released(&event) - || (touch_lifted(&event) && fingers_pressed == 1)) + && (left_button_released(event) + || (touch_lifted(event) && fingers_pressed == 1)) { shell.publish(on_close(key)); shell.capture_event(); @@ -1395,14 +1392,14 @@ where } } - if is_lifted(&event) { + if is_lifted(event) { state.unfocus(); } if let Some(on_activate) = self.on_activate.as_ref() { if is_pressed(event) { state.pressed_item = Some(Item::Tab(key)); - } else if is_lifted(&event) && self.button_is_pressed(state, key) { + } else if is_lifted(event) && self.button_is_pressed(state, key) { shell.publish(on_activate(key)); state.set_focused(); state.focused_item = Item::Tab(key); @@ -1442,8 +1439,8 @@ where // Present a context menu on a right click event. if self.context_menu.is_some() && let Some(on_context) = self.on_context.as_ref() - && (right_button_released(&event) - || (touch_lifted(&event) && fingers_pressed == 2)) + && (right_button_released(event) + || (touch_lifted(event) && fingers_pressed == 2)) { state.show_context = Some(key); state.context_cursor = cursor_position.position().unwrap_or_default(); @@ -1539,12 +1536,12 @@ where } if state.is_focused() { // Unfocus on clicks outside of the boundaries of the segmented button. - if is_pressed(&event) { + if is_pressed(event) { state.unfocus(); state.pressed_item = None; return; } - } else if is_lifted(&event) { + } else if is_lifted(event) { state.pressed_item = None; } } @@ -2163,8 +2160,8 @@ where ); } - if show_drop_hint_marker { - if matches!( + if show_drop_hint_marker + && matches!( drop_hint_marker, Some(DropHint { entity, @@ -2179,7 +2176,6 @@ where appearance.active.text_color, ); } - } nth += 1; }); diff --git a/src/widget/table/widget/standard.rs b/src/widget/table/widget/standard.rs index 9ab76c9d..c4cd648c 100644 --- a/src/widget/table/widget/standard.rs +++ b/src/widget/table/widget/standard.rs @@ -88,15 +88,14 @@ where let mut sort_state = 0; - if let Some(sort) = val.model.sort { - if sort.0 == category { + if let Some(sort) = val.model.sort + && sort.0 == category { if sort.1 { sort_state = 1; } else { sort_state = 2; } - } - }; + }; // Build the category header widget::row::with_capacity(2) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 6227d706..b0647c89 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -670,18 +670,15 @@ where let old_value = Value::new(&old_value); if state.is_focused() && let cursor::State::Index(index) = state.cursor.state(&old_value) - { - if index == old_value.len() { + && index == old_value.len() { state.cursor.move_to(self.value.len()); } - } - if let Some(f) = state.is_focused.as_ref().filter(|f| f.focused) { - if f.updated_at != LAST_FOCUS_UPDATE.with(|f| f.get()) { + if let Some(f) = state.is_focused.as_ref().filter(|f| f.focused) + && f.updated_at != LAST_FOCUS_UPDATE.with(|f| f.get()) { state.unfocus(); state.emit_unfocus = true; } - } if self.is_editable_variant { if !state.is_focused() { @@ -895,8 +892,8 @@ where let line_height = self.line_height; // Disables editing of the editable variant when clicking outside of, or for tab focus changes. - if self.is_editable_variant { - if let Some(ref on_edit) = self.on_toggle_edit { + if self.is_editable_variant + && let Some(ref on_edit) = self.on_toggle_edit { let state = tree.state.downcast_mut::(); if !state.is_read_only && state.is_focused.is_some_and(|f| !f.focused) { state.is_read_only = true; @@ -908,7 +905,6 @@ where shell.publish((on_edit)(f.focused)); } } - } // Calculates the layout of the trailing icon button element. if !tree.children.is_empty() { @@ -919,9 +915,9 @@ where trailing_icon_layout = Some(text_layout.children().last().unwrap()); // Enable custom buttons defined on the trailing icon position to be handled. - if !self.is_editable_variant { - if let Some(trailing_layout) = trailing_icon_layout { - let _res = trailing_icon.as_widget_mut().update( + if !self.is_editable_variant + && let Some(trailing_layout) = trailing_icon_layout { + trailing_icon.as_widget_mut().update( tree, event, trailing_layout, @@ -936,18 +932,16 @@ where return; } } - } } } let state = tree.state.downcast_mut::(); - if let Some(on_unfocus) = self.on_unfocus.as_ref() { - if state.emit_unfocus { + if let Some(on_unfocus) = self.on_unfocus.as_ref() + && state.emit_unfocus { state.emit_unfocus = false; shell.publish(on_unfocus.clone()); } - } let dnd_id = self.dnd_id(); let id = Widget::id(self); @@ -1656,11 +1650,10 @@ pub fn update<'a, Message: Clone + 'static>( if matches!(state.dragging_state, None | Some(DraggingState::Selection)) && (!state.is_focused() || (is_editable_variant && state.is_read_only)) { - if !state.is_focused() { - if let Some(on_focus) = on_focus { + if !state.is_focused() + && let Some(on_focus) = on_focus { shell.publish(on_focus.clone()); } - } if state.is_read_only { state.is_read_only = false; @@ -1685,7 +1678,6 @@ pub fn update<'a, Message: Clone + 'static>( state.last_click = Some(click); shell.capture_event(); - return; } else { state.unfocus(); @@ -1721,7 +1713,6 @@ pub fn update<'a, Message: Clone + 'static>( if cursor.is_over(layout.bounds()) { shell.capture_event(); } - return; } Event::Mouse(mouse::Event::CursorMoved { position }) | Event::Touch(touch::Event::FingerMoved { position, .. }) => { @@ -1804,7 +1795,6 @@ pub fn update<'a, Message: Clone + 'static>( } shell.capture_event(); - return; } } Event::Keyboard(keyboard::Event::KeyPressed { @@ -1829,14 +1819,13 @@ pub fn update<'a, Message: Clone + 'static>( if state.keyboard_modifiers.command() { match key.to_latin(*physical_key) { Some('c') => { - if !is_secure { - if let Some((start, end)) = state.cursor.selection(value) { + if !is_secure + && let Some((start, end)) = state.cursor.selection(value) { clipboard.write( iced_core::clipboard::Kind::Standard, value.select(start, end).to_string(), ); } - } } // XXX if we want to allow cutting of secure text, we need to // update the cache and decide which value to cut @@ -2091,7 +2080,6 @@ pub fn update<'a, Message: Clone + 'static>( } shell.capture_event(); - return; } } Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => { @@ -2111,7 +2099,6 @@ pub fn update<'a, Message: Clone + 'static>( } shell.capture_event(); - return; } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { @@ -2127,7 +2114,6 @@ pub fn update<'a, Message: Clone + 'static>( state.preedit = matches!(event, input_method::Event::Opened) .then(input_method::Preedit::new); shell.capture_event(); - return; } input_method::Event::Preedit(content, selection) => { if state.is_focused() { @@ -2137,7 +2123,6 @@ pub fn update<'a, Message: Clone + 'static>( text_size: Some(size.into()), }); shell.capture_event(); - return; } } input_method::Event::Commit(text) => { @@ -2155,7 +2140,7 @@ pub fn update<'a, Message: Clone + 'static>( LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at)); let mut editor = Editor::new(unsecured_value, &mut state.cursor); - editor.paste(Value::new(&text)); + editor.paste(Value::new(text)); let contents = editor.contents(); let unsecured_value = Value::new(&contents); @@ -2175,7 +2160,6 @@ pub fn update<'a, Message: Clone + 'static>( update_cache(state, &value); shell.capture_event(); - return; } } } @@ -2205,7 +2189,6 @@ pub fn update<'a, Message: Clone + 'static>( // TODO: restore value in text input state.dragging_state = None; shell.capture_event(); - return; } } #[cfg(all(feature = "wayland", target_os = "linux"))] @@ -2255,7 +2238,6 @@ pub fn update<'a, Message: Clone + 'static>( state.cursor.set_affinity(affinity); state.cursor.move_to(position); shell.capture_event(); - return; } } #[cfg(all(feature = "wayland", target_os = "linux"))] @@ -2284,7 +2266,6 @@ pub fn update<'a, Message: Clone + 'static>( state.cursor.set_affinity(affinity); state.cursor.move_to(position); shell.capture_event(); - return; } #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if *rectangle == Some(dnd_id) => { @@ -2301,8 +2282,6 @@ pub fn update<'a, Message: Clone + 'static>( }; state.dnd_offer = DndOfferState::Dropped; } - - return; } #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if Some(dnd_id) != *id => {} @@ -2322,7 +2301,6 @@ pub fn update<'a, Message: Clone + 'static>( } }; shell.capture_event(); - return; } #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type })) @@ -2359,9 +2337,7 @@ pub fn update<'a, Message: Clone + 'static>( }; update_cache(state, &value); shell.capture_event(); - return; } - return; } _ => {} } diff --git a/src/widget/text_input/value.rs b/src/widget/text_input/value.rs index 3f7b8d73..59e77752 100644 --- a/src/widget/text_input/value.rs +++ b/src/widget/text_input/value.rs @@ -45,9 +45,7 @@ impl Value { pub fn previous_start_of_word(&self, index: usize) -> usize { let previous_string = &self.graphemes[..index.min(self.graphemes.len())].concat(); - UnicodeSegmentation::split_word_bound_indices(previous_string as &str) - .filter(|(_, word)| !word.trim_start().is_empty()) - .next_back() + UnicodeSegmentation::split_word_bound_indices(previous_string as &str).rfind(|(_, word)| !word.trim_start().is_empty()) .map_or(0, |(i, previous_word)| { index - UnicodeSegmentation::graphemes(previous_word, true).count() diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index 27203c05..5e5bc2ee 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -182,7 +182,8 @@ impl<'a, Message> Widget for Toggler<'a, ) -> layout::Node { let limits = limits.width(self.width); - let res = next_to_each_other( + + next_to_each_other( &limits, self.spacing, |limits| { @@ -221,8 +222,7 @@ impl<'a, Message> Widget for Toggler<'a, } }, |_| layout::Node::new(Size::new(48., 24.)), - ); - res + ) } fn update( diff --git a/src/widget/wayland/tooltip/widget.rs b/src/widget/wayland/tooltip/widget.rs index 7bfd1115..0526e8db 100644 --- a/src/widget/wayland/tooltip/widget.rs +++ b/src/widget/wayland/tooltip/widget.rs @@ -456,7 +456,6 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>( shell.publish(on_leave.clone()); shell.capture_event(); - return; } } @@ -484,8 +483,8 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>( } } else { *guard = cursor.is_over(bounds); - if *guard { - if let Some(settings) = settings { + if *guard + && let Some(settings) = settings { if let Some(delay) = delay { let s = settings.clone(); let view = view.clone(); @@ -569,7 +568,6 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>( shell.publish((on_surface_action)(sm)); } } - } } } _ => {} From 528513725d3856e5094cfbff5f0dc1a79d6c4018 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 7 May 2026 12:40:51 -0400 Subject: [PATCH 46/75] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 3928e0d3..d7595fab 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 3928e0d38572d63ff1ef1d768b17770e9a91d7c0 +Subproject commit d7595fab95579f640d43d90e16c174709c4b5c01 From b105e9a108484f2199cbc55b1c4e68bfe0a7a010 Mon Sep 17 00:00:00 2001 From: KENZ Date: Sat, 9 May 2026 00:25:49 +0900 Subject: [PATCH 47/75] feat(iced): IME support improvements --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index d7595fab..1444d5c4 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit d7595fab95579f640d43d90e16c174709c4b5c01 +Subproject commit 1444d5c43e8b103ca8077555606443ab77e03ce5 From 2aa9133f9d3334e8f77bd596c67727a2698e8bed Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 13 May 2026 16:14:34 +0200 Subject: [PATCH 48/75] docs(readme): add Made for COSMIC Flatpak section --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 23da97bc..698316db 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,24 @@ While libcosmic is written entirely in Rust, some of its dependencies may requir sudo apt install cargo cmake just libexpat1-dev libfontconfig-dev libfreetype-dev libxkbcommon-dev pkgconf ``` +## Made-for-COSMIC Flatpak IDs + +To identify a project as a COSMIC Application, add `com.system76.CosmicApplication` to the provides section of the project's metainfo. + +```xml + + com.system76.CosmicApplication + +``` + +For COSMIC Applets, use `com.system76.CosmicApplet`. + +```xml + + com.system76.CosmicApplet + +``` + ## Examples Some examples are included in the [examples](./examples) directory to to kickstart your From cab9d5aff2d8561fc540fd0bbc3b615ba56d641a Mon Sep 17 00:00:00 2001 From: Ilia Malanin Date: Thu, 14 May 2026 16:10:59 +0200 Subject: [PATCH 49/75] feat!(menu): support physical key matches for non-Latin keyboard layouts --- src/widget/menu/key_bind.rs | 86 +++++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 8 deletions(-) diff --git a/src/widget/menu/key_bind.rs b/src/widget/menu/key_bind.rs index 8b4ed227..d6e40e07 100644 --- a/src/widget/menu/key_bind.rs +++ b/src/widget/menu/key_bind.rs @@ -1,3 +1,4 @@ +use iced_core::keyboard::key::{Code, Physical}; use iced_core::keyboard::{Key, Modifiers}; use std::fmt; @@ -27,28 +28,97 @@ pub struct KeyBind { } impl KeyBind { - /// Checks if the given key and modifiers match the `KeyBind`. + /// Checks if the given key and modifiers match the `KeyBind`, with an + /// optional fallback to the physical key position for non-Latin keyboard + /// layouts. /// /// # Arguments /// /// * `modifiers` - A `Modifiers` instance representing the current active modifiers. /// * `key` - A reference to the `Key` that is being checked. + /// * `physical_key` - An optional reference to the physical key position, + /// used as a fallback when the logical `key` does not match (e.g. on + /// Cyrillic or other non-Latin layouts). Can be `None` for keys where + /// the physical position is not relevant (e.g. `Key::Named`). /// /// # Returns /// /// * `bool` - `true` if the key and modifiers match the `KeyBind`, `false` otherwise. - pub fn matches(&self, modifiers: Modifiers, key: &Key) -> bool { - let key_eq = match (key, &self.key) { - // CapsLock and Shift change the case of Key::Character, so we compare these in a case insensitive way - (Key::Character(a), Key::Character(b)) => a.eq_ignore_ascii_case(b), - (a, b) => a.eq(b), - }; + pub fn matches(&self, modifiers: Modifiers, key: &Key, physical_key: Option<&Physical>) -> bool { + let key_eq = self.key_eq(key) + || physical_key + .and_then(physical_key_to_latin) + .is_some_and(|latin| self.key_eq(&latin)); key_eq && modifiers.logo() == self.modifiers.contains(&Modifier::Super) && modifiers.control() == self.modifiers.contains(&Modifier::Ctrl) && modifiers.alt() == self.modifiers.contains(&Modifier::Alt) && modifiers.shift() == self.modifiers.contains(&Modifier::Shift) } + + fn key_eq(&self, key: &Key) -> bool { + match (key, &self.key) { + // CapsLock and Shift change the case of Key::Character, so we compare these in a case insensitive way + (Key::Character(a), Key::Character(b)) => a.eq_ignore_ascii_case(b), + (a, b) => a.eq(b), + } + } +} + +/// Converts a physical key code to the corresponding US-layout Latin `Key`. +/// +/// This mapping is intentionally limited to keys that may produce different +/// characters on non-Latin keyboard layouts (letters and punctuation). Keys +/// like digits are not included because they remain the same across layouts. +/// +/// Only used as a fallback when the primary key comparison in +/// [`KeyBind::matches`] does not match. +fn physical_key_to_latin(physical_key: &Physical) -> Option { + let code = match physical_key { + Physical::Code(code) => code, + Physical::Unidentified(_) => return None, + }; + let ch = match code { + Code::KeyA => "a", + Code::KeyB => "b", + Code::KeyC => "c", + Code::KeyD => "d", + Code::KeyE => "e", + Code::KeyF => "f", + Code::KeyG => "g", + Code::KeyH => "h", + Code::KeyI => "i", + Code::KeyJ => "j", + Code::KeyK => "k", + Code::KeyL => "l", + Code::KeyM => "m", + Code::KeyN => "n", + Code::KeyO => "o", + Code::KeyP => "p", + Code::KeyQ => "q", + Code::KeyR => "r", + Code::KeyS => "s", + Code::KeyT => "t", + Code::KeyU => "u", + Code::KeyV => "v", + Code::KeyW => "w", + Code::KeyX => "x", + Code::KeyY => "y", + Code::KeyZ => "z", + Code::Minus => "-", + Code::Equal => "=", + Code::BracketLeft => "[", + Code::BracketRight => "]", + Code::Backslash => "\\", + Code::Semicolon => ";", + Code::Quote => "'", + Code::Backquote => "`", + Code::Comma => ",", + Code::Period => ".", + Code::Slash => "/", + _ => return None, + }; + Some(Key::Character(ch.into())) } impl fmt::Display for KeyBind { @@ -62,4 +132,4 @@ impl fmt::Display for KeyBind { other => write!(f, "{:?}", other), } } -} +} \ No newline at end of file From 631c81b170b792fa57898161b73a5a673ad79b48 Mon Sep 17 00:00:00 2001 From: DanielKaleby <74084485+DanielKaleby@users.noreply.github.com> Date: Thu, 14 May 2026 11:19:43 -0300 Subject: [PATCH 50/75] feat(cosmic-config): add `new_data` method. --- cosmic-config/src/lib.rs | 34 ++++++++++++++++++++++++++++++++++ examples/config/src/main.rs | 3 +++ 2 files changed, 37 insertions(+) diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 7ca9b907..2af498ff 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -51,6 +51,22 @@ fn get_state_dir() -> Option { dirs::state_dir() } +/// Get the data directory, with Flatpak sandbox support. +fn get_data_dir() -> Option { + // Check if we're running in Flatpak + if env::var_os("FLATPAK_ID").is_some() { + // Try HOST_XDG_DATA_HOME first + if let Some(host_data) = env::var_os("HOST_XDG_DATA_HOME") { + return Some(PathBuf::from(host_data)); + } + // Fallback: try to construct from HOME + if let Some(home) = env::var_os("HOME") { + return Some(PathBuf::from(home).join(".local").join("share")); + } + } + dirs::data_dir() +} + #[cfg(feature = "subscription")] mod subscription; #[cfg(feature = "subscription")] @@ -263,6 +279,24 @@ impl Config { }) } + /// Get data for the given application name and config version. + pub fn new_data(name: &str, version: u64) -> Result { + // Look for [name]/v[version] + let path = sanitize_name(name)?.join(format!("v{}", version)); + + // Get libcosmic user data directory + let mut user_path = get_data_dir().ok_or(Error::NoConfigDirectory)?; + user_path.push("cosmic"); + user_path.push(path); + // Create new data directory if not found. + fs::create_dir_all(&user_path)?; + + Ok(Self { + system_path: None, + user_path: Some(user_path), + }) + } + // Start a transaction (to set multiple configs at the same time) #[inline] pub fn transaction(&self) -> ConfigTransaction<'_> { diff --git a/examples/config/src/main.rs b/examples/config/src/main.rs index f6fb5c0d..dfcc5b99 100644 --- a/examples/config/src/main.rs +++ b/examples/config/src/main.rs @@ -88,4 +88,7 @@ pub fn main() { println!("Testing state"); test_config(Config::new_state("com.system76.Example", 1).unwrap()); + + println!("Testing data"); + test_config(Config::new_data("com.system76.Example", 1).unwrap()); } From 5782cb927ede98978f8f76fec2cf0867ff19166b Mon Sep 17 00:00:00 2001 From: Andrei Ivanou Date: Sun, 17 May 2026 11:08:40 +0200 Subject: [PATCH 51/75] fix(process): safely exit forked child after spawn Co-Authored-By: Michael Murphy --- src/process.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/process.rs b/src/process.rs index 2b6c4e0e..e2cb0f70 100644 --- a/src/process.rs +++ b/src/process.rs @@ -5,7 +5,7 @@ use smol::io::AsyncReadExt; use std::io; use std::os::fd::OwnedFd; -use std::process::{Command, Stdio, exit}; +use std::process::{Command, Stdio}; #[cfg(feature = "tokio")] use tokio::io::AsyncReadExt; @@ -56,24 +56,33 @@ pub async fn spawn(mut command: Command) -> Option { match unsafe { libc::fork() } { // Parent process - 1.. => { + fork_pid @ 1..=i32::MAX => { // Drop copy of write end, then read PID from pipe drop(write); let pid = read_from_pipe(read).await; - // wait to prevent zombie - _ = rustix::process::wait(rustix::process::WaitOptions::empty()); + _ = rustix::process::waitpid( + rustix::process::Pid::from_raw(fork_pid), + rustix::process::WaitOptions::empty(), + ); pid } // Child process 0 => { let _res = rustix::process::setsid(); - if let Ok(child) = command.spawn() { + let exit_status = if let Ok(child) = command.spawn() { // Write PID to pipe let _ = rustix::io::write(write, &child.id().to_be_bytes()); - } + 0 + } else { + 1 + }; - exit(0) + // # Safety + // Required for child fork to exit without affecting the parent. + unsafe { + libc::_exit(exit_status); + } } ..=-1 => { From 6f863f19052f1ac576d1a9bef2897d14b29b3f7d Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 18 May 2026 15:05:11 +0200 Subject: [PATCH 52/75] chore: cargo +nightly fmt --- src/widget/menu/key_bind.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/widget/menu/key_bind.rs b/src/widget/menu/key_bind.rs index d6e40e07..ef87b344 100644 --- a/src/widget/menu/key_bind.rs +++ b/src/widget/menu/key_bind.rs @@ -44,7 +44,12 @@ impl KeyBind { /// # Returns /// /// * `bool` - `true` if the key and modifiers match the `KeyBind`, `false` otherwise. - pub fn matches(&self, modifiers: Modifiers, key: &Key, physical_key: Option<&Physical>) -> bool { + pub fn matches( + &self, + modifiers: Modifiers, + key: &Key, + physical_key: Option<&Physical>, + ) -> bool { let key_eq = self.key_eq(key) || physical_key .and_then(physical_key_to_latin) @@ -132,4 +137,4 @@ impl fmt::Display for KeyBind { other => write!(f, "{:?}", other), } } -} \ No newline at end of file +} From 466e25d9f8c08e76215b9435fc5badc4e0e98e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Tue, 19 May 2026 15:01:44 +0200 Subject: [PATCH 53/75] feat(progress_bar): linear progress markers --- examples/application/src/main.rs | 4 +- src/widget/progress_bar/linear.rs | 169 +++++++++++++++++++++++++----- 2 files changed, 143 insertions(+), 30 deletions(-) diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index 9f89061b..05841f5b 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -240,7 +240,9 @@ impl cosmic::Application for App { widget::progress_bar::linear::Linear::new() .girth(10.0) .progress(self.progress) - .width(Length::Fill), + .width(Length::Fill) + .markers([0.25, 0.5, 0.75]) + .segment_spacing(2), ) .push( widget::progress_bar::circular::Circular::new() diff --git a/src/widget/progress_bar/linear.rs b/src/widget/progress_bar/linear.rs index 7672579e..0ebe402f 100644 --- a/src/widget/progress_bar/linear.rs +++ b/src/widget/progress_bar/linear.rs @@ -3,7 +3,7 @@ use super::animation::{Animation, Progress}; use super::style::StyleSheet; use iced::advanced::widget::tree::{self, Tree}; use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer}; -use iced::{Background, Element, Event, Length, Rectangle, Size, mouse, window}; +use iced::{Background, Element, Event, Length, Pixels, Rectangle, Size, mouse, window}; use std::time::Duration; @@ -21,6 +21,8 @@ where cycle_duration: Duration, period: Duration, progress: Option, + markers: Vec, + segment_spacing: f32, } impl Linear @@ -36,6 +38,8 @@ where cycle_duration: Duration::from_millis(1500), period: Duration::from_secs(2), progress: None, + markers: Vec::new(), + segment_spacing: 0.0, } } @@ -75,6 +79,26 @@ where self.progress = Some(progress.clamp(0.0, 1.0)); self } + + /// Sets the markers of a determinate progress bar, which divide the bar into segments. + /// Each value is a progress fraction between `0.0` and `1.0 at which a visual gap is inserted. + pub fn markers(mut self, markers: impl Into>) -> Self { + let mut markers = markers.into(); + for bp in &mut markers { + *bp = bp.clamp(0.0, 1.0); + } + markers.sort_by(f32::total_cmp); + markers.dedup(); + + self.markers = markers; + self + } + + /// Sets the spacing between segments at each marker. + pub fn segment_spacing(mut self, spacing: impl Into) -> Self { + self.segment_spacing = spacing.into().0; + self + } } impl Default for Linear @@ -165,26 +189,17 @@ where let custom_style = theme.appearance(&self.style, self.progress.is_some(), false); let state = tree.state.downcast_ref::(); - renderer.fill_quad( - renderer::Quad { - bounds, - border: iced::Border { - width: if custom_style.border_color.is_some() { - 1.0 - } else { - 0.0 - }, - color: custom_style.border_color.unwrap_or(custom_style.bar_color), - radius: custom_style.border_radius.into(), - }, - snap: true, - ..renderer::Quad::default() - }, - Background::Color(custom_style.track_color), - ); + let border_width = if custom_style.border_color.is_some() { + 1.0 + } else { + 0.0 + }; + let border_color = custom_style.border_color.unwrap_or(custom_style.bar_color); + let radius = custom_style.border_radius; - let mut draw_segment = |x: f32, width: f32| { - if width > 0.001 { + let mut draw_quad = |x: f32, width: f32, color: iced::Color, border: iced::Border| { + // don't draw if width is less than 0.1 pixels + if width * bounds.width > 0.1 { renderer.fill_quad( renderer::Quad { bounds: Rectangle { @@ -193,22 +208,102 @@ where width: width * bounds.width, height: bounds.height, }, - border: iced::Border { - width: 0.0, - color: iced::Color::TRANSPARENT, - radius: custom_style.border_radius.into(), - }, + border, snap: true, ..renderer::Quad::default() }, - Background::Color(custom_style.bar_color), + Background::Color(color), ); } }; if self.progress.is_some() { - draw_segment(0.0, state.progress.current); + let spacing = self.segment_spacing.max(1.0); + let radius_inner = radius.min(spacing); + + let gap = if self.markers.is_empty() { + 0.0 + } else { + spacing / bounds.width + }; + let drawable = 1.0 - gap * self.markers.len() as f32; + let num_segments = self.markers.len() + 1; + + let segment_bounds = |i: usize| { + let seg_lo = if i == 0 { 0.0 } else { self.markers[i - 1] }; + let seg_hi = if i == num_segments - 1 { + 1.0 + } else { + self.markers[i] + }; + (seg_lo, seg_hi) + }; + let get_radius = |i: usize| { + let r_left = if i == 0 { radius } else { radius_inner }; + let r_right = if i == num_segments - 1 { + radius + } else { + radius_inner + }; + [r_left, r_right, r_right, r_left].into() + }; + + // draw track segments + for i in 0..num_segments { + let (seg_lo, seg_hi) = segment_bounds(i); + let x_start = seg_lo * drawable + i as f32 * gap; + let x_width = (seg_hi - seg_lo) * drawable; + + draw_quad( + x_start, + x_width, + custom_style.track_color, + iced::Border { + width: border_width, + color: border_color, + radius: get_radius(i), + }, + ); + } + + // draw bar segments + let current_p = state.progress.current; + for i in 0..num_segments { + let (seg_lo, seg_hi) = segment_bounds(i); + + // don't iterate over non-filled segments + if current_p < seg_lo { + break; + } + + let x_start = seg_lo * drawable + i as f32 * gap; + let x_width = (seg_hi - seg_lo) * drawable; + let fill = ((current_p - seg_lo) / (seg_hi - seg_lo)).clamp(0.0, 1.0); + + draw_quad( + x_start, + x_width * fill, + custom_style.bar_color, + iced::Border { + radius: get_radius(i), + ..iced::Border::default() + }, + ); + } } else { + // draw track + draw_quad( + 0.0, + 1.0, + custom_style.track_color, + iced::Border { + width: border_width, + color: border_color, + radius: radius.into(), + }, + ); + + // draw bar let (bar_start, bar_end) = state .animation @@ -218,8 +313,24 @@ where let right_width = (1.0 - start).min(length); let left_width = length - right_width; - draw_segment(start, right_width); - draw_segment(0.0, left_width); + draw_quad( + start, + right_width, + custom_style.bar_color, + iced::Border { + radius: radius.into(), + ..iced::Border::default() + }, + ); + draw_quad( + 0.0, + left_width, + custom_style.bar_color, + iced::Border { + radius: radius.into(), + ..iced::Border::default() + }, + ); } } } From 147306d581722456ec240bf9962a6fc4a5ab566f Mon Sep 17 00:00:00 2001 From: Hojjat Date: Wed, 20 May 2026 00:30:52 -0600 Subject: [PATCH 54/75] chore: update iced --- iced | 2 +- src/theme/style/iced.rs | 11 +++++++++-- src/widget/menu/menu_tree.rs | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/iced b/iced index 1444d5c4..251254ad 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 1444d5c43e8b103ca8077555606443ab77e03ce5 +Subproject commit 251254ad5179f05b849e420425b72d4c40091db1 diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 1761f6a5..131538e4 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -1341,9 +1341,16 @@ impl iced_widget::text::Catalog for Theme { match class { Text::Accent => iced_widget::text::Style { color: Some(self.cosmic().accent_text_color().into()), + ..Default::default() + }, + Text::Default => iced_widget::text::Style { + color: None, + ..Default::default() + }, + Text::Color(c) => iced_widget::text::Style { + color: Some(*c), + ..Default::default() }, - Text::Default => iced_widget::text::Style { color: None }, - Text::Color(c) => iced_widget::text::Style { color: Some(*c) }, Text::Custom(f) => f(self), } } diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index 41cf1dff..efcf9800 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -234,6 +234,7 @@ pub fn menu_items< color.alpha *= 0.75; TextStyle { color: Some(color.into()), + ..Default::default() } } let key_class = theme::Text::Custom(key_style); From b279d82240fc74710187043b8ee6b9cfa2469d2f Mon Sep 17 00:00:00 2001 From: Hojjat Date: Wed, 20 May 2026 00:32:14 -0600 Subject: [PATCH 55/75] fix: circular progressbar crashes on very small windows --- src/widget/progress_bar/circular.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/widget/progress_bar/circular.rs b/src/widget/progress_bar/circular.rs index 2123d2e6..3a3fedc4 100644 --- a/src/widget/progress_bar/circular.rs +++ b/src/widget/progress_bar/circular.rs @@ -177,6 +177,9 @@ where let geometry = state.cache.draw(renderer, bounds.size(), |frame| { let track_radius = frame.width() / 2.0 - self.bar_height; + if track_radius <= 0.0 { + return; + } let track_path = canvas::Path::circle(frame.center(), track_radius); frame.stroke( From e2b172e0bc8c38c1a499d761d9abaaa9d921bca0 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 20 May 2026 16:06:07 +0200 Subject: [PATCH 56/75] feat(iced): X11 resize fix, f16 shader compat, & selectable text feature --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 251254ad..b007caba 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 251254ad5179f05b849e420425b72d4c40091db1 +Subproject commit b007caba44b0be1d282edbe1cd6fcd440ef06eab From 2f022280f2f974a567d33d5921f96bbf5fa0f9b4 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 20 May 2026 17:04:07 +0200 Subject: [PATCH 57/75] docs: fix cargo doc generation --- Cargo.toml | 2 +- iced | 2 +- src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index de87a395..3d42ed05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,7 +127,7 @@ ashpd = { version = "0.12.3", default-features = false, optional = true } async-fs = { version = "2.2", optional = true } async-std = { workspace = true, optional = true } auto_enums = "0.8.8" -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "c253ec1", optional = true } jiff = "0.2" cosmic-config = { path = "cosmic-config" } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } diff --git a/iced b/iced index b007caba..98fb6a0f 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit b007caba44b0be1d282edbe1cd6fcd440ef06eab +Subproject commit 98fb6a0fba98cfdb6c04f33ccf0be6abad7fcfff diff --git a/src/lib.rs b/src/lib.rs index 02623799..875d9148 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ #![allow(clippy::module_name_repetitions)] #![cfg_attr(target_os = "redox", feature(lazy_cell))] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] /// Recommended default imports. pub mod prelude { From 113e0ae1f9f4faf7c7bd38a8b1e55dae1c282078 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 22 May 2026 01:21:03 +0200 Subject: [PATCH 58/75] chore(applet): gracefully exit with log when panel exits This was causing confusion for users that thought this symptom may be the cause of a problem. --- src/applet/token/wayland_handler.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/applet/token/wayland_handler.rs b/src/applet/token/wayland_handler.rs index 4ab73235..3bbaf573 100644 --- a/src/applet/token/wayland_handler.rs +++ b/src/applet/token/wayland_handler.rs @@ -164,7 +164,18 @@ pub(crate) fn wayland_handler( if app_data.exit { break; } - event_loop.dispatch(None, &mut app_data).unwrap(); + + if let Err(why) = event_loop.dispatch(None, &mut app_data) { + if let calloop::Error::IoError(ref why) = why + && why.kind() == std::io::ErrorKind::BrokenPipe + { + tracing::info!("Connection to panel has ended. The applet will now exit with it."); + break; + } + + tracing::error!(?why, "dispatch error on Wayland connection to panel"); + break; + } } } From 8fa6a01d049905ce99eb9adf8415b2fa73611818 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Mon, 18 May 2026 13:49:14 -0600 Subject: [PATCH 59/75] feat: add ability to provide extra paths to search for icons --- src/widget/icon/named.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/widget/icon/named.rs b/src/widget/icon/named.rs index c6bec753..89050301 100644 --- a/src/widget/icon/named.rs +++ b/src/widget/icon/named.rs @@ -39,6 +39,10 @@ pub struct Named { /// Prioritizes SVG over PNG pub prefer_svg: bool, + + /// Extra directories to search as flat paths before the icon theme chain. + #[setters(skip)] + pub extra_paths: Vec, } impl Named { @@ -52,19 +56,30 @@ impl Named { size: None, scale: None, prefer_svg: symbolic, + extra_paths: Vec::new(), } } + pub fn with_extra_paths(mut self, paths: Vec) -> Self { + self.extra_paths = paths; + self + } + #[cfg(all(unix, not(target_os = "macos")))] #[must_use] pub fn path(self) -> Option { let name = &*self.name; let fallback = &self.fallback; + let extra_paths = &self.extra_paths; let locate = |theme: &str, name| { let mut lookup = freedesktop_icons::lookup(name) .with_theme(theme.as_ref()) .with_cache(); + if !extra_paths.is_empty() { + lookup = lookup.with_extra_paths(extra_paths); + } + if let Some(scale) = self.scale { lookup = lookup.with_scale(scale); } From 675f3b59e36a54ebb752e4104ab9dc245de3e3d4 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Sat, 23 May 2026 20:49:24 +0200 Subject: [PATCH 60/75] chore: reduce local stack warnings --- iced | 2 +- src/app/cosmic.rs | 11 ++++++----- src/app/mod.rs | 2 +- src/applet/column.rs | 2 +- src/applet/mod.rs | 14 +------------- src/applet/row.rs | 3 +-- src/dbus_activation.rs | 2 +- src/desktop.rs | 12 ++++++++---- src/lib.rs | 11 +++++++++++ src/widget/button/icon.rs | 8 +++++--- src/widget/button/image.rs | 8 +++++--- src/widget/button/link.rs | 8 +++++--- src/widget/button/text.rs | 8 +++++--- src/widget/spin_button.rs | 16 ++++++++++------ src/widget/text_input/input.rs | 4 ++-- 15 files changed, 63 insertions(+), 48 deletions(-) diff --git a/iced b/iced index 6b069b2b..f4870187 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 6b069b2b4bc252d8fdc4d2a93ff533376c4f0dee +Subproject commit f4870187088a14d398f2075c5ccda611387dcdcc diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index dcbf36c6..86af0992 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -100,9 +100,10 @@ impl Cosmic where T::Message: Send + 'static, { - pub fn init( - (mut core, flags): (Core, T::Flags), - ) -> (Self, iced::Task>) { + pub fn init((core, flags): (Core, T::Flags)) -> (Self, iced::Task>) { + #[cfg(all(feature = "dbus-config", target_os = "linux"))] + let mut core = core; + #[cfg(all(feature = "dbus-config", target_os = "linux"))] { use iced_futures::futures::executor::block_on; @@ -625,9 +626,9 @@ impl Cosmic { .app .core() .main_window_id() - .is_some_and(|main_id| main_id == id) + .is_some_and(|main_id| main_id == _id) { - self.app.core_mut().window.sharp_corners = maximized; + self.app.core_mut().window.sharp_corners = _maximized; } } diff --git a/src/app/mod.rs b/src/app/mod.rs index a4013205..f53ee519 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -284,7 +284,7 @@ where // app = app.window(window_settings); core.main_window = Some(iced_core::window::Id::RESERVED); } - let mut app = iced::daemon( + let app = iced::daemon( BootData(Rc::new(RefCell::new(Some(BootDataInner:: { flags, core, diff --git a/src/applet/column.rs b/src/applet/column.rs index 9657b566..bb16f085 100644 --- a/src/applet/column.rs +++ b/src/applet/column.rs @@ -1,7 +1,7 @@ //! Distribute content vertically. use crate::iced; use iced::core::alignment::{self, Alignment}; -use iced::core::event::{self, Event}; +use iced::core::event::Event; use iced::core::layout; use iced::core::mouse; use iced::core::overlay; diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 48721e1c..d1ae6e87 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -10,11 +10,7 @@ use crate::{ widget::{ self, autosize::{self, Autosize, autosize}, - column::Column, layer_container, - row::Row, - space::horizontal, - space::vertical, }, }; @@ -217,12 +213,6 @@ impl Context { icon: widget::icon::Handle, ) -> crate::widget::Button<'a, Message> { let suggested = self.suggested_size(icon.symbolic); - let (applet_padding_major_axis, applet_padding_minor_axis) = self.suggested_padding(true); - let (horizontal_padding, vertical_padding) = if self.is_horizontal() { - (applet_padding_major_axis, applet_padding_minor_axis) - } else { - (applet_padding_minor_axis, applet_padding_major_axis) - }; let symbolic = icon.symbolic; let icon = widget::icon(icon) .class(if symbolic { @@ -465,10 +455,8 @@ impl Context { &self, content: impl Into>, ) -> Autosize<'a, Message, crate::Theme, crate::Renderer> { - let force_configured = matches!(&self.panel_type, PanelType::Other(n) if n.is_empty()); let w = autosize(content, AUTOSIZE_MAIN_ID.clone()); let mut limits = Limits::NONE; - let suggested_window_size = self.suggested_window_size(); if let Some(width) = self .suggested_bounds @@ -579,7 +567,7 @@ pub fn run(flags: App::Flags) -> iced::Result { // window_settings = window_settings.clone(); core.main_window = Some(iced_core::window::Id::RESERVED); } - let mut app = iced::daemon( + let app = iced::daemon( BootData(Rc::new(RefCell::new(Some(BootDataInner:: { flags, core, diff --git a/src/applet/row.rs b/src/applet/row.rs index a6745d1c..3ac9f7e0 100644 --- a/src/applet/row.rs +++ b/src/applet/row.rs @@ -1,7 +1,7 @@ //! Distribute content horizontally. use crate::iced; use iced::core::alignment::{self, Alignment}; -use iced::core::event::{self, Event}; +use iced::core::event::Event; use iced::core::layout::{self, Layout}; use iced::core::mouse; use iced::core::overlay; @@ -10,7 +10,6 @@ use iced::core::widget::{Operation, Tree}; use iced::core::{ Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, widget, }; -use iced::touch; /// A container that distributes its contents horizontally. /// diff --git a/src/dbus_activation.rs b/src/dbus_activation.rs index 99e2f9f0..10857fac 100644 --- a/src/dbus_activation.rs +++ b/src/dbus_activation.rs @@ -45,7 +45,7 @@ pub fn subscription() -> Subscription( exec: S, env_vars: I, - app_id: Option<&str>, + _app_id: Option<&str>, terminal: bool, ) where S: AsRef, @@ -816,13 +816,17 @@ pub async fn spawn_desktop_exec( // https://systemd.io/DESKTOP_ENVIRONMENTS // // Similar to what Gnome sets, for now. - if let Some(pid) = crate::process::spawn(cmd).await { + if let Some(_pid) = crate::process::spawn(cmd).await { #[cfg(feature = "desktop-systemd-scope")] if let Ok(session) = zbus::Connection::session().await { if let Ok(systemd_manager) = SystemdMangerProxy::new(&session).await { let _ = systemd_manager .start_transient_unit( - &format!("app-cosmic-{}-{}.scope", app_id.unwrap_or(&executable), pid), + &format!( + "app-cosmic-{}-{}.scope", + _app_id.unwrap_or(&executable), + _pid + ), "fail", &[ ( @@ -833,7 +837,7 @@ pub async fn spawn_desktop_exec( ), ( "PIDs".to_string(), - zbus::zvariant::Value::from(vec![pid]) + zbus::zvariant::Value::from(vec![_pid]) .try_to_owned() .unwrap(), ), diff --git a/src/lib.rs b/src/lib.rs index 1eacb962..b1f893af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,6 +63,17 @@ pub mod font; #[doc(inline)] pub use iced; +#[doc(inline)] +pub use iced_core; +#[doc(inline)] +pub use iced_futures; +#[doc(inline)] +pub use iced_runtime; +#[doc(inline)] +pub use iced_widget; +#[doc(inline)] +#[cfg(feature = "wayland")] +pub use iced_winit; pub mod icon_theme; pub mod keyboard_nav; diff --git a/src/widget/button/icon.rs b/src/widget/button/icon.rs index 04d2bdd5..a0e06323 100644 --- a/src/widget/button/icon.rs +++ b/src/widget/button/icon.rs @@ -152,7 +152,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes ); } - let mut button = if builder.variant.vertical { + let button = if builder.variant.vertical { crate::widget::column::with_children(content) .padding(builder.padding) .spacing(builder.spacing) @@ -169,9 +169,11 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes }; #[cfg(feature = "a11y")] - { + let button = { + let mut button = button; button = button.name(builder.name).description(builder.description); - } + button + }; let button = button .padding(0) diff --git a/src/widget/button/image.rs b/src/widget/button/image.rs index ab51e667..c5a40fd5 100644 --- a/src/widget/button/image.rs +++ b/src/widget/button/image.rs @@ -83,7 +83,7 @@ where .width(builder.width) .height(builder.height); - let mut button = super::custom_image_button(content, builder.variant.on_remove) + let button = super::custom_image_button(content, builder.variant.on_remove) .padding(0) .selected(builder.variant.selected) .id(builder.id) @@ -91,9 +91,11 @@ where .class(builder.class); #[cfg(feature = "a11y")] - { + let button = { + let mut button = button; button = button.name(builder.name).description(builder.description); - } + button + }; button.into() } diff --git a/src/widget/button/link.rs b/src/widget/button/link.rs index 9ce81268..9bcd7487 100644 --- a/src/widget/button/link.rs +++ b/src/widget/button/link.rs @@ -66,7 +66,7 @@ pub fn icon() -> Handle { impl<'a, Message: Clone + 'static> From> for Element<'a, Message> { fn from(mut builder: Button<'a, Message>) -> Element<'a, Message> { - let mut button: super::Button<'a, Message> = row::with_capacity(2) + let button: super::Button<'a, Message> = row::with_capacity(2) .push({ // TODO: Avoid allocation crate::widget::text(builder.label.to_string()) @@ -94,13 +94,15 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .class(builder.class); #[cfg(feature = "a11y")] - { + let button = { + let mut button = button; if !builder.label.is_empty() { button = button.name(builder.label); } button = button.description(builder.description); - } + button + }; if builder.tooltip.is_empty() { button.into() diff --git a/src/widget/button/text.rs b/src/widget/button/text.rs index bcdd02ba..71cd1f53 100644 --- a/src/widget/button/text.rs +++ b/src/widget/button/text.rs @@ -119,7 +119,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .into() }); - let mut button: super::Button<'a, Message> = row::with_capacity(3) + let button: super::Button<'a, Message> = row::with_capacity(3) // Optional icon to place before label. .push_maybe(leading_icon) // Optional label between icons. @@ -138,13 +138,15 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .class(builder.class); #[cfg(feature = "a11y")] - { + let button = { + let mut button = button; if !builder.label.is_empty() { button = button.name(builder.label) } button = button.description(builder.description); - } + button + }; if builder.tooltip.is_empty() { button.into() diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index db055519..3ff0e7bf 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -26,7 +26,7 @@ pub fn spin_button<'a, T, M>( where T: Copy + Sub + Add + PartialOrd, { - let mut button = SpinButton::new( + let button = SpinButton::new( label, value, step, @@ -37,9 +37,11 @@ where ); #[cfg(feature = "a11y")] - { + let button = { + let mut button = button; button = button.name(name.into()); - } + button + }; button } @@ -57,7 +59,7 @@ pub fn vertical<'a, T, M>( where T: Copy + Sub + Add + PartialOrd, { - let mut button = SpinButton::new( + let button = SpinButton::new( label, value, step, @@ -68,9 +70,11 @@ where ); #[cfg(feature = "a11y")] - { + let button = { + let mut button = button; button = button.name(name.into()); - } + button + }; button } diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index b0647c89..903c81d1 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -2198,7 +2198,7 @@ pub fn update<'a, Message: Clone + 'static>( x, y, mime_types, - surface, + surface: _surface, }, )) if *rectangle == Some(dnd_id) => { cold(); @@ -2241,7 +2241,7 @@ pub fn update<'a, Message: Clone + 'static>( } } #[cfg(all(feature = "wayland", target_os = "linux"))] - Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y })) + Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y: _ })) if *rectangle == Some(dnd_id) => { let state = state(); From 7dd0ee835dc6942eae89938db7c2782c6c98af2f Mon Sep 17 00:00:00 2001 From: Hojjat Date: Fri, 1 May 2026 13:21:07 -0600 Subject: [PATCH 61/75] feat: reorderable flex row --- src/widget/mod.rs | 4 + src/widget/reorderable_flex_row/mod.rs | 8 + src/widget/reorderable_flex_row/widget.rs | 1296 +++++++++++++++++++++ 3 files changed, 1308 insertions(+) create mode 100644 src/widget/reorderable_flex_row/mod.rs create mode 100644 src/widget/reorderable_flex_row/widget.rs diff --git a/src/widget/mod.rs b/src/widget/mod.rs index a5e70788..d83a74e2 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -213,6 +213,10 @@ pub mod flex_row; #[doc(inline)] pub use flex_row::{FlexRow, flex_row}; +pub mod reorderable_flex_row; +#[doc(inline)] +pub use reorderable_flex_row::{ReorderableFlexRow, reorderable_flex_row}; + pub mod grid; #[doc(inline)] pub use grid::{Grid, grid}; diff --git a/src/widget/reorderable_flex_row/mod.rs b/src/widget/reorderable_flex_row/mod.rs new file mode 100644 index 00000000..d5da9f4a --- /dev/null +++ b/src/widget/reorderable_flex_row/mod.rs @@ -0,0 +1,8 @@ +// Copyright 2026 System76 +// SPDX-License-Identifier: MPL-2.0 + +//! A keyed wrapping flex row whose items can be dragged to reorder. + +mod widget; + +pub use widget::{ReorderableFlexRow, reorderable_flex_row}; diff --git a/src/widget/reorderable_flex_row/widget.rs b/src/widget/reorderable_flex_row/widget.rs new file mode 100644 index 00000000..ed95a34f --- /dev/null +++ b/src/widget/reorderable_flex_row/widget.rs @@ -0,0 +1,1296 @@ +// Copyright 2026 System76 +// SPDX-License-Identifier: MPL-2.0 + +use crate::{Element, Renderer}; +use iced::{Alignment, Pixels, alignment}; +use iced_core::event::Event; +use iced_core::layout::{self, Layout}; +use iced_core::mouse::{self, Cursor}; +use iced_core::widget::Operation; +use iced_core::widget::tree::{self, Tree}; +#[cfg(feature = "wgpu")] +use iced_core::{Background, Border, Shadow}; +use iced_core::{ + Clipboard, Length, Padding, Point, Rectangle, Renderer as _, Shell, Size, Vector, Widget, + overlay, renderer, window, +}; +use std::collections::{HashMap, HashSet}; +use std::hash::Hash; +use std::time::{Duration, Instant}; + +const DEFAULT_ANIMATION_DURATION: Duration = Duration::from_millis(180); +const DEFAULT_DRAG_LIFT: f32 = 10.0; +const DEFAULT_DRAG_THRESHOLD: f32 = 6.0; +const SHADOW_BLUR_RADIUS: f32 = 20.0; +const POSITION_EPSILON: f32 = 0.5; + +#[derive(Debug, Clone)] +struct SlotAnimation { + from: Point, + to: Point, + started_at: Option, +} + +impl SlotAnimation { + fn new(position: Point) -> Self { + Self { + from: position, + to: position, + started_at: None, + } + } + + fn current_position(&self, duration: Duration) -> Point { + let Some(started_at) = self.started_at else { + return self.to; + }; + + let duration_secs = duration.as_secs_f32(); + if duration_secs <= f32::EPSILON { + return self.to; + } + + let progress = (started_at.elapsed().as_secs_f32() / duration_secs).clamp(0.0, 1.0); + let eased = 1.0 - (1.0 - progress).powi(3); + + Point::new( + self.from.x + (self.to.x - self.from.x) * eased, + self.from.y + (self.to.y - self.from.y) * eased, + ) + } + + fn retarget(&mut self, new_target: Point, duration: Duration) { + if approx_eq_point(self.to, new_target) { + if !self.is_animating(duration) { + self.from = new_target; + self.to = new_target; + self.started_at = None; + } + return; + } + + self.from = self.current_position(duration); + self.to = new_target; + self.started_at = Some(Instant::now()); + } + + fn is_animating(&self, duration: Duration) -> bool { + self.started_at + .is_some_and(|started_at| started_at.elapsed() < duration) + } + + fn finish_if_done(&mut self, duration: Duration) { + if self + .started_at + .is_some_and(|started_at| started_at.elapsed() >= duration) + { + self.from = self.to; + self.started_at = None; + } + } +} + +#[derive(Debug, Clone)] +struct PendingDragState +where + Key: Clone + Eq + Hash + 'static, +{ + key: Key, + item_index: usize, + original_index: usize, + press_local: Point, + pointer_offset: Vector, +} + +#[derive(Debug, Clone)] +struct DragState +where + Key: Clone + Eq + Hash + 'static, +{ + key: Key, + item_index: usize, + original_index: usize, + current_index: usize, + cursor_local: Point, + pointer_offset: Vector, +} + +#[derive(Debug, Clone)] +struct State +where + Key: Clone + Eq + Hash + 'static, +{ + keys: Vec, + slot_positions: HashMap, + pending_drag: Option>, + drag: Option>, + wrap_width: f32, + initialized: bool, +} + +impl Default for State +where + Key: Clone + Eq + Hash + 'static, +{ + fn default() -> Self { + Self { + keys: Vec::new(), + slot_positions: HashMap::new(), + pending_drag: None, + drag: None, + wrap_width: f32::INFINITY, + initialized: false, + } + } +} + +impl State +where + Key: Clone + Eq + Hash + 'static, +{ + fn retain_keys(&mut self, keys: &[Key]) { + let keep: HashSet<_> = keys.iter().cloned().collect(); + self.slot_positions.retain(|key, _| keep.contains(key)); + + if self + .pending_drag + .as_ref() + .is_some_and(|drag| !keep.contains(&drag.key)) + { + self.pending_drag = None; + } + + if self + .drag + .as_ref() + .is_some_and(|drag| !keep.contains(&drag.key)) + { + self.drag = None; + } + } + + fn finish_animations(&mut self, duration: Duration) { + self.slot_positions + .values_mut() + .for_each(|slot| slot.finish_if_done(duration)); + } + + fn is_animating(&self, duration: Duration) -> bool { + self.slot_positions + .values() + .any(|slot| slot.is_animating(duration)) + } + + fn current_slot_position(&self, key: &Key, fallback: Point, duration: Duration) -> Point { + self.slot_positions + .get(key) + .map(|slot| slot.current_position(duration)) + .unwrap_or(fallback) + } + + fn retarget_slot(&mut self, key: &Key, target: Point, duration: Duration) { + self.slot_positions + .entry(key.clone()) + .or_insert_with(|| SlotAnimation::new(target)) + .retarget(target, duration); + } + + fn snap_slot(&mut self, key: &Key, target: Point) { + self.slot_positions + .insert(key.clone(), SlotAnimation::new(target)); + } + + fn apply_layout_position(&mut self, key: &Key, target: Point, duration: Duration) { + if self.initialized { + self.retarget_slot(key, target, duration); + } else { + self.snap_slot(key, target); + } + } +} + +#[derive(Debug, Clone)] +struct LocalSlot +where + Key: Clone + Eq + Hash + 'static, +{ + key: Key, + locked: bool, + bounds: Rectangle, +} + +/// A horizontal flex row with drag-to-reorder behavior. +#[must_use] +pub struct ReorderableFlexRow<'a, Key, Message> +where + Key: Clone + Eq + Hash + 'static, +{ + spacing: f32, + padding: Padding, + width: Length, + height: Length, + align: Alignment, + clip: bool, + drag_lift: f32, + animation_duration: Duration, + on_reorder: Box) -> Message + 'a>, + keys: Vec, + locked: Vec, + children: Vec>, +} + +impl<'a, Key, Message> ReorderableFlexRow<'a, Key, Message> +where + Key: Clone + Eq + Hash + 'static, +{ + pub fn new(on_reorder: impl Fn(Vec) -> Message + 'a) -> Self { + Self { + spacing: 0.0, + padding: Padding::ZERO, + width: Length::Shrink, + height: Length::Shrink, + align: Alignment::Start, + clip: false, + drag_lift: DEFAULT_DRAG_LIFT, + animation_duration: DEFAULT_ANIMATION_DURATION, + on_reorder: Box::new(on_reorder), + keys: Vec::new(), + locked: Vec::new(), + children: Vec::new(), + } + } + + pub fn with_capacity(capacity: usize, on_reorder: impl Fn(Vec) -> Message + 'a) -> Self { + let mut row = Self::new(on_reorder); + row.keys = Vec::with_capacity(capacity); + row.locked = Vec::with_capacity(capacity); + row.children = Vec::with_capacity(capacity); + row + } + + pub fn spacing(mut self, amount: impl Into) -> Self { + self.spacing = amount.into().0; + self + } + + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } + + pub fn align_y(mut self, align: impl Into) -> Self { + self.align = Alignment::from(align.into()); + self + } + + /// Leave disabled for dragged item to visibly lift above the row. + pub fn clip(mut self, clip: bool) -> Self { + self.clip = clip; + self + } + + pub fn drag_lift(mut self, lift: f32) -> Self { + self.drag_lift = lift.max(0.0); + self + } + + pub fn animation_duration(mut self, duration: Duration) -> Self { + self.animation_duration = duration; + self + } + + pub fn push(self, key: Key, child: impl Into>) -> Self { + self.push_with_lock(key, false, child) + } + + /// Item stays fixed, never participates in reordering. + pub fn push_locked(self, key: Key, child: impl Into>) -> Self { + self.push_with_lock(key, true, child) + } + + fn push_with_lock( + mut self, + key: Key, + locked: bool, + child: impl Into>, + ) -> Self { + let child = child.into(); + let child_size = child.as_widget().size_hint(); + + if !child_size.is_void() { + self.width = self.width.enclose(child_size.width); + self.height = self.height.enclose(child_size.height); + self.keys.push(key); + self.locked.push(locked); + self.children.push(child); + } + + self + } + + pub fn extend(self, items: impl IntoIterator)>) -> Self { + items + .into_iter() + .fold(self, |row, (key, child)| row.push(key, child)) + } + + pub fn extend_locked( + self, + items: impl IntoIterator)>, + ) -> Self { + items + .into_iter() + .fold(self, |row, (key, child)| row.push_locked(key, child)) + } + + fn item_local_slots_from_layout( + &self, + bounds: Rectangle, + child_layouts: &[Layout<'_>], + ) -> Vec> { + self.keys + .iter() + .zip(self.locked.iter()) + .zip(child_layouts.iter()) + .map(|((key, locked), child_layout)| { + let child_bounds = child_layout.bounds(); + LocalSlot { + key: key.clone(), + locked: *locked, + bounds: Rectangle { + x: child_bounds.x - bounds.x, + y: child_bounds.y - bounds.y, + width: child_bounds.width, + height: child_bounds.height, + }, + } + }) + .collect() + } + + fn sync_slot_positions(&self, state: &mut State, slots: &[LocalSlot]) { + let ordered_keys: Vec = slots.iter().map(|slot| slot.key.clone()).collect(); + state.retain_keys(&ordered_keys); + + let size_by_key: HashMap = slots + .iter() + .map(|slot| { + ( + slot.key.clone(), + Size::new(slot.bounds.width, slot.bounds.height), + ) + }) + .collect(); + let locked_by_key: HashMap = slots + .iter() + .map(|slot| (slot.key.clone(), slot.locked)) + .collect(); + + let Some(drag_snapshot) = state.drag.as_ref().map(|drag| { + ( + drag.key.clone(), + drag.cursor_local, + drag.pointer_offset, + drag.item_index, + ) + }) else { + for slot in slots { + state.apply_layout_position( + &slot.key, + Point::new(slot.bounds.x, slot.bounds.y), + self.animation_duration, + ); + } + return; + }; + + let (drag_key, cursor_local, pointer_offset, drag_item_index) = drag_snapshot; + + let Some(dragged_slot) = slots.iter().find(|slot| slot.key == drag_key) else { + state.drag = None; + for slot in slots { + state.apply_layout_position( + &slot.key, + Point::new(slot.bounds.x, slot.bounds.y), + self.animation_duration, + ); + } + return; + }; + + if dragged_slot.locked { + state.drag = None; + return; + } + + let dragged_center = Point::new( + cursor_local.x - pointer_offset.x + dragged_slot.bounds.width / 2.0, + cursor_local.y - pointer_offset.y + dragged_slot.bounds.height / 2.0, + ); + let target_index = target_index_for_drag(slots, &drag_key, dragged_center); + let prior_index = state.drag.as_ref().map(|drag| drag.current_index); + + if let Some(drag) = state.drag.as_mut() { + drag.current_index = target_index; + drag.item_index = drag_item_index; + } + + if prior_index == Some(target_index) { + return; + } + + let reordered_keys = + reordered_keys_for_drag(&ordered_keys, &locked_by_key, &drag_key, target_index); + let (target_slots, _) = compute_wrapped_slots( + &reordered_keys, + &locked_by_key, + &size_by_key, + state.wrap_width, + self.padding, + self.spacing, + self.align, + ); + let target_positions: HashMap = target_slots + .iter() + .map(|slot| (slot.key.clone(), Point::new(slot.bounds.x, slot.bounds.y))) + .collect(); + + for slot in slots { + let target = target_positions + .get(&slot.key) + .copied() + .unwrap_or(Point::new(slot.bounds.x, slot.bounds.y)); + state.retarget_slot(&slot.key, target, self.animation_duration); + } + } +} + +impl<'a, Key, Message> Widget + for ReorderableFlexRow<'a, Key, Message> +where + Key: Clone + Eq + Hash + 'static, + Message: 'a, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + tree::State::new(State { + keys: self.keys.clone(), + ..State::default() + }) + } + + fn children(&self) -> Vec { + self.children.iter().map(Tree::new).collect() + } + + fn diff(&mut self, tree: &mut Tree) { + let Tree { + state, children, .. + } = tree; + let state = state.downcast_mut::>(); + let previous_keys = state.keys.clone(); + let previous_children = std::mem::take(children); + let mut previous_by_key = HashMap::with_capacity(previous_keys.len()); + + for (key, child_tree) in previous_keys.into_iter().zip(previous_children) { + previous_by_key.insert(key, child_tree); + } + + children.reserve(self.children.len()); + + for (key, child) in self.keys.iter().cloned().zip(self.children.iter_mut()) { + let mut child_tree = previous_by_key + .remove(&key) + .unwrap_or_else(|| Tree::new(child.as_widget())); + child.as_widget_mut().diff(&mut child_tree); + children.push(child_tree); + } + + if state.keys != self.keys { + state.keys.clone_from(&self.keys); + } + state.retain_keys(&self.keys); + } + + fn size(&self) -> Size { + Size { + width: self.width, + height: self.height, + } + } + + fn layout( + &mut self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits + .width(self.width) + .height(self.height) + .shrink(self.padding); + let child_limits = limits.loose(); + let wrap_width = limits.max().width; + + let mut nodes = Vec::with_capacity(self.children.len()); + let mut size_by_key = HashMap::with_capacity(self.children.len()); + let locked_by_key: HashMap = self + .keys + .iter() + .cloned() + .zip(self.locked.iter().copied()) + .collect(); + + for ((key, child), child_tree) in self + .keys + .iter() + .zip(self.children.iter_mut()) + .zip(tree.children.iter_mut()) + { + let node = child + .as_widget_mut() + .layout(child_tree, renderer, &child_limits); + size_by_key.insert(key.clone(), node.size()); + nodes.push(node); + } + + let (slots, intrinsic_size) = compute_wrapped_slots( + &self.keys, + &locked_by_key, + &size_by_key, + wrap_width, + self.padding, + self.spacing, + self.align, + ); + + for (node, slot) in nodes.iter_mut().zip(&slots) { + node.move_to_mut(Point::new(slot.bounds.x, slot.bounds.y)); + } + + let size = limits.resolve(self.width, self.height, intrinsic_size); + let node = layout::Node::with_children(size.expand(self.padding), nodes); + let state = tree.state.downcast_mut::>(); + state.wrap_width = wrap_width; + self.sync_slot_positions(state, &slots); + + node + } + + fn operate( + &mut self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn Operation, + ) { + operation.container(None, layout.bounds()); + operation.traverse(&mut |operation| { + self.children + .iter_mut() + .zip(&mut tree.children) + .zip(layout.children()) + .for_each(|((child, state), child_layout)| { + child.as_widget_mut().operate( + state, + child_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + operation, + ); + }); + }); + } + + fn update( + &mut self, + tree: &mut Tree, + event: &Event, + layout: Layout<'_>, + cursor: Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + let state = tree.state.downcast_mut::>(); + let child_layouts: Vec<_> = layout.children().collect(); + + if let Event::Window(window::Event::RedrawRequested(_)) = event { + state.initialized = true; + state.finish_animations(self.animation_duration); + if state.drag.is_some() || state.is_animating(self.animation_duration) { + shell.request_redraw(); + } + } + + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { + if state.drag.is_none() + && state.pending_drag.is_none() + && let Some(cursor_pos) = cursor.position() + && let Some((index, child_layout)) = child_layouts + .iter() + .enumerate() + .find(|(_, child_layout)| child_layout.bounds().contains(cursor_pos)) + && !self.locked.get(index).copied().unwrap_or(false) + && let Some(reorder_index) = reorderable_index_for_child(&self.locked, index) + { + let child_bounds = child_layout.bounds(); + state.pending_drag = Some(PendingDragState { + key: self.keys[index].clone(), + item_index: index, + original_index: reorder_index, + press_local: Point::new(cursor_pos.x - bounds.x, cursor_pos.y - bounds.y), + pointer_offset: Vector::new( + cursor_pos.x - child_bounds.x, + cursor_pos.y - child_bounds.y, + ), + }); + } + } + Event::Mouse(mouse::Event::CursorMoved { .. }) => { + if let Some(pending) = state.pending_drag.clone() + && let Some(cursor_pos) = cursor.position() + { + let cursor_local = Point::new(cursor_pos.x - bounds.x, cursor_pos.y - bounds.y); + let delta = Vector::new( + cursor_local.x - pending.press_local.x, + cursor_local.y - pending.press_local.y, + ); + let distance = (delta.x.powi(2) + delta.y.powi(2)).sqrt(); + + if distance >= DEFAULT_DRAG_THRESHOLD { + if let (Some(child), Some(child_tree), Some(child_layout)) = ( + self.children.get_mut(pending.item_index), + tree.children.get_mut(pending.item_index), + child_layouts.get(pending.item_index), + ) { + let cursor_left = Event::Mouse(mouse::Event::CursorLeft); + child.as_widget_mut().update( + child_tree, + &cursor_left, + child_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } + + state.pending_drag = None; + state.drag = Some(DragState { + key: pending.key, + item_index: pending.item_index, + original_index: pending.original_index, + current_index: pending.original_index, + cursor_local, + pointer_offset: pending.pointer_offset, + }); + let slots = self.item_local_slots_from_layout(bounds, &child_layouts); + self.sync_slot_positions(state, &slots); + shell.capture_event(); + shell.request_redraw(); + return; + } + } + + if let Some(drag) = state.drag.as_mut() + && let Some(cursor_pos) = cursor.position() + { + drag.cursor_local = + Point::new(cursor_pos.x - bounds.x, cursor_pos.y - bounds.y); + let slots = self.item_local_slots_from_layout(bounds, &child_layouts); + self.sync_slot_positions(state, &slots); + shell.capture_event(); + shell.request_redraw(); + return; + } + } + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { + state.pending_drag = None; + + if state.drag.is_some() { + let slots = self.item_local_slots_from_layout(bounds, &child_layouts); + self.sync_slot_positions(state, &slots); + } + if let Some(drag) = state.drag.take() { + if drag.current_index != drag.original_index { + let locked_by_key: HashMap = self + .keys + .iter() + .cloned() + .zip(self.locked.iter().copied()) + .collect(); + let new_order = reordered_keys_for_drag( + &self.keys, + &locked_by_key, + &drag.key, + drag.current_index, + ); + shell.publish((self.on_reorder)(new_order)); + } + shell.capture_event(); + shell.request_redraw(); + return; + } + } + _ => {} + } + + if state.drag.is_some() { + return; + } + + for ((item, tree), child_layout) in self + .children + .iter_mut() + .zip(&mut tree.children) + .zip(child_layouts.into_iter()) + { + item.as_widget_mut().update( + tree, + event, + child_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + let state = tree.state.downcast_ref::>(); + + if state.drag.is_some() { + return mouse::Interaction::Grabbing; + } + + if let Some(cursor_pos) = cursor.position() + && self + .locked + .iter() + .zip(layout.children()) + .any(|(locked, child_layout)| { + !*locked && child_layout.bounds().contains(cursor_pos) + }) + { + return mouse::Interaction::Grab; + } + + self.children + .iter() + .zip(&tree.children) + .zip(layout.children()) + .map(|((child, tree), child_layout)| { + child.as_widget().mouse_interaction( + tree, + child_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, + renderer, + ) + }) + .max() + .unwrap_or_default() + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &crate::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: Cursor, + viewport: &Rectangle, + ) { + let state = tree.state.downcast_ref::>(); + let bounds = layout.bounds(); + + let Some(clipped_viewport) = bounds.intersection(viewport) else { + return; + }; + + let viewport = if self.clip { + &clipped_viewport + } else { + viewport + }; + let drag_key = state.drag.as_ref().map(|drag| &drag.key); + let drag_item = state.drag.as_ref().and_then(|drag| { + self.keys + .iter() + .zip(&self.children) + .zip(&tree.children) + .zip(layout.children()) + .find_map(|(((key, child), state), child_layout)| { + (key == &drag.key).then_some((key, child, state, child_layout, drag)) + }) + }); + + for (((key, child), child_tree), child_layout) in self + .keys + .iter() + .zip(&self.children) + .zip(&tree.children) + .zip(layout.children()) + { + if drag_key.is_some_and(|drag_key| drag_key == key) { + continue; + } + + let child_layout = child_layout.with_virtual_offset(layout.virtual_offset()); + let child_bounds = child_layout.bounds(); + let base_local = Point::new(child_bounds.x - bounds.x, child_bounds.y - bounds.y); + let target_local = + state.current_slot_position(key, base_local, self.animation_duration); + let translation = + Vector::new(target_local.x - base_local.x, target_local.y - base_local.y); + let translated_bounds = translate_rect(child_bounds, translation); + + if translated_bounds.intersects(viewport) { + renderer.with_translation(translation, |renderer| { + child.as_widget().draw( + child_tree, + renderer, + theme, + style, + child_layout, + cursor, + viewport, + ); + }); + } + } + + if let Some((_key, child, child_tree, child_layout, drag)) = drag_item { + let child_layout = child_layout.with_virtual_offset(layout.virtual_offset()); + let child_bounds = child_layout.bounds(); + let base_local = Point::new(child_bounds.x - bounds.x, child_bounds.y - bounds.y); + let drag_local = Point::new( + drag.cursor_local.x - drag.pointer_offset.x, + drag.cursor_local.y - drag.pointer_offset.y - self.drag_lift, + ); + let translation = Vector::new(drag_local.x - base_local.x, drag_local.y - base_local.y); + + #[cfg(feature = "wgpu")] + { + let translated_bounds = translate_rect(child_bounds, translation); + draw_drag_backdrop(renderer, theme, translated_bounds); + } + + renderer.with_translation(translation, |renderer| { + child.as_widget().draw( + child_tree, + renderer, + theme, + style, + child_layout, + cursor, + viewport, + ); + }); + } + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'b>, + renderer: &Renderer, + viewport: &Rectangle, + translation: Vector, + ) -> Option> { + overlay::from_children( + &mut self.children, + tree, + layout, + renderer, + viewport, + translation, + ) + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, + ) { + for ((item, child_layout), child_state) in self + .children + .iter() + .zip(layout.children()) + .zip(state.children.iter()) + { + item.as_widget().drag_destinations( + child_state, + child_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + dnd_rectangles, + ); + } + } +} + +impl<'a, Key, Message> From> for Element<'a, Message> +where + Key: Clone + Eq + Hash + 'static, + Message: 'a, +{ + fn from(row: ReorderableFlexRow<'a, Key, Message>) -> Self { + Element::new(row) + } +} + +/// Create a horizontal flex row with drag-to-reorder behavior. +pub fn reorderable_flex_row<'a, Key, Message>( + on_reorder: impl Fn(Vec) -> Message + 'a, +) -> ReorderableFlexRow<'a, Key, Message> +where + Key: Clone + Eq + Hash + 'static, +{ + ReorderableFlexRow::new(on_reorder) +} + +fn compute_wrapped_slots( + ordered_keys: &[Key], + locked_by_key: &HashMap, + size_by_key: &HashMap, + wrap_width: f32, + padding: Padding, + spacing: f32, + align: Alignment, +) -> (Vec>, Size) +where + Key: Clone + Eq + Hash + 'static, +{ + let wrap_width = if wrap_width.is_finite() { + wrap_width.max(0.0) + } else { + f32::INFINITY + }; + + let mut slots = Vec::with_capacity(ordered_keys.len()); + let mut intrinsic_size = Size::ZERO; + let mut row_start = 0; + let mut row_height = 0.0; + let mut x = 0.0; + let mut y = 0.0; + + let align_factor = match align { + Alignment::Start => 0.0, + Alignment::Center => 2.0, + Alignment::End => 1.0, + }; + + let align_row = + |range: std::ops::Range, row_height: f32, slots: &mut [LocalSlot]| { + if align_factor == 0.0 { + return; + } + + for slot in &mut slots[range] { + slot.bounds.y += (row_height - slot.bounds.height) / align_factor; + } + }; + + for (index, key) in ordered_keys.iter().enumerate() { + let size = size_by_key.get(key).copied().unwrap_or(Size::ZERO); + + if x != 0.0 && x + size.width > wrap_width { + intrinsic_size.width = intrinsic_size.width.max(x - spacing); + align_row(row_start..index, row_height, &mut slots); + y += row_height + spacing; + x = 0.0; + row_start = index; + row_height = 0.0; + } + + row_height = row_height.max(size.height); + + slots.push(LocalSlot { + key: key.clone(), + locked: locked_by_key.get(key).copied().unwrap_or(false), + bounds: Rectangle { + x: x + padding.left, + y: y + padding.top, + width: size.width, + height: size.height, + }, + }); + + x += size.width + spacing; + } + + if x != 0.0 { + intrinsic_size.width = intrinsic_size.width.max(x - spacing); + } + + intrinsic_size.height = y + row_height; + align_row(row_start..slots.len(), row_height, &mut slots); + + (slots, intrinsic_size) +} + +fn reordered_keys_for_drag( + ordered_keys: &[Key], + locked_by_key: &HashMap, + dragged_key: &Key, + target_index: usize, +) -> Vec +where + Key: Clone + Eq + Hash + 'static, +{ + let movable_keys: Vec = ordered_keys + .iter() + .filter(|key| !locked_by_key.get(*key).copied().unwrap_or(false)) + .cloned() + .collect(); + let mut remaining: Vec = movable_keys + .iter() + .filter(|key| *key != dragged_key) + .cloned() + .collect(); + + remaining.insert(target_index.min(remaining.len()), dragged_key.clone()); + merge_locked_and_reordered_keys(ordered_keys, locked_by_key, &remaining) +} + +/// Picks insertion index among movable items using row-aware midpoint rule. +/// +/// Walks laid-out slots in reading order, counting how many non-dragged movable +/// items the cursor has moved past. Skips locked slots. O(n) single pass, no +/// allocations. +fn target_index_for_drag( + slots: &[LocalSlot], + dragged_key: &Key, + dragged_center: Point, +) -> usize +where + Key: Clone + Eq + Hash + 'static, +{ + let mut target = 0; + let mut passed_movable: usize = 0; + let mut found_target = false; + + let mut i = 0; + while i < slots.len() { + let slot = &slots[i]; + + if slot.locked || &slot.key == dragged_key { + i += 1; + continue; + } + + let row_top = slot.bounds.y; + let row_bottom = row_top + slot.bounds.height; + + if !found_target && dragged_center.y < row_top { + target = passed_movable; + found_target = true; + break; + } + + if dragged_center.y > row_bottom { + passed_movable += 1; + i += 1; + continue; + } + + let center_x = slot.bounds.x + slot.bounds.width / 2.0; + if dragged_center.x < center_x { + target = passed_movable; + found_target = true; + break; + } + + passed_movable += 1; + i += 1; + } + + if !found_target { + target = passed_movable; + } + + target +} + +fn reorderable_index_for_child(locked: &[bool], item_index: usize) -> Option { + (!locked.get(item_index).copied().unwrap_or(false)).then(|| { + locked[..item_index] + .iter() + .filter(|is_locked| !**is_locked) + .count() + }) +} + +fn merge_locked_and_reordered_keys( + ordered_keys: &[Key], + locked_by_key: &HashMap, + reordered_movable_keys: &[Key], +) -> Vec +where + Key: Clone + Eq + Hash + 'static, +{ + let mut movable = reordered_movable_keys.iter(); + + ordered_keys + .iter() + .map(|key| { + if locked_by_key.get(key).copied().unwrap_or(false) { + key.clone() + } else { + movable.next().cloned().unwrap_or_else(|| key.clone()) + } + }) + .collect() +} + +fn approx_eq_point(a: Point, b: Point) -> bool { + (a.x - b.x).abs() <= POSITION_EPSILON && (a.y - b.y).abs() <= POSITION_EPSILON +} + +fn translate_rect(bounds: Rectangle, translation: Vector) -> Rectangle { + Rectangle { + x: bounds.x + translation.x, + y: bounds.y + translation.y, + width: bounds.width, + height: bounds.height, + } +} + +#[cfg(feature = "wgpu")] +fn draw_drag_backdrop(renderer: &mut Renderer, theme: &crate::Theme, bounds: Rectangle) { + let cosmic = theme.cosmic(); + let backdrop = iced::Color { + a: 0.08, + ..iced::Color::from(cosmic.bg_component_color()) + }; + + renderer.fill_quad( + renderer::Quad { + bounds, + border: Border { + radius: cosmic.corner_radii.radius_m.into(), + ..Border::default() + }, + shadow: Shadow { + color: cosmic.shade.into(), + offset: Vector::new(0.0, 8.0), + blur_radius: SHADOW_BLUR_RADIUS, + }, + snap: true, + }, + Background::Color(backdrop), + ); +} + +#[cfg(test)] +mod tests { + use super::{compute_wrapped_slots, reordered_keys_for_drag, target_index_for_drag}; + use iced::{Alignment, Padding, Point, Size}; + use std::collections::HashMap; + + fn size_map(keys: &[usize], width: f32, height: f32) -> HashMap { + keys.iter() + .copied() + .map(|key| (key, Size::new(width, height))) + .collect() + } + + fn locked_map(keys: &[usize], locked_keys: &[usize]) -> HashMap { + keys.iter() + .copied() + .map(|key| (key, locked_keys.contains(&key))) + .collect() + } + + #[test] + fn compute_wrapped_slots_creates_new_rows() { + let ordered_keys = vec![0, 1, 2]; + let locked_by_key = locked_map(&ordered_keys, &[]); + let size_by_key = size_map(&ordered_keys, 100.0, 40.0); + let (slots, intrinsic_size) = compute_wrapped_slots( + &ordered_keys, + &locked_by_key, + &size_by_key, + 220.0, + Padding::ZERO, + 10.0, + Alignment::Start, + ); + + assert_eq!(slots[0].bounds.x, 0.0); + assert_eq!(slots[0].bounds.y, 0.0); + assert_eq!(slots[1].bounds.x, 110.0); + assert_eq!(slots[1].bounds.y, 0.0); + assert_eq!(slots[2].bounds.x, 0.0); + assert_eq!(slots[2].bounds.y, 50.0); + assert_eq!(intrinsic_size.width, 210.0); + assert_eq!(intrinsic_size.height, 90.0); + } + + #[test] + fn reordered_keys_for_drag_inserts_key_at_target_index() { + let keys = [0, 1, 2, 3]; + let locked_by_key = locked_map(&keys, &[]); + let reordered = reordered_keys_for_drag(&keys, &locked_by_key, &0, 3); + assert_eq!(reordered, vec![1, 2, 3, 0]); + } + + #[test] + fn target_index_tracks_wrapped_drop_positions() { + let ordered_keys = vec![0, 1, 2, 3]; + let locked_by_key = locked_map(&ordered_keys, &[]); + let size_by_key = size_map(&ordered_keys, 100.0, 40.0); + + let (slots, _) = compute_wrapped_slots( + &ordered_keys, + &locked_by_key, + &size_by_key, + 220.0, + Padding::ZERO, + 10.0, + Alignment::Start, + ); + + let target_index = target_index_for_drag(&slots, &0, Point::new(160.0, 70.0)); + + assert_eq!(target_index, 3); + } + + #[test] + fn reordered_keys_for_drag_preserves_locked_positions() { + let keys = [10, 11, 12, 13]; + let locked_by_key = locked_map(&keys, &[10, 13]); + let reordered = reordered_keys_for_drag(&keys, &locked_by_key, &11, 1); + + assert_eq!(reordered, vec![10, 12, 11, 13]); + } +} From b1b8203a3b39f8e4ff3c3b5e17b59d82b4660382 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Sun, 24 May 2026 10:01:43 +0200 Subject: [PATCH 62/75] fix: gate reorderable flex row shadow constant --- src/widget/reorderable_flex_row/widget.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/widget/reorderable_flex_row/widget.rs b/src/widget/reorderable_flex_row/widget.rs index ed95a34f..03789877 100644 --- a/src/widget/reorderable_flex_row/widget.rs +++ b/src/widget/reorderable_flex_row/widget.rs @@ -21,6 +21,7 @@ use std::time::{Duration, Instant}; const DEFAULT_ANIMATION_DURATION: Duration = Duration::from_millis(180); const DEFAULT_DRAG_LIFT: f32 = 10.0; const DEFAULT_DRAG_THRESHOLD: f32 = 6.0; +#[cfg(feature = "wgpu")] const SHADOW_BLUR_RADIUS: f32 = 20.0; const POSITION_EPSILON: f32 = 0.5; From 8a1d3cf10a0a9b11b1b5246b724a6e0f129bdbec Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Mon, 25 May 2026 09:55:11 +0200 Subject: [PATCH 63/75] yoda: use local dbus settings bindings --- Cargo.toml | 2 +- cosmic-config/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 85847c4c..64172899 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -176,7 +176,7 @@ float-cmp = "0.10.0" # Enable DBus feature on Linux targets [target.'cfg(target_os = "linux")'.dependencies] cosmic-config = { path = "cosmic-config", features = ["dbus"] } -cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" } +cosmic-settings-daemon = { path = "../dbus-settings-bindings/cosmic-settings-daemon" } zbus = { version = "5.14.0", default-features = false } [target.'cfg(all(unix, not(target_os = "macos")))'.dependencies] diff --git a/cosmic-config/Cargo.toml b/cosmic-config/Cargo.toml index 0a7653e0..2a626f6b 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -10,7 +10,7 @@ macro = ["cosmic-config-derive"] subscription = ["iced_futures"] [dependencies] -cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } +cosmic-settings-daemon = { path = "../../dbus-settings-bindings/cosmic-settings-daemon", optional = true } zbus = { version = "5.14.0", default-features = false, optional = true } atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" } calloop = { version = "0.14.4", optional = true } From b284e754fda88bd8f82a06248af6eecae6c137ba Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Mon, 25 May 2026 11:37:02 +0200 Subject: [PATCH 64/75] chore: update iced submodule pointer --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index f4870187..81639935 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit f4870187088a14d398f2075c5ccda611387dcdcc +Subproject commit 81639935398a856f3164dc406fbac78922c258fc From ea17ada93159057d81dc537a4f769c473abffb47 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Mon, 25 May 2026 13:01:53 +0200 Subject: [PATCH 65/75] perf: throttle malloc_trim + avoid VecDeque clones (squashed) Squash of 2 yoda commits: - 77262dd0 perf(malloc): throttle malloc_trim to 1 Hz in hot paths - 1d98eee6 perf(widget): avoid VecDeque clone in segmented_button/table Model::clear --- src/malloc.rs | 34 +++++++++++++++++++++--- src/widget/segmented_button/model/mod.rs | 5 +++- src/widget/table/model/mod.rs | 5 +++- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/malloc.rs b/src/malloc.rs index b99a66f4..bc5e8353 100644 --- a/src/malloc.rs +++ b/src/malloc.rs @@ -1,21 +1,49 @@ // Copyright 2025 System76 // SPDX-License-Identifier: MPL-2.0 +use std::cell::Cell; use std::os::raw::c_int; +use std::time::{Duration, Instant}; const M_MMAP_THRESHOLD: c_int = -3; +/// Minimum interval between two actual `malloc_trim` calls. +/// +/// `trim` is called at the end of every `update()` and `view()`, which can +/// reach 60-200 Hz during typical scrolling, resize, or animation. Each +/// `malloc_trim` walks the glibc heap (10 µs to several ms depending on +/// fragmentation), so calling it at render frequency can consume a +/// substantial fraction of the frame budget. Throttling to 1 Hz keeps RSS +/// bounded while removing the per-frame syscall from the hot path. +const TRIM_MIN_INTERVAL: Duration = Duration::from_millis(1000); + +thread_local! { + static LAST_TRIM: Cell> = const { Cell::new(None) }; +} + unsafe extern "C" { fn malloc_trim(pad: usize); fn mallopt(param: c_int, value: c_int) -> c_int; } +/// Throttled wrapper over `malloc_trim`. Safe to call at render frequency: +/// consecutive calls within `TRIM_MIN_INTERVAL` (per-thread) skip the syscall. #[inline] pub fn trim(pad: usize) { - unsafe { - malloc_trim(pad); - } + LAST_TRIM.with(|last| { + let now = Instant::now(); + let should_trim = match last.get() { + None => true, + Some(prev) => now.duration_since(prev) >= TRIM_MIN_INTERVAL, + }; + if should_trim { + last.set(Some(now)); + unsafe { + malloc_trim(pad); + } + } + }); } /// Prevents glibc from hoarding memory via memory fragmentation. diff --git a/src/widget/segmented_button/model/mod.rs b/src/widget/segmented_button/model/mod.rs index e0dd8c54..fff1cf6b 100644 --- a/src/widget/segmented_button/model/mod.rs +++ b/src/widget/segmented_button/model/mod.rs @@ -132,7 +132,10 @@ where /// ``` #[inline] pub fn clear(&mut self) { - for entity in self.order.clone() { + // `remove()` mutates `self.order`, so transfer ownership first: + // the inner `self.order.remove(index)` then no-ops because + // `position()` can't find the entity in the empty VecDeque. + for entity in std::mem::take(&mut self.order) { self.remove(entity); } } diff --git a/src/widget/table/model/mod.rs b/src/widget/table/model/mod.rs index 93998f9f..f0e5e61c 100644 --- a/src/widget/table/model/mod.rs +++ b/src/widget/table/model/mod.rs @@ -98,7 +98,10 @@ where /// model.clear(); /// ``` pub fn clear(&mut self) { - for entity in self.order.clone() { + // `remove()` mutates `self.order`, so transfer ownership first: + // the inner `self.order.remove(index)` then no-ops because + // `position()` can't find the entity in the empty VecDeque. + for entity in std::mem::take(&mut self.order) { self.remove(entity); } } From 87510782aeb0aa732c439df8fbf336150512d7fb Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Mon, 25 May 2026 13:01:53 +0200 Subject: [PATCH 66/75] feat(segmented_button): on_double_click + internal tab reorder (squashed) Squash of 2 yoda commits: - 108441ef segmented_button: add on_double_click callback - a322516f segmented_button: fix internal tab reorder end-to-end --- src/widget/segmented_button/widget.rs | 67 ++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index edd58eb7..aa5d1624 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -175,6 +175,10 @@ where pub(super) on_context: Option Message + 'static>>, #[setters(skip)] pub(super) on_middle_press: Option Message + 'static>>, + /// Emits the ID of the item that was double-clicked with the left button. + /// Fires in addition to `on_activate` (which keeps firing on each click). + #[setters(skip)] + pub(super) on_double_click: Option Message + 'static>>, #[setters(skip)] pub(super) on_dnd_drop: Option, String, DndAction) -> Message + 'static>>, @@ -234,6 +238,7 @@ where on_close: None, on_context: None, on_middle_press: None, + on_double_click: None, on_dnd_drop: None, on_dnd_enter: None, on_dnd_leave: None, @@ -356,6 +361,16 @@ where self } + /// Emitted when a tab is double-clicked with the left mouse button. + /// Fires in addition to `on_activate`, which keeps firing on each click. + pub fn on_double_click(mut self, on_double_click: T) -> Self + where + T: Fn(Entity) -> Message + 'static, + { + self.on_double_click = Some(Box::new(on_double_click)); + self + } + /// Enable drag-and-drop support for tabs using the provided payload builder. pub fn enable_tab_drag(mut self, mime: String) -> Self { self.tab_drag = Some(TabDragSource::new(mime)); @@ -393,11 +408,12 @@ where { return None; } - let position = state - .drop_hint - .filter(|hint| hint.entity == target) - .map(|hint| InsertPosition::from(hint.side)) - .unwrap_or_else(|| self.default_insert_position(dragged, target)); + // Always use positional swap (Konsole/Firefox/Chrome semantics): + // dropping onto any part of a different tab swaps it with the dragged + // tab. drop_hint.side-based Before/After is counter-intuitive: dropping + // A (pos 0) on the left half of B (pos 1) resolves to "Before B" which, + // after removing A, lands at pos 0 — so the tab appears not to move. + let position = self.default_insert_position(dragged, target); Some(ReorderEvent { dragged, target, @@ -914,6 +930,7 @@ where hovered: Default::default(), known_length: Default::default(), middle_clicked: Default::default(), + last_click: None, internal_layout: Default::default(), context_cursor: Point::default(), show_context: Default::default(), @@ -1187,7 +1204,14 @@ where .dnd_state .drag_offer .as_ref() - .is_some_and(|offer| offer.selected_action.contains(DndAction::Move)); + .is_some_and(|offer| offer.selected_action.contains(DndAction::Move)) + // Self-drop fallback: some compositors (cosmic-comp + // observed) do not emit OfferEvent::SelectedAction for + // internal drags, leaving selected_action empty. + // dragging_tab is only set by start_tab_drag on this + // same widget, so this covers the self-drop case + // safely; mime and on_reorder are checked below. + || state.dragging_tab.is_some(); let pending_reorder = if allow_reorder && self.on_reorder.is_some() && self.tab_drag.as_ref().is_some_and(|d| d.mime == *mime_type) @@ -1385,6 +1409,33 @@ where state.set_focused(); state.focused_item = Item::Tab(key); state.pressed_item = None; + + // Double-click detection on the same entity + // within 400 ms — fires after on_activate so + // the tab is already focused when the handler + // runs. + if let Some(on_double_click) = + self.on_double_click.as_ref() + { + let now = Instant::now(); + let is_double = match state.last_click { + Some((prev, t)) => { + prev == key + && now.duration_since(t) + < Duration::from_millis(400) + } + None => false, + }; + state.last_click = if is_double { + None + } else { + Some((key, now)) + }; + if is_double { + shell.publish(on_double_click(key)); + } + } + shell.capture_event(); return; } @@ -2391,6 +2442,9 @@ pub struct LocalState { hovered: Item, /// The ID of the button that was middle-clicked, but not yet released. middle_clicked: Option, + /// Entity and timestamp of the most recent left-click activation, used + /// to detect double-clicks on the same tab. + last_click: Option<(Entity, Instant)>, /// Last known length of the model. pub(super) known_length: usize, /// Dimensions of internal buttons when shrinking @@ -2536,6 +2590,7 @@ mod tests { hovered: Item::default(), known_length: 0, middle_clicked: None, + last_click: None, internal_layout: Vec::new(), context_cursor: Point::ORIGIN, show_context: None, From 10422b8f4a2c1fbe11566a028be0bd27355d3d75 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Wed, 22 Apr 2026 15:08:12 +0200 Subject: [PATCH 67/75] header_bar: add WindowControlsPosition (macOS-style left controls) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new public enum `WindowControlsPosition { Start, End }` and a matching field on `HeaderBar`, allowing window controls (close / minimize / maximize) to be packed on the start side of the headerbar (macOS style, icon order close → minimize → maximize) instead of the default end side (Linux / GNOME style, minimize → maximize → close). Wiring: - `crate::widget::WindowControlsPosition` re-exported alongside `HeaderBar`. - `HeaderBar::controls_position(Option)` setter; when left unset, falls back to `crate::config::window_controls_position()` (reads `CosmicTk.window_controls_position`), mirroring how `density` falls back to `header_size()`. - New `CosmicTk.window_controls_position` field with default `End` for backwards compatibility; serde-friendly enum so existing configs keep working via `#[serde(default)]` semantics. Tested with cosmic-yoterm, cosmic-settings, cosmic-edit, cosmic-files rebuilt against this libcosmic via a local `[patch]` override. Config changes picked up live through the existing cosmic-config subscription. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/config/mod.rs | 12 +++++ src/widget/header_bar.rs | 104 ++++++++++++++++++++++++++++++--------- src/widget/mod.rs | 2 +- 3 files changed, 94 insertions(+), 24 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 9807961c..1691caa8 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -4,6 +4,7 @@ //! Configurations available to libcosmic applications. use crate::cosmic_theme::Density; +use crate::widget::WindowControlsPosition; use cosmic_config::cosmic_config_derive::CosmicConfigEntry; use cosmic_config::{Config, CosmicConfigEntry}; use serde::{Deserialize, Serialize}; @@ -67,6 +68,12 @@ pub fn header_size() -> Density { COSMIC_TK.read().unwrap().header_size } +/// Position of the window control buttons (close / minimize / maximize). +#[allow(clippy::missing_panics_doc)] +pub fn window_controls_position() -> WindowControlsPosition { + COSMIC_TK.read().unwrap().window_controls_position +} + /// Interface density. #[allow(clippy::missing_panics_doc)] pub fn interface_density() -> Density { @@ -109,6 +116,10 @@ pub struct CosmicTk { /// Mono font family pub monospace_font: FontConfig, + + /// Side on which window control buttons (close / minimize / maximize) + /// are placed. `End` = right (Linux / GNOME), `Start` = left (macOS). + pub window_controls_position: WindowControlsPosition, } impl Default for CosmicTk { @@ -132,6 +143,7 @@ impl Default for CosmicTk { stretch: iced::font::Stretch::Normal, style: iced::font::Style::Normal, }, + window_controls_position: WindowControlsPosition::default(), } } } diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 556466fd..82a7e8e9 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -27,9 +27,32 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { sharp_corners: false, is_ssd: false, on_double_click: None, + transparent: false, + controls_position: None, } } +/// Position of the window control buttons (close/min/max) within the headerbar. +#[derive( + Clone, + Copy, + Debug, + Default, + PartialEq, + Eq, + serde::Serialize, + serde::Deserialize, +)] +pub enum WindowControlsPosition { + /// Controls packed at the start (left on LTR) — macOS style. + /// Internal icon order becomes close → minimize → maximize. + Start, + /// Controls packed at the end (right on LTR) — Linux / GNOME style. + /// Internal icon order is minimize → maximize → close. + #[default] + End, +} + #[derive(Setters)] pub struct HeaderBar<'a, Message> { /// Defines the title of the window @@ -88,6 +111,17 @@ pub struct HeaderBar<'a, Message> { /// HeaderBar used for server-side decorations is_ssd: bool, + + /// Whether the headerbar should be transparent + transparent: bool, + + /// Side on which the window control buttons (close / minimize / maximize) + /// are rendered. `None` falls back to the user's CosmicTk config + /// (`crate::config::window_controls_position()`). `Some` overrides it. + /// `End` matches Linux / GNOME conventions; `Start` provides macOS-style + /// left-side controls. + #[setters(strip_option)] + controls_position: Option, } impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { @@ -370,12 +404,20 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { let is_ssd = self.is_ssd; // Take ownership of the regions to be packed. - let start = std::mem::take(&mut self.start); + let mut start = std::mem::take(&mut self.start); let center = std::mem::take(&mut self.center); let mut end = std::mem::take(&mut self.end); - // Also packs the window controls at the very end. - end.push(self.window_controls(space_xxs)); + // Pack window controls on the configured side (reads CosmicTk + // config when the builder did not set an explicit override). + let controls_position = self + .controls_position + .unwrap_or_else(crate::config::window_controls_position); + let controls = self.window_controls(space_xxs, controls_position); + match controls_position { + WindowControlsPosition::End => end.push(controls), + WindowControlsPosition::Start => start.insert(0, controls), + } let padding = if is_ssd { [2, 8, 2, 8] @@ -445,7 +487,11 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { } /// Creates the widget for window controls. - fn window_controls(&mut self, spacing: u16) -> Element<'a, Message> { + fn window_controls( + &mut self, + spacing: u16, + controls_position: WindowControlsPosition, + ) -> Element<'a, Message> { macro_rules! icon { ($name:expr, $size:expr, $on_press:expr) => {{ widget::icon::from_name($name) @@ -458,25 +504,37 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { }}; } - widget::row::with_capacity(3) - .push_maybe( - self.on_minimize - .take() - .map(|m| icon!("window-minimize-symbolic", 16, m)), - ) - .push_maybe(self.on_maximize.take().map(|m| { - if self.maximized { - icon!("window-restore-symbolic", 16, m) - } else { - icon!("window-maximize-symbolic", 16, m) - } - })) - .push_maybe( - self.on_close - .take() - .map(|m| icon!("window-close-symbolic", 16, m)), - ) - .spacing(spacing) + let minimize = self + .on_minimize + .take() + .map(|m| icon!("window-minimize-symbolic", 16, m)); + let maximize = self.on_maximize.take().map(|m| { + if self.maximized { + icon!("window-restore-symbolic", 16, m) + } else { + icon!("window-maximize-symbolic", 16, m) + } + }); + let close = self + .on_close + .take() + .map(|m| icon!("window-close-symbolic", 16, m)); + + // Icon order follows the OS convention for the chosen side: + // End → minimize, maximize, close (Linux / GNOME) + // Start → close, minimize, maximize (macOS) + let row = widget::row::with_capacity(3); + let row = match controls_position { + WindowControlsPosition::End => row + .push_maybe(minimize) + .push_maybe(maximize) + .push_maybe(close), + WindowControlsPosition::Start => row + .push_maybe(close) + .push_maybe(minimize) + .push_maybe(maximize), + }; + row.spacing(spacing) .align_y(iced::Alignment::Center) .into() } diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 5089b997..331d6c21 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -225,7 +225,7 @@ pub use grid::{Grid, grid}; mod header_bar; #[doc(inline)] -pub use header_bar::{HeaderBar, header_bar}; +pub use header_bar::{HeaderBar, WindowControlsPosition, header_bar}; pub mod icon; #[doc(inline)] From e3dcdf1fce2344a4626268fcd74dce0e12daef74 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Mon, 25 May 2026 13:02:07 +0200 Subject: [PATCH 68/75] =?UTF-8?q?yoda:=20fork=20pivot=20=E2=80=94=20Waylan?= =?UTF-8?q?d-only=20+=20ungate=20winit=20+=20soft-fork=20libcosmic-yoda=20?= =?UTF-8?q?(squashed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squash of 7 yoda commits forming the fork pivot: - 255cf7cc rename: libcosmic -> libcosmic-yoda (fork 0.1.0-yoda) - 8701aa31 feat(yoda): Wayland-only cut — drop winit and x11 features - 6736a596 yoda: soft-fork pivot — keep Cargo name 'libcosmic' for dep unification - 3e23d087 yoda: re-apply hard rename — libcosmic -> libcosmic-yoda (0.1.0-yoda) - aec3eb61 yoda: ungate remaining winit+wayland combined cfgs - 8ab7b158 yoda-v2: color_picker Theme ref + context_menu/menu ungate winit - 8d1d8739 yoda: drop x11 defaults on iced_winit + iced_tiny_skia --- Cargo.toml | 51 +++++++++++-------- examples/about/Cargo.toml | 2 +- examples/applet/Cargo.toml | 2 +- examples/application/Cargo.toml | 4 +- examples/calendar/Cargo.toml | 2 +- examples/context-menu/Cargo.toml | 2 +- examples/cosmic/Cargo.toml | 2 +- examples/image-button/Cargo.toml | 2 +- examples/menu/Cargo.toml | 2 +- examples/multi-window/Cargo.toml | 2 +- examples/nav-context/Cargo.toml | 2 +- examples/open-dialog/Cargo.toml | 6 +-- examples/spin-button/Cargo.toml | 2 +- examples/subscriptions/Cargo.toml | 2 +- examples/table-view/Cargo.toml | 2 +- examples/text-input/Cargo.toml | 2 +- i18n/af/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ar/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/be/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/bg/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/bn/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ca/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/cs/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/da/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/de/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/el/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/en/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/eo/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/es/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/et/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/eu/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/fa/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/fi/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/fr/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/fy/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ga/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/gd/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/gu/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/he/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/hi/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/hr/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/hu/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/id/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ie/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/is/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/it/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ja/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/jv/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ka/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../kab/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/kk/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../kmr/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/kn/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ko/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/li/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/lt/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ml/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ms/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/nl/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/nn/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/oc/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/pa/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/pl/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/pt/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ro/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ru/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/sk/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/sl/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/sr/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/sv/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ta/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/th/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/ti/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/tr/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/uk/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/uz/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 i18n/vi/{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 .../{libcosmic.ftl => libcosmic_yoda.ftl} | 0 src/action.rs | 3 -- src/command.rs | 2 - src/core.rs | 6 --- src/lib.rs | 3 -- src/surface/action.rs | 13 +++-- src/theme/style/mod.rs | 4 +- src/widget/color_picker/mod.rs | 6 ++- src/widget/context_menu.rs | 6 --- src/widget/dropdown/mod.rs | 2 +- src/widget/dropdown/widget.rs | 22 ++++---- src/widget/menu/menu_bar.rs | 9 ---- src/widget/menu/menu_inner.rs | 6 +-- src/widget/mod.rs | 2 +- src/widget/responsive_menu_bar.rs | 4 +- 101 files changed, 77 insertions(+), 98 deletions(-) rename i18n/af/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ar/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/be/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/bg/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/bn/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ca/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/cs/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/da/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/de/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/el/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/en-GB/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/en/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/eo/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/es-419/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/es-MX/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/es/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/et/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/eu/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/fa/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/fi/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/fr/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/fy/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ga/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/gd/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/gu/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/he/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/hi/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/hr/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/hu/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/id/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ie/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/is/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/it/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ja/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/jv/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ka/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/kab/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/kk/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/kmr/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/kn/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ko/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/li/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/lt/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ml/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ms/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/nb-NO/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/nl/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/nn/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/oc/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/pa/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/pl/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/pt-BR/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/pt/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ro/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ru/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sk/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sl/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sr-Cyrl/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sr-Latn/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sr/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/sv/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ta/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/th/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/ti/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/tr/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/uk/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/uz/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/vi/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/yue-Hant/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/zh-Hans/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) rename i18n/zh-Hant/{libcosmic.ftl => libcosmic_yoda.ftl} (100%) diff --git a/Cargo.toml b/Cargo.toml index 3d42ed05..effcc93b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,11 @@ [package] -name = "libcosmic" -version = "1.0.0" +# Yoda fork: hard-renamed. Every consumer (leyoda/cosmic-files fork + each +# leyoda/cosmic-* app) depends directly on `libcosmic-yoda` by path, bypassing +# pop-os/libcosmic entirely. No [patch] shenanigans needed — transitive deps +# that used to ask for `libcosmic` are replaced by deps on our forks that ask +# for `libcosmic-yoda`. +name = "libcosmic-yoda" +version = "0.1.0-yoda.2" edition = "2024" rust-version = "1.93" @@ -9,12 +14,10 @@ name = "cosmic" [features] default = [ - "winit", "tokio", "a11y", "dbus-config", - "x11", - "iced-wayland", + "wayland", "multi-window", ] advanced-shaping = ["iced/advanced-shaping"] @@ -35,7 +38,6 @@ animated-image = [ autosize = [] applet = [ "autosize", - "winit", "wayland", "tokio", "cosmic-panel-config", @@ -81,32 +83,34 @@ tokio = [ "cosmic-config/tokio", ] # Tokio async runtime -# Wayland window support -iced-wayland = [ +# Wayland window support (yoda fork is Wayland-only; always active in default). +# We still need iced/winit because pop-os/iced hosts the runtime dispatcher +# (`iced_winit as shell`) there — the name is a misnomer, it's the same crate +# that provides both the winit path AND the sctk/cctk wayland path. +wayland = [ "ashpd?/wayland", "autosize", + "iced/winit", "iced/wayland", "iced_winit/wayland", - "surface-message", -] -wayland = [ - "iced-wayland", "iced_runtime/cctk", "iced_winit/cctk", "iced_wgpu/cctk", "iced/cctk", + "dep:iced_winit", "dep:cctk", + "surface-message", ] surface-message = [] # multi-window support multi-window = [] # Render with wgpu wgpu = ["iced/wgpu", "iced_wgpu"] -# X11 window support via winit -winit = ["iced/winit", "iced_winit"] -winit_debug = ["winit", "debug"] -winit_tokio = ["winit", "tokio"] -winit_wgpu = ["winit", "wgpu"] +# Compat stubs — kept empty so upstream deps (cosmic-files, cosmic-text, …) +# that still ask for `winit` / `x11` features resolve cleanly against the +# yoda fork. Activating them has no effect: no code is gated on these. +winit = [] +x11 = [] # Enables XDG portal integrations xdg-portal = ["ashpd"] qr_code = ["iced/qr_code"] @@ -119,7 +123,6 @@ async-std = [ "zbus?/async-io", "iced/async-std", ] -x11 = ["iced/x11", "iced_winit/x11"] [dependencies] apply = "0.3.0" @@ -224,10 +227,17 @@ optional = true [dependencies.iced_tiny_skia] path = "./iced/tiny_skia" +# Yoda: drop the x11 default → softbuffer no longer pulls tiny-xlib/x11-dl/etc. +default-features = false +features = ["wayland"] [dependencies.iced_winit] path = "./iced/winit" optional = true +# Yoda: drop the x11 default → winit won't pull winit-x11/tiny-xlib/x11-dl. +# Keep wayland + wayland-dlopen (default behaviour minus x11). +default-features = false +features = ["wayland", "wayland-dlopen"] [dependencies.iced_wgpu] path = "./iced/wgpu" @@ -243,9 +253,10 @@ members = [ "cosmic-config", "cosmic-config-derive", "cosmic-theme", - "examples/*", ] -exclude = ["iced"] +# examples/* excluded — many depend on the removed winit/x11 features. +# They will be revisited and adapted in a later phase. +exclude = ["iced", "examples"] [workspace.dependencies] async-std = "1.13" diff --git a/examples/about/Cargo.toml b/examples/about/Cargo.toml index f980811c..b27b5134 100644 --- a/examples/about/Cargo.toml +++ b/examples/about/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] open = "5.3.3" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = [ "debug", diff --git a/examples/applet/Cargo.toml b/examples/applet/Cargo.toml index 13eff684..265fbe7f 100644 --- a/examples/applet/Cargo.toml +++ b/examples/applet/Cargo.toml @@ -12,7 +12,7 @@ tracing = "0.1" env_logger = "0.10.2" log = "0.4.29" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" default-features = false features = ["applet-token"] diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index 7a6083e0..d4c75171 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -5,12 +5,12 @@ edition = "2021" [features] default = ["wayland"] -wayland = ["libcosmic/wayland"] +wayland = ["libcosmic-yoda/wayland"] [dependencies] env_logger = "0.11" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = [ "debug", diff --git a/examples/calendar/Cargo.toml b/examples/calendar/Cargo.toml index b7286825..203f7c17 100644 --- a/examples/calendar/Cargo.toml +++ b/examples/calendar/Cargo.toml @@ -8,6 +8,6 @@ edition = "2024" [dependencies] jiff = "0.2" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/context-menu/Cargo.toml b/examples/context-menu/Cargo.toml index 39c550f4..4c1eed60 100644 --- a/examples/context-menu/Cargo.toml +++ b/examples/context-menu/Cargo.toml @@ -8,7 +8,7 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = [ "debug", diff --git a/examples/cosmic/Cargo.toml b/examples/cosmic/Cargo.toml index 8c2a3126..eebf6c31 100644 --- a/examples/cosmic/Cargo.toml +++ b/examples/cosmic/Cargo.toml @@ -8,7 +8,7 @@ publish = false [dependencies] apply = "0.3.0" fraction = "0.15.3" -libcosmic = { path = "../..", features = [ +libcosmic-yoda = { path = "../..", features = [ "debug", "winit", "tokio", diff --git a/examples/image-button/Cargo.toml b/examples/image-button/Cargo.toml index c219a53b..8bc521f7 100644 --- a/examples/image-button/Cargo.toml +++ b/examples/image-button/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" tracing = "0.1.44" tracing-subscriber = "0.3.22" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "wgpu", "tokio"] diff --git a/examples/menu/Cargo.toml b/examples/menu/Cargo.toml index 430b26ea..047055e5 100644 --- a/examples/menu/Cargo.toml +++ b/examples/menu/Cargo.toml @@ -8,6 +8,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/multi-window/Cargo.toml b/examples/multi-window/Cargo.toml index 0b5440f8..c38595fa 100644 --- a/examples/multi-window/Cargo.toml +++ b/examples/multi-window/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "wgpu", "wayland"] } +libcosmic-yoda = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "wgpu", "wayland"] } diff --git a/examples/nav-context/Cargo.toml b/examples/nav-context/Cargo.toml index d829df0f..ea2bc2b8 100644 --- a/examples/nav-context/Cargo.toml +++ b/examples/nav-context/Cargo.toml @@ -8,6 +8,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/open-dialog/Cargo.toml b/examples/open-dialog/Cargo.toml index 94049270..b09b98ca 100644 --- a/examples/open-dialog/Cargo.toml +++ b/examples/open-dialog/Cargo.toml @@ -5,8 +5,8 @@ edition = "2021" [features] default = ["xdg-portal"] -rfd = ["libcosmic/rfd"] -xdg-portal = ["libcosmic/xdg-portal"] +rfd = ["libcosmic-yoda/rfd"] +xdg-portal = ["libcosmic-yoda/xdg-portal"] [dependencies] apply = "0.3.0" @@ -15,6 +15,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" url = "2.5.8" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] features = ["debug", "winit", "wgpu", "wayland", "tokio"] path = "../../" diff --git a/examples/spin-button/Cargo.toml b/examples/spin-button/Cargo.toml index a522050b..082c0fd5 100644 --- a/examples/spin-button/Cargo.toml +++ b/examples/spin-button/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] fraction = "0.15.3" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] features = ["debug", "wgpu", "winit", "desktop", "tokio"] path = "../.." default-features = false diff --git a/examples/subscriptions/Cargo.toml b/examples/subscriptions/Cargo.toml index 8eb69ff3..ae31a395 100644 --- a/examples/subscriptions/Cargo.toml +++ b/examples/subscriptions/Cargo.toml @@ -5,6 +5,6 @@ edition = "2024" [dependencies] -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"] diff --git a/examples/table-view/Cargo.toml b/examples/table-view/Cargo.toml index 8ed45928..8f71e5b4 100644 --- a/examples/table-view/Cargo.toml +++ b/examples/table-view/Cargo.toml @@ -9,6 +9,6 @@ tracing-subscriber = "0.3.22" tracing-log = "0.2.0" chrono = "*" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] features = ["debug", "wgpu", "winit", "desktop", "tokio"] path = "../.." diff --git a/examples/text-input/Cargo.toml b/examples/text-input/Cargo.toml index fe6105c2..69bd2a19 100644 --- a/examples/text-input/Cargo.toml +++ b/examples/text-input/Cargo.toml @@ -8,6 +8,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic] +[dependencies.libcosmic-yoda] path = "../../" features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"] diff --git a/i18n/af/libcosmic.ftl b/i18n/af/libcosmic_yoda.ftl similarity index 100% rename from i18n/af/libcosmic.ftl rename to i18n/af/libcosmic_yoda.ftl diff --git a/i18n/ar/libcosmic.ftl b/i18n/ar/libcosmic_yoda.ftl similarity index 100% rename from i18n/ar/libcosmic.ftl rename to i18n/ar/libcosmic_yoda.ftl diff --git a/i18n/be/libcosmic.ftl b/i18n/be/libcosmic_yoda.ftl similarity index 100% rename from i18n/be/libcosmic.ftl rename to i18n/be/libcosmic_yoda.ftl diff --git a/i18n/bg/libcosmic.ftl b/i18n/bg/libcosmic_yoda.ftl similarity index 100% rename from i18n/bg/libcosmic.ftl rename to i18n/bg/libcosmic_yoda.ftl diff --git a/i18n/bn/libcosmic.ftl b/i18n/bn/libcosmic_yoda.ftl similarity index 100% rename from i18n/bn/libcosmic.ftl rename to i18n/bn/libcosmic_yoda.ftl diff --git a/i18n/ca/libcosmic.ftl b/i18n/ca/libcosmic_yoda.ftl similarity index 100% rename from i18n/ca/libcosmic.ftl rename to i18n/ca/libcosmic_yoda.ftl diff --git a/i18n/cs/libcosmic.ftl b/i18n/cs/libcosmic_yoda.ftl similarity index 100% rename from i18n/cs/libcosmic.ftl rename to i18n/cs/libcosmic_yoda.ftl diff --git a/i18n/da/libcosmic.ftl b/i18n/da/libcosmic_yoda.ftl similarity index 100% rename from i18n/da/libcosmic.ftl rename to i18n/da/libcosmic_yoda.ftl diff --git a/i18n/de/libcosmic.ftl b/i18n/de/libcosmic_yoda.ftl similarity index 100% rename from i18n/de/libcosmic.ftl rename to i18n/de/libcosmic_yoda.ftl diff --git a/i18n/el/libcosmic.ftl b/i18n/el/libcosmic_yoda.ftl similarity index 100% rename from i18n/el/libcosmic.ftl rename to i18n/el/libcosmic_yoda.ftl diff --git a/i18n/en-GB/libcosmic.ftl b/i18n/en-GB/libcosmic_yoda.ftl similarity index 100% rename from i18n/en-GB/libcosmic.ftl rename to i18n/en-GB/libcosmic_yoda.ftl diff --git a/i18n/en/libcosmic.ftl b/i18n/en/libcosmic_yoda.ftl similarity index 100% rename from i18n/en/libcosmic.ftl rename to i18n/en/libcosmic_yoda.ftl diff --git a/i18n/eo/libcosmic.ftl b/i18n/eo/libcosmic_yoda.ftl similarity index 100% rename from i18n/eo/libcosmic.ftl rename to i18n/eo/libcosmic_yoda.ftl diff --git a/i18n/es-419/libcosmic.ftl b/i18n/es-419/libcosmic_yoda.ftl similarity index 100% rename from i18n/es-419/libcosmic.ftl rename to i18n/es-419/libcosmic_yoda.ftl diff --git a/i18n/es-MX/libcosmic.ftl b/i18n/es-MX/libcosmic_yoda.ftl similarity index 100% rename from i18n/es-MX/libcosmic.ftl rename to i18n/es-MX/libcosmic_yoda.ftl diff --git a/i18n/es/libcosmic.ftl b/i18n/es/libcosmic_yoda.ftl similarity index 100% rename from i18n/es/libcosmic.ftl rename to i18n/es/libcosmic_yoda.ftl diff --git a/i18n/et/libcosmic.ftl b/i18n/et/libcosmic_yoda.ftl similarity index 100% rename from i18n/et/libcosmic.ftl rename to i18n/et/libcosmic_yoda.ftl diff --git a/i18n/eu/libcosmic.ftl b/i18n/eu/libcosmic_yoda.ftl similarity index 100% rename from i18n/eu/libcosmic.ftl rename to i18n/eu/libcosmic_yoda.ftl diff --git a/i18n/fa/libcosmic.ftl b/i18n/fa/libcosmic_yoda.ftl similarity index 100% rename from i18n/fa/libcosmic.ftl rename to i18n/fa/libcosmic_yoda.ftl diff --git a/i18n/fi/libcosmic.ftl b/i18n/fi/libcosmic_yoda.ftl similarity index 100% rename from i18n/fi/libcosmic.ftl rename to i18n/fi/libcosmic_yoda.ftl diff --git a/i18n/fr/libcosmic.ftl b/i18n/fr/libcosmic_yoda.ftl similarity index 100% rename from i18n/fr/libcosmic.ftl rename to i18n/fr/libcosmic_yoda.ftl diff --git a/i18n/fy/libcosmic.ftl b/i18n/fy/libcosmic_yoda.ftl similarity index 100% rename from i18n/fy/libcosmic.ftl rename to i18n/fy/libcosmic_yoda.ftl diff --git a/i18n/ga/libcosmic.ftl b/i18n/ga/libcosmic_yoda.ftl similarity index 100% rename from i18n/ga/libcosmic.ftl rename to i18n/ga/libcosmic_yoda.ftl diff --git a/i18n/gd/libcosmic.ftl b/i18n/gd/libcosmic_yoda.ftl similarity index 100% rename from i18n/gd/libcosmic.ftl rename to i18n/gd/libcosmic_yoda.ftl diff --git a/i18n/gu/libcosmic.ftl b/i18n/gu/libcosmic_yoda.ftl similarity index 100% rename from i18n/gu/libcosmic.ftl rename to i18n/gu/libcosmic_yoda.ftl diff --git a/i18n/he/libcosmic.ftl b/i18n/he/libcosmic_yoda.ftl similarity index 100% rename from i18n/he/libcosmic.ftl rename to i18n/he/libcosmic_yoda.ftl diff --git a/i18n/hi/libcosmic.ftl b/i18n/hi/libcosmic_yoda.ftl similarity index 100% rename from i18n/hi/libcosmic.ftl rename to i18n/hi/libcosmic_yoda.ftl diff --git a/i18n/hr/libcosmic.ftl b/i18n/hr/libcosmic_yoda.ftl similarity index 100% rename from i18n/hr/libcosmic.ftl rename to i18n/hr/libcosmic_yoda.ftl diff --git a/i18n/hu/libcosmic.ftl b/i18n/hu/libcosmic_yoda.ftl similarity index 100% rename from i18n/hu/libcosmic.ftl rename to i18n/hu/libcosmic_yoda.ftl diff --git a/i18n/id/libcosmic.ftl b/i18n/id/libcosmic_yoda.ftl similarity index 100% rename from i18n/id/libcosmic.ftl rename to i18n/id/libcosmic_yoda.ftl diff --git a/i18n/ie/libcosmic.ftl b/i18n/ie/libcosmic_yoda.ftl similarity index 100% rename from i18n/ie/libcosmic.ftl rename to i18n/ie/libcosmic_yoda.ftl diff --git a/i18n/is/libcosmic.ftl b/i18n/is/libcosmic_yoda.ftl similarity index 100% rename from i18n/is/libcosmic.ftl rename to i18n/is/libcosmic_yoda.ftl diff --git a/i18n/it/libcosmic.ftl b/i18n/it/libcosmic_yoda.ftl similarity index 100% rename from i18n/it/libcosmic.ftl rename to i18n/it/libcosmic_yoda.ftl diff --git a/i18n/ja/libcosmic.ftl b/i18n/ja/libcosmic_yoda.ftl similarity index 100% rename from i18n/ja/libcosmic.ftl rename to i18n/ja/libcosmic_yoda.ftl diff --git a/i18n/jv/libcosmic.ftl b/i18n/jv/libcosmic_yoda.ftl similarity index 100% rename from i18n/jv/libcosmic.ftl rename to i18n/jv/libcosmic_yoda.ftl diff --git a/i18n/ka/libcosmic.ftl b/i18n/ka/libcosmic_yoda.ftl similarity index 100% rename from i18n/ka/libcosmic.ftl rename to i18n/ka/libcosmic_yoda.ftl diff --git a/i18n/kab/libcosmic.ftl b/i18n/kab/libcosmic_yoda.ftl similarity index 100% rename from i18n/kab/libcosmic.ftl rename to i18n/kab/libcosmic_yoda.ftl diff --git a/i18n/kk/libcosmic.ftl b/i18n/kk/libcosmic_yoda.ftl similarity index 100% rename from i18n/kk/libcosmic.ftl rename to i18n/kk/libcosmic_yoda.ftl diff --git a/i18n/kmr/libcosmic.ftl b/i18n/kmr/libcosmic_yoda.ftl similarity index 100% rename from i18n/kmr/libcosmic.ftl rename to i18n/kmr/libcosmic_yoda.ftl diff --git a/i18n/kn/libcosmic.ftl b/i18n/kn/libcosmic_yoda.ftl similarity index 100% rename from i18n/kn/libcosmic.ftl rename to i18n/kn/libcosmic_yoda.ftl diff --git a/i18n/ko/libcosmic.ftl b/i18n/ko/libcosmic_yoda.ftl similarity index 100% rename from i18n/ko/libcosmic.ftl rename to i18n/ko/libcosmic_yoda.ftl diff --git a/i18n/li/libcosmic.ftl b/i18n/li/libcosmic_yoda.ftl similarity index 100% rename from i18n/li/libcosmic.ftl rename to i18n/li/libcosmic_yoda.ftl diff --git a/i18n/lt/libcosmic.ftl b/i18n/lt/libcosmic_yoda.ftl similarity index 100% rename from i18n/lt/libcosmic.ftl rename to i18n/lt/libcosmic_yoda.ftl diff --git a/i18n/ml/libcosmic.ftl b/i18n/ml/libcosmic_yoda.ftl similarity index 100% rename from i18n/ml/libcosmic.ftl rename to i18n/ml/libcosmic_yoda.ftl diff --git a/i18n/ms/libcosmic.ftl b/i18n/ms/libcosmic_yoda.ftl similarity index 100% rename from i18n/ms/libcosmic.ftl rename to i18n/ms/libcosmic_yoda.ftl diff --git a/i18n/nb-NO/libcosmic.ftl b/i18n/nb-NO/libcosmic_yoda.ftl similarity index 100% rename from i18n/nb-NO/libcosmic.ftl rename to i18n/nb-NO/libcosmic_yoda.ftl diff --git a/i18n/nl/libcosmic.ftl b/i18n/nl/libcosmic_yoda.ftl similarity index 100% rename from i18n/nl/libcosmic.ftl rename to i18n/nl/libcosmic_yoda.ftl diff --git a/i18n/nn/libcosmic.ftl b/i18n/nn/libcosmic_yoda.ftl similarity index 100% rename from i18n/nn/libcosmic.ftl rename to i18n/nn/libcosmic_yoda.ftl diff --git a/i18n/oc/libcosmic.ftl b/i18n/oc/libcosmic_yoda.ftl similarity index 100% rename from i18n/oc/libcosmic.ftl rename to i18n/oc/libcosmic_yoda.ftl diff --git a/i18n/pa/libcosmic.ftl b/i18n/pa/libcosmic_yoda.ftl similarity index 100% rename from i18n/pa/libcosmic.ftl rename to i18n/pa/libcosmic_yoda.ftl diff --git a/i18n/pl/libcosmic.ftl b/i18n/pl/libcosmic_yoda.ftl similarity index 100% rename from i18n/pl/libcosmic.ftl rename to i18n/pl/libcosmic_yoda.ftl diff --git a/i18n/pt-BR/libcosmic.ftl b/i18n/pt-BR/libcosmic_yoda.ftl similarity index 100% rename from i18n/pt-BR/libcosmic.ftl rename to i18n/pt-BR/libcosmic_yoda.ftl diff --git a/i18n/pt/libcosmic.ftl b/i18n/pt/libcosmic_yoda.ftl similarity index 100% rename from i18n/pt/libcosmic.ftl rename to i18n/pt/libcosmic_yoda.ftl diff --git a/i18n/ro/libcosmic.ftl b/i18n/ro/libcosmic_yoda.ftl similarity index 100% rename from i18n/ro/libcosmic.ftl rename to i18n/ro/libcosmic_yoda.ftl diff --git a/i18n/ru/libcosmic.ftl b/i18n/ru/libcosmic_yoda.ftl similarity index 100% rename from i18n/ru/libcosmic.ftl rename to i18n/ru/libcosmic_yoda.ftl diff --git a/i18n/sk/libcosmic.ftl b/i18n/sk/libcosmic_yoda.ftl similarity index 100% rename from i18n/sk/libcosmic.ftl rename to i18n/sk/libcosmic_yoda.ftl diff --git a/i18n/sl/libcosmic.ftl b/i18n/sl/libcosmic_yoda.ftl similarity index 100% rename from i18n/sl/libcosmic.ftl rename to i18n/sl/libcosmic_yoda.ftl diff --git a/i18n/sr-Cyrl/libcosmic.ftl b/i18n/sr-Cyrl/libcosmic_yoda.ftl similarity index 100% rename from i18n/sr-Cyrl/libcosmic.ftl rename to i18n/sr-Cyrl/libcosmic_yoda.ftl diff --git a/i18n/sr-Latn/libcosmic.ftl b/i18n/sr-Latn/libcosmic_yoda.ftl similarity index 100% rename from i18n/sr-Latn/libcosmic.ftl rename to i18n/sr-Latn/libcosmic_yoda.ftl diff --git a/i18n/sr/libcosmic.ftl b/i18n/sr/libcosmic_yoda.ftl similarity index 100% rename from i18n/sr/libcosmic.ftl rename to i18n/sr/libcosmic_yoda.ftl diff --git a/i18n/sv/libcosmic.ftl b/i18n/sv/libcosmic_yoda.ftl similarity index 100% rename from i18n/sv/libcosmic.ftl rename to i18n/sv/libcosmic_yoda.ftl diff --git a/i18n/ta/libcosmic.ftl b/i18n/ta/libcosmic_yoda.ftl similarity index 100% rename from i18n/ta/libcosmic.ftl rename to i18n/ta/libcosmic_yoda.ftl diff --git a/i18n/th/libcosmic.ftl b/i18n/th/libcosmic_yoda.ftl similarity index 100% rename from i18n/th/libcosmic.ftl rename to i18n/th/libcosmic_yoda.ftl diff --git a/i18n/ti/libcosmic.ftl b/i18n/ti/libcosmic_yoda.ftl similarity index 100% rename from i18n/ti/libcosmic.ftl rename to i18n/ti/libcosmic_yoda.ftl diff --git a/i18n/tr/libcosmic.ftl b/i18n/tr/libcosmic_yoda.ftl similarity index 100% rename from i18n/tr/libcosmic.ftl rename to i18n/tr/libcosmic_yoda.ftl diff --git a/i18n/uk/libcosmic.ftl b/i18n/uk/libcosmic_yoda.ftl similarity index 100% rename from i18n/uk/libcosmic.ftl rename to i18n/uk/libcosmic_yoda.ftl diff --git a/i18n/uz/libcosmic.ftl b/i18n/uz/libcosmic_yoda.ftl similarity index 100% rename from i18n/uz/libcosmic.ftl rename to i18n/uz/libcosmic_yoda.ftl diff --git a/i18n/vi/libcosmic.ftl b/i18n/vi/libcosmic_yoda.ftl similarity index 100% rename from i18n/vi/libcosmic.ftl rename to i18n/vi/libcosmic_yoda.ftl diff --git a/i18n/yue-Hant/libcosmic.ftl b/i18n/yue-Hant/libcosmic_yoda.ftl similarity index 100% rename from i18n/yue-Hant/libcosmic.ftl rename to i18n/yue-Hant/libcosmic_yoda.ftl diff --git a/i18n/zh-Hans/libcosmic.ftl b/i18n/zh-Hans/libcosmic_yoda.ftl similarity index 100% rename from i18n/zh-Hans/libcosmic.ftl rename to i18n/zh-Hans/libcosmic_yoda.ftl diff --git a/i18n/zh-Hant/libcosmic.ftl b/i18n/zh-Hant/libcosmic_yoda.ftl similarity index 100% rename from i18n/zh-Hant/libcosmic.ftl rename to i18n/zh-Hant/libcosmic_yoda.ftl diff --git a/src/action.rs b/src/action.rs index b7162896..19e228b8 100644 --- a/src/action.rs +++ b/src/action.rs @@ -1,7 +1,6 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -#[cfg(feature = "winit")] use crate::app; #[cfg(feature = "single-instance")] use crate::dbus_activation; @@ -9,7 +8,6 @@ use crate::dbus_activation; pub const fn app(message: M) -> Action { Action::App(message) } -#[cfg(feature = "winit")] pub const fn cosmic(message: app::Action) -> Action { Action::Cosmic(message) } @@ -23,7 +21,6 @@ pub const fn none() -> Action { pub enum Action { /// Messages from the application, for the application. App(M), - #[cfg(feature = "winit")] /// Internal messages to be handled by libcosmic. Cosmic(app::Action), #[cfg(feature = "single-instance")] diff --git a/src/command.rs b/src/command.rs index 1d6f635c..c5c1c62b 100644 --- a/src/command.rs +++ b/src/command.rs @@ -27,12 +27,10 @@ pub fn set_title(id: window::Id, title: String) -> iced::Task(factor: f32) -> iced::Task> { iced::Task::done(crate::app::Action::ScaleFactor(factor)).map(crate::Action::Cosmic) } -#[cfg(feature = "winit")] pub fn set_theme(theme: crate::Theme) -> iced::Task> { iced::Task::done(crate::app::Action::AppThemeChange(theme)).map(crate::Action::Cosmic) } diff --git a/src/core.rs b/src/core.rs index 970a5351..44f3b3df 100644 --- a/src/core.rs +++ b/src/core.rs @@ -432,7 +432,6 @@ impl Core { id } - #[cfg(feature = "winit")] pub fn drag(&self, id: Option) -> crate::app::Task { let Some(id) = id.or(self.main_window) else { return iced::Task::none(); @@ -440,7 +439,6 @@ impl Core { crate::command::drag(id) } - #[cfg(feature = "winit")] pub fn maximize( &self, id: Option, @@ -452,7 +450,6 @@ impl Core { crate::command::maximize(id, maximized) } - #[cfg(feature = "winit")] pub fn minimize(&self, id: Option) -> crate::app::Task { let Some(id) = id.or(self.main_window) else { return iced::Task::none(); @@ -460,7 +457,6 @@ impl Core { crate::command::minimize(id) } - #[cfg(feature = "winit")] pub fn set_title( &self, id: Option, @@ -472,7 +468,6 @@ impl Core { crate::command::set_title(id, title) } - #[cfg(feature = "winit")] pub fn set_windowed(&self, id: Option) -> crate::app::Task { let Some(id) = id.or(self.main_window) else { return iced::Task::none(); @@ -480,7 +475,6 @@ impl Core { crate::command::set_windowed(id) } - #[cfg(feature = "winit")] pub fn toggle_maximize( &self, id: Option, diff --git a/src/lib.rs b/src/lib.rs index 875d9148..2e658437 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,6 @@ /// Recommended default imports. pub mod prelude { - #[cfg(feature = "winit")] pub use crate::ApplicationExt; pub use crate::ext::*; pub use crate::{Also, Apply, Element, Renderer, Task, Theme}; @@ -21,9 +20,7 @@ pub use action::Action; pub mod anim; -#[cfg(feature = "winit")] pub mod app; -#[cfg(feature = "winit")] #[doc(inline)] pub use app::{Application, ApplicationExt}; diff --git a/src/surface/action.rs b/src/surface/action.rs index 5bd5a294..16816fff 100644 --- a/src/surface/action.rs +++ b/src/surface/action.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MPL-2.0 use super::Action; -#[cfg(feature = "winit")] use crate::Application; use iced::window; @@ -28,7 +27,7 @@ pub fn destroy_window(id: iced_core::window::Id) -> Action { Action::DestroyWindow(id) } -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] #[must_use] pub fn app_window( settings: impl Fn(&mut App) -> window::Settings + Send + Sync + 'static, @@ -61,7 +60,7 @@ pub fn app_window( } /// Used to create a window message from within a widget. -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] #[must_use] pub fn simple_window( settings: impl Fn() -> window::Settings + Send + Sync + 'static, @@ -93,7 +92,7 @@ pub fn simple_window( ) } -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] #[must_use] pub fn app_popup( settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings @@ -127,7 +126,7 @@ pub fn app_popup( } /// Used to create a subsurface message from within a widget. -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] #[must_use] pub fn simple_subsurface( settings: impl Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings @@ -156,7 +155,7 @@ pub fn simple_subsurface( } /// Used to create a popup message from within a widget. -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] #[must_use] pub fn simple_popup( settings: impl Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings @@ -187,7 +186,7 @@ pub fn simple_popup( ) } -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] #[must_use] pub fn subsurface( settings: impl Fn( diff --git a/src/theme/style/mod.rs b/src/theme/style/mod.rs index bc648a73..cc489312 100644 --- a/src/theme/style/mod.rs +++ b/src/theme/style/mod.rs @@ -32,7 +32,7 @@ mod text_input; #[doc(inline)] pub use self::text_input::TextInput; -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] pub mod tooltip; -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] pub use tooltip::Tooltip; diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 66ffd847..ba60235d 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -619,8 +619,10 @@ where let bounds = canvas_layout.bounds(); // Draw the handle on the saturation value canvas - let t = THEME.lock().unwrap().clone(); - let t = t.cosmic(); + // Yoda: use the Theme passed into draw() instead of locking the global + // THEME Mutex and cloning the whole Theme. Fires on every color-picker + // redraw, so saving the Mutex lock + full Theme clone adds up. + let t = theme.cosmic(); let handle_radius = f32::from(t.space_xs()) / 2.0; let (x, y) = ( self.active_color diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index bd950bff..663e9001 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -6,7 +6,6 @@ #[cfg(all( feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; @@ -67,7 +66,6 @@ impl ContextMenu<'_, Message> { #[cfg(all( feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] #[allow(clippy::too_many_lines)] @@ -378,7 +376,6 @@ impl Widget #[cfg(all( feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) @@ -422,7 +419,6 @@ impl Widget #[cfg(all( feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { @@ -444,7 +440,6 @@ impl Widget #[cfg(all( feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) @@ -483,7 +478,6 @@ impl Widget #[cfg(all( feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) diff --git a/src/widget/dropdown/mod.rs b/src/widget/dropdown/mod.rs index b5fd4c06..0ea3a212 100644 --- a/src/widget/dropdown/mod.rs +++ b/src/widget/dropdown/mod.rs @@ -50,7 +50,7 @@ pub fn popup_dropdown< let dropdown: Dropdown<'_, S, Message, AppMessage> = Dropdown::new(selections.into(), selected, on_selected); - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] let dropdown = dropdown.with_popup(_parent_id, _on_surface_action, _map_action); dropdown diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 046cf84d..23476a0b 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -60,7 +60,7 @@ where action_map: Option AppMessage + 'static + Send + Sync>>, #[setters(strip_option)] window_id: Option, - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, } @@ -96,14 +96,14 @@ where text_line_height: text::LineHeight::Relative(1.2), font: None, window_id: None, - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), on_surface_action: None, action_map: None, } } - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] /// Handle dropdown requests for popup creation. /// Intended to be used with [`crate::app::message::get_popup`] pub fn with_popup( @@ -154,7 +154,7 @@ where self } - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] pub fn with_positioner( mut self, positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, @@ -268,7 +268,7 @@ where layout, cursor, shell, - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] self.positioner.clone(), self.on_selected.clone(), self.selected, @@ -346,7 +346,7 @@ where viewport: &Rectangle, translation: Vector, ) -> Option> { - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] if self.window_id.is_some() || self.on_surface_action.is_some() { return None; } @@ -545,7 +545,7 @@ pub fn update< layout: Layout<'_>, cursor: mouse::Cursor, shell: &mut Shell<'_, Message>, - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, on_selected: Arc Message + Send + Sync + 'static>, selected: Option, @@ -571,7 +571,7 @@ pub fn update< *hovered_guard = selected; let id = window::Id::unique(); state.popup_id = id; - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] if let Some(((on_surface_action, parent), action_map)) = on_surface_action .as_ref() .zip(_window_id) @@ -658,7 +658,7 @@ pub fn update< state.close_operation = false; state.is_open.store(false, Ordering::SeqCst); if is_open { - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] if let Some(ref on_close) = on_surface_action { shell.publish(on_close(surface::action::destroy_popup(state.popup_id))); } @@ -681,7 +681,7 @@ pub fn update< // Event wasn't processed by overlay, so cursor was clicked either outside it's // bounds or on the drop-down, either way we close the overlay. state.is_open.store(false, Ordering::Relaxed); - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] if let Some(on_close) = on_surface_action { shell.publish(on_close(surface::action::destroy_popup(state.popup_id))); } @@ -726,7 +726,7 @@ pub fn mouse_interaction(layout: Layout<'_>, cursor: mouse::Cursor) -> mouse::In } } -#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] /// Returns the current menu widget of a [`Dropdown`]. #[allow(clippy::too_many_arguments)] pub fn menu_widget< diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index b5ffca42..3cefaf86 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -13,7 +13,6 @@ use crate::Renderer; feature = "multi-window", feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; @@ -194,7 +193,6 @@ pub struct MenuBar { #[cfg(all( feature = "multi-window", feature = "wayland", - feature = "winit", target_os = "linux" ))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, @@ -234,7 +232,6 @@ where #[cfg(all( feature = "multi-window", feature = "wayland", - feature = "winit", target_os = "linux" ))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), @@ -333,7 +330,6 @@ where #[cfg(all( feature = "multi-window", feature = "wayland", - feature = "winit", target_os = "linux" ))] pub fn with_positioner( @@ -371,7 +367,6 @@ where feature = "multi-window", feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] #[allow(clippy::too_many_lines)] @@ -643,7 +638,6 @@ where #[cfg(all( feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] { @@ -667,7 +661,6 @@ where feature = "multi-window", feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { @@ -682,7 +675,6 @@ where feature = "multi-window", feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { @@ -765,7 +757,6 @@ where feature = "multi-window", feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 6211e201..e303b3f1 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -10,7 +10,6 @@ use super::menu_tree::MenuTree; feature = "multi-window", feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; @@ -680,7 +679,6 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { feature = "multi-window", feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) @@ -965,7 +963,6 @@ impl Widget( feature = "multi-window", feature = "wayland", target_os = "linux", - feature = "winit", feature = "surface-message" ))] pub(super) fn init_root_popup_menu( @@ -1525,7 +1521,7 @@ where .as_ref() .is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty()); - #[cfg(all(feature = "multi-window", feature = "wayland",target_os = "linux", feature = "winit", feature = "surface-message"))] + #[cfg(all(feature = "multi-window", feature = "wayland", target_os = "linux", feature = "surface-message"))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove { if let Some(id) = state.popup_id.remove(&menu.window_id) { state.active_root.truncate(menu.depth + 1); diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 331d6c21..e68e7477 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -312,7 +312,7 @@ pub use toggler::{Toggler, toggler}; #[doc(inline)] pub use tooltip::{Tooltip, tooltip}; -#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux"))] pub mod wayland; pub mod tooltip { diff --git a/src/widget/responsive_menu_bar.rs b/src/widget/responsive_menu_bar.rs index 2cae68d8..de323c13 100644 --- a/src/widget/responsive_menu_bar.rs +++ b/src/widget/responsive_menu_bar.rs @@ -23,7 +23,7 @@ impl Default for ResponsiveMenuBar { fn default() -> ResponsiveMenuBar { ResponsiveMenuBar { collapsed_item_width: { - #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "wayland", target_os = "linux"))] if matches!( crate::app::cosmic::WINDOWING_SYSTEM.get(), Some(crate::app::cosmic::WindowingSystem::Wayland) @@ -32,7 +32,7 @@ impl Default for ResponsiveMenuBar { } else { ItemWidth::Static(84) } - #[cfg(not(all(feature = "winit", feature = "wayland", target_os = "linux")))] + #[cfg(not(all(feature = "wayland", target_os = "linux")))] { ItemWidth::Static(84) } From 597aba3e9fcbcecb19694ce1f17778930dd4ca10 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Mon, 25 May 2026 13:02:25 +0200 Subject: [PATCH 69/75] yoda: bump iced submodule (Wayland-only + softbuffer cuts) (squashed) Squash of 3 yoda commits: - c118f5a2 yoda: bump iced submodule ref -> yoda-wayland-only HEAD 8f6be798 - 388e0655 yoda: bump iced submodule -> softbuffer + window_clipboard cuts - 9b2a3643 Update iced warning fixes --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 98fb6a0f..81639935 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 98fb6a0fba98cfdb6c04f33ccf0be6abad7fcfff +Subproject commit 81639935398a856f3164dc406fbac78922c258fc From ef8f69134fe47de62d961c3d0e52f781951af008 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Tue, 5 May 2026 09:29:47 +0200 Subject: [PATCH 70/75] feat(cosmic-theme): add apply_gtk_decoration_layout helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Writes the gtk-decoration-layout key to ~/.config/gtk-{3,4}.0/settings.ini and best-effort updates GNOME's button-layout GSettings key for apps that still consult it. Factors out write_gtk_settings_key for reuse. Leyoda 2026 – GPLv3 --- cosmic-theme/src/output/gtk4_output.rs | 77 +++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/cosmic-theme/src/output/gtk4_output.rs b/cosmic-theme/src/output/gtk4_output.rs index 16a3c36e..bbb4f246 100644 --- a/cosmic-theme/src/output/gtk4_output.rs +++ b/cosmic-theme/src/output/gtk4_output.rs @@ -1,12 +1,13 @@ -use crate::composite::over; -use crate::steps::steps; -use crate::{Component, Theme}; -use palette::rgb::Rgba; -use palette::{Darken, IntoColor, Lighten, Srgba, WithAlpha}; -use std::fs::{self, File}; -use std::io::{self, Write}; -use std::num::NonZeroUsize; -use std::path::Path; +use crate::{Component, Theme, composite::over, steps::steps}; +use configparser::ini::Ini; +use palette::{Darken, IntoColor, Lighten, Srgba, WithAlpha, rgb::Rgba}; +use std::{ + fs::{self, File}, + io::{self, Write}, + num::NonZeroUsize, + path::Path, + process::Command, +}; use super::{OutputError, to_rgba}; @@ -218,6 +219,50 @@ impl Theme { Ok(()) } + /// Apply the preferred GTK client-side decoration button layout. + /// + /// This writes the GTK 3/4 `settings.ini` value used by GTK header bars and + /// also best-effort updates GNOME's `button-layout` GSettings key for apps + /// that still consult it. + /// + /// # Errors + /// + /// Returns an `OutputError` if the GTK settings files cannot be written. + #[cold] + pub fn apply_gtk_decoration_layout(buttons_at_start: bool) -> Result<(), OutputError> { + let Some(config_dir) = dirs::config_dir() else { + return Err(OutputError::MissingConfigDir); + }; + + let layout = if buttons_at_start { + "close,minimize,maximize:" + } else { + ":minimize,maximize,close" + }; + + for gtk_version in ["gtk-3.0", "gtk-4.0"] { + let gtk_dir = config_dir.join(gtk_version); + fs::create_dir_all(>k_dir).map_err(OutputError::Io)?; + Self::write_gtk_settings_key( + >k_dir.join("settings.ini"), + "gtk-decoration-layout", + layout, + )?; + } + + // best-effort: gsettings is absent on non-GNOME systems + let _ = Command::new("gsettings") + .args([ + "set", + "org.gnome.desktop.wm.preferences", + "button-layout", + layout, + ]) + .status(); + + Ok(()) + } + /// Reset the applied gtk css /// /// # Errors @@ -257,6 +302,20 @@ impl Theme { Ok(()) } + #[cold] + fn write_gtk_settings_key(path: &Path, key: &str, value: &str) -> Result<(), OutputError> { + let mut ini = Ini::new_cs(); + + if path.exists() { + let file_content = fs::read_to_string(path).map_err(OutputError::Io)?; + ini.read(file_content).map_err(OutputError::Ini)?; + } + + ini.setstr("Settings", key, Some(value)); + ini.pretty_write(path, &super::qt_settings_ini_style()) + .map_err(OutputError::Io) + } + fn is_cosmic_css(path: &Path, cosmic_css: &Path) -> io::Result> { if !path.exists() { return Ok(None); From 38acba82b466b88a679a2f201b7a8190fe31ae91 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Mon, 25 May 2026 13:02:25 +0200 Subject: [PATCH 71/75] yoda: bump iced submodule + cargo auto-fix sweeps (squashed) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squash of 6 yoda commits: - 282813c8 yoda: bump iced -> window_clipboard via public Forgejo fork - cdf34938 yoda: cargo fix --lib (libcosmic-yoda) — drop 99 trivial warnings - 38a988cb yoda: cargo fix on cosmic-config + bump iced auto-fix commit - 301bbf6e yoda: bump iced submodule -> iced_winit warning cleanup (0 left) - b94c03d9 yoda: bump iced submodule -> iced_widget cleanup (0 left) - a9492d76 yoda: bump iced submodule -> all iced crates at 0 warnings --- src/app/cosmic.rs | 4 ++-- src/app/mod.rs | 2 +- src/ext.rs | 1 - src/scroll.rs | 3 +-- src/theme/style/iced.rs | 32 ++++++++++++++--------------- src/theme/style/segmented_button.rs | 4 ++-- src/widget/button/widget.rs | 8 ++++---- src/widget/cards.rs | 2 +- src/widget/color_picker/mod.rs | 2 +- src/widget/context_menu.rs | 4 ++-- src/widget/dropdown/menu/mod.rs | 11 +++++----- src/widget/dropdown/multi/menu.rs | 2 +- src/widget/dropdown/multi/widget.rs | 7 +++---- src/widget/dropdown/operation.rs | 3 --- src/widget/dropdown/widget.rs | 15 +++++++------- src/widget/flex_row/widget.rs | 2 +- src/widget/grid/widget.rs | 2 +- src/widget/icon/bundle.rs | 2 +- src/widget/mod.rs | 2 -- src/widget/text_input/input.rs | 18 ++++++++-------- 20 files changed, 58 insertions(+), 68 deletions(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 030ed041..a6266b85 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -408,7 +408,7 @@ where f64::from(self.app.core().scale_factor()) } - pub fn style(&self, theme: &Theme) -> theme::Style { + pub fn style(&self, _theme: &Theme) -> theme::Style { if let Some(style) = self.app.style() { style } else if self.app.core().window.is_maximized { @@ -621,7 +621,7 @@ impl Cosmic { #[allow(clippy::too_many_lines)] fn cosmic_update(&mut self, message: Action) -> iced::Task> { match message { - Action::WindowMaximized(id, maximized) => { + Action::WindowMaximized(_id, _maximized) => { #[cfg(not(all(feature = "wayland", target_os = "linux")))] if self .app diff --git a/src/app/mod.rs b/src/app/mod.rs index 264f1d16..8df30cf0 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -136,7 +136,7 @@ pub fn run(settings: Settings, flags: App::Flags) -> iced::Res crate::malloc::limit_mmap_threshold(threshold); } - let default_font = settings.default_font; + let _default_font = settings.default_font; let (settings, (mut core, flags), window_settings) = iced_settings::(settings, flags); #[cfg(not(feature = "multi-window"))] { diff --git a/src/ext.rs b/src/ext.rs index 8eb749e5..65ca0145 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MPL-2.0 use iced::Color; -use iced_core::Widget; pub trait ElementExt { #[must_use] diff --git a/src/scroll.rs b/src/scroll.rs index b6d42378..67947399 100644 --- a/src/scroll.rs +++ b/src/scroll.rs @@ -1,4 +1,3 @@ -use iced::Task; use iced::mouse::ScrollDelta; use std::time::{Duration, Instant}; @@ -95,7 +94,7 @@ impl Scroll { } else { // Return integer part of scroll, and keep remainder self.scroll = Some((scroll.fract(), Instant::now())); - let mut discrete = scroll.trunc() as isize; + let discrete = scroll.trunc() as isize; if discrete != 0 { self.last_discrete = Some(Instant::now()); } diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 131538e4..05b858b6 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -791,7 +791,7 @@ impl menu::Catalog for Theme { fn default<'a>() -> ::Class<'a> {} - fn style(&self, class: &::Class<'_>) -> menu::Style { + fn style(&self, _class: &::Class<'_>) -> menu::Style { let cosmic = self.cosmic(); menu::Style { @@ -815,7 +815,7 @@ impl pick_list::Catalog for Theme { fn style( &self, - class: &::Class<'_>, + _class: &::Class<'_>, status: pick_list::Status, ) -> pick_list::Style { let cosmic = &self.cosmic(); @@ -856,7 +856,7 @@ impl radio::Catalog for Theme { fn default<'a>() -> Self::Class<'a> {} - fn style(&self, class: &Self::Class<'_>, status: radio::Status) -> radio::Style { + fn style(&self, _class: &Self::Class<'_>, status: radio::Status) -> radio::Style { let cur_container = self.current_container(); let theme = self.cosmic(); @@ -909,7 +909,7 @@ impl toggler::Catalog for Theme { fn default<'a>() -> Self::Class<'a> {} - fn style(&self, class: &Self::Class<'_>, status: toggler::Status) -> toggler::Style { + fn style(&self, _class: &Self::Class<'_>, status: toggler::Status) -> toggler::Style { let cosmic = self.cosmic(); const HANDLE_MARGIN: f32 = 2.0; let neutral_10 = cosmic.palette.neutral_10.with_alpha(0.1); @@ -937,8 +937,8 @@ impl toggler::Catalog for Theme { padding_ratio: 0.0, }; match status { - toggler::Status::Active { is_toggled } => active, - toggler::Status::Hovered { is_toggled } => { + toggler::Status::Active { is_toggled: _ } => active, + toggler::Status::Hovered { is_toggled: _ } => { let is_active = matches!(status, toggler::Status::Hovered { is_toggled: true }); toggler::Style { background: if is_active { @@ -957,7 +957,7 @@ impl toggler::Catalog for Theme { ..active } } - toggler::Status::Disabled { is_toggled } => { + toggler::Status::Disabled { is_toggled: _ } => { active.background = active.background.scale_alpha(0.5); active.foreground = active.foreground.scale_alpha(0.5); active @@ -974,7 +974,7 @@ impl pane_grid::Catalog for Theme { fn default<'a>() -> ::Class<'a> {} - fn style(&self, class: &::Class<'_>) -> pane_grid::Style { + fn style(&self, _class: &::Class<'_>) -> pane_grid::Style { let theme = self.cosmic(); pane_grid::Style { @@ -1142,8 +1142,8 @@ impl scrollable::Catalog for Theme { fn style(&self, class: &Self::Class<'_>, status: scrollable::Status) -> scrollable::Style { match status { scrollable::Status::Active { - is_horizontal_scrollbar_disabled, - is_vertical_scrollbar_disabled, + is_horizontal_scrollbar_disabled: _, + is_vertical_scrollbar_disabled: _, } => { let cosmic = self.cosmic(); let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7); @@ -1302,7 +1302,7 @@ impl svg::Catalog for Theme { Svg::default() } - fn style(&self, class: &Self::Class<'_>, status: svg::Status) -> svg::Style { + fn style(&self, class: &Self::Class<'_>, _status: svg::Status) -> svg::Style { #[allow(clippy::match_same_arms)] match class { Svg::Default => svg::Style::default(), @@ -1439,7 +1439,7 @@ impl text_input::Catalog for Theme { }, } } - text_input::Status::Focused { is_hovered } => { + text_input::Status::Focused { is_hovered: _ } => { let bg = self.current_container().small_widget.with_alpha(0.25); match class { @@ -1516,7 +1516,7 @@ impl iced_widget::text_editor::Catalog for Theme { let selection = cosmic.accent.base.into(); let value = cosmic.palette.neutral_9.into(); let placeholder = cosmic.palette.neutral_9.with_alpha(0.7).into(); - let icon: Color = cosmic.background.on.into(); + let _icon: Color = cosmic.background.on.into(); // TODO do we need to add icon color back? match status { @@ -1533,7 +1533,7 @@ impl iced_widget::text_editor::Catalog for Theme { value, selection, }, - iced_widget::text_editor::Status::Focused { is_hovered } => { + iced_widget::text_editor::Status::Focused { is_hovered: _ } => { iced_widget::text_editor::Style { background: iced::Color::from(cosmic.bg_color()).into(), border: Border { @@ -1636,8 +1636,8 @@ impl Base for Theme { crate::theme::ThemeType::Light => "Cosmic Light Theme", crate::theme::ThemeType::HighContrastDark => "Cosmic High Contrast Dark Theme", crate::theme::ThemeType::HighContrastLight => "Cosmic High Contrast Light Theme", - crate::theme::ThemeType::Custom(theme) => "Custom Cosmic Theme", - crate::theme::ThemeType::System { prefer_dark, theme } => &theme.name, + crate::theme::ThemeType::Custom(_theme) => "Custom Cosmic Theme", + crate::theme::ThemeType::System { prefer_dark: _, theme } => &theme.name, } } } diff --git a/src/theme/style/segmented_button.rs b/src/theme/style/segmented_button.rs index 381c4a05..45fcc0b8 100644 --- a/src/theme/style/segmented_button.rs +++ b/src/theme/style/segmented_button.rs @@ -185,7 +185,7 @@ mod horizontal { pub fn selection_active( cosmic: &cosmic_theme::Theme, - component: &Component, + _component: &Component, ) -> ItemStatusAppearance { let rad_xl = cosmic.corner_radii.radius_xl; let rad_0 = cosmic.corner_radii.radius_0; @@ -280,7 +280,7 @@ mod vertical { pub fn selection_active( cosmic: &cosmic_theme::Theme, - component: &Component, + _component: &Component, ) -> ItemStatusAppearance { let rad_0 = cosmic.corner_radii.radius_0; let rad_xl = cosmic.corner_radii.radius_xl; diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index cca54fd2..0f8875c4 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -9,7 +9,7 @@ use iced_runtime::core::widget::Id; use iced_runtime::{Action, Task, keyboard, task}; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::renderer::{self, Quad, Renderer}; use iced_core::widget::Operation; use iced_core::widget::tree::{self, Tree}; @@ -662,7 +662,7 @@ impl<'a, Message: 'a + Clone> Widget height, } = layout.bounds(); let bounds = Rect::new(x as f64, y as f64, (x + width) as f64, (y + height) as f64); - let is_hovered = state.state.downcast_ref::().is_hovered; + let _is_hovered = state.state.downcast_ref::().is_hovered; let mut node = Node::new(Role::Button); node.add_action(Action::Focus); @@ -822,7 +822,7 @@ pub fn update<'a, Message: Clone>( } } #[cfg(feature = "a11y")] - Event::A11y(event_id, iced_accessibility::accesskit::ActionRequest { action, .. }) => { + Event::A11y(_event_id, iced_accessibility::accesskit::ActionRequest { action, .. }) => { let state = state(); if let Some(on_press) = matches!(action, iced_accessibility::accesskit::Action::Click) .then_some(on_press) @@ -865,7 +865,7 @@ pub fn draw( viewport_bounds: Rectangle, styling: &super::style::Style, draw_contents: impl FnOnce(&mut Renderer, &Style), - is_image: bool, + _is_image: bool, ) where Theme: super::style::Catalog, { diff --git a/src/widget/cards.rs b/src/widget/cards.rs index 00014740..bde183fb 100644 --- a/src/widget/cards.rs +++ b/src/widget/cards.rs @@ -93,7 +93,7 @@ where /// Get an expandable stack of cards #[allow(clippy::too_many_arguments)] pub fn new( - id: widget::Id, + _id: widget::Id, card_inner_elements: Vec>, on_clear_all: Message, on_show_more: Option, diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index ba60235d..7d40145f 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -17,7 +17,7 @@ use crate::widget::segmented_button::Entity; use crate::widget::{container, slider}; use derive_setters::Setters; use iced::Task; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::gradient::{ColorStop, Linear}; use iced_core::renderer::Quad; use iced_core::widget::{Tree, tree}; diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 663e9001..9256c954 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -89,7 +89,7 @@ impl ContextMenu<'_, Message> { bounds.x = my_state.context_cursor.x; bounds.y = my_state.context_cursor.y; - let (id, root_list) = my_state.menu_bar_state.inner.with_data_mut(|state| { + let (id, _root_list) = my_state.menu_bar_state.inner.with_data_mut(|state| { if let Some(id) = state.popup_id.get(&self.window_id).copied() { // close existing popups state.menu_states.clear(); @@ -147,7 +147,7 @@ impl ContextMenu<'_, Message> { layout.bounds(), -bounds.height, ); - let (anchor_rect, gravity) = my_state.menu_bar_state.inner.with_data_mut(|state| { + let (anchor_rect, _gravity) = my_state.menu_bar_state.inner.with_data_mut(|state| { use iced::Rectangle; state.popup_id.insert(self.window_id, id); diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index 0c96c1c6..bb3cead1 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -8,9 +8,8 @@ use std::sync::{Arc, Mutex}; pub use appearance::{Appearance, StyleSheet}; -use crate::surface; use crate::widget::{Container, RcWrapper, icon}; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::layout::{self, Layout}; use iced_core::text::{self, Text}; use iced_core::widget::Tree; @@ -391,7 +390,7 @@ impl<'a, Message: Clone + 'a> crate::widget::Widget, - cursor: mouse::Cursor, + _cursor: mouse::Cursor, viewport: &Rectangle, ) { let appearance = theme.appearance(&()); diff --git a/src/widget/dropdown/multi/menu.rs b/src/widget/dropdown/multi/menu.rs index 0a761097..a1da9da7 100644 --- a/src/widget/dropdown/multi/menu.rs +++ b/src/widget/dropdown/multi/menu.rs @@ -2,7 +2,7 @@ use super::Model; pub use crate::widget::dropdown::menu::{Appearance, StyleSheet}; use crate::widget::Container; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::layout::{self, Layout}; use iced_core::text::{self, Text}; use iced_core::widget::Tree; diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index 3c01f5f8..90c6ab71 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -5,7 +5,7 @@ use super::menu::{self, Menu}; use crate::widget::icon; use derive_setters::Setters; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::text::{self, Paragraph, Text}; use iced_core::widget::tree::{self, Tree}; use iced_core::{ @@ -13,7 +13,6 @@ use iced_core::{ alignment, keyboard, layout, mouse, overlay, renderer, svg, touch, }; use iced_widget::pick_list; -use std::ffi::OsStr; pub use iced_widget::pick_list::{Catalog, Style}; @@ -253,7 +252,7 @@ impl Default for State { /// Computes the layout of a [`Dropdown`]. #[allow(clippy::too_many_arguments)] pub fn layout( - renderer: &crate::Renderer, + _renderer: &crate::Renderer, limits: &layout::Limits, width: Length, gap: f32, @@ -376,7 +375,7 @@ pub fn mouse_interaction(layout: Layout<'_>, cursor: mouse::Cursor) -> mouse::In #[allow(clippy::too_many_arguments)] pub fn overlay<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static>( layout: Layout<'_>, - renderer: &crate::Renderer, + _renderer: &crate::Renderer, state: &'a mut State, gap: f32, padding: Padding, diff --git a/src/widget/dropdown/operation.rs b/src/widget/dropdown/operation.rs index 1a4e1a9f..4cd266d4 100644 --- a/src/widget/dropdown/operation.rs +++ b/src/widget/dropdown/operation.rs @@ -2,9 +2,6 @@ // SPDX-License-Identifier: MPL-2.0 AND MIT //! Operate on dropdown widgets. -use super::State; -use iced::Rectangle; -use iced_core::widget::{Id, Operation}; pub trait Dropdown { fn close(&mut self); diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 23476a0b..d28d1a4a 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -8,7 +8,7 @@ use crate::widget::icon::{self, Handle}; use crate::{Element, surface}; use derive_setters::Setters; use iced::window; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::text::{self, Paragraph, Text}; use iced_core::widget::tree::{self, Tree}; use iced_core::{ @@ -17,7 +17,6 @@ use iced_core::{ }; use iced_widget::pick_list::{self, Catalog}; use std::borrow::Cow; -use std::ffi::OsStr; use std::hash::{DefaultHasher, Hash, Hasher}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, LazyLock, Mutex}; @@ -328,10 +327,10 @@ where fn operate( &mut self, - tree: &mut Tree, + _tree: &mut Tree, _layout: Layout<'_>, _renderer: &crate::Renderer, - operation: &mut dyn iced_core::widget::Operation, + _operation: &mut dyn iced_core::widget::Operation, ) { // TODO: double check operation handling // let state = tree.state.downcast_mut::(); @@ -343,7 +342,7 @@ where tree: &'b mut Tree, layout: Layout<'b>, renderer: &crate::Renderer, - viewport: &Rectangle, + _viewport: &Rectangle, translation: Vector, ) -> Option> { #[cfg(all(feature = "wayland", target_os = "linux"))] @@ -452,7 +451,7 @@ impl super::operation::Dropdown for State { /// Computes the layout of a [`Dropdown`]. #[allow(clippy::too_many_arguments)] pub fn layout( - renderer: &crate::Renderer, + _renderer: &crate::Renderer, limits: &layout::Limits, width: Length, gap: f32, @@ -546,7 +545,7 @@ pub fn update< cursor: mouse::Cursor, shell: &mut Shell<'_, Message>, #[cfg(all(feature = "wayland", target_os = "linux"))] - positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, + _positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, on_selected: Arc Message + Send + Sync + 'static>, selected: Option, selections: &[S], @@ -558,7 +557,7 @@ pub fn update< gap: f32, padding: Padding, text_size: Option, - font: Option, + _font: Option, selected_option: Option, ) { let state = state(); diff --git a/src/widget/flex_row/widget.rs b/src/widget/flex_row/widget.rs index 0b2e6e13..e5c0b615 100644 --- a/src/widget/flex_row/widget.rs +++ b/src/widget/flex_row/widget.rs @@ -3,7 +3,7 @@ use crate::{Element, Renderer}; use derive_setters::Setters; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::widget::{Operation, Tree}; use iced_core::{ Clipboard, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, layout, mouse, overlay, diff --git a/src/widget/grid/widget.rs b/src/widget/grid/widget.rs index e59ba90d..55ce3c91 100644 --- a/src/widget/grid/widget.rs +++ b/src/widget/grid/widget.rs @@ -3,7 +3,7 @@ use crate::{Element, Renderer}; use derive_setters::Setters; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::widget::{Operation, Tree}; use iced_core::{ Alignment, Clipboard, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, layout, mouse, diff --git a/src/widget/icon/bundle.rs b/src/widget/icon/bundle.rs index bb6ce244..30a9938a 100644 --- a/src/widget/icon/bundle.rs +++ b/src/widget/icon/bundle.rs @@ -5,7 +5,7 @@ /// Icon bundling is not enabled on unix platforms. #[cfg(all(unix, not(target_os = "macos")))] -pub fn get(icon_name: &str) -> Option { +pub fn get(_icon_name: &str) -> Option { None } diff --git a/src/widget/mod.rs b/src/widget/mod.rs index e68e7477..5764ab44 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -119,8 +119,6 @@ pub mod calendar; pub use calendar::{Calendar, calendar}; pub mod card; -#[doc(inline)] -pub use card::*; pub mod color_picker; #[doc(inline)] diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index ea1122ea..f91e41de 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -21,12 +21,12 @@ use apply::Apply; use iced::Limits; use iced::clipboard::dnd::{DndAction, DndEvent, OfferEvent, SourceEvent}; use iced::clipboard::mime::AsMimeTypes; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::input_method::{self, InputMethod, Preedit}; use iced_core::mouse::{self, click}; use iced_core::overlay::Group; use iced_core::renderer::{self, Renderer as CoreRenderer}; -use iced_core::text::{self, Affinity, Paragraph, Renderer, Text}; +use iced_core::text::{self, Paragraph, Renderer, Text}; use iced_core::time::{Duration, Instant}; use iced_core::widget::Id; use iced_core::widget::operation::{self, Operation}; @@ -825,7 +825,7 @@ where &mut self, tree: &mut Tree, layout: Layout<'_>, - renderer: &crate::Renderer, + _renderer: &crate::Renderer, operation: &mut dyn Operation, ) { operation.container(Some(&self.id), layout.bounds()); @@ -917,7 +917,7 @@ where // Enable custom buttons defined on the trailing icon position to be handled. if !self.is_editable_variant { if let Some(trailing_layout) = trailing_icon_layout { - let res = trailing_icon.as_widget_mut().update( + let _res = trailing_icon.as_widget_mut().update( tree, event, trailing_layout, @@ -2216,7 +2216,7 @@ pub fn update<'a, Message: Clone + 'static>( )) if *rectangle == Some(dnd_id) => { cold(); let state = state(); - let is_clicked = text_layout.bounds().contains(Point { + let _is_clicked = text_layout.bounds().contains(Point { x: *x as f32, y: *y as f32, }); @@ -2224,7 +2224,7 @@ pub fn update<'a, Message: Clone + 'static>( let mut accepted = false; for m in mime_types { if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) { - let clone = m.clone(); + let _clone = m.clone(); accepted = true; } } @@ -2287,7 +2287,7 @@ pub fn update<'a, Message: Clone + 'static>( cold(); let state = state(); if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() { - let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES + let Some(_mime_type) = SUPPORTED_TEXT_MIME_TYPES .iter() .find(|&&m| mime_types.iter().any(|t| t == m)) else { @@ -2304,7 +2304,7 @@ pub fn update<'a, Message: Clone + 'static>( Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if Some(dnd_id) != *id => {} #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer( - rectangle, + _rectangle, OfferEvent::Leave | OfferEvent::LeaveDestination, )) => { cold(); @@ -2607,7 +2607,7 @@ pub fn draw<'a, Message>( let handling_dnd_offer = !matches!(state.dnd_offer, DndOfferState::None); #[cfg(not(all(feature = "wayland", target_os = "linux")))] let handling_dnd_offer = false; - let (cursors, offset, is_selecting) = if let Some(focus) = + let (cursors, offset, _is_selecting) = if let Some(focus) = state.is_focused.filter(|f| f.focused).or_else(|| { let now = Instant::now(); handling_dnd_offer.then_some(Focus { From 7a191cf086975d287dba373878c102afda50f113 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Mon, 25 May 2026 13:02:38 +0200 Subject: [PATCH 72/75] yoda: warning cleanup sweep (dead code + clippy --fix) (squashed) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squash of 4 yoda commits: - 84437e21 yoda: libcosmic-yoda dead-code purge (14->0 warnings) - 999db0a4 yoda: cosmic-theme cleanup (4->0 warnings) — workspace at 0 warnings total - 4743bb8e yoda: cargo clippy --fix on libcosmic-yoda (115->33 warnings) - 675f3b59 chore: reduce local stack warnings --- Cargo.toml | 8 ++++ cosmic-theme/Cargo.toml | 3 -- cosmic-theme/src/model/theme.rs | 6 +++ cosmic-theme/src/output/vs_code.rs | 15 +++++++ src/app/cosmic.rs | 17 ++++--- src/app/mod.rs | 2 +- src/applet/row.rs | 3 +- src/config/mod.rs | 5 +-- src/core.rs | 3 -- src/dbus_activation.rs | 2 +- src/desktop.rs | 12 +++-- src/lib.rs | 11 +++++ src/theme/style/iced.rs | 11 ++--- src/widget/button/icon.rs | 8 ++-- src/widget/button/image.rs | 8 ++-- src/widget/button/link.rs | 8 ++-- src/widget/button/text.rs | 8 ++-- src/widget/button/widget.rs | 23 +++------- src/widget/cards.rs | 4 +- src/widget/color_picker/mod.rs | 29 ++++++------ src/widget/dnd_destination.rs | 9 +--- src/widget/dropdown/menu/mod.rs | 12 ++--- src/widget/dropdown/multi/menu.rs | 6 +-- src/widget/dropdown/multi/widget.rs | 4 +- src/widget/dropdown/widget.rs | 2 +- src/widget/menu/flex.rs | 7 +-- src/widget/menu/menu_bar.rs | 18 ++------ src/widget/menu/menu_inner.rs | 5 +-- src/widget/popover.rs | 11 +---- src/widget/radio.rs | 1 - src/widget/segmented_button/widget.rs | 40 +++++++---------- src/widget/spin_button.rs | 18 +++++--- src/widget/table/widget/standard.rs | 7 ++- src/widget/text_input/input.rs | 64 +++++++++------------------ src/widget/text_input/value.rs | 4 +- src/widget/toggler.rs | 6 +-- 36 files changed, 181 insertions(+), 219 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index effcc93b..c80d47f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -269,5 +269,13 @@ tracing = "0.1" tokio = "1.52" zbus = {version = "5.15", default-features = false} +# Speed up snapshot diffing in cosmic-theme tests. Cargo silently ignores +# [profile.*] blocks in non-root manifests, so this lives at the +# workspace root. +[profile.dev.package.insta] +opt-level = 3 +[profile.dev.package.similar] +opt-level = 3 + [dev-dependencies] tempfile = "3.27.0" diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index 15aee334..b5bffa17 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -34,6 +34,3 @@ thiserror.workspace = true [dev-dependencies] insta = "1.47.2" -[profile.dev.package] -insta.opt-level = 3 -similar.opt-level = 3 diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index a403403c..0c06005e 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -955,6 +955,12 @@ impl ThemeBuilder { } #[allow(clippy::too_many_lines)] + // The component_hovered/pressed_overlay vars are seeded once near the + // top of this fn and then reassigned inside each container block + // (primary, secondary, …) before being read again. The initial seed + // is therefore overwritten before any read, which is what the + // unused_assignments lint flags below. + #[allow(unused_assignments)] /// build the theme pub fn build(self) -> Theme { let Self { diff --git a/cosmic-theme/src/output/vs_code.rs b/cosmic-theme/src/output/vs_code.rs index 43c36bb6..f49c888f 100644 --- a/cosmic-theme/src/output/vs_code.rs +++ b/cosmic-theme/src/output/vs_code.rs @@ -266,6 +266,14 @@ impl From for VsTheme { } impl Theme { + /// Write this theme to VS Code's `settings.json` as a + /// `workbench.colorCustomizations` entry, and enable + /// `window.autoDetectColorScheme` so VS Code follows the system theme. + /// + /// # Errors + /// + /// Returns an `OutputError` if the user config dir is missing, the + /// settings file cannot be read/written, or its JSON is invalid. #[cold] pub fn apply_vs_code(self) -> Result<(), OutputError> { let vs_theme = VsTheme::from(self); @@ -291,6 +299,13 @@ impl Theme { Ok(()) } + /// Remove the `workbench.colorCustomizations` entry previously written + /// by [`Theme::apply_vs_code`] from VS Code's `settings.json`. + /// + /// # Errors + /// + /// Returns an `OutputError` if the user config dir is missing, the + /// settings file cannot be read/written, or its JSON is invalid. #[cold] pub fn reset_vs_code() -> Result<(), OutputError> { let mut config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?; diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index a6266b85..86af0992 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -100,9 +100,10 @@ impl Cosmic where T::Message: Send + 'static, { - pub fn init( - (mut core, flags): (Core, T::Flags), - ) -> (Self, iced::Task>) { + pub fn init((core, flags): (Core, T::Flags)) -> (Self, iced::Task>) { + #[cfg(all(feature = "dbus-config", target_os = "linux"))] + let mut core = core; + #[cfg(all(feature = "dbus-config", target_os = "linux"))] { use iced_futures::futures::executor::block_on; @@ -364,7 +365,6 @@ where crate::surface::Action::Task(f) => { f().map(|sm| crate::Action::Cosmic(Action::Surface(sm))) } - _ => iced::Task::none(), } #[cfg(not(feature = "surface-message"))] @@ -480,12 +480,11 @@ where .into_iter() .filter(cosmic_config::Error::is_err) { - if let cosmic_config::Error::GetKey(_, err) = &why { - if err.kind() == std::io::ErrorKind::NotFound { + if let cosmic_config::Error::GetKey(_, err) = &why + && err.kind() == std::io::ErrorKind::NotFound { // No system default config installed; don't error continue; } - } tracing::error!(?why, "cosmic toolkit config update error"); } @@ -627,9 +626,9 @@ impl Cosmic { .app .core() .main_window_id() - .is_some_and(|main_id| main_id == id) + .is_some_and(|main_id| main_id == _id) { - self.app.core_mut().window.sharp_corners = maximized; + self.app.core_mut().window.sharp_corners = _maximized; } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 8df30cf0..b2d82dcd 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -284,7 +284,7 @@ where // app = app.window(window_settings); core.main_window = Some(iced_core::window::Id::RESERVED); } - let mut app = iced::daemon( + let app = iced::daemon( BootData(Rc::new(RefCell::new(Some(BootDataInner:: { flags, core, diff --git a/src/applet/row.rs b/src/applet/row.rs index 718f366c..888c68ef 100644 --- a/src/applet/row.rs +++ b/src/applet/row.rs @@ -1,14 +1,13 @@ //! Distribute content horizontally. use crate::iced; use iced::core::alignment::{self, Alignment}; -use iced::core::event::{self, Event}; +use iced::core::event::Event; use iced::core::layout::{self, Layout}; use iced::core::widget::{Operation, Tree}; use iced::core::{ Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, mouse, overlay, renderer, widget, }; -use iced::touch; /// A container that distributes its contents horizontally. /// diff --git a/src/config/mod.rs b/src/config/mod.rs index 1691caa8..08a4c0ca 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -23,12 +23,11 @@ pub static COSMIC_TK: LazyLock> = LazyLock::new(|| { .map(|c| { CosmicTk::get_entry(&c).unwrap_or_else(|(errors, mode)| { for why in errors.into_iter().filter(cosmic_config::Error::is_err) { - if let cosmic_config::Error::GetKey(_, err) = &why { - if err.kind() == std::io::ErrorKind::NotFound { + if let cosmic_config::Error::GetKey(_, err) = &why + && err.kind() == std::io::ErrorKind::NotFound { // No system default config installed; don't error continue; } - } tracing::error!(?why, "CosmicTk config entry error"); } mode diff --git a/src/core.rs b/src/core.rs index 44f3b3df..f80954f9 100644 --- a/src/core.rs +++ b/src/core.rs @@ -78,8 +78,6 @@ pub struct Core { pub(super) portal_accent: Option, - pub(super) portal_is_high_contrast: Option, - pub(super) title: HashMap, pub window: Window, @@ -155,7 +153,6 @@ impl Default for Core { settings_daemon: None, portal_is_dark: None, portal_accent: None, - portal_is_high_contrast: None, main_window: None, exit_on_main_window_closed: true, menu_bars: HashMap::new(), diff --git a/src/dbus_activation.rs b/src/dbus_activation.rs index e7bc6900..880db634 100644 --- a/src/dbus_activation.rs +++ b/src/dbus_activation.rs @@ -43,7 +43,7 @@ pub fn subscription() -> Subscription( exec: S, env_vars: I, - app_id: Option<&str>, + _app_id: Option<&str>, terminal: bool, ) where S: AsRef, @@ -816,13 +816,17 @@ pub async fn spawn_desktop_exec( // https://systemd.io/DESKTOP_ENVIRONMENTS // // Similar to what Gnome sets, for now. - if let Some(pid) = crate::process::spawn(cmd).await { + if let Some(_pid) = crate::process::spawn(cmd).await { #[cfg(feature = "desktop-systemd-scope")] if let Ok(session) = zbus::Connection::session().await { if let Ok(systemd_manager) = SystemdMangerProxy::new(&session).await { let _ = systemd_manager .start_transient_unit( - &format!("app-cosmic-{}-{}.scope", app_id.unwrap_or(&executable), pid), + &format!( + "app-cosmic-{}-{}.scope", + _app_id.unwrap_or(&executable), + _pid + ), "fail", &[ ( @@ -833,7 +837,7 @@ pub async fn spawn_desktop_exec( ), ( "PIDs".to_string(), - zbus::zvariant::Value::from(vec![pid]) + zbus::zvariant::Value::from(vec![_pid]) .try_to_owned() .unwrap(), ), diff --git a/src/lib.rs b/src/lib.rs index 2e658437..9c31506d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,6 +63,17 @@ pub mod font; #[doc(inline)] pub use iced; +#[doc(inline)] +pub use iced_core; +#[doc(inline)] +pub use iced_futures; +#[doc(inline)] +pub use iced_runtime; +#[doc(inline)] +pub use iced_widget; +#[doc(inline)] +#[cfg(feature = "wayland")] +pub use iced_winit; pub mod icon_theme; pub mod keyboard_nav; diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 05b858b6..8f9fa461 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -170,18 +170,15 @@ impl Button { * TODO: Checkbox */ #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Default)] pub enum Checkbox { + #[default] Primary, Secondary, Success, Danger, } -impl Default for Checkbox { - fn default() -> Self { - Self::Primary - } -} impl iced_checkbox::Catalog for Theme { type Class<'a> = Checkbox; @@ -1192,7 +1189,7 @@ impl scrollable::Catalog for Theme { background: Color::TRANSPARENT.into(), border: Border::default(), shadow: Shadow::default(), - icon: Color::TRANSPARENT.into(), + icon: Color::TRANSPARENT, }, }; let small_widget_container = self.current_container().small_widget.with_alpha(0.7); @@ -1260,7 +1257,7 @@ impl scrollable::Catalog for Theme { background: Color::TRANSPARENT.into(), border: Border::default(), shadow: Shadow::default(), - icon: Color::TRANSPARENT.into(), + icon: Color::TRANSPARENT, }, }; diff --git a/src/widget/button/icon.rs b/src/widget/button/icon.rs index 6be13008..47713536 100644 --- a/src/widget/button/icon.rs +++ b/src/widget/button/icon.rs @@ -156,7 +156,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes ); } - let mut button = if builder.variant.vertical { + let button = if builder.variant.vertical { crate::widget::column::with_children(content) .padding(builder.padding) .spacing(builder.spacing) @@ -173,9 +173,11 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes }; #[cfg(feature = "a11y")] - { + let button = { + let mut button = button; button = button.name(builder.name).description(builder.description); - } + button + }; let button = button .padding(0) diff --git a/src/widget/button/image.rs b/src/widget/button/image.rs index 3b382035..915cc532 100644 --- a/src/widget/button/image.rs +++ b/src/widget/button/image.rs @@ -84,7 +84,7 @@ where .width(builder.width) .height(builder.height); - let mut button = super::custom_image_button(content, builder.variant.on_remove) + let button = super::custom_image_button(content, builder.variant.on_remove) .padding(0) .selected(builder.variant.selected) .id(builder.id) @@ -92,9 +92,11 @@ where .class(builder.class); #[cfg(feature = "a11y")] - { + let button = { + let mut button = button; button = button.name(builder.name).description(builder.description); - } + button + }; button.into() } diff --git a/src/widget/button/link.rs b/src/widget/button/link.rs index c0afd327..e6f8ac7e 100644 --- a/src/widget/button/link.rs +++ b/src/widget/button/link.rs @@ -67,7 +67,7 @@ pub fn icon() -> Handle { impl<'a, Message: Clone + 'static> From> for Element<'a, Message> { fn from(mut builder: Button<'a, Message>) -> Element<'a, Message> { - let mut button: super::Button<'a, Message> = row::with_capacity(2) + let button: super::Button<'a, Message> = row::with_capacity(2) .push({ // TODO: Avoid allocation crate::widget::text(builder.label.to_string()) @@ -95,13 +95,15 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .class(builder.class); #[cfg(feature = "a11y")] - { + let button = { + let mut button = button; if !builder.label.is_empty() { button = button.name(builder.label); } button = button.description(builder.description); - } + button + }; if builder.tooltip.is_empty() { button.into() diff --git a/src/widget/button/text.rs b/src/widget/button/text.rs index 52ab6145..55623ff8 100644 --- a/src/widget/button/text.rs +++ b/src/widget/button/text.rs @@ -122,7 +122,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .into() }); - let mut button: super::Button<'a, Message> = row::with_capacity(3) + let button: super::Button<'a, Message> = row::with_capacity(3) // Optional icon to place before label. .push_maybe(leading_icon) // Optional label between icons. @@ -141,13 +141,15 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .class(builder.class); #[cfg(feature = "a11y")] - { + let button = { + let mut button = button; if !builder.label.is_empty() { button = button.name(builder.label) } button = button.description(builder.description); - } + button + }; if builder.tooltip.is_empty() { button.into() diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 0f8875c4..e6b83d7b 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -378,13 +378,12 @@ impl<'a, Message: 'a + Clone> Widget match event { Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { - if let Some(position) = cursor.position() { - if removal_bounds(layout.bounds(), 4.0).contains(position) { + if let Some(position) = cursor.position() + && removal_bounds(layout.bounds(), 4.0).contains(position) { shell.publish(on_remove.clone()); shell.capture_event(); return; } - } } _ => (), @@ -557,9 +556,9 @@ impl<'a, Message: 'a + Clone> Widget } } - if on_remove.is_some() { - if let Some(position) = cursor.position() { - if bounds.contains(position) { + if on_remove.is_some() + && let Some(position) = cursor.position() + && bounds.contains(position) { let bounds = removal_bounds(layout.bounds(), 4.0); renderer.fill_quad( renderer::Quad { @@ -591,8 +590,6 @@ impl<'a, Message: 'a + Clone> Widget }, ); } - } - } }); } } @@ -743,12 +740,6 @@ impl State { self.is_focused } - /// Returns whether the [`Button`] is currently hovered or not. - #[inline] - pub fn is_hovered(self) -> bool { - self.is_hovered - } - /// Focuses the [`Button`]. #[inline] pub fn focus(&mut self) { @@ -794,7 +785,6 @@ pub fn update<'a, Message: Clone>( } shell.capture_event(); - return; } } } @@ -814,7 +804,6 @@ pub fn update<'a, Message: Clone>( } shell.capture_event(); - return; } } else if on_press_down.is_some() { let state = state(); @@ -834,7 +823,6 @@ pub fn update<'a, Message: Clone>( shell.publish(msg); } shell.capture_event(); - return; } Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { if let Some(on_press) = on_press { @@ -845,7 +833,6 @@ pub fn update<'a, Message: Clone>( shell.publish(msg); shell.capture_event(); - return; } } } diff --git a/src/widget/cards.rs b/src/widget/cards.rs index bde183fb..4d6770fa 100644 --- a/src/widget/cards.rs +++ b/src/widget/cards.rs @@ -527,7 +527,7 @@ where let c_layout = layout.next().unwrap(); let state = clear_all_state.unwrap(); self.clear_all_button.as_widget_mut().update( - state, &event, c_layout, cursor, renderer, clipboard, shell, viewport, + state, event, c_layout, cursor, renderer, clipboard, shell, viewport, ); } @@ -537,7 +537,7 @@ where for ((inner, layout), c_state) in self.elements.iter_mut().zip(layout).zip(tree_children) { inner.as_widget_mut().update( - c_state, &event, layout, cursor, renderer, clipboard, shell, viewport, + c_state, event, layout, cursor, renderer, clipboard, shell, viewport, ); if shell.is_event_captured() || fully_unexpanded { break; diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 7d40145f..879cc604 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -745,7 +745,7 @@ where let column_tree = &mut tree.children[0]; self.inner.as_widget_mut().update( column_tree, - &event, + event, column_layout, cursor, renderer, @@ -758,22 +758,19 @@ where return; } - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { - let bounds = column_layout.children().nth(1).unwrap().bounds(); - if let Some(point) = cursor.position_over(bounds) { - let relative_pos = point - bounds.position(); - let (s, v) = ( - relative_pos.x / bounds.width, - 1.0 - relative_pos.y / bounds.height, - ); - state.dragging = true; - let hsv: palette::Hsv = palette::Hsv::new(self.active_color.hue, s, v); - shell.publish((self.on_update)(ColorPickerUpdate::ActiveColor(hsv))); - shell.capture_event(); - } + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) = event { + let bounds = column_layout.children().nth(1).unwrap().bounds(); + if let Some(point) = cursor.position_over(bounds) { + let relative_pos = point - bounds.position(); + let (s, v) = ( + relative_pos.x / bounds.width, + 1.0 - relative_pos.y / bounds.height, + ); + state.dragging = true; + let hsv: palette::Hsv = palette::Hsv::new(self.active_color.hue, s, v); + shell.publish((self.on_update)(ColorPickerUpdate::ActiveColor(hsv))); + shell.capture_event(); } - _ => {} } } diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index 0ebff3dc..afe135bc 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -357,7 +357,7 @@ impl Widget x, y, mime_types, .. }, )) if *id == Some(my_id) => { - if !self.mime_matches(&mime_types) { + if !self.mime_matches(mime_types) { log::trace!( target: DND_DEST_LOG_TARGET, "offer enter id={my_id:?} ignored (mimes={mime_types:?} not in {:?})", @@ -396,7 +396,6 @@ impl Widget ); } shell.capture_event(); - return; } Event::Dnd(DndEvent::Offer(_, OfferEvent::Leave)) => { log::trace!( @@ -424,7 +423,6 @@ impl Widget viewport, ); } - return; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Motion { x, y })) if *id == Some(my_id) => { log::trace!( @@ -459,7 +457,6 @@ impl Widget ); } shell.capture_event(); - return; } Event::Dnd(DndEvent::Offer(_, OfferEvent::LeaveDestination)) => { log::trace!( @@ -472,7 +469,6 @@ impl Widget { shell.publish(msg); } - return; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Drop)) if *id == Some(my_id) => { log::trace!( @@ -485,7 +481,6 @@ impl Widget shell.publish(msg); } shell.capture_event(); - return; } Event::Dnd(DndEvent::Offer(id, OfferEvent::SelectedAction(action))) if *id == Some(my_id) => @@ -503,7 +498,6 @@ impl Widget shell.publish(msg); } shell.capture_event(); - return; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Data { data, mime_type })) if *id == Some(my_id) => @@ -543,7 +537,6 @@ impl Widget return; } shell.capture_event(); - return; } _ => {} } diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index bb3cead1..99bee955 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -474,16 +474,14 @@ where match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { let hovered_guard = self.hovered_option.lock().unwrap(); - if cursor.is_over(layout.bounds()) { - if let Some(index) = *hovered_guard { + if cursor.is_over(layout.bounds()) + && let Some(index) = *hovered_guard { shell.publish((self.on_selected)(index)); if let Some(close_on_selected) = self.close_on_selected.as_ref() { shell.publish(close_on_selected.clone()); } shell.capture_event(); - return; } - } } Event::Mouse(mouse::Event::CursorMoved { .. }) => { if let Some(cursor_position) = cursor.position_in(layout.bounds()) { @@ -498,11 +496,10 @@ where let new_hovered_option = (cursor_position.y / option_height) as usize; let mut hovered_guard = self.hovered_option.lock().unwrap(); - if let Some(on_option_hovered) = self.on_option_hovered { - if *hovered_guard != Some(new_hovered_option) { + if let Some(on_option_hovered) = self.on_option_hovered + && *hovered_guard != Some(new_hovered_option) { shell.publish(on_option_hovered(new_hovered_option)); } - } *hovered_guard = Some(new_hovered_option); } @@ -526,7 +523,6 @@ where shell.publish(close_on_selected.clone()); } shell.capture_event(); - return; } } } diff --git a/src/widget/dropdown/multi/menu.rs b/src/widget/dropdown/multi/menu.rs index a1da9da7..883b8ada 100644 --- a/src/widget/dropdown/multi/menu.rs +++ b/src/widget/dropdown/multi/menu.rs @@ -343,13 +343,11 @@ where match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { - if cursor.is_over(bounds) { - if let Some(item) = self.hovered_option.as_ref() { + if cursor.is_over(bounds) + && let Some(item) = self.hovered_option.as_ref() { shell.publish((self.on_selected)(item.clone())); shell.capture_event(); - return; } - } } Event::Mouse(mouse::Event::CursorMoved { .. }) => { if let Some(cursor_position) = cursor.position_in(bounds) { diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index 90c6ab71..c7a46592 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -6,7 +6,7 @@ use super::menu::{self, Menu}; use crate::widget::icon; use derive_setters::Setters; use iced_core::event::Event; -use iced_core::text::{self, Paragraph, Text}; +use iced_core::text::{self, Text}; use iced_core::widget::tree::{self, Tree}; use iced_core::{ Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shadow, Shell, Size, Vector, Widget, @@ -127,7 +127,7 @@ impl<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static> _viewport: &Rectangle, ) { update( - &event, + event, layout, cursor, shell, diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index d28d1a4a..ebcf175a 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -263,7 +263,7 @@ where _viewport: &Rectangle, ) { update::( - &event, + event, layout, cursor, shell, diff --git a/src/widget/menu/flex.rs b/src/widget/menu/flex.rs index 213ee694..e4a32870 100644 --- a/src/widget/menu/flex.rs +++ b/src/widget/menu/flex.rs @@ -48,6 +48,7 @@ impl Axis { /// padding and alignment to the items as needed. /// /// It returns a new layout [`Node`]. +#[allow(dead_code)] // kept as public helper; not currently called by libcosmic pub fn resolve<'a, E, Message, Renderer>( axis: &Axis, renderer: &Renderer, @@ -246,7 +247,7 @@ pub fn resolve_wrapper<'a, Message>( if align_items == Alignment::Center { let mut fill_cross = axis.cross(limits.min()); - for (child, tree) in items.into_iter().zip(tree.iter_mut()) { + for (child, tree) in items.iter_mut().zip(tree.iter_mut()) { let c_size = child.size(); let cross_fill_factor = match axis { Axis::Horizontal => c_size.height, @@ -269,7 +270,7 @@ pub fn resolve_wrapper<'a, Message>( cross = fill_cross; } - for (i, (child, tree)) in items.into_iter().zip(tree.iter_mut()).enumerate() { + for (i, (child, tree)) in items.iter_mut().zip(tree.iter_mut()).enumerate() { let c_size = child.size(); let fill_factor = match axis { Axis::Horizontal => c_size.width, @@ -312,7 +313,7 @@ pub fn resolve_wrapper<'a, Message>( let remaining = available.max(0.0); - for (i, (child, tree)) in items.into_iter().zip(tree.iter_mut()).enumerate() { + for (i, (child, tree)) in items.iter_mut().zip(tree.iter_mut()).enumerate() { let c_size = child.size(); let fill_factor = match axis { Axis::Horizontal => c_size.width, diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 3cefaf86..3843c349 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -50,7 +50,6 @@ pub(crate) struct MenuBarStateInner { pub(crate) tree: Tree, pub(crate) popup_id: HashMap, pub(crate) pressed: bool, - pub(crate) bar_pressed: bool, pub(crate) view_cursor: Cursor, pub(crate) open: bool, pub(crate) active_root: Vec, @@ -87,7 +86,6 @@ impl Default for MenuBarStateInner { vertical_direction: Direction::Positive, menu_states: Vec::new(), popup_id: HashMap::new(), - bar_pressed: false, } } } @@ -164,14 +162,6 @@ where } } -pub fn get_mut_or_default(vec: &mut Vec, index: usize) -> &mut T { - if index < vec.len() { - &mut vec[index] - } else { - vec.resize_with(index + 1, T::default); - &mut vec[index] - } -} /// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing. #[allow(missing_debug_implementations)] @@ -605,14 +595,12 @@ where .with_data(|d| !d.open && !d.active_root.is_empty()); let open = my_state.inner.with_data_mut(|state| { - if reset { - if let Some(popup_id) = state.popup_id.get(&self.window_id).copied() { - if let Some(handler) = self.on_surface_action.as_ref() { + if reset + && let Some(popup_id) = state.popup_id.get(&self.window_id).copied() + && let Some(handler) = self.on_surface_action.as_ref() { shell.publish((handler)(crate::surface::Action::DestroyPopup(popup_id))); state.reset(); } - } - } state.open }); diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index e303b3f1..c534c5b7 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -1522,14 +1522,13 @@ where .is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty()); #[cfg(all(feature = "multi-window", feature = "wayland", target_os = "linux", feature = "surface-message"))] - if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove { - if let Some(id) = state.popup_id.remove(&menu.window_id) { + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove + && let Some(id) = state.popup_id.remove(&menu.window_id) { state.active_root.truncate(menu.depth + 1); shell.publish((menu.on_surface_action.as_ref().unwrap())({ crate::surface::action::destroy_popup(id) })); } - } let item = &active_menu[new_index]; // set new index let old_index = last_menu_state.index.replace(new_index); diff --git a/src/widget/popover.rs b/src/widget/popover.rs index 57e4568a..138d5a19 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -160,8 +160,8 @@ where shell.capture_event(); return; } - } else if let Some(on_close) = self.on_close.as_ref() { - if matches!( + } else if let Some(on_close) = self.on_close.as_ref() + && matches!( event, Event::Mouse(mouse::Event::ButtonPressed(_)) | Event::Touch(touch::Event::FingerPressed { .. }) @@ -169,7 +169,6 @@ where { shell.publish(on_close.clone()); } - } } // Hide cursor from background content when modal popup is active @@ -472,12 +471,6 @@ where } } -/// The local state of a [`Popover`]. -#[derive(Debug, Default)] -struct State { - is_open: bool, -} - /// The first child in [`Popover::children`] is always the wrapped content. fn content_tree(tree: &Tree) -> &Tree { &tree.children[0] diff --git a/src/widget/radio.rs b/src/widget/radio.rs index 7e6ef424..f8e174e1 100644 --- a/src/widget/radio.rs +++ b/src/widget/radio.rs @@ -262,7 +262,6 @@ where if cursor.is_over(layout.bounds()) { shell.publish(self.on_click.clone()); shell.capture_event(); - return; } } _ => {} diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index aa5d1624..5e0b4f7f 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -694,11 +694,10 @@ where if let Some(icon) = self.model.icon(key) { non_text_width += f32::from(icon.size) + f32::from(self.button_spacing); - } else if self.model.is_active(key) { - if let crate::theme::SegmentedButton::Control = self.style { + } else if self.model.is_active(key) + && let crate::theme::SegmentedButton::Control = self.style { non_text_width += 16.0 + f32::from(self.button_spacing); } - } if self.model.is_closable(key) { non_text_width += @@ -1085,11 +1084,10 @@ where { state.drop_hint = None; self.emit_drop_hint(shell, state.drop_hint); - if let Some(Some(entity)) = entity { - if let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() { + if let Some(Some(entity)) = entity + && let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() { shell.publish(on_dnd_leave(entity)); } - } log::trace!( target: TAB_REORDER_LOG_TARGET, "offer leave id={my_id:?} entity={entity:?}" @@ -1165,11 +1163,10 @@ where None:: Message>, None, ); - if let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() { - if let Some(Some(entity)) = entity { + if let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() + && let Some(Some(entity)) = entity { shell.publish(on_dnd_leave(entity)); } - } } } DndEvent::Offer(id, OfferEvent::Drop) if Some(my_id) == *id => { @@ -1342,8 +1339,8 @@ where // Emit close message if the close button is pressed. if let Some(on_close) = self.on_close.as_ref() { if over_close_button - && (left_button_released(&event) - || (touch_lifted(&event) && fingers_pressed == 1)) + && (left_button_released(event) + || (touch_lifted(event) && fingers_pressed == 1)) { shell.publish(on_close(key)); shell.capture_event(); @@ -1397,14 +1394,14 @@ where } } - if is_lifted(&event) { + if is_lifted(event) { state.unfocus(); } if let Some(on_activate) = self.on_activate.as_ref() { if is_pressed(event) { state.pressed_item = Some(Item::Tab(key)); - } else if is_lifted(&event) && self.button_is_pressed(state, key) { + } else if is_lifted(event) && self.button_is_pressed(state, key) { shell.publish(on_activate(key)); state.set_focused(); state.focused_item = Item::Tab(key); @@ -1444,8 +1441,8 @@ where // Present a context menu on a right click event. if self.context_menu.is_some() && let Some(on_context) = self.on_context.as_ref() - && (right_button_released(&event) - || (touch_lifted(&event) && fingers_pressed == 2)) + && (right_button_released(event) + || (touch_lifted(event) && fingers_pressed == 2)) { state.show_context = Some(key); state.context_cursor = cursor_position.position().unwrap_or_default(); @@ -1543,12 +1540,12 @@ where } if state.is_focused() { // Unfocus on clicks outside of the boundaries of the segmented button. - if is_pressed(&event) { + if is_pressed(event) { state.unfocus(); state.pressed_item = None; return; } - } else if is_lifted(&event) { + } else if is_lifted(event) { state.pressed_item = None; } } @@ -2167,8 +2164,8 @@ where ); } - if show_drop_hint_marker { - if matches!( + if show_drop_hint_marker + && matches!( drop_hint_marker, Some(DropHint { entity, @@ -2183,7 +2180,6 @@ where appearance.active.text_color, ); } - } nth += 1; }); @@ -2368,7 +2364,7 @@ where } } -struct TabDragSource { +pub(super) struct TabDragSource { mime: String, threshold: f32, _marker: PhantomData, @@ -2419,7 +2415,6 @@ struct TabDragCandidate { #[derive(Debug, Clone, Copy)] struct Focus { updated_at: Instant, - now: Instant, } /// State that is maintained by each individual widget. @@ -2492,7 +2487,6 @@ impl LocalState { self.focused = Some(Focus { updated_at: now, - now, }); } } diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index 71933124..b2528861 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -23,7 +23,7 @@ pub fn spin_button<'a, T, M>( where T: Copy + Sub + Add + PartialOrd, { - let mut button = SpinButton::new( + let button = SpinButton::new( label, value, step, @@ -34,9 +34,11 @@ where ); #[cfg(feature = "a11y")] - { + let button = { + let mut button = button; button = button.name(name.into()); - } + button + }; button } @@ -54,20 +56,22 @@ pub fn vertical<'a, T, M>( where T: Copy + Sub + Add + PartialOrd, { - let mut button = SpinButton::new( + let button = SpinButton::new( label, value, step, min, max, - Orientation::Horizontal, + Orientation::Vertical, on_press, ); #[cfg(feature = "a11y")] - { + let button = { + let mut button = button; button = button.name(name.into()); - } + button + }; button } diff --git a/src/widget/table/widget/standard.rs b/src/widget/table/widget/standard.rs index 0e206238..e21cd22f 100644 --- a/src/widget/table/widget/standard.rs +++ b/src/widget/table/widget/standard.rs @@ -84,15 +84,14 @@ where let mut sort_state = 0; - if let Some(sort) = val.model.sort { - if sort.0 == category { + if let Some(sort) = val.model.sort + && sort.0 == category { if sort.1 { sort_state = 1; } else { sort_state = 2; } - } - }; + }; // Build the category header widget::row::with_capacity(2) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index f91e41de..730fae25 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -666,18 +666,15 @@ where let old_value = Value::new(&old_value); if state.is_focused() && let cursor::State::Index(index) = state.cursor.state(&old_value) - { - if index == old_value.len() { + && index == old_value.len() { state.cursor.move_to(self.value.len()); } - } - if let Some(f) = state.is_focused.as_ref().filter(|f| f.focused) { - if f.updated_at != LAST_FOCUS_UPDATE.with(|f| f.get()) { + if let Some(f) = state.is_focused.as_ref().filter(|f| f.focused) + && f.updated_at != LAST_FOCUS_UPDATE.with(|f| f.get()) { state.unfocus(); state.emit_unfocus = true; } - } if self.is_editable_variant { if !state.is_focused() { @@ -891,8 +888,8 @@ where let line_height = self.line_height; // Disables editing of the editable variant when clicking outside of, or for tab focus changes. - if self.is_editable_variant { - if let Some(ref on_edit) = self.on_toggle_edit { + if self.is_editable_variant + && let Some(ref on_edit) = self.on_toggle_edit { let state = tree.state.downcast_mut::(); if !state.is_read_only && state.is_focused.is_some_and(|f| !f.focused) { state.is_read_only = true; @@ -904,7 +901,6 @@ where shell.publish((on_edit)(f.focused)); } } - } // Calculates the layout of the trailing icon button element. if !tree.children.is_empty() { @@ -915,9 +911,9 @@ where trailing_icon_layout = Some(text_layout.children().last().unwrap()); // Enable custom buttons defined on the trailing icon position to be handled. - if !self.is_editable_variant { - if let Some(trailing_layout) = trailing_icon_layout { - let _res = trailing_icon.as_widget_mut().update( + if !self.is_editable_variant + && let Some(trailing_layout) = trailing_icon_layout { + trailing_icon.as_widget_mut().update( tree, event, trailing_layout, @@ -932,18 +928,16 @@ where return; } } - } } } let state = tree.state.downcast_mut::(); - if let Some(on_unfocus) = self.on_unfocus.as_ref() { - if state.emit_unfocus { + if let Some(on_unfocus) = self.on_unfocus.as_ref() + && state.emit_unfocus { state.emit_unfocus = false; shell.publish(on_unfocus.clone()); } - } let dnd_id = self.dnd_id(); let id = Widget::id(self); @@ -1652,11 +1646,10 @@ pub fn update<'a, Message: Clone + 'static>( if matches!(state.dragging_state, None | Some(DraggingState::Selection)) && (!state.is_focused() || (is_editable_variant && state.is_read_only)) { - if !state.is_focused() { - if let Some(on_focus) = on_focus { + if !state.is_focused() + && let Some(on_focus) = on_focus { shell.publish(on_focus.clone()); } - } if state.is_read_only { state.is_read_only = false; @@ -1681,7 +1674,6 @@ pub fn update<'a, Message: Clone + 'static>( state.last_click = Some(click); shell.capture_event(); - return; } else { state.unfocus(); @@ -1717,7 +1709,6 @@ pub fn update<'a, Message: Clone + 'static>( if cursor.is_over(layout.bounds()) { shell.capture_event(); } - return; } Event::Mouse(mouse::Event::CursorMoved { position }) | Event::Touch(touch::Event::FingerMoved { position, .. }) => { @@ -1800,7 +1791,6 @@ pub fn update<'a, Message: Clone + 'static>( } shell.capture_event(); - return; } } Event::Keyboard(keyboard::Event::KeyPressed { @@ -1825,14 +1815,13 @@ pub fn update<'a, Message: Clone + 'static>( if state.keyboard_modifiers.command() { match key.to_latin(*physical_key) { Some('c') => { - if !is_secure { - if let Some((start, end)) = state.cursor.selection(value) { + if !is_secure + && let Some((start, end)) = state.cursor.selection(value) { clipboard.write( iced_core::clipboard::Kind::Standard, value.select(start, end).to_string(), ); } - } } // XXX if we want to allow cutting of secure text, we need to // update the cache and decide which value to cut @@ -2087,7 +2076,6 @@ pub fn update<'a, Message: Clone + 'static>( } shell.capture_event(); - return; } } Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => { @@ -2107,7 +2095,6 @@ pub fn update<'a, Message: Clone + 'static>( } shell.capture_event(); - return; } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { @@ -2123,7 +2110,6 @@ pub fn update<'a, Message: Clone + 'static>( state.preedit = matches!(event, input_method::Event::Opened) .then(input_method::Preedit::new); shell.capture_event(); - return; } input_method::Event::Preedit(content, selection) => { if state.is_focused() { @@ -2133,7 +2119,6 @@ pub fn update<'a, Message: Clone + 'static>( text_size: Some(size.into()), }); shell.capture_event(); - return; } } input_method::Event::Commit(text) => { @@ -2151,7 +2136,7 @@ pub fn update<'a, Message: Clone + 'static>( LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at)); let mut editor = Editor::new(unsecured_value, &mut state.cursor); - editor.paste(Value::new(&text)); + editor.paste(Value::new(text)); let contents = editor.contents(); let unsecured_value = Value::new(&contents); @@ -2171,7 +2156,6 @@ pub fn update<'a, Message: Clone + 'static>( update_cache(state, &value); shell.capture_event(); - return; } } } @@ -2201,7 +2185,6 @@ pub fn update<'a, Message: Clone + 'static>( // TODO: restore value in text input state.dragging_state = None; shell.capture_event(); - return; } } #[cfg(all(feature = "wayland", target_os = "linux"))] @@ -2211,7 +2194,7 @@ pub fn update<'a, Message: Clone + 'static>( x, y, mime_types, - surface, + surface: _surface, }, )) if *rectangle == Some(dnd_id) => { cold(); @@ -2251,11 +2234,10 @@ pub fn update<'a, Message: Clone + 'static>( state.cursor.set_affinity(affinity); state.cursor.move_to(position); shell.capture_event(); - return; } } #[cfg(all(feature = "wayland", target_os = "linux"))] - Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y })) + Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y: _ })) if *rectangle == Some(dnd_id) => { let state = state(); @@ -2280,7 +2262,6 @@ pub fn update<'a, Message: Clone + 'static>( state.cursor.set_affinity(affinity); state.cursor.move_to(position); shell.capture_event(); - return; } #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if *rectangle == Some(dnd_id) => { @@ -2297,8 +2278,6 @@ pub fn update<'a, Message: Clone + 'static>( }; state.dnd_offer = DndOfferState::Dropped; } - - return; } #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if Some(dnd_id) != *id => {} @@ -2318,7 +2297,6 @@ pub fn update<'a, Message: Clone + 'static>( } }; shell.capture_event(); - return; } #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type })) @@ -2355,9 +2333,7 @@ pub fn update<'a, Message: Clone + 'static>( }; update_cache(state, &value); shell.capture_event(); - return; } - return; } _ => {} } @@ -2790,11 +2766,11 @@ pub fn draw<'a, Message>( // draw the end icon in the text input if let (Some(icon), Some(tree)) = (trailing_icon, trailing_icon_tree) { let mut children = text_layout.children(); - let mut icon_layout = children.next().unwrap(); + children.next().unwrap(); // skip text layout if has_start_icon { - icon_layout = children.next().unwrap(); + children.next().unwrap(); // skip start-icon layout } - icon_layout = children.next().unwrap(); + let icon_layout = children.next().unwrap(); // trailing-icon layout icon.as_widget().draw( tree, diff --git a/src/widget/text_input/value.rs b/src/widget/text_input/value.rs index 3f7b8d73..59e77752 100644 --- a/src/widget/text_input/value.rs +++ b/src/widget/text_input/value.rs @@ -45,9 +45,7 @@ impl Value { pub fn previous_start_of_word(&self, index: usize) -> usize { let previous_string = &self.graphemes[..index.min(self.graphemes.len())].concat(); - UnicodeSegmentation::split_word_bound_indices(previous_string as &str) - .filter(|(_, word)| !word.trim_start().is_empty()) - .next_back() + UnicodeSegmentation::split_word_bound_indices(previous_string as &str).rfind(|(_, word)| !word.trim_start().is_empty()) .map_or(0, |(i, previous_word)| { index - UnicodeSegmentation::graphemes(previous_word, true).count() diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index 2dad2ed9..2254759e 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -182,7 +182,8 @@ impl<'a, Message> Widget for Toggler<'a, ) -> layout::Node { let limits = limits.width(self.width); - let res = next_to_each_other( + + next_to_each_other( &limits, self.spacing, |limits| { @@ -221,8 +222,7 @@ impl<'a, Message> Widget for Toggler<'a, } }, |_| layout::Node::new(Size::new(48., 24.)), - ); - res + ) } fn update( From 5f50d74477238d94638bb605ecabbbeb3adc68cb Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Mon, 25 May 2026 13:02:38 +0200 Subject: [PATCH 73/75] feat(flex_row): reorderable rows with shadow constant gated (squashed) Squash of 2 yoda commits: - 7dd0ee83 feat: reorderable flex row - b1b8203a fix: gate reorderable flex row shadow constant --- src/widget/reorderable_flex_row/widget.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/widget/reorderable_flex_row/widget.rs b/src/widget/reorderable_flex_row/widget.rs index ed95a34f..03789877 100644 --- a/src/widget/reorderable_flex_row/widget.rs +++ b/src/widget/reorderable_flex_row/widget.rs @@ -21,6 +21,7 @@ use std::time::{Duration, Instant}; const DEFAULT_ANIMATION_DURATION: Duration = Duration::from_millis(180); const DEFAULT_DRAG_LIFT: f32 = 10.0; const DEFAULT_DRAG_THRESHOLD: f32 = 6.0; +#[cfg(feature = "wgpu")] const SHADOW_BLUR_RADIUS: f32 = 20.0; const POSITION_EPSILON: f32 = 0.5; From 6722f1e58173c03b20f826bee1ff43d6fc50fd42 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Mon, 25 May 2026 09:55:11 +0200 Subject: [PATCH 74/75] yoda: use local dbus settings bindings --- Cargo.toml | 4 ++-- cosmic-config/Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c80d47f4..1d4e8ba0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -177,8 +177,8 @@ ron = { workspace = true, optional = true } # Enable DBus feature on Linux targets [target.'cfg(target_os = "linux")'.dependencies] cosmic-config = { path = "cosmic-config", features = ["dbus"] } -cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" } -zbus.workspace = true +cosmic-settings-daemon = { path = "../dbus-settings-bindings/cosmic-settings-daemon" } +zbus = { version = "5.14.0", default-features = false } [target.'cfg(all(unix, not(target_os = "macos")))'.dependencies] freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } diff --git a/cosmic-config/Cargo.toml b/cosmic-config/Cargo.toml index 0316c707..a81d13fd 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -10,8 +10,8 @@ macro = ["cosmic-config-derive"] subscription = ["iced_futures"] [dependencies] -cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } -zbus = { workspace = true, default-features = false, optional = true } +cosmic-settings-daemon = { path = "../../dbus-settings-bindings/cosmic-settings-daemon", optional = true } +zbus = { version = "5.14.0", default-features = false, optional = true } atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" } calloop = { version = "0.14.4", optional = true } notify = "8.2.0" From f3db54342c28b75e3cd34282913db5f05b39e791 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Mon, 25 May 2026 13:13:30 +0200 Subject: [PATCH 75/75] fix: drop iced_winit/single-instance feature ref MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upstream dc84488c "fix(iced): double IME commit on GNOME desktop" wires the libcosmic single-instance feature through iced_winit/single-instance, but our pinned iced fork (yoda-wayland-only @ 81639935) does not carry that feature yet. Drop the ref so libcosmic-yoda compiles after rebase; the GNOME IME fix needs to be ported separately into the iced submodule before this can be reinstated. Leyoda 2026 – GPLv3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1d4e8ba0..d220eed3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ desktop-systemd-scope = ["desktop", "dep:zbus"] # Enables keycode serialization serde-keycode = ["iced_core/serde"] # Prevents multiple separate process instances. -single-instance = ["iced_winit/single-instance", "zbus/blocking-api", "ron"] +single-instance = ["zbus/blocking-api", "ron"] # smol async runtime smol = ["dep:smol", "iced/smol", "zbus?/async-io", "rfd?/async-std"] tokio = [