From bd438a8581f6ab781144e3fbf87ea0aaeb192d33 Mon Sep 17 00:00:00 2001 From: Cheong Lau <234708519+Cheong-Lau@users.noreply.github.com> Date: Sat, 11 Oct 2025 13:36:35 +1000 Subject: [PATCH] perf: reduce memory allocations This also changes `widget::column::with_children` and `widget::row::with_children` to take an `impl IntoIterator` instead of a `Vec`, like the `iced` variants of these functions do. This shouldn't be a breaking change since passing in a `Vec` will still compile and function exactly as before. (Using `iced::widget::Column::from_vec` or `iced::widget::Row::from_vec` isn't possible, since the elements of the `Vec` aren't checked, so the size of the resulting `Column` or `Row` won't adapt to the size of its children. Perhaps a new function could be added to mirror `iced`'s?) --- cosmic-config/src/lib.rs | 21 +++++++++------------ cosmic-theme/src/output/gtk4_output.rs | 19 ++++++++----------- cosmic-theme/src/output/vs_code.rs | 11 ++++++----- src/applet/mod.rs | 6 +++--- src/applet/token/wayland_handler.rs | 2 +- src/core.rs | 16 ++++++++++++---- src/widget/about.rs | 5 +---- src/widget/calendar.rs | 4 ++-- src/widget/color_picker/mod.rs | 25 ++++++++++--------------- src/widget/dialog.rs | 3 +-- src/widget/dropdown/menu/mod.rs | 8 ++++---- src/widget/dropdown/multi/widget.rs | 4 ++-- src/widget/menu/menu_inner.rs | 2 +- src/widget/menu/menu_tree.rs | 6 +++--- src/widget/mod.rs | 16 ++++++++++------ src/widget/popover.rs | 4 ++-- src/widget/segmented_button/widget.rs | 4 ++-- src/widget/table/widget/compact.rs | 1 - src/widget/table/widget/standard.rs | 2 -- src/widget/text_input/input.rs | 12 ++++++------ 20 files changed, 83 insertions(+), 88 deletions(-) diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 261b4412..5f424cc3 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -170,11 +170,10 @@ impl Config { .map(|x| x.join("COSMIC").join(&path)); // Get libcosmic user configuration directory - let cosmic_user_path = dirs::config_dir() - .ok_or(Error::NoConfigDirectory)? - .join("cosmic"); + let mut user_path = dirs::config_dir().ok_or(Error::NoConfigDirectory)?; + user_path.push("cosmic"); + user_path.push(path); - let user_path = cosmic_user_path.join(path); // Create new configuration directory if not found. fs::create_dir_all(&user_path)?; @@ -190,9 +189,9 @@ impl Config { // Look for [name]/v[version] let path = sanitize_name(name)?.join(format!("v{version}")); - let cosmic_user_path = custom_path.join("cosmic"); - - let user_path = cosmic_user_path.join(path); + let mut user_path = custom_path; + user_path.push("cosmic"); + user_path.push(path); // Create new configuration directory if not found. fs::create_dir_all(&user_path)?; @@ -213,11 +212,9 @@ impl Config { let path = sanitize_name(name)?.join(format!("v{}", version)); // Get libcosmic user state directory - let cosmic_user_path = dirs::state_dir() - .ok_or(Error::NoConfigDirectory)? - .join("cosmic"); - - let user_path = cosmic_user_path.join(path); + let mut user_path = dirs::state_dir().ok_or(Error::NoConfigDirectory)?; + user_path.push("cosmic"); + user_path.push(path); // Create new state directory if not found. fs::create_dir_all(&user_path)?; diff --git a/cosmic-theme/src/output/gtk4_output.rs b/cosmic-theme/src/output/gtk4_output.rs index df6aca6a..6fdf26d5 100644 --- a/cosmic-theme/src/output/gtk4_output.rs +++ b/cosmic-theme/src/output/gtk4_output.rs @@ -148,7 +148,7 @@ impl Theme { #[cold] pub fn write_gtk4(&self) -> Result<(), OutputError> { let css_str = self.as_gtk4(); - let Some(config_dir) = dirs::config_dir() else { + let Some(mut config_dir) = dirs::config_dir() else { return Err(OutputError::MissingConfigDir); }; @@ -158,7 +158,7 @@ impl Theme { "light.css" }; - let config_dir = config_dir.join("gtk-4.0").join("cosmic"); + config_dir.extend(["gtk-4.0", "cosmic"]); if !config_dir.exists() { std::fs::create_dir_all(&config_dir).map_err(OutputError::Io)?; } @@ -181,23 +181,20 @@ impl Theme { return Err(OutputError::MissingConfigDir); }; - let gtk4 = config_dir.join("gtk-4.0"); - let gtk3 = config_dir.join("gtk-3.0"); + let mut gtk4 = config_dir.join("gtk-4.0"); + let mut gtk3 = config_dir.join("gtk-3.0"); fs::create_dir_all(>k4).map_err(OutputError::Io)?; fs::create_dir_all(>k3).map_err(OutputError::Io)?; let cosmic_css_dir = gtk4.join("cosmic"); - let cosmic_css = - cosmic_css_dir - .clone() - .join(if is_dark { "dark.css" } else { "light.css" }); + let cosmic_css = cosmic_css_dir.join(if is_dark { "dark.css" } else { "light.css" }); - let gtk4_dest = gtk4.join("gtk.css"); - let gtk3_dest = gtk3.join("gtk.css"); + gtk4.push("gtk.css"); + gtk3.push("gtk.css"); #[cfg(target_family = "unix")] - for gtk_dest in [>k4_dest, >k3_dest] { + for gtk_dest in [>k4, >k3] { use std::os::unix::fs::symlink; Self::backup_non_cosmic_css(gtk_dest, &cosmic_css_dir).map_err(OutputError::Io)?; diff --git a/cosmic-theme/src/output/vs_code.rs b/cosmic-theme/src/output/vs_code.rs index b07c82e1..43c36bb6 100644 --- a/cosmic-theme/src/output/vs_code.rs +++ b/cosmic-theme/src/output/vs_code.rs @@ -269,8 +269,9 @@ impl Theme { #[cold] pub fn apply_vs_code(self) -> Result<(), OutputError> { let vs_theme = VsTheme::from(self); - let config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?; - let vs_code_dir = config_dir.join("Code").join("User"); + let mut config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?; + config_dir.extend(["Code", "User"]); + let vs_code_dir = config_dir; if !vs_code_dir.exists() { std::fs::create_dir_all(&vs_code_dir).map_err(OutputError::Io)?; } @@ -292,9 +293,9 @@ impl Theme { #[cold] pub fn reset_vs_code() -> Result<(), OutputError> { - let config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?; - let vs_code_dir = config_dir.join("Code").join("User"); - let settings_file = vs_code_dir.join("settings.json"); + let mut config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?; + config_dir.extend(["Code", "User", "settings.json"]); + let settings_file = config_dir; // just remove the json entry for workbench.colorCustomizations let settings = std::fs::read_to_string(&settings_file).unwrap_or_default(); let mut settings: serde_json::Value = serde_json::from_str(&settings).unwrap_or_default(); diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 6dfaeef6..659b7e92 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -76,7 +76,7 @@ impl From for PanelType { match value.as_str() { "Panel" => PanelType::Panel, "Dock" => PanelType::Dock, - other => PanelType::Other(other.to_string()), + _ => PanelType::Other(value), } } } @@ -470,8 +470,8 @@ pub fn run(flags: App::Flags) -> iced::Result { crate::malloc::limit_mmap_threshold(threshold); } - if let Some(icon_theme) = settings.default_icon_theme.clone() { - crate::icon_theme::set_default(icon_theme); + if let Some(icon_theme) = settings.default_icon_theme.as_ref() { + crate::icon_theme::set_default(icon_theme.clone()); } THEME diff --git a/src/applet/token/wayland_handler.rs b/src/applet/token/wayland_handler.rs index ee8f9b4e..3db84fc4 100644 --- a/src/applet/token/wayland_handler.rs +++ b/src/applet/token/wayland_handler.rs @@ -162,8 +162,8 @@ pub(crate) fn wayland_handler( exit: false, tx, seat_state: SeatState::new(&globals, &qh), - queue_handle: qh.clone(), activation_state: ActivationState::bind::(&globals, &qh).ok(), + queue_handle: qh, registry_state, }; diff --git a/src/core.rs b/src/core.rs index 338e0e85..4d50e764 100644 --- a/src/core.rs +++ b/src/core.rs @@ -361,8 +361,12 @@ impl Core { config_id: &'static str, ) -> iced::Subscription> { #[cfg(all(feature = "dbus-config", target_os = "linux"))] - if let Some(settings_daemon) = self.settings_daemon.clone() { - return cosmic_config::dbus::watcher_subscription(settings_daemon, config_id, false); + if let Some(settings_daemon) = self.settings_daemon.as_ref() { + return cosmic_config::dbus::watcher_subscription( + settings_daemon.clone(), + config_id, + false, + ); } cosmic_config::config_subscription( std::any::TypeId::of::(), @@ -378,8 +382,12 @@ impl Core { state_id: &'static str, ) -> iced::Subscription> { #[cfg(all(feature = "dbus-config", target_os = "linux"))] - if let Some(settings_daemon) = self.settings_daemon.clone() { - return cosmic_config::dbus::watcher_subscription(settings_daemon, state_id, true); + if let Some(settings_daemon) = self.settings_daemon.as_ref() { + return cosmic_config::dbus::watcher_subscription( + settings_daemon.clone(), + state_id, + true, + ); } cosmic_config::config_subscription( std::any::TypeId::of::(), diff --git a/src/widget/about.rs b/src/widget/about.rs index 628f53c6..384aee4a 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -113,10 +113,7 @@ pub fn about<'a, Message: Clone + 'static>( let section = |list: &'a Vec<(String, String)>, title: String| { (!list.is_empty()).then_some({ - let items: Vec> = list - .iter() - .map(|(name, url)| section_button(name, url)) - .collect(); + let items = list.iter().map(|(name, url)| section_button(name, url)); widget::settings::section().title(title).extend(items) }) }; diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index a1aace33..8531ab3d 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -179,8 +179,8 @@ where )); } - let content_list = column::with_children(vec![ - row::with_children(vec![ + let content_list = column::with_children([ + row::with_children([ date.into(), crate::widget::Space::with_width(Length::Fill).into(), month_controls.into(), diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 87e7a4d3..8dba2e1a 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -455,21 +455,16 @@ where // TODO get global colors from some cache? // TODO how to handle overflow? should this use a grid widget for the list or a horizontal scroll and a limit for the max? crate::widget::scrollable( - Row::with_children( - self.recent_colors - .iter() - .map(|c| { - let initial_srgb = palette::Srgb::from(*c); - let hsv = palette::Hsv::from_color(initial_srgb); - color_button( - Some(on_update(ColorPickerUpdate::ActiveColor(hsv))), - Some(*c), - Length::FillPortion(12), - ) - .into() - }) - .collect::>(), - ) + Row::with_children(self.recent_colors.iter().map(|c| { + let initial_srgb = palette::Srgb::from(*c); + let hsv = palette::Hsv::from_color(initial_srgb); + color_button( + Some(on_update(ColorPickerUpdate::ActiveColor(hsv))), + Some(*c), + Length::FillPortion(12), + ) + .into() + })) .padding([0.0, 0.0, f32::from(spacing.space_m), 0.0]) .spacing(spacing.space_xxs), ) diff --git a/src/widget/dialog.rs b/src/widget/dialog.rs index ecc6ef05..ba5b55e2 100644 --- a/src/widget/dialog.rs +++ b/src/widget/dialog.rs @@ -158,8 +158,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes } let mut container = widget::container( - widget::column::with_children(vec![content_row.into(), button_row.into()]) - .spacing(space_l), + widget::column::with_children([content_row.into(), button_row.into()]).spacing(space_l), ) .class(style::Container::Dialog) .padding(space_m) diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index 0026283c..021fcc60 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -477,8 +477,8 @@ where if cursor.is_over(layout.bounds()) { if let Some(index) = *hovered_guard { shell.publish((self.on_selected)(index)); - if let Some(close_on_selected) = self.close_on_selected.clone() { - shell.publish(close_on_selected); + if let Some(close_on_selected) = self.close_on_selected.as_ref() { + shell.publish(close_on_selected.clone()); } return event::Status::Captured; } @@ -521,8 +521,8 @@ where if let Some(index) = *hovered_guard { shell.publish((self.on_selected)(index)); - if let Some(close_on_selected) = self.close_on_selected.clone() { - shell.publish(close_on_selected); + if let Some(close_on_selected) = self.close_on_selected.as_ref() { + shell.publish(close_on_selected.clone()); } return event::Status::Captured; } diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index 1b0637bb..9c183292 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -521,8 +521,8 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>( style.background, ); - if let Some(handle) = state.icon.clone() { - let svg_handle = iced_core::Svg::new(handle).color(style.text_color); + if let Some(handle) = state.icon.as_ref() { + let svg_handle = iced_core::Svg::new(handle.clone()).color(style.text_color); svg::Renderer::draw_svg( renderer, svg_handle, diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 18f9940d..c88a7570 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -1653,7 +1653,7 @@ fn get_children_layout( let child_sizes: Vec = match item_height { ItemHeight::Uniform(u) => { let count = menu_tree.children.len(); - (0..count).map(|_| Size::new(width, f32::from(u))).collect() + vec![Size::new(width, f32::from(u)); count] } ItemHeight::Static(s) => menu_tree .children diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index e63e523b..15dd5810 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -144,7 +144,7 @@ where Message: std::clone::Clone + 'a, { widget::button::custom( - widget::Row::with_children(children) + widget::Row::from_vec(children) .align_y(Alignment::Center) .height(Length::Fill) .width(Length::Fill), @@ -252,7 +252,7 @@ pub fn menu_items< let l: Cow<'static, str> = label.into(); let key = find_key(&action, key_binds); let mut items = vec![ - widget::text(l.clone()).into(), + widget::text(l).into(), widget::horizontal_space().into(), widget::text(key).class(key_class).into(), ]; @@ -272,7 +272,7 @@ pub fn menu_items< let key = find_key(&action, key_binds); let mut items = vec![ - widget::text(l.clone()).into(), + widget::text(l).into(), widget::horizontal_space().into(), widget::text(key).class(key_class).into(), ]; diff --git a/src/widget/mod.rs b/src/widget/mod.rs index f212906a..202173ef 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -147,12 +147,14 @@ pub mod column { #[must_use] /// A pre-allocated [`column`]. pub fn with_capacity<'a, Message>(capacity: usize) -> Column<'a, Message> { - Column::with_children(Vec::with_capacity(capacity)) + Column::with_capacity(capacity) } #[must_use] - /// A [`column`] that will be assigned a [`Vec`] of children. - pub fn with_children(children: Vec>) -> Column { + /// A [`column`] that will be assigned an [`Iterator`] of children. + pub fn with_children<'a, Message>( + children: impl IntoIterator>, + ) -> Column<'a, Message> { Column::with_children(children) } } @@ -298,12 +300,14 @@ pub mod row { #[must_use] /// A pre-allocated [`row`]. pub fn with_capacity<'a, Message>(capacity: usize) -> Row<'a, Message> { - Row::with_children(Vec::with_capacity(capacity)) + Row::with_capacity(capacity) } #[must_use] - /// A [`row`] that will be assigned a [`Vec`] of children. - pub fn with_children(children: Vec>) -> Row { + /// A [`row`] that will be assigned an [`Iterator`] of children. + pub fn with_children<'a, Message>( + children: impl IntoIterator>, + ) -> Row<'a, Message> { Row::with_children(children) } } diff --git a/src/widget/popover.rs b/src/widget/popover.rs index 6c6f6652..ddc31455 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -141,14 +141,14 @@ where if matches!(event, Event::Mouse(_) | Event::Touch(_)) { return event::Status::Captured; } - } else if let Some(on_close) = self.on_close.clone() { + } else if let Some(on_close) = self.on_close.as_ref() { if matches!( event, Event::Mouse(mouse::Event::ButtonPressed(_)) | Event::Touch(touch::Event::FingerPressed { .. }) ) && !cursor_position.is_over(layout.bounds()) { - shell.publish(on_close); + shell.publish(on_close.clone()); } } } diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 0e725132..5dd8e7c7 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -274,7 +274,7 @@ where self.on_dnd_drop = Some(Box::new(move |entity, data, mime, action| { dnd_drop_handler(entity, D::try_from((data, mime)).ok(), action) })); - self.mimes = D::allowed().iter().cloned().collect(); + self.mimes = D::allowed().into_owned(); self } @@ -1867,7 +1867,7 @@ fn draw_icon( }); Widget::::draw( - Element::::from(icon.clone()).as_widget(), + Element::::from(icon).as_widget(), &Tree::empty(), renderer, theme, diff --git a/src/widget/table/widget/compact.rs b/src/widget/table/widget/compact.rs index 7cda2dfb..0ad92166 100644 --- a/src/widget/table/widget/compact.rs +++ b/src/widget/table/widget/compact.rs @@ -170,7 +170,6 @@ where ) .apply(Element::from) }) - .collect::>>() .apply(widget::column::with_children) .spacing(val.item_spacing) .padding(val.element_padding) diff --git a/src/widget/table/widget/standard.rs b/src/widget/table/widget/standard.rs index 3ee1ac4a..c0207f06 100644 --- a/src/widget/table/widget/standard.rs +++ b/src/widget/table/widget/standard.rs @@ -125,7 +125,6 @@ where .apply(|mouse_area| widget::context_menu(mouse_area, cat_context_tree)) .apply(Element::from) }) - .collect::>>() .apply(widget::row::with_children) .apply(Element::from); // Build the items @@ -166,7 +165,6 @@ where .align_y(Alignment::Center) .apply(Element::from) }) - .collect::>>() .apply(widget::row::with_children) .apply(container) .padding(val.item_padding) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 4c9fa0f9..958673ef 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -760,14 +760,14 @@ where if state.dirty { state.dirty = false; let value = if self.is_secure { - self.value.secure() + &self.value.secure() } else { - self.value.clone() + &self.value }; replace_paragraph( state, Layout::new(&res), - &value, + value, font, iced::Pixels(size), line_height, @@ -2022,7 +2022,7 @@ pub fn update<'a, Message: Clone + 'static>( if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() { let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES .iter() - .find(|m| mime_types.contains(&(**m).to_string())) + .find(|&&m| mime_types.iter().any(|t| t == m)) else { state.dnd_offer = DndOfferState::None; return event::Status::Captured; @@ -2057,7 +2057,7 @@ pub fn update<'a, Message: Clone + 'static>( { cold(); let state = state(); - if let DndOfferState::Dropped = state.dnd_offer.clone() { + if matches!(&state.dnd_offer, DndOfferState::Dropped) { state.dnd_offer = DndOfferState::None; if !SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type.as_str()) || data.is_empty() { return event::Status::Captured; @@ -2536,7 +2536,7 @@ impl AsMimeTypes for TextInputString { fn as_bytes(&self, mime_type: &str) -> Option> { if SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type) { - Some(Cow::Owned(self.0.clone().as_bytes().to_vec())) + Some(Cow::Owned(self.0.clone().into_bytes())) } else { None }