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?)
This commit is contained in:
Cheong Lau 2025-10-11 13:36:35 +10:00 committed by Michael Murphy
parent 840ef21e4d
commit bd438a8581
20 changed files with 83 additions and 88 deletions

View file

@ -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)?;

View file

@ -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(&gtk4).map_err(OutputError::Io)?;
fs::create_dir_all(&gtk3).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 [&gtk4_dest, &gtk3_dest] {
for gtk_dest in [&gtk4, &gtk3] {
use std::os::unix::fs::symlink;
Self::backup_non_cosmic_css(gtk_dest, &cosmic_css_dir).map_err(OutputError::Io)?;

View file

@ -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();

View file

@ -76,7 +76,7 @@ impl From<String> 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<App: Application>(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

View file

@ -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::<AppData>(&globals, &qh).ok(),
queue_handle: qh,
registry_state,
};

View file

@ -361,8 +361,12 @@ impl Core {
config_id: &'static str,
) -> iced::Subscription<cosmic_config::Update<T>> {
#[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::<T>(),
@ -378,8 +382,12 @@ impl Core {
state_id: &'static str,
) -> iced::Subscription<cosmic_config::Update<T>> {
#[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::<T>(),

View file

@ -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<Element<Message>> = 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)
})
};

View file

@ -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(),

View file

@ -455,10 +455,7 @@ 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| {
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(
@ -467,9 +464,7 @@ where
Length::FillPortion(12),
)
.into()
})
.collect::<Vec<_>>(),
)
}))
.padding([0.0, 0.0, f32::from(spacing.space_m), 0.0])
.spacing(spacing.space_xxs),
)

View file

@ -158,8 +158,7 @@ impl<'a, Message: Clone + 'static> From<Dialog<'a, Message>> 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)

View file

@ -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;
}

View file

@ -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,

View file

@ -1653,7 +1653,7 @@ fn get_children_layout<Message>(
let child_sizes: Vec<Size> = 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

View file

@ -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(),
];

View file

@ -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<Message>(children: Vec<crate::Element<Message>>) -> Column<Message> {
/// A [`column`] that will be assigned an [`Iterator`] of children.
pub fn with_children<'a, Message>(
children: impl IntoIterator<Item = crate::Element<'a, Message>>,
) -> 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<Message>(children: Vec<crate::Element<Message>>) -> Row<Message> {
/// A [`row`] that will be assigned an [`Iterator`] of children.
pub fn with_children<'a, Message>(
children: impl IntoIterator<Item = crate::Element<'a, Message>>,
) -> Row<'a, Message> {
Row::with_children(children)
}
}

View file

@ -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());
}
}
}

View file

@ -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<Message: 'static>(
});
Widget::<Message, crate::Theme, Renderer>::draw(
Element::<Message>::from(icon.clone()).as_widget(),
Element::<Message>::from(icon).as_widget(),
&Tree::empty(),
renderer,
theme,

View file

@ -170,7 +170,6 @@ where
)
.apply(Element::from)
})
.collect::<Vec<Element<'a, Message>>>()
.apply(widget::column::with_children)
.spacing(val.item_spacing)
.padding(val.element_padding)

View file

@ -125,7 +125,6 @@ where
.apply(|mouse_area| widget::context_menu(mouse_area, cat_context_tree))
.apply(Element::from)
})
.collect::<Vec<Element<'a, Message>>>()
.apply(widget::row::with_children)
.apply(Element::from);
// Build the items
@ -166,7 +165,6 @@ where
.align_y(Alignment::Center)
.apply(Element::from)
})
.collect::<Vec<Element<'static, Message>>>()
.apply(widget::row::with_children)
.apply(container)
.padding(val.item_padding)

View file

@ -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<Cow<'static, [u8]>> {
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
}