From 0059fe182bfc4f6f5e1dd0e611cc700a4fa45835 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 3 Oct 2025 12:09:55 -0400 Subject: [PATCH 1/8] refactor: set sharp corner window radius to 0 instead of unsetting --- src/app/cosmic.rs | 132 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 122 insertions(+), 10 deletions(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 58b73b81..fc602d20 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -657,7 +657,21 @@ impl Cosmic { }; let rounded = !self.app.core().window.sharp_corners; return Task::batch(vec![ - corner_radius(id, rounded.then_some(cur_rad)).discard(), + corner_radius( + id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), ]); } } @@ -792,19 +806,63 @@ impl Cosmic { .main_window_id() .unwrap_or(window::Id::RESERVED); let mut cmds = vec![ - corner_radius(main_window_id, rounded.then_some(cur_rad)).discard(), + corner_radius( + main_window_id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), ]; // Update radius for each tracked view with the window surface type for (id, (_, surface_type, _)) in self.surface_views.iter() { if let SurfaceIdWrapper::Window(_) = surface_type { cmds.push( - corner_radius(*id, rounded.then_some(cur_rad)).discard(), + corner_radius( + *id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), ); } } // Update radius for all tracked windows for id in self.tracked_windows.iter() { - cmds.push(corner_radius(*id, rounded.then_some(cur_rad)).discard()); + cmds.push( + corner_radius( + *id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), + ); } return Task::batch(cmds); @@ -894,22 +952,62 @@ impl Cosmic { .main_window_id() .unwrap_or(window::Id::RESERVED); let mut cmds = vec![ - corner_radius(main_window_id, rounded.then_some(cur_rad)) - .discard(), + corner_radius( + main_window_id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), ]; // Update radius for each tracked view with the window surface type for (id, (_, surface_type, _)) in self.surface_views.iter() { if let SurfaceIdWrapper::Window(_) = surface_type { cmds.push( - corner_radius(*id, rounded.then_some(cur_rad)) - .discard(), + corner_radius( + *id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), ); } } // Update radius for all tracked windows for id in self.tracked_windows.iter() { cmds.push( - corner_radius(*id, rounded.then_some(cur_rad)).discard(), + corner_radius( + *id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), ); } @@ -1120,7 +1218,21 @@ impl Cosmic { let rounded = !self.app.core().window.sharp_corners; return Task::batch(vec![ - corner_radius(id, rounded.then_some(cur_rad)).discard(), + corner_radius( + id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), iced_runtime::window::run_with_handle(id, init_windowing_system), ]); } From 5cd774241308e807dedefd34d353492b3db51848 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: Fri, 3 Oct 2025 16:18:50 +0200 Subject: [PATCH 2/8] chore(about): styling fixes Also reduces code duplication a bit. --- src/widget/about.rs | 162 +++++++++++++++++++++----------------------- 1 file changed, 76 insertions(+), 86 deletions(-) diff --git a/src/widget/about.rs b/src/widget/about.rs index f1538d8f..9f2276c8 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -1,5 +1,5 @@ use crate::{ - Element, fl, + Apply, Element, fl, iced::{Alignment, Length}, widget::{self, horizontal_space}, }; @@ -22,7 +22,7 @@ pub struct About { copyright: Option, /// The license name. license: Option, - /// The license url. If None spdx.org url is used. + /// The license url. license_url: Option, /// Artists who contributed to the application. #[setters(skip)] @@ -51,36 +51,28 @@ fn add_contributors(contributors: Vec<(&str, &str)>) -> Vec<(String, String)> { .collect() } +macro_rules! set_contributors { + ($field:ident, $doc:expr) => { + #[doc = $doc] + pub fn $field(mut self, contributors: impl Into>) -> Self { + self.$field = add_contributors(contributors.into()); + self + } + }; +} + impl<'a> About { - /// Artists who contributed to the application. - pub fn artists(mut self, artists: impl Into>) -> Self { - self.artists = add_contributors(artists.into()); - self - } - - /// Designers who contributed to the application. - pub fn designers(mut self, designers: impl Into>) -> Self { - self.designers = add_contributors(designers.into()); - self - } - - /// Developers who contributed to the application. - pub fn developers(mut self, developers: impl Into>) -> Self { - self.developers = add_contributors(developers.into()); - self - } - - /// Documenters who contributed to the application. - pub fn documenters(mut self, documenters: impl Into>) -> Self { - self.documenters = add_contributors(documenters.into()); - self - } - - /// Translators who contributed to the application. - pub fn translators(mut self, translators: impl Into>) -> Self { - self.translators = add_contributors(translators.into()); - self - } + set_contributors!(artists, "Artists who contributed to the application."); + set_contributors!(designers, "Designers who contributed to the application."); + set_contributors!(developers, "Developers who contributed to the application."); + set_contributors!( + documenters, + "Documenters who contributed to the application." + ); + set_contributors!( + translators, + "Translators who contributed to the application." + ); /// Links associated with the application. pub fn links, V: Into>( @@ -104,88 +96,86 @@ pub fn about<'a, Message: Clone + 'static>( space_xxs, space_m, .. } = crate::theme::spacing(); + let section_button = |name: &'a str, url: &'a str| -> Element<'a, Message> { + widget::row() + .push(widget::text(name)) + .push(horizontal_space()) + .push_maybe( + (!url.is_empty()).then_some(crate::widget::icon::from_name("link-symbolic").icon()), + ) + .align_y(Alignment::Center) + .apply(widget::button::custom) + .class(crate::theme::Button::Link) + .on_press(on_url_press(url)) + .width(Length::Fill) + .into() + }; + let section = |list: &'a Vec<(String, String)>, title: String| { (!list.is_empty()).then_some({ - let items: Vec> = - list.iter() - .map(|(name, url)| { - widget::button::custom( - widget::row() - .push(widget::text(name)) - .push(horizontal_space()) - .push_maybe((!url.is_empty()).then_some( - crate::widget::icon::from_name("link-symbolic").icon(), - )) - .align_y(Alignment::Center), - ) - .class(crate::theme::Button::Link) - .on_press(on_url_press(url)) - .width(Length::Fill) - .into() - }) - .collect(); + let items: Vec> = list + .iter() + .map(|(name, url)| section_button(name, url)) + .collect(); widget::settings::section().title(title).extend(items) }) }; - let application_name = about.name.as_ref().map(widget::text::title3); - let application_icon = about.icon.as_ref().map(|i| { - i.clone() - .icon() - .content_fit(iced::ContentFit::Contain) - .width(Length::Fixed(128.)) - .height(Length::Fixed(128.)) - }); - let author = about.author.as_ref().map(widget::text::body); - let version = about.version.as_ref().map(widget::button::standard); + let header_children: Vec> = [ + about.icon.as_ref().map(|i| { + i.clone() + .icon() + .size(256) + .width(Length::Fixed(128.)) + .height(Length::Fixed(128.)) + .content_fit(iced::ContentFit::Contain) + .into() + }), + about.name.as_ref().map(|n| widget::text::title3(n).into()), + about.author.as_ref().map(|a| widget::text::body(a).into()), + about.version.as_ref().map(|v| { + widget::button::standard(v) + .apply(widget::container) + .padding([space_xxs, 0, 0, 0]) + .into() + }), + ] + .into_iter() + .flatten() + .collect(); + let header = (!header_children.is_empty()) + .then_some(widget::column::with_children(header_children).align_x(Alignment::Center)); + let links_section = section(&about.links, fl!("links")); let developers_section = section(&about.developers, fl!("developers")); let designers_section = section(&about.designers, fl!("designers")); let artists_section = section(&about.artists, fl!("artists")); let translators_section = section(&about.translators, fl!("translators")); let documenters_section = section(&about.documenters, fl!("documenters")); - let license = about.license.as_ref().map(|license| { - let url = about.license_url.as_deref(); - widget::settings::section().title(fl!("license")).add( - widget::button::custom( - widget::row() - .push(widget::text(license)) - .push(horizontal_space()) - .push_maybe( - url.is_some() - .then_some(crate::widget::icon::from_name("link-symbolic").icon()), - ) - .align_y(Alignment::Center), - ) - .class(crate::theme::Button::Link) - .on_press(on_url_press(url.unwrap_or_default())) - .width(Length::Fill), + let license_section = about.license.as_ref().and_then(|license| { + let url = about.license_url.as_deref().unwrap_or_default(); + Some( + widget::settings::section() + .title(fl!("license")) + .add(section_button(license, url)), ) }); let copyright = about.copyright.as_ref().map(widget::text::body); let comments = about.comments.as_ref().map(widget::text::body); widget::column() - .push( - widget::column() - .push_maybe(application_icon) - .push_maybe(application_name) - .push_maybe(author) - .push_maybe(version) - .align_x(Alignment::Center) - .spacing(space_xxs), - ) - .push_maybe(license) + .push_maybe(header) .push_maybe(links_section) .push_maybe(developers_section) .push_maybe(designers_section) .push_maybe(artists_section) .push_maybe(translators_section) .push_maybe(documenters_section) + .push_maybe(license_section) .push_maybe(comments) .push_maybe(copyright) - .align_x(Alignment::Center) .spacing(space_m) .width(Length::Fill) + .align_x(Alignment::Center) .into() } From ad1672b8815389f7c13643615c54968597ffc07e 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: Fri, 3 Oct 2025 18:19:19 +0200 Subject: [PATCH 3/8] fix: window corner handling --- src/app/cosmic.rs | 4 ++- src/app/mod.rs | 54 +++++++++++++++++++--------------------- src/core.rs | 2 ++ src/theme/style/iced.rs | 2 +- src/widget/header_bar.rs | 6 ++++- 5 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index fc602d20..42ae122b 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -542,7 +542,7 @@ where } #[cfg(feature = "multi-window")] - pub fn view(&self, id: window::Id) -> Element> { + pub fn view(&self, id: window::Id) -> Element<'_, crate::Action> { #[cfg(feature = "wayland")] if let Some((_, _, v)) = self.surface_views.get(&id) { return v(&self.app); @@ -641,6 +641,8 @@ impl Cosmic { | WindowState::TILED_TOP | WindowState::TILED_BOTTOM, ); + self.app.core_mut().window.is_maximized = + state.intersects(WindowState::MAXIMIZED | WindowState::FULLSCREEN); } if self.app.core().sync_window_border_radii_to_theme() { use iced_runtime::platform_specific::wayland::CornerRadius; diff --git a/src/app/mod.rs b/src/app/mod.rs index 11053142..a2f8d6ad 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -287,37 +287,37 @@ where /// Displays a context drawer on the side of the application window when `Some`. /// Use the [`ApplicationExt::set_show_context`] function for this to take effect. - fn context_drawer(&self) -> Option> { + fn context_drawer(&self) -> Option> { None } /// Displays a dialog in the center of the application window when `Some`. - fn dialog(&self) -> Option> { + fn dialog(&self) -> Option> { None } /// Displays a footer at the bottom of the application window when `Some`. - fn footer(&self) -> Option> { + fn footer(&self) -> Option> { None } /// Attaches elements to the start section of the header. - fn header_start(&self) -> Vec> { + fn header_start(&self) -> Vec> { Vec::new() } /// Attaches elements to the center of the header. - fn header_center(&self) -> Vec> { + fn header_center(&self) -> Vec> { Vec::new() } /// Attaches elements to the end section of the header. - fn header_end(&self) -> Vec> { + fn header_end(&self) -> Vec> { Vec::new() } /// Allows overriding the default nav bar widget. - fn nav_bar(&self) -> Option>> { + fn nav_bar(&self) -> Option>> { if !self.core().nav_bar_active() { return None; } @@ -485,7 +485,7 @@ pub trait ApplicationExt: Application { fn set_window_title(&mut self, title: String, id: window::Id) -> Task; /// View template for the main window. - fn view_main(&self) -> Element>; + fn view_main(&self) -> Element<'_, crate::Action>; fn watch_config( &self, @@ -546,12 +546,11 @@ impl ApplicationExt for App { #[allow(clippy::too_many_lines)] /// Creates the view for the main window. - fn view_main(&self) -> Element> { + fn view_main(&self) -> Element<'_, crate::Action> { let core = self.core(); let is_condensed = core.is_condensed(); - // TODO: More granularity might be needed for different window border - // handling of maximized and tiled windows let sharp_corners = core.window.sharp_corners; + let maximized = core.window.is_maximized; let content_container = core.window.content_container; let show_context = core.window.show_context; let nav_bar_active = core.nav_bar_active(); @@ -560,7 +559,7 @@ impl ApplicationExt for App { .iter() .any(|i| Some(*i) == self.core().main_window_id()); - let border_padding = if sharp_corners { 8 } else { 7 }; + let border_padding = if maximized { 8 } else { 7 }; let main_content_padding = if !content_container { [0, 0, 0, 0] @@ -698,17 +697,22 @@ impl ApplicationExt for App { }; // Ensures visually aligned radii for content and window corners - let window_corner_radius = crate::theme::active() - .cosmic() - .radius_s() - .map(|x| if x < 4.0 { x } else { x + 4.0 }); + let window_corner_radius = if sharp_corners { + crate::theme::active().cosmic().radius_0() + } else { + crate::theme::active() + .cosmic() + .radius_s() + .map(|x| if x < 4.0 { x } else { x + 4.0 }) + }; let view_column = crate::widget::column::with_capacity(2) .push_maybe(if core.window.show_headerbar { Some({ let mut header = crate::widget::header_bar() .focused(focused) - .maximized(sharp_corners) + .maximized(maximized) + .sharp_corners(sharp_corners) .title(&core.window.header_title) .on_drag(crate::Action::Cosmic(Action::Drag)) .on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu)) @@ -766,16 +770,8 @@ impl ApplicationExt for App { )), border: iced::Border { radius: [ - if sharp_corners { - cosmic.radius_0()[0] - } else { - window_corner_radius[0] - 1.0 - }, - if sharp_corners { - cosmic.radius_0()[1] - } else { - window_corner_radius[1] - 1.0 - }, + (window_corner_radius[0] - 1.0).max(0.0), + (window_corner_radius[1] - 1.0).max(0.0), cosmic.radius_0()[2], cosmic.radius_0()[3], ] @@ -794,7 +790,7 @@ impl ApplicationExt for App { // The content element contains every element beneath the header. .push(content) .apply(container) - .padding(if sharp_corners { 0 } else { 1 }) + .padding(if maximized { 0 } else { 1 }) .class(crate::theme::Container::custom(move |theme| { container::Style { background: if content_container { @@ -806,7 +802,7 @@ impl ApplicationExt for App { }, border: iced::Border { color: theme.cosmic().bg_divider().into(), - width: if sharp_corners { 0.0 } else { 1.0 }, + width: if maximized { 0.0 } else { 1.0 }, radius: window_corner_radius.into(), }, ..Default::default() diff --git a/src/core.rs b/src/core.rs index 2e4e0497..338e0e85 100644 --- a/src/core.rs +++ b/src/core.rs @@ -38,6 +38,7 @@ pub struct Window { pub show_close: bool, pub show_maximize: bool, pub show_minimize: bool, + pub is_maximized: bool, height: f32, width: f32, } @@ -141,6 +142,7 @@ impl Default for Core { show_maximize: true, show_minimize: true, show_window_menu: false, + is_maximized: false, height: 0., width: 0., }, diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 1f212d13..c8dacbb9 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -148,7 +148,7 @@ impl iced_button::Catalog for Theme { impl Button { #[allow(clippy::trivially_copy_pass_by_ref)] #[allow(clippy::match_same_arms)] - fn cosmic<'a>(&'a self, theme: &'a Theme) -> &CosmicComponent { + fn cosmic<'a>(&'a self, theme: &'a Theme) -> &'a CosmicComponent { let cosmic = theme.cosmic(); match self { Self::Primary => &cosmic.accent_button, diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index bed7d363..3f4b5d88 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -24,6 +24,7 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { density: None, focused: false, maximized: false, + sharp_corners: false, is_ssd: false, on_double_click: None, is_condensed: false, @@ -83,6 +84,9 @@ pub struct HeaderBar<'a, Message> { /// Maximized state of the window maximized: bool, + /// Whether the corners of the window should be sharp + sharp_corners: bool, + /// HeaderBar used for server-side decorations is_ssd: bool, @@ -409,7 +413,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .apply(widget::container) .class(crate::theme::Container::HeaderBar { focused: self.focused, - sharp_corners: self.maximized, + sharp_corners: self.sharp_corners, }) .center_y(Length::Shrink) .apply(widget::mouse_area); From 34f55d6720b8623050b7ac6153d07cabae253bf8 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 3 Oct 2025 17:52:31 -0400 Subject: [PATCH 4/8] fix: surface cleanup --- src/app/cosmic.rs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 42ae122b..c53bb6a6 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -93,6 +93,7 @@ pub struct Cosmic { ), >, pub tracked_windows: HashSet, + pub opened_surfaces: HashMap, } impl Cosmic @@ -161,6 +162,7 @@ where } }) { let settings = settings(&mut self.app); + self.get_subsurface(settings, *view) } else { iced_winit::commands::subsurface::get_subsurface(settings(&mut self.app)) @@ -188,6 +190,7 @@ where } }) { let settings = settings(); + self.get_subsurface(settings, Box::new(move |_| view())) } else { iced_winit::commands::subsurface::get_subsurface(settings()) @@ -292,6 +295,8 @@ where } }) { let settings = settings(&mut self.app); + self.tracked_windows.insert(id); + self.get_window(id, settings, *view) } else { let settings = settings(&mut self.app); @@ -327,6 +332,8 @@ where } }) { let settings = settings(); + self.tracked_windows.insert(id); + self.get_window(id, settings, Box::new(move |_| view())) } else { let settings = settings(); @@ -1035,9 +1042,15 @@ impl Cosmic { Action::Surface(action) => return self.surface_update(action), Action::SurfaceClosed(id) => { - #[cfg(feature = "wayland")] - self.surface_views.remove(&id); - self.tracked_windows.remove(&id); + if self.opened_surfaces.get_mut(&id).is_some_and(|v| { + *v = v.saturating_sub(1); + *v == 0 + }) { + self.opened_surfaces.remove(&id); + #[cfg(feature = "wayland")] + self.surface_views.remove(&id); + self.tracked_windows.remove(&id); + } let mut ret = if let Some(msg) = self.app.on_close_requested(id) { self.app.update(msg) @@ -1201,7 +1214,6 @@ impl Cosmic { core.applet.suggested_bounds = b; } Action::Opened(id) => { - self.tracked_windows.insert(id); #[cfg(feature = "wayland")] if self.app.core().sync_window_border_radii_to_theme() { use iced_runtime::platform_specific::wayland::CornerRadius; @@ -1254,6 +1266,7 @@ impl Cosmic { #[cfg(feature = "wayland")] surface_views: HashMap::new(), tracked_windows: HashSet::new(), + opened_surfaces: HashMap::new(), } } @@ -1268,6 +1281,7 @@ impl Cosmic { ) -> Task> { use iced_winit::commands::subsurface::get_subsurface; + *self.opened_surfaces.entry(settings.id).or_insert_with(|| 0) += 1; self.surface_views.insert( settings.id, ( @@ -1289,7 +1303,7 @@ impl Cosmic { >, ) -> Task> { use iced_winit::commands::popup::get_popup; - + *self.opened_surfaces.entry(settings.id).or_insert_with(|| 0) += 1; self.surface_views.insert( settings.id, ( @@ -1312,7 +1326,7 @@ impl Cosmic { >, ) -> Task> { use iced_winit::SurfaceIdWrapper; - + *self.opened_surfaces.entry(id.clone()).or_insert_with(|| 0) += 1; self.surface_views.insert( id.clone(), ( From a27bb5e05ddb89651f86b1576a2567e36f570352 Mon Sep 17 00:00:00 2001 From: Cheong Lau <234708519+Cheong-Lau@users.noreply.github.com> Date: Sun, 5 Oct 2025 12:27:32 +1000 Subject: [PATCH 5/8] chore: apply clippy suggestions --- cosmic-config/src/lib.rs | 2 +- cosmic-config/src/subscription.rs | 2 +- cosmic-theme/src/model/theme.rs | 8 ++--- cosmic-theme/src/steps.rs | 2 +- src/app/cosmic.rs | 42 +++++++++++------------ src/app/mod.rs | 4 +-- src/desktop.rs | 7 ++-- src/dialog/file_chooser/save.rs | 6 ++++ src/theme/mod.rs | 8 ++--- src/theme/style/iced.rs | 21 ++++-------- src/widget/about.rs | 10 +++--- src/widget/button/widget.rs | 10 +++--- src/widget/calendar.rs | 2 +- src/widget/color_picker/mod.rs | 2 +- src/widget/dropdown/multi/menu.rs | 10 +++--- src/widget/dropdown/multi/model.rs | 4 +-- src/widget/header_bar.rs | 6 ++-- src/widget/menu/menu_bar.rs | 6 ++-- src/widget/menu/menu_inner.rs | 4 +-- src/widget/menu/menu_tree.rs | 2 +- src/widget/nav_bar.rs | 4 +-- src/widget/responsive_menu_bar.rs | 2 +- src/widget/segmented_button/horizontal.rs | 2 +- src/widget/segmented_button/model/mod.rs | 8 +++-- src/widget/segmented_button/vertical.rs | 2 +- src/widget/segmented_button/widget.rs | 32 +++++++---------- src/widget/segmented_control.rs | 4 +-- src/widget/settings/item.rs | 2 +- src/widget/tab_bar.rs | 4 +-- src/widget/table/model/mod.rs | 8 ++--- src/widget/table/widget/compact.rs | 9 +++-- src/widget/table/widget/standard.rs | 16 ++++----- src/widget/text_input/input.rs | 7 ++-- src/widget/text_input/value.rs | 4 +-- 34 files changed, 116 insertions(+), 146 deletions(-) diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 72b02371..261b4412 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -229,7 +229,7 @@ impl Config { // Start a transaction (to set multiple configs at the same time) #[inline] - pub fn transaction(&self) -> ConfigTransaction { + pub fn transaction(&self) -> ConfigTransaction<'_> { ConfigTransaction { config: self, updates: Mutex::new(Vec::new()), diff --git a/cosmic-config/src/subscription.rs b/cosmic-config/src/subscription.rs index 88f8bfa2..32f48849 100644 --- a/cosmic-config/src/subscription.rs +++ b/cosmic-config/src/subscription.rs @@ -93,7 +93,7 @@ async fn start_listening { let update = crate::Update { - errors: errors, + errors, keys: Vec::new(), config: t.clone(), }; diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index d1d3ae0a..1f94f5a2 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -814,7 +814,7 @@ pub struct ThemeBuilder { impl Default for ThemeBuilder { fn default() -> Self { Self { - palette: DARK_PALETTE.to_owned().into(), + palette: DARK_PALETTE.to_owned(), spacing: Spacing::default(), corner_radii: CornerRadii::default(), neutral_tint: Default::default(), @@ -1077,7 +1077,7 @@ impl ThemeBuilder { component_pressed_overlay = component_hovered_overlay; component_pressed_overlay.alpha = 0.2; - let container = Container::new( + Container::new( Component::component( component_base, accent, @@ -1101,9 +1101,7 @@ impl ThemeBuilder { ), get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]), is_high_contrast, - ); - - container + ) }; let accent_text = if is_dark { diff --git a/cosmic-theme/src/steps.rs b/cosmic-theme/src/steps.rs index 6c0779c2..143cf532 100644 --- a/cosmic-theme/src/steps.rs +++ b/cosmic-theme/src/steps.rs @@ -93,7 +93,7 @@ pub fn get_text( let index = get_index(base_index, 70, step_array.len(), is_dark) .or_else(|| get_index(base_index, 50, step_array.len(), is_dark)) - .unwrap_or_else(|| if is_dark { 99 } else { 0 }); + .unwrap_or(if is_dark { 99 } else { 0 }); *step_array.get(index).unwrap_or(fallback) } diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index c53bb6a6..2e4b3cb9 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -114,7 +114,7 @@ where ( Self::new(model), - Task::batch(vec![ + Task::batch([ command, iced_runtime::window::run_with_handle(id, init_windowing_system), ]), @@ -665,23 +665,21 @@ impl Cosmic { bottom_left: radii[3].round() as u32, }; let rounded = !self.app.core().window.sharp_corners; - return Task::batch(vec![ - corner_radius( - id, - if rounded { - Some(cur_rad) - } else { - let rad_0 = t.radius_0(); - Some(CornerRadius { - top_left: rad_0[0].round() as u32, - top_right: rad_0[1].round() as u32, - bottom_right: rad_0[2].round() as u32, - bottom_left: rad_0[3].round() as u32, - }) - }, - ) - .discard(), - ]); + return Task::batch([corner_radius( + id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard()]); } } @@ -1061,7 +1059,7 @@ impl Cosmic { if core.exit_on_main_window_closed && core.main_window_id().is_some_and(|m_id| id == m_id) { - ret = Task::batch(vec![iced::exit::>()]); + ret = Task::batch([iced::exit::>()]); } return ret; } @@ -1231,7 +1229,7 @@ impl Cosmic { // TODO do we need per window sharp corners? let rounded = !self.app.core().window.sharp_corners; - return Task::batch(vec![ + return Task::batch([ corner_radius( id, if rounded { @@ -1326,9 +1324,9 @@ impl Cosmic { >, ) -> Task> { use iced_winit::SurfaceIdWrapper; - *self.opened_surfaces.entry(id.clone()).or_insert_with(|| 0) += 1; + *self.opened_surfaces.entry(id).or_insert(0) += 1; self.surface_views.insert( - id.clone(), + id, ( None, // TODO parent for window, platform specific option maybe? SurfaceIdWrapper::Window(id), diff --git a/src/app/mod.rs b/src/app/mod.rs index a2f8d6ad..eaf0bae6 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -420,10 +420,10 @@ where } /// Constructs the view for the main window. - fn view(&self) -> Element; + fn view(&self) -> Element<'_, Self::Message>; /// Constructs views for other windows. - fn view_window(&self, id: window::Id) -> Element { + fn view_window(&self, id: window::Id) -> Element<'_, Self::Message> { panic!("no view for window {id:?}"); } diff --git a/src/desktop.rs b/src/desktop.rs index d41f29a2..687fa6c4 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -136,7 +136,7 @@ pub fn load_applications_for_app_ids<'a>( } #[cfg(not(windows))] -pub fn load_desktop_file<'a>(locales: &'a [String], path: PathBuf) -> Option { +pub fn load_desktop_file(locales: &[String], path: PathBuf) -> Option { fde::DesktopEntry::from_path(path, Some(locales)) .ok() .map(|de| DesktopEntryData::from_desktop_entry(locales, de)) @@ -144,10 +144,7 @@ pub fn load_desktop_file<'a>(locales: &'a [String], path: PathBuf) -> Option( - locales: &'a [String], - de: fde::DesktopEntry, - ) -> DesktopEntryData { + pub fn from_desktop_entry(locales: &[String], de: fde::DesktopEntry) -> DesktopEntryData { let name = de .name(locales) .unwrap_or(Cow::Borrowed(&de.appid)) diff --git a/src/dialog/file_chooser/save.rs b/src/dialog/file_chooser/save.rs index cfb1382b..d7a2a34e 100644 --- a/src/dialog/file_chooser/save.rs +++ b/src/dialog/file_chooser/save.rs @@ -120,6 +120,12 @@ impl Dialog { } } +impl Default for Dialog { + fn default() -> Self { + Self::new() + } +} + #[cfg(feature = "xdg-portal")] mod portal { use super::Dialog; diff --git a/src/theme/mod.rs b/src/theme/mod.rs index f01180c1..b7e85237 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -22,15 +22,15 @@ pub type CosmicColor = ::palette::rgb::Srgba; pub type CosmicComponent = cosmic_theme::Component; pub type CosmicTheme = cosmic_theme::Theme; -pub static COSMIC_DARK: LazyLock = LazyLock::new(|| CosmicTheme::dark_default()); +pub static COSMIC_DARK: LazyLock = LazyLock::new(CosmicTheme::dark_default); pub static COSMIC_HC_DARK: LazyLock = - LazyLock::new(|| CosmicTheme::high_contrast_dark_default()); + LazyLock::new(CosmicTheme::high_contrast_dark_default); -pub static COSMIC_LIGHT: LazyLock = LazyLock::new(|| CosmicTheme::light_default()); +pub static COSMIC_LIGHT: LazyLock = LazyLock::new(CosmicTheme::light_default); pub static COSMIC_HC_LIGHT: LazyLock = - LazyLock::new(|| CosmicTheme::high_contrast_light_default()); + LazyLock::new(CosmicTheme::high_contrast_light_default); pub static TRANSPARENT_COMPONENT: LazyLock = LazyLock::new(|| Component { base: CosmicColor::new(0.0, 0.0, 0.0, 0.0), diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index c8dacbb9..32309860 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -898,12 +898,10 @@ impl toggler::Catalog for Theme { let mut active = toggler::Style { background: if matches!(status, toggler::Status::Active { is_toggled: true }) { cosmic.accent.base.into() + } else if cosmic.is_dark { + cosmic.palette.neutral_6.into() } else { - if cosmic.is_dark { - cosmic.palette.neutral_6.into() - } else { - cosmic.palette.neutral_5.into() - } + cosmic.palette.neutral_5.into() }, foreground: cosmic.palette.neutral_2.into(), border_radius: cosmic.radius_xl().into(), @@ -1166,11 +1164,7 @@ impl scrollable::Catalog for Theme { }, gap: None, }; - let small_widget_container = self - .current_container() - .small_widget - .clone() - .with_alpha(0.7); + let small_widget_container = self.current_container().small_widget.with_alpha(0.7); if matches!(class, Scrollable::Permanent) { a.horizontal_rail.background = @@ -1233,11 +1227,8 @@ impl scrollable::Catalog for Theme { }; if matches!(class, Scrollable::Permanent) { - let small_widget_container = self - .current_container() - .small_widget - .clone() - .with_alpha(0.7); + let small_widget_container = + self.current_container().small_widget.with_alpha(0.7); a.horizontal_rail.background = Some(Background::Color(small_widget_container.into())); diff --git a/src/widget/about.rs b/src/widget/about.rs index 9f2276c8..628f53c6 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -152,13 +152,11 @@ pub fn about<'a, Message: Clone + 'static>( let artists_section = section(&about.artists, fl!("artists")); let translators_section = section(&about.translators, fl!("translators")); let documenters_section = section(&about.documenters, fl!("documenters")); - let license_section = about.license.as_ref().and_then(|license| { + let license_section = about.license.as_ref().map(|license| { let url = about.license_url.as_deref().unwrap_or_default(); - Some( - widget::settings::section() - .title(fl!("license")) - .add(section_button(license, url)), - ) + widget::settings::section() + .title(fl!("license")) + .add(section_button(license, url)) }); let copyright = about.copyright.as_ref().map(widget::text::body); let comments = about.comments.as_ref().map(widget::text::body); diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 3f5a1fdf..87233330 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -793,7 +793,7 @@ pub fn update<'a, Message: Clone>( } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerLifted { .. }) => { - if let Some(on_press) = on_press.clone() { + if let Some(on_press) = on_press { let state = state(); if state.is_pressed { @@ -816,9 +816,9 @@ pub fn update<'a, Message: Clone>( #[cfg(feature = "a11y")] Event::A11y(event_id, iced_accessibility::accesskit::ActionRequest { action, .. }) => { let state = state(); - if let Some(Some(on_press)) = (event_id == event_id - && matches!(action, iced_accessibility::accesskit::Action::Default)) - .then(|| on_press.clone()) + if let Some(on_press) = matches!(action, iced_accessibility::accesskit::Action::Default) + .then_some(on_press) + .flatten() { state.is_pressed = false; let msg = (on_press)(layout.virtual_offset(), layout.bounds()); @@ -828,7 +828,7 @@ pub fn update<'a, Message: Clone>( return event::Status::Captured; } Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { - if let Some(on_press) = on_press.clone() { + if let Some(on_press) = on_press { let state = state(); if state.is_focused && key == keyboard::Key::Named(keyboard::key::Named::Enter) { state.is_pressed = true; diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index 303a1ed9..a1aace33 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -17,7 +17,7 @@ pub fn calendar( on_prev: impl Fn() -> M + 'static, on_next: impl Fn() -> M + 'static, first_day_of_week: Weekday, -) -> Calendar { +) -> Calendar<'_, M> { Calendar { model, on_select: Box::new(on_select), diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index a17625dc..536531a4 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -233,7 +233,7 @@ impl ColorPickerModel { pub fn builder( &self, on_update: fn(ColorPickerUpdate) -> Message, - ) -> ColorPickerBuilder { + ) -> ColorPickerBuilder<'_, Message> { ColorPickerBuilder { model: &self.segmented_model, active_color: self.active_color, diff --git a/src/widget/dropdown/multi/menu.rs b/src/widget/dropdown/multi/menu.rs index da103f8a..10b0d8d4 100644 --- a/src/widget/dropdown/multi/menu.rs +++ b/src/widget/dropdown/multi/menu.rs @@ -673,8 +673,8 @@ pub(super) enum OptionElement<'a, S, Item> { } impl Model { - pub(super) fn elements(&self) -> impl Iterator> + '_ { - let iterator = self.lists.iter().flat_map(|list| { + pub(super) fn elements(&self) -> impl Iterator> + '_ { + self.lists.iter().flat_map(|list| { let description = list .description .as_ref() @@ -686,9 +686,7 @@ impl Model { description .chain(options) .chain(std::iter::once(OptionElement::Separator)) - }); - - iterator + }) } fn element_heights( @@ -709,7 +707,7 @@ impl Model { text_line_height: f32, offset: f32, height: f32, - ) -> impl Iterator, f32)> + '_ { + ) -> impl Iterator, f32)> + '_ { let heights = self.element_heights(padding_vertical, text_line_height); let mut current = 0.0; diff --git a/src/widget/dropdown/multi/model.rs b/src/widget/dropdown/multi/model.rs index 12bf4269..f67f8edd 100644 --- a/src/widget/dropdown/multi/model.rs +++ b/src/widget/dropdown/multi/model.rs @@ -66,9 +66,7 @@ impl Model { } pub(super) fn next(&self) -> Option<&(S, Item)> { - let Some(item) = self.selected.as_ref() else { - return None; - }; + let item = self.selected.as_ref()?; let mut next = false; for list in &self.lists { diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 3f4b5d88..01a8d559 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -293,11 +293,9 @@ impl Widget ) -> iced_accessibility::A11yTree { let c_layout = layout.children().next().unwrap(); let c_state = &state.children[0]; - let ret = self - .header_bar_inner + self.header_bar_inner .as_widget() - .a11y_nodes(c_layout, c_state, p); - ret + .a11y_nodes(c_layout, c_state, p) } } diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 30c802c1..bbbb4a2b 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -97,7 +97,7 @@ impl Default for MenuBarStateInner { } } -pub(crate) fn menu_roots_children(menu_roots: &Vec>) -> Vec +pub(crate) fn menu_roots_children(menu_roots: &[MenuTree]) -> Vec where Message: Clone + 'static, { @@ -126,7 +126,7 @@ where } #[allow(invalid_reference_casting)] -pub(crate) fn menu_roots_diff(menu_roots: &mut Vec>, tree: &mut Tree) +pub(crate) fn menu_roots_diff(menu_roots: &mut [MenuTree], tree: &mut Tree) where Message: Clone + 'static, { @@ -381,7 +381,7 @@ where let surface_action = self.on_surface_action.as_ref().unwrap(); let old_active_root = my_state .inner - .with_data(|state| state.active_root.get(0).copied()); + .with_data(|state| state.active_root.first().copied()); // if position is not on menu bar button skip. let hovered_root = layout diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 6c694de7..18f9940d 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -435,7 +435,7 @@ impl MenuState { pub(crate) struct Menu<'b, Message: std::clone::Clone> { pub(crate) tree: MenuBarState, // Flattened menu tree - pub(crate) menu_roots: Cow<'b, Vec>>, + pub(crate) menu_roots: Cow<'b, [MenuTree]>, pub(crate) bounds_expand: u16, /// Allows menu overlay items to overlap the parent pub(crate) menu_overlays_parent: bool, @@ -740,7 +740,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { let styling = theme.appearance(&self.style); let roots = active_root.iter().skip(1).fold( &self.menu_roots[active_root[0]].children, - |mt, next_active_root| (&mt[*next_active_root].children), + |mt, next_active_root| &mt[*next_active_root].children, ); let indices = state.get_trimmed_indices(self.depth).collect::>(); state.menu_states[if self.is_overlay { 0 } else { self.depth }..=if self.is_overlay { diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index 67f999f7..e63e523b 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -119,7 +119,7 @@ impl MenuTree { }); mt.children.iter().for_each(|c| { - rec(&c, flat); + rec(c, flat); }); } diff --git a/src/widget/nav_bar.rs b/src/widget/nav_bar.rs index 1ae4005d..140385bc 100644 --- a/src/widget/nav_bar.rs +++ b/src/widget/nav_bar.rs @@ -26,7 +26,7 @@ pub type Model = segmented_button::SingleSelectModel; pub fn nav_bar( model: &segmented_button::SingleSelectModel, on_activate: fn(segmented_button::Entity) -> Message, -) -> NavBar { +) -> NavBar<'_, Message> { NavBar { segmented_button: segmented_button::vertical(model).on_activate(on_activate), } @@ -41,7 +41,7 @@ pub fn nav_bar_dnd( on_dnd_leave: impl Fn(segmented_button::Entity) -> Message + 'static, on_dnd_drop: impl Fn(segmented_button::Entity, Option, DndAction) -> Message + 'static, id: DragId, -) -> NavBar +) -> NavBar<'_, Message> where Message: Clone + 'static, { diff --git a/src/widget/responsive_menu_bar.rs b/src/widget/responsive_menu_bar.rs index 3c9151e7..5f855260 100644 --- a/src/widget/responsive_menu_bar.rs +++ b/src/widget/responsive_menu_bar.rs @@ -132,7 +132,7 @@ impl ResponsiveMenuBar { key_binds, trees .into_iter() - .map(|mt| menu::Item::Folder(mt.0, mt.1.into())) + .map(|mt| menu::Item::Folder(mt.0, mt.1)) .collect(), ) .into_iter() diff --git a/src/widget/segmented_button/horizontal.rs b/src/widget/segmented_button/horizontal.rs index 966f3a7c..3e46dd5e 100644 --- a/src/widget/segmented_button/horizontal.rs +++ b/src/widget/segmented_button/horizontal.rs @@ -23,7 +23,7 @@ pub struct Horizontal; /// For details on the model, see the [`segmented_button`](super) module for more details. pub fn horizontal( model: &Model, -) -> SegmentedButton +) -> SegmentedButton<'_, Horizontal, SelectionMode, Message> where Model: Selectable, { diff --git a/src/widget/segmented_button/model/mod.rs b/src/widget/segmented_button/model/mod.rs index 83a1702d..6b5a8a64 100644 --- a/src/widget/segmented_button/model/mod.rs +++ b/src/widget/segmented_button/model/mod.rs @@ -292,7 +292,7 @@ where /// ``` #[must_use] #[inline] - pub fn insert(&mut self) -> EntityMut { + pub fn insert(&mut self) -> EntityMut<'_, SelectionMode> { let id = self.items.insert(Settings::default()); self.order.push_back(id); EntityMut { model: self, id } @@ -447,7 +447,11 @@ where /// println!("{:?} had text {}", id, old_text) /// } /// ``` - pub fn text_set(&mut self, id: Entity, text: impl Into>) -> Option> { + pub fn text_set( + &mut self, + id: Entity, + text: impl Into>, + ) -> Option> { if !self.contains_item(id) { return None; } diff --git a/src/widget/segmented_button/vertical.rs b/src/widget/segmented_button/vertical.rs index ce9f50fe..7963e9c8 100644 --- a/src/widget/segmented_button/vertical.rs +++ b/src/widget/segmented_button/vertical.rs @@ -22,7 +22,7 @@ pub type VerticalSegmentedButton<'a, SelectionMode, Message> = /// For details on the model, see the [`segmented_button`](super) module for more details. pub fn vertical( model: &Model, -) -> SegmentedButton +) -> SegmentedButton<'_, Vertical, SelectionMode, Message> where Model: Selectable, SelectionMode: Default, diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 0fd8dcd6..3cbe12f9 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -263,7 +263,7 @@ where /// Check if an item is enabled. fn is_enabled(&self, key: Entity) -> bool { - self.model.items.get(key).map_or(false, |item| item.enabled) + self.model.items.get(key).is_some_and(|item| item.enabled) } /// Handle the dnd drop event. @@ -987,7 +987,7 @@ where let current = Instant::now(); // Permit successive scroll wheel events only after a given delay. - if state.wheel_timestamp.map_or(true, |previous| { + if state.wheel_timestamp.is_none_or(|previous| { current.duration_since(previous) > Duration::from_millis(250) }) { state.wheel_timestamp = Some(current); @@ -1607,23 +1607,16 @@ where let state = tree.state.downcast_ref::(); let menu_state = state.menu_state.clone(); - let Some(entity) = state.show_context else { - return None; - }; + let entity = state.show_context?; - let bounds = self - .variant_bounds(state, layout.bounds()) - .find_map(|item| match item { - ItemBounds::Button(e, bounds) if e == entity => Some(bounds), - _ => None, - }); - let Some(mut bounds) = bounds else { - return None; - }; + let mut bounds = + self.variant_bounds(state, layout.bounds()) + .find_map(|item| match item { + ItemBounds::Button(e, bounds) if e == entity => Some(bounds), + _ => None, + })?; - let Some(context_menu) = self.context_menu.as_mut() else { - return None; - }; + let context_menu = self.context_menu.as_mut()?; if !menu_state.inner.with_data(|data| data.open) { // If the menu is not open, we don't need to show it. @@ -1777,9 +1770,8 @@ impl LocalState { impl operation::Focusable for LocalState { fn is_focused(&self) -> bool { - self.focused.map_or(false, |f| { - f.updated_at == LAST_FOCUS_UPDATE.with(|f| f.get()) - }) + self.focused + .is_some_and(|f| f.updated_at == LAST_FOCUS_UPDATE.with(|f| f.get())) } fn focus(&mut self) { diff --git a/src/widget/segmented_control.rs b/src/widget/segmented_control.rs index 0c213b2c..046956c7 100644 --- a/src/widget/segmented_control.rs +++ b/src/widget/segmented_control.rs @@ -16,7 +16,7 @@ use super::segmented_button::{ /// For details on the model, see the [`segmented_button`] module for more details. pub fn horizontal( model: &Model, -) -> HorizontalSegmentedButton +) -> HorizontalSegmentedButton<'_, SelectionMode, Message> where Model: Selectable, { @@ -39,7 +39,7 @@ where /// For details on the model, see the [`segmented_button`] module for more details. pub fn vertical( model: &Model, -) -> VerticalSegmentedButton +) -> VerticalSegmentedButton<'_, SelectionMode, Message> where Model: Selectable, SelectionMode: Default, diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index a8c38a0d..d62bbc99 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -131,7 +131,7 @@ impl<'a, Message: 'static> Item<'a, Message> { contents.push(text(self.title).width(Length::Fill).into()); } - contents.push(widget.into()); + contents.push(widget); contents } diff --git a/src/widget/tab_bar.rs b/src/widget/tab_bar.rs index 4f4c6149..a08128b4 100644 --- a/src/widget/tab_bar.rs +++ b/src/widget/tab_bar.rs @@ -16,7 +16,7 @@ use super::segmented_button::{ /// For details on the model, see the [`segmented_button`] module for more details. pub fn horizontal( model: &Model, -) -> HorizontalSegmentedButton +) -> HorizontalSegmentedButton<'_, SelectionMode, Message> where Model: Selectable, { @@ -37,7 +37,7 @@ where /// For details on the model, see the [`segmented_button`] module for more details. pub fn vertical( model: &Model, -) -> VerticalSegmentedButton +) -> VerticalSegmentedButton<'_, SelectionMode, Message> where Model: Selectable, SelectionMode: Default, diff --git a/src/widget/table/model/mod.rs b/src/widget/table/model/mod.rs index f664e438..d6250eaf 100644 --- a/src/widget/table/model/mod.rs +++ b/src/widget/table/model/mod.rs @@ -221,7 +221,7 @@ where /// let id = model.insert().text("Item A").icon("custom-icon").id(); /// ``` #[must_use] - pub fn insert(&mut self, item: Item) -> EntityMut { + pub fn insert(&mut self, item: Item) -> EntityMut<'_, SelectionMode, Item, Category> { let id = self.items.insert(item); self.order.push_back(id); EntityMut { model: self, id } @@ -244,7 +244,7 @@ where /// ``` #[must_use] pub fn is_enabled(&self, id: Entity) -> bool { - self.active.get(id).map_or(false, |e| *e) + self.active.get(id).is_some_and(|e| *e) } /// Iterates across items in the model in the order that they are displayed. @@ -288,9 +288,7 @@ where /// } /// ``` pub fn position_set(&mut self, id: Entity, position: u16) -> Option { - let Some(index) = self.position(id) else { - return None; - }; + let index = self.position(id)?; self.order.remove(index as usize); diff --git a/src/widget/table/widget/compact.rs b/src/widget/table/widget/compact.rs index 47864f6d..7cda2dfb 100644 --- a/src/widget/table/widget/compact.rs +++ b/src/widget/table/widget/compact.rs @@ -63,7 +63,7 @@ where .map(|entity| { let item = val.model.item(entity).unwrap(); let selected = val.model.is_active(entity); - let context_menu = (val.item_context_builder)(&item); + let context_menu = (val.item_context_builder)(item); widget::column() .spacing(val.item_spacing) @@ -89,14 +89,13 @@ where .categories .iter() .skip_while(|cat| **cat != Category::default()) - .map(|category| { - vec![ + .flat_map(|category| { + [ widget::text::caption(item.get_text(*category)) .apply(Element::from), widget::text::caption("-").apply(Element::from), ] }) - .flatten() .collect::>>(); elements.pop(); elements @@ -201,7 +200,7 @@ where divider_padding: Padding::from(0).left(space_xxxs).right(space_xxxs), - item_padding: Padding::from(space_xxs).into(), + item_padding: Padding::from(space_xxs), item_spacing: 0, icon_size: 48, diff --git a/src/widget/table/widget/standard.rs b/src/widget/table/widget/standard.rs index eb9ba7a4..3ee1ac4a 100644 --- a/src/widget/table/widget/standard.rs +++ b/src/widget/table/widget/standard.rs @@ -139,13 +139,13 @@ where } else { val.model .iter() - .map(move |entity| { + .flat_map(move |entity| { let item = val.model.item(entity).unwrap(); let categories = &val.model.categories; let selected = val.model.is_active(entity); - let item_context = (val.item_context_builder)(&item); + let item_context = (val.item_context_builder)(item); - vec![ + [ divider::horizontal::default() .apply(container) .padding(val.divider_padding) @@ -233,13 +233,11 @@ where .apply(Element::from), ] }) - .flatten() .collect::>>() }; - vec![vec![header_row], items_full] - .into_iter() - .flatten() - .collect::>>() + let mut elements = items_full; + elements.insert(0, header_row); + elements .apply(widget::column::with_children) .width(val.width) .height(val.height) @@ -272,7 +270,7 @@ where width: Length::Fill, height: Length::Shrink, - item_padding: Padding::from(space_xxs).into(), + item_padding: Padding::from(space_xxs), item_spacing: 0, icon_spacing: space_xxxs, icon_size: 24, diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 12e8e7ce..ab38a718 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -546,7 +546,6 @@ where } /// Get the layout node of the actual text input - fn text_layout<'b>(&'a self, layout: Layout<'b>) -> Layout<'b> { if self.dnd_icon { layout @@ -1389,8 +1388,8 @@ pub fn update<'a, Message: Clone + 'static>( if let Some(cursor_position) = click_position { // Check if the edit button was clicked. - if state.dragging_state == None - && edit_button_layout.map_or(false, |l| cursor.is_over(l.bounds())) + if state.dragging_state.is_none() + && edit_button_layout.is_some_and(|l| cursor.is_over(l.bounds())) { if is_editable_variant { state.is_read_only = !state.is_read_only; @@ -2277,7 +2276,7 @@ pub fn draw<'a, Message>( let (cursor, offset) = if let Some(focus) = state.is_focused.filter(|f| f.focused).or_else(|| { let now = Instant::now(); - handling_dnd_offer.then(|| Focus { + handling_dnd_offer.then_some(Focus { needs_update: false, updated_at: now, now, diff --git a/src/widget/text_input/value.rs b/src/widget/text_input/value.rs index 60647db3..900aac0f 100644 --- a/src/widget/text_input/value.rs +++ b/src/widget/text_input/value.rs @@ -129,9 +129,7 @@ impl Value { #[must_use] pub fn secure(&self) -> Self { Self { - graphemes: std::iter::repeat(String::from("•")) - .take(self.graphemes.len()) - .collect(), + graphemes: std::iter::repeat_n(String::from("•"), self.graphemes.len()).collect(), } } } From 4c4eddb50c79ace202c76b0f6972596930537e1b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 6 Oct 2025 14:52:39 -0400 Subject: [PATCH 6/8] fix: use is_maximized --- src/app/cosmic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 2e4b3cb9..ae554846 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -391,7 +391,7 @@ where pub fn style(&self, theme: &Theme) -> iced_runtime::Appearance { if let Some(style) = self.app.style() { style - } else if self.app.core().window.sharp_corners { + } else if self.app.core().window.is_maximized { let theme = THEME.lock().unwrap(); crate::style::iced::application::appearance(theme.borrow()) } else { From 4d4f754318998ea3318ffab15fb96d04b3d33e81 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 6 Oct 2025 11:02:11 +0200 Subject: [PATCH 7/8] i18n: translation updates from weblate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Aindriú Mac Giolla Eoin Co-authored-by: Aliaksandr Truš Co-authored-by: Fedorov Alexei Co-authored-by: Hosted Weblate Co-authored-by: Priit Jõerüüt Co-authored-by: Yago Raña Gayoso Co-authored-by: mikenu Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/be/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/es/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/et/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ga/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ja/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ru/ Translation: Pop OS/libcosmic --- i18n/be/libcosmic.ftl | 8 ++++++++ i18n/es/libcosmic.ftl | 7 +++++++ i18n/et/libcosmic.ftl | 1 + i18n/ja/libcosmic.ftl | 8 ++++++++ i18n/ru/libcosmic.ftl | 8 ++++++++ i18n/sr-Cyrl/libcosmic.ftl | 6 ------ 6 files changed, 32 insertions(+), 6 deletions(-) diff --git a/i18n/be/libcosmic.ftl b/i18n/be/libcosmic.ftl index e69de29b..eb3abf33 100644 --- a/i18n/be/libcosmic.ftl +++ b/i18n/be/libcosmic.ftl @@ -0,0 +1,8 @@ +close = Закрыць +license = Ліцэнзія +links = Спасылкі +developers = Распрацоўшчыкі +designers = Дызайнеры +artists = Мастакі +translators = Перакладчыкі +documenters = Дакументалісты diff --git a/i18n/es/libcosmic.ftl b/i18n/es/libcosmic.ftl index e69de29b..6d30b5ad 100644 --- a/i18n/es/libcosmic.ftl +++ b/i18n/es/libcosmic.ftl @@ -0,0 +1,7 @@ +license = Licencia +links = Enlaces +developers = Desarrolladores +designers = Diseñadores +artists = Artistas +translators = Traductores +documenters = Documentadores diff --git a/i18n/et/libcosmic.ftl b/i18n/et/libcosmic.ftl index 1449e0af..38b16698 100644 --- a/i18n/et/libcosmic.ftl +++ b/i18n/et/libcosmic.ftl @@ -5,3 +5,4 @@ developers = Arendajad artists = Kunstnikud translators = Tõlkijad documenters = Dokumenteerijad +designers = Kujundajad diff --git a/i18n/ja/libcosmic.ftl b/i18n/ja/libcosmic.ftl index e69de29b..c6b9ed1a 100644 --- a/i18n/ja/libcosmic.ftl +++ b/i18n/ja/libcosmic.ftl @@ -0,0 +1,8 @@ +close = 閉じる +license = ライセンス +links = リンク +developers = 開発者 +designers = デザイナー +artists = アーティスト +translators = 翻訳者 +documenters = ドキュメント作成者 diff --git a/i18n/ru/libcosmic.ftl b/i18n/ru/libcosmic.ftl index e69de29b..0ef03fb1 100644 --- a/i18n/ru/libcosmic.ftl +++ b/i18n/ru/libcosmic.ftl @@ -0,0 +1,8 @@ +close = Закрыть +license = Лицензия +links = Ссылки +developers = Разработчики +designers = Дизайнеры +artists = Художники +translators = Переводчики +documenters = Авторы документации diff --git a/i18n/sr-Cyrl/libcosmic.ftl b/i18n/sr-Cyrl/libcosmic.ftl index 579392f4..30ed82d3 100644 --- a/i18n/sr-Cyrl/libcosmic.ftl +++ b/i18n/sr-Cyrl/libcosmic.ftl @@ -1,11 +1,5 @@ # Context Drawer close = Затвори - # About license = Лиценца links = Линкови -Developers = Програмери -Designers = Дизајнери -Artists = Уметници -Translators = Преводиоци -Documenters = Документатори From dc4e0edd7311152963c1574ee51540ae5b20e683 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 7 Oct 2025 13:28:42 -0400 Subject: [PATCH 8/8] fix(input): drag threshold --- src/widget/text_input/input.rs | 176 +++++++++++++++++++-------------- 1 file changed, 102 insertions(+), 74 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index ab38a718..fb889138 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -211,6 +211,7 @@ pub struct TextInput<'a, Message> { always_active: bool, /// The text input tracks and manages the input value in its state. manage_value: bool, + drag_threshold: f32, } impl<'a, Message> TextInput<'a, Message> @@ -259,6 +260,7 @@ where helper_text: None, always_active: false, manage_value: false, + drag_threshold: 20.0, } } @@ -557,6 +559,12 @@ where layout.children().next().unwrap() } } + + /// Set the drag threshold. + pub fn drag_threshold(mut self, drag_threshold: f32) -> Self { + self.drag_threshold = drag_threshold; + self + } } impl Widget for TextInput<'_, Message> @@ -926,6 +934,7 @@ where line_height, layout, self.manage_value, + self.drag_threshold, ) } @@ -1346,6 +1355,7 @@ pub fn update<'a, Message: Clone + 'static>( line_height: text::LineHeight, layout: Layout<'_>, manage_value: bool, + drag_threshold: f32, ) -> event::Status { let update_cache = |state, value| { replace_paragraph( @@ -1424,84 +1434,39 @@ pub fn update<'a, Message: Clone + 'static>( ) { #[cfg(feature = "wayland")] (None, click::Kind::Single, cursor::State::Selection { start, end }) => { - // if something is already selected, we can start a drag and drop for a - // single click that is on top of the selected text - // is the click on selected text? + let left = start.min(end); + let right = end.max(start); - if on_input.is_some() || manage_value { - let left = start.min(end); - let right = end.max(start); + let (left_position, _left_offset) = measure_cursor_and_scroll_offset( + state.value.raw(), + text_layout.bounds(), + left, + ); - let (left_position, _left_offset) = measure_cursor_and_scroll_offset( - state.value.raw(), - text_layout.bounds(), - left, - ); + let (right_position, _right_offset) = measure_cursor_and_scroll_offset( + state.value.raw(), + text_layout.bounds(), + right, + ); - let (right_position, _right_offset) = measure_cursor_and_scroll_offset( - state.value.raw(), - text_layout.bounds(), - right, - ); + let width = right_position - left_position; + let selection_bounds = Rectangle { + x: text_layout.bounds().x + left_position, + y: text_layout.bounds().y, + width, + height: text_layout.bounds().height, + }; - let width = right_position - left_position; - let selection_bounds = Rectangle { - x: text_layout.bounds().x + left_position, - y: text_layout.bounds().y, - width, - height: text_layout.bounds().height, - }; - - if cursor.is_over(selection_bounds) { - // XXX never start a dnd if the input is secure - if is_secure { - return event::Status::Ignored; - } - let input_text = - state.selected_text(&value.to_string()).unwrap_or_default(); - state.dragging_state = Some(DraggingState::Dnd( - DndAction::empty(), - input_text.clone(), - )); - let mut editor = Editor::new(unsecured_value, &mut state.cursor); - editor.delete(); - - let contents = editor.contents(); - let unsecured_value = Value::new(&contents); - state.tracked_value = unsecured_value.clone(); - if let Some(on_input) = on_input { - let message = (on_input)(contents); - shell.publish(message); - } - if let Some(on_start_dnd) = on_start_dnd_source { - shell.publish(on_start_dnd(state.clone())); - } - let state_clone = state.clone(); - - iced_core::clipboard::start_dnd( - clipboard, - false, - id.map(iced_core::clipboard::DndSource::Widget), - Some(iced_core::clipboard::IconSurface::new( - Element::from( - TextInput::<'static, ()>::new("", input_text.clone()) - .dnd_icon(true), - ), - iced_core::widget::tree::State::new(state_clone), - Vector::ZERO, - )), - Box::new(TextInputString(input_text)), - DndAction::Move, - ); - - update_cache(state, &unsecured_value); - } else { - update_cache(state, value); - state.setting_selection(value, text_layout.bounds(), target); - } - } else { - state.setting_selection(value, text_layout.bounds(), target); + if cursor.is_over(selection_bounds) && (on_input.is_some() || manage_value) + { + state.dragging_state = Some(DraggingState::PrepareDnd(cursor_position)); + return event::Status::Captured; } + // clear selection and place cursor at click position + update_cache(state, value); + state.setting_selection(value, text_layout.bounds(), target); + state.dragging_state = None; + return event::Status::Captured; } (None, click::Kind::Single, _) => { state.setting_selection(value, text_layout.bounds(), target); @@ -1575,6 +1540,15 @@ pub fn update<'a, Message: Clone + 'static>( | Event::Touch(touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }) => { cold(); let state = state(); + #[cfg(feature = "wayland")] + if matches!(state.dragging_state, Some(DraggingState::PrepareDnd(_))) { + // clear selection and place cursor at click position + update_cache(state, value); + if let Some(position) = cursor.position_over(layout.bounds()) { + let target = position.x - text_layout.bounds().x; + state.setting_selection(value, text_layout.bounds(), target); + } + } state.dragging_state = None; return if cursor.is_over(layout.bounds()) { @@ -1598,6 +1572,58 @@ pub fn update<'a, Message: Clone + 'static>( .cursor .select_range(state.cursor.start(value), position); + return event::Status::Captured; + } + #[cfg(feature = "wayland")] + if let Some(DraggingState::PrepareDnd(start_position)) = state.dragging_state { + let distance = ((position.x - start_position.x).powi(2) + + (position.y - start_position.y).powi(2)) + .sqrt(); + + if distance >= drag_threshold { + if is_secure { + return event::Status::Ignored; + } + + let input_text = state.selected_text(&value.to_string()).unwrap_or_default(); + state.dragging_state = + Some(DraggingState::Dnd(DndAction::empty(), input_text.clone())); + let mut editor = Editor::new(unsecured_value, &mut state.cursor); + editor.delete(); + + let contents = editor.contents(); + let unsecured_value = Value::new(&contents); + state.tracked_value = unsecured_value.clone(); + if let Some(on_input) = on_input { + let message = (on_input)(contents); + shell.publish(message); + } + if let Some(on_start_dnd) = on_start_dnd_source { + shell.publish(on_start_dnd(state.clone())); + } + let state_clone = state.clone(); + + iced_core::clipboard::start_dnd( + clipboard, + false, + id.map(iced_core::clipboard::DndSource::Widget), + Some(iced_core::clipboard::IconSurface::new( + Element::from( + TextInput::<'static, ()>::new("", input_text.clone()) + .dnd_icon(true), + ), + iced_core::widget::tree::State::new(state_clone), + Vector::ZERO, + )), + Box::new(TextInputString(input_text)), + DndAction::Move, + ); + + update_cache(state, &unsecured_value); + } else { + state.dragging_state = Some(DraggingState::PrepareDnd(start_position)); + } + return event::Status::Captured; } } @@ -2519,10 +2545,12 @@ impl AsMimeTypes for TextInputString { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq)] pub(crate) enum DraggingState { Selection, #[cfg(feature = "wayland")] + PrepareDnd(Point), + #[cfg(feature = "wayland")] Dnd(DndAction, String), }