From 0491c4baaa1b30f6a8ac4c397c3372e1120533b3 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 16 Oct 2024 20:36:46 -0400 Subject: [PATCH 001/556] libcosmic updates --- Cargo.toml | 32 +- cosmic-config/src/dbus.rs | 18 +- cosmic-config/src/lib.rs | 1 + cosmic-config/src/subscription.rs | 36 +- examples/applet/Cargo.toml | 14 +- examples/applet/src/main.rs | 7 +- examples/applet/src/window.rs | 43 +- examples/application/Cargo.toml | 13 +- examples/application/src/main.rs | 56 +- examples/calendar/src/main.rs | 10 +- examples/context-menu/src/main.rs | 8 +- examples/cosmic/src/window.rs | 16 +- examples/cosmic/src/window/demo.rs | 2 +- examples/image-button/src/main.rs | 10 +- examples/menu/src/main.rs | 14 +- examples/multi-window/src/window.rs | 36 +- examples/nav-context/src/main.rs | 12 +- examples/open-dialog/src/main.rs | 10 +- examples/text-input/src/main.rs | 12 +- iced | 2 +- src/app/command.rs | 104 +- src/app/core.rs | 30 +- src/app/cosmic.rs | 273 +-- src/app/mod.rs | 290 ++-- src/app/multi_window.rs | 238 +++ src/app/settings.rs | 2 - src/applet/mod.rs | 222 +-- src/applet/token/subscription.rs | 18 +- src/command/mod.rs | 101 +- src/ext.rs | 4 +- src/font.rs | 2 +- src/keyboard_nav.rs | 2 +- src/lib.rs | 19 +- src/theme/mod.rs | 12 + src/theme/portal.rs | 154 +- src/theme/style/button.rs | 28 +- src/theme/style/iced.rs | 1583 ++++++++++-------- src/theme/style/mod.rs | 2 - src/widget/aspect_ratio.rs | 24 +- src/widget/autosize.rs | 276 +++ src/widget/button/icon.rs | 30 +- src/widget/button/image.rs | 4 +- src/widget/button/link.rs | 26 +- src/widget/button/mod.rs | 6 +- src/widget/button/style.rs | 21 +- src/widget/button/text.rs | 34 +- src/widget/button/widget.rs | 41 +- src/widget/calendar.rs | 12 +- src/widget/card/style.rs | 8 +- src/widget/color_picker/mod.rs | 108 +- src/widget/context_drawer/overlay.rs | 17 +- src/widget/context_drawer/widget.rs | 35 +- src/widget/context_menu.rs | 8 +- src/widget/dialog.rs | 10 +- src/widget/dnd_destination.rs | 9 +- src/widget/dnd_source.rs | 104 +- src/widget/dropdown/menu/mod.rs | 34 +- src/widget/dropdown/multi/menu.rs | 33 +- src/widget/dropdown/multi/mod.rs | 2 +- src/widget/dropdown/multi/widget.rs | 42 +- src/widget/dropdown/widget.rs | 63 +- src/widget/flex_row/widget.rs | 11 +- src/widget/grid/widget.rs | 10 +- src/widget/header_bar.rs | 45 +- src/widget/icon/handle.rs | 13 +- src/widget/icon/mod.rs | 6 +- src/widget/id_container.rs | 16 +- src/widget/layer_container.rs | 53 +- src/widget/list/column.rs | 18 +- src/widget/list/mod.rs | 2 +- src/widget/menu/menu_bar.rs | 3 + src/widget/menu/menu_inner.rs | 14 +- src/widget/menu/menu_tree.rs | 22 +- src/widget/min_size_tracker/mod.rs | 319 ++++ src/widget/min_size_tracker/subscription.rs | 88 + src/widget/mod.rs | 35 +- src/widget/nav_bar.rs | 7 +- src/widget/nav_bar_toggle.rs | 6 +- src/widget/popover.rs | 46 +- src/widget/radio.rs | 59 +- src/widget/rectangle_tracker/mod.rs | 36 +- src/widget/rectangle_tracker/subscription.rs | 18 +- src/widget/segmented_button/widget.rs | 35 +- src/widget/settings/item.rs | 19 +- src/widget/spin_button/mod.rs | 13 +- src/widget/text.rs | 20 +- src/widget/text_input/input.rs | 491 +++--- src/widget/toaster/mod.rs | 12 +- src/widget/toaster/widget.rs | 36 +- src/widget/toggler.rs | 6 +- src/widget/warning.rs | 8 +- 91 files changed, 3550 insertions(+), 2300 deletions(-) create mode 100644 src/app/multi_window.rs create mode 100644 src/widget/autosize.rs create mode 100644 src/widget/min_size_tracker/mod.rs create mode 100644 src/widget/min_size_tracker/subscription.rs diff --git a/Cargo.toml b/Cargo.toml index 8696f881..5e7cb83a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,15 +8,22 @@ rust-version = "1.80" name = "cosmic" [features] -default = ["clipboard"] # Accessibility support a11y = ["iced/a11y", "iced_accessibility"] # Builds support for animated images animated-image = ["image", "dep:async-fs", "tokio?/io-util", "tokio?/fs"] -# XXX Use "a11y"; which is causing a panic currently -applet = ["wayland", "tokio", "cosmic-panel-config", "ron"] +# XXX autosize should not be used on winit windows unless dialogs +autosize = [] +applet = [ + "a11y", + "autosize", + "wayland", + "tokio", + "cosmic-panel-config", + "ron", + "multi-window", +] applet-token = [] -clipboard = ["iced_sctk?/clipboard"] # Use the cosmic-settings-daemon for config handling dbus-config = ["cosmic-config/dbus", "dep:zbus", "cosmic-settings-daemon"] # Debug features @@ -61,7 +68,7 @@ wayland = [ "ashpd?/wayland", "iced_runtime/wayland", "iced/wayland", - "iced_sctk", + "iced_winit/wayland", "cctk", ] # multi-window support @@ -120,7 +127,7 @@ path = "cosmic-theme" [dependencies.iced] path = "./iced" default-features = false -features = ["advanced", "image", "lazy", "svg", "web-colors"] +features = ["advanced", "image", "lazy", "svg", "web-colors", "tiny-skia"] [dependencies.iced_runtime] path = "./iced/runtime" @@ -146,13 +153,6 @@ optional = true [dependencies.iced_tiny_skia] path = "./iced/tiny_skia" -[dependencies.iced_style] -path = "./iced/style" - -[dependencies.iced_sctk] -path = "./iced/sctk" -optional = true - [dependencies.iced_winit] path = "./iced/winit" optional = true @@ -189,3 +189,9 @@ dirs = "5.0.1" [patch."https://github.com/pop-os/libcosmic"] libcosmic = { path = "./" } + +# FIXME update winit deps where necessary to use this +# [patch.crates-io] +[patch."https://github.com/pop-os/winit.git"] +# winit = { git = "https://github.com/rust-windowing/winit.git", rev = "241b7a80bba96c91fa3901729cd5dec66abb9be4" } +# winit = { path = "../../winit" } diff --git a/cosmic-config/src/dbus.rs b/cosmic-config/src/dbus.rs index a36d49db..dd0612c8 100644 --- a/cosmic-config/src/dbus.rs +++ b/cosmic-config/src/dbus.rs @@ -3,7 +3,10 @@ use std::ops::Deref; use crate::{CosmicConfigEntry, Update}; use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy}; use futures_util::SinkExt; -use iced_futures::futures::{self, future::pending, StreamExt}; +use iced_futures::{ + futures::{self, future::pending, Stream, StreamExt}, + stream, Subscription, +}; pub async fn settings_daemon_proxy() -> zbus::Result> { let conn = zbus::Connection::session().await?; @@ -58,13 +61,20 @@ pub fn watcher_subscription iced_futures::Subscription> { + let id = std::any::TypeId::of::(); + Subscription::run_with_id(id, watcher_stream(settings_daemon, config_id, is_state)) +} + +fn watcher_stream( + settings_daemon: CosmicSettingsDaemonProxy<'static>, + config_id: &'static str, + is_state: bool, +) -> impl Stream> { enum Change { Changes(Changed), OwnerChanged(bool), } - - let id = std::any::TypeId::of::(); - iced_futures::subscription::channel((is_state, config_id, id), 5, move |mut tx| async move { + stream::channel(5, move |mut tx| async move { let version = T::VERSION; let Ok(cosmic_config) = (if is_state { diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 48a18a52..f915da71 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -384,6 +384,7 @@ where ) -> (Vec, Vec<&'static str>); } +#[derive(Debug)] pub struct Update { pub errors: Vec, pub keys: Vec<&'static str>, diff --git a/cosmic-config/src/subscription.rs b/cosmic-config/src/subscription.rs index ef88866c..7468af0a 100644 --- a/cosmic-config/src/subscription.rs +++ b/cosmic-config/src/subscription.rs @@ -1,5 +1,5 @@ -use iced_futures::futures::SinkExt; -use iced_futures::{futures::channel::mpsc, subscription}; +use iced_futures::futures::{SinkExt, Stream}; +use iced_futures::{futures::channel::mpsc, stream}; use notify::RecommendedWatcher; use std::{borrow::Cow, hash::Hash}; @@ -24,17 +24,7 @@ pub fn config_subscription< config_id: Cow<'static, str>, config_version: u64, ) -> iced_futures::Subscription> { - subscription::channel(id, 100, move |mut output| { - let config_id = config_id.clone(); - async move { - let config_id = config_id.clone(); - let mut state = ConfigState::Init(config_id, config_version, false); - - loop { - state = start_listening(state, &mut output, id).await; - } - } - }) + iced_futures::Subscription::run_with_id(id, watcher_stream(config_id, config_version, false)) } pub fn config_state_subscription< @@ -45,26 +35,30 @@ pub fn config_state_subscription< config_id: Cow<'static, str>, config_version: u64, ) -> iced_futures::Subscription> { - subscription::channel(id, 100, move |mut output| { + iced_futures::Subscription::run_with_id(id, watcher_stream(config_id, config_version, true)) +} + +fn watcher_stream( + config_id: Cow<'static, str>, + config_version: u64, + is_state: bool, +) -> impl Stream> { + stream::channel(100, move |mut output| { let config_id = config_id.clone(); async move { let config_id = config_id.clone(); - let mut state = ConfigState::Init(config_id, config_version, true); + let mut state = ConfigState::Init(config_id, config_version, is_state); loop { - state = start_listening(state, &mut output, id).await; + state = start_listening::(state, &mut output).await; } } }) } -async fn start_listening< - I: Copy, - T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry, ->( +async fn start_listening( state: ConfigState, output: &mut mpsc::Sender>, - id: I, ) -> ConfigState { use iced_futures::futures::{future::pending, StreamExt}; diff --git a/examples/applet/Cargo.toml b/examples/applet/Cargo.toml index 965e30ac..ba4ce662 100644 --- a/examples/applet/Cargo.toml +++ b/examples/applet/Cargo.toml @@ -9,8 +9,20 @@ edition = "2021" once_cell = "1" rust-embed = "8.0.0" tracing = "0.1" +env_logger = "0.10.0" +log = "0.4.17" [dependencies.libcosmic] git = "https://github.com/pop-os/libcosmic" default-features = false -features = ["applet", "tokio", "wayland"] +features = [ + "applet", + "applet-token", + "multi-window", + "tokio", + "wayland", + "winit", + "desktop", + "dbus-config", + "image", +] diff --git a/examples/applet/src/main.rs b/examples/applet/src/main.rs index 28e893a3..4ff0c0c5 100644 --- a/examples/applet/src/main.rs +++ b/examples/applet/src/main.rs @@ -3,5 +3,10 @@ use crate::window::Window; mod window; fn main() -> cosmic::iced::Result { - cosmic::applet::run::(true, ()) + let env = env_logger::Env::default() + .filter_or("MY_LOG_LEVEL", "warn") + .write_style_or("MY_LOG_STYLE", "always"); + + env_logger::init_from_env(env); + cosmic::applet::run::(()) } diff --git a/examples/applet/src/window.rs b/examples/applet/src/window.rs index c1706c65..58306573 100644 --- a/examples/applet/src/window.rs +++ b/examples/applet/src/window.rs @@ -1,9 +1,10 @@ use cosmic::app::Core; -use cosmic::iced::wayland::popup::{destroy_popup, get_popup}; +use cosmic::iced::application; +use cosmic::iced::platform_specific::shell::commands::popup::{destroy_popup, get_popup}; use cosmic::iced::window::Id; -use cosmic::iced::{Command, Limits}; +use cosmic::iced::{Length, Limits, Task}; use cosmic::iced_runtime::core::window; -use cosmic::iced_style::application; +use cosmic::theme::iced; use cosmic::widget::{list_column, settings, toggler}; use cosmic::{Element, Theme}; @@ -37,22 +38,19 @@ impl cosmic::Application for Window { &mut self.core } - fn init( - core: Core, - _flags: Self::Flags, - ) -> (Self, Command>) { + fn init(core: Core, _flags: Self::Flags) -> (Self, Task>) { let window = Window { core, ..Default::default() }; - (window, Command::none()) + (window, Task::none()) } fn on_close_requested(&self, id: window::Id) -> Option { Some(Message::PopupClosed(id)) } - fn update(&mut self, message: Self::Message) -> Command> { + fn update(&mut self, message: Self::Message) -> Task> { match message { Message::TogglePopup => { return if let Some(p) = self.popup.take() { @@ -60,17 +58,23 @@ impl cosmic::Application for Window { } else { let new_id = Id::unique(); self.popup.replace(new_id); - let mut popup_settings = - self.core - .applet - .get_popup_settings(Id::MAIN, new_id, None, None, None); + let mut popup_settings = self.core.applet.get_popup_settings( + self.core.main_window_id().unwrap(), + new_id, + None, + None, + None, + ); popup_settings.positioner.size_limits = Limits::NONE .max_width(372.0) .min_width(300.0) .min_height(200.0) - .max_height(1080.0); + .max_height(1080.0) + .height(500) + .width(500); + popup_settings.positioner.size = Some((500, 500)); get_popup(popup_settings) - } + }; } Message::PopupClosed(id) => { if self.popup.as_ref() == Some(&id) { @@ -79,7 +83,7 @@ impl cosmic::Application for Window { } Message::ToggleExampleRow(toggled) => self.example_row = toggled, } - Command::none() + Task::none() } fn view(&self) -> Element { @@ -93,15 +97,16 @@ impl cosmic::Application for Window { fn view_window(&self, _id: Id) -> Element { let content_list = list_column().padding(5).spacing(0).add(settings::item( "Example row", - toggler(None, self.example_row, |value| { + cosmic::widget::container(toggler(self.example_row, |value| { Message::ToggleExampleRow(value) - }), + })) + .height(Length::Fixed(50.)), )); self.core.applet.popup_container(content_list).into() } - fn style(&self) -> Option<::Style> { + fn style(&self) -> Option { Some(cosmic::applet::style()) } } diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index 7f188b27..695f9897 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -11,4 +11,15 @@ tracing-log = "0.2.0" [dependencies.libcosmic] path = "../../" default-features = false -features = ["debug", "winit", "tokio", "xdg-portal", "dbus-config", "a11y"] +features = [ + "debug", + "winit", + "tokio", + "xdg-portal", + "dbus-config", + "a11y", + "wayland", + "wgpu", + "single-instance", + "multi-window", +] diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index 0eba0a86..24568f0a 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -3,7 +3,8 @@ //! Application API example -use cosmic::app::{Command, Core, Settings}; +use cosmic::app::{Core, Settings, Task}; +use cosmic::iced::widget::column; use cosmic::iced_core::Size; use cosmic::widget::nav_bar; use cosmic::{executor, iced, ApplicationExt, Element}; @@ -50,12 +51,17 @@ fn main() -> Result<(), Box> { /// Messages that are used specifically by our [`App`]. #[derive(Clone, Debug)] -pub enum Message {} +pub enum Message { + Input1(String), + Input2(String), +} /// The [`App`] stores application-specific state. pub struct App { core: Core, nav_model: nav_bar::Model, + input_1: String, + input_2: String, } /// Implement [`cosmic::Application`] to integrate with COSMIC. @@ -81,7 +87,7 @@ impl cosmic::Application for App { } /// Creates the application, and optionally emits command on initialize. - fn init(core: Core, input: Self::Flags) -> (Self, Command) { + fn init(core: Core, input: Self::Flags) -> (Self, Task) { let mut nav_model = nav_bar::Model::default(); for (title, content) in input { @@ -90,7 +96,12 @@ impl cosmic::Application for App { nav_model.activate_position(0); - let mut app = App { core, nav_model }; + let mut app = App { + core, + nav_model, + input_1: String::new(), + input_2: String::new(), + }; let command = app.update_title(); @@ -103,14 +114,22 @@ impl cosmic::Application for App { } /// Called when a navigation item is selected. - fn on_nav_select(&mut self, id: nav_bar::Id) -> Command { + fn on_nav_select(&mut self, id: nav_bar::Id) -> Task { self.nav_model.activate(id); self.update_title() } /// Handle application events here. - fn update(&mut self, _message: Self::Message) -> Command { - Command::none() + fn update(&mut self, message: Self::Message) -> Task { + match message { + Message::Input1(v) => { + self.input_1 = v; + } + Message::Input2(v) => { + self.input_2 = v; + } + } + Task::none() } /// Creates a view after each update. @@ -122,11 +141,20 @@ impl cosmic::Application for App { let text = cosmic::widget::text(page_content); - let centered = cosmic::widget::container(text) + let centered = cosmic::widget::container( + column![ + text, + cosmic::widget::text_input::text_input("", &self.input_1).on_input(Message::Input1), + cosmic::widget::text_input::text_input("", &self.input_2).on_input(Message::Input2), + ] .width(iced::Length::Fill) .height(iced::Length::Shrink) - .align_x(iced::alignment::Horizontal::Center) - .align_y(iced::alignment::Vertical::Center); + .align_x(iced::alignment::Horizontal::Center), + ) + .width(iced::Length::Fill) + .height(iced::Length::Shrink) + .align_x(iced::alignment::Horizontal::Center) + .align_y(iced::alignment::Vertical::Center); Element::from(centered) } @@ -142,10 +170,14 @@ where .unwrap_or("Unknown Page") } - fn update_title(&mut self) -> Command { + fn update_title(&mut self) -> Task { let header_title = self.active_page_title().to_owned(); let window_title = format!("{header_title} — COSMIC AppDemo"); self.set_header_title(header_title); - self.set_window_title(window_title) + if let Some(id) = self.core.main_window_id() { + self.set_window_title(window_title, id) + } else { + Task::none() + } } } diff --git a/examples/calendar/src/main.rs b/examples/calendar/src/main.rs index 1b20f356..df36b730 100644 --- a/examples/calendar/src/main.rs +++ b/examples/calendar/src/main.rs @@ -4,7 +4,7 @@ //! Calendar widget example use chrono::{Local, NaiveDate}; -use cosmic::app::{Command, Core, Settings}; +use cosmic::app::{Core, Settings, Task}; use cosmic::{executor, iced, ApplicationExt, Element}; /// Runs application with these settings @@ -50,7 +50,7 @@ impl cosmic::Application for App { } /// Creates the application, and optionally emits command on initialize. - fn init(core: Core, _input: Self::Flags) -> (Self, Command) { + fn init(core: Core, _input: Self::Flags) -> (Self, Task) { let now = Local::now(); let mut app = App { @@ -64,7 +64,7 @@ impl cosmic::Application for App { } /// Handle application events here. - fn update(&mut self, message: Self::Message) -> Command { + fn update(&mut self, message: Self::Message) -> Task { match message { Message::DateSelected(date) => { self.date_selected = date; @@ -73,7 +73,7 @@ impl cosmic::Application for App { println!("Date selected: {:?}", self.date_selected); - Command::none() + Task::none() } /// Creates a view after each update. @@ -99,7 +99,7 @@ impl App where Self: cosmic::Application, { - fn update_title(&mut self) -> Command { + fn update_title(&mut self) -> Task { self.set_header_title(String::from("Calendar Demo")); self.set_window_title(String::from("Calendar Demo")) } diff --git a/examples/context-menu/src/main.rs b/examples/context-menu/src/main.rs index cfc99184..66077226 100644 --- a/examples/context-menu/src/main.rs +++ b/examples/context-menu/src/main.rs @@ -3,7 +3,7 @@ //! Application API example -use cosmic::app::{Command, Core, Settings}; +use cosmic::app::{Task, Core, Settings}; use cosmic::iced_core::Size; use cosmic::widget::{menu, segmented_button}; use cosmic::{executor, iced, ApplicationExt, Element}; @@ -65,7 +65,7 @@ impl cosmic::Application for App { } /// Creates the application, and optionally emits command on initialize. - fn init(core: Core, _input: Self::Flags) -> (Self, Command) { + fn init(core: Core, _input: Self::Flags) -> (Self, Task) { let mut app = App { core, button_label: String::from("Right click me"), @@ -80,10 +80,10 @@ impl cosmic::Application for App { } /// Handle application events here. - fn update(&mut self, message: Self::Message) -> Command { + fn update(&mut self, message: Self::Message) -> Task { self.button_label = format!("Clicked {message:?}"); - Command::none() + Task::none() } /// Creates a view after each update. diff --git a/examples/cosmic/src/window.rs b/examples/cosmic/src/window.rs index b4785340..a554772c 100644 --- a/examples/cosmic/src/window.rs +++ b/examples/cosmic/src/window.rs @@ -6,7 +6,7 @@ use cosmic::{ ThemeBuilder, }, font::load_fonts, - iced::{self, Application, Command, Length, Subscription}, + iced::{self, Application, Length, Subscription, Task}, iced::{ subscription, widget::{self, column, container, horizontal_space, row, text}, @@ -324,7 +324,7 @@ impl Application for Window { type Message = Message; type Theme = Theme; - fn new(_flags: ()) -> (Self, Command) { + fn new(_flags: ()) -> (Self, Task) { let mut window = Window::default() .nav_bar_toggled(true) .show_maximize(true) @@ -389,8 +389,8 @@ impl Application for Window { ]) } - fn update(&mut self, message: Message) -> iced::Command { - let mut ret = Command::none(); + fn update(&mut self, message: Message) -> iced::Task { + let mut ret = Task::none(); match message { Message::NavBar(key) => { if let Some(page) = self.nav_id_to_page.get(key).copied() { @@ -437,10 +437,10 @@ impl Application for Window { Message::ToggleNavBarCondensed => { self.nav_bar_toggled_condensed = !self.nav_bar_toggled_condensed } - Message::Drag => return drag(window::Id::MAIN), - Message::Close => return close(window::Id::MAIN), - Message::Minimize => return minimize(window::Id::MAIN, true), - Message::Maximize => return toggle_maximize(window::Id::MAIN), + Message::Drag => return drag(self.core.main_window_id().unwrap()), + Message::Close => return close(self.core.main_window_id().unwrap()), + Message::Minimize => return minimize(self.core.main_window_id().unwrap(), true), + Message::Maximize => return toggle_maximize(self.core.main_window_id().unwrap()), Message::InputChanged => {} diff --git a/examples/cosmic/src/window/demo.rs b/examples/cosmic/src/window/demo.rs index f0097937..9ca84ef7 100644 --- a/examples/cosmic/src/window/demo.rs +++ b/examples/cosmic/src/window/demo.rs @@ -482,7 +482,7 @@ impl State { )) .layer(cosmic::cosmic_theme::Layer::Secondary) .padding(16) - .style(cosmic::theme::Container::Background) + .class(cosmic::theme::Container::Background) .into(), cosmic::widget::text_input::secure_input( "Type to search apps or type “?” for more options...", diff --git a/examples/image-button/src/main.rs b/examples/image-button/src/main.rs index bfa51ba2..40cb70b3 100644 --- a/examples/image-button/src/main.rs +++ b/examples/image-button/src/main.rs @@ -3,7 +3,7 @@ //! Application API example -use cosmic::app::{Command, Core, Settings}; +use cosmic::app::{Task, Core, Settings}; use cosmic::{executor, iced, ApplicationExt, Element}; /// Runs application with these settings @@ -51,7 +51,7 @@ impl cosmic::Application for App { } /// Creates the application, and optionally emits command on initialize. - fn init(core: Core, _input: Self::Flags) -> (Self, Command) { + fn init(core: Core, _input: Self::Flags) -> (Self, Task) { let mut app = App { core, selected: 0, @@ -67,7 +67,7 @@ impl cosmic::Application for App { } /// Handle application events here. - fn update(&mut self, message: Self::Message) -> Command { + fn update(&mut self, message: Self::Message) -> Task { match message { Message::Clicked(id) => self.selected = id, Message::Remove(id) => { @@ -75,7 +75,7 @@ impl cosmic::Application for App { } } - Command::none() + Task::none() } /// Creates a view after each update. @@ -106,7 +106,7 @@ impl App where Self: cosmic::Application, { - fn update_title(&mut self) -> Command { + fn update_title(&mut self) -> Task { self.set_header_title(String::from("Image Button Demo")); self.set_window_title(String::from("Image Button Demo")) } diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs index ab668cb6..c7c686db 100644 --- a/examples/menu/src/main.rs +++ b/examples/menu/src/main.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use std::{env, process}; -use cosmic::app::{Command, Core, Settings}; +use cosmic::app::{Task, Core, Settings}; use cosmic::iced::window; use cosmic::iced_core::alignment::{Horizontal, Vertical}; use cosmic::iced_core::keyboard::Key; @@ -97,7 +97,7 @@ impl cosmic::Application for App { } /// Creates the application, and optionally emits command on initialize. - fn init(core: Core, _input: Self::Flags) -> (Self, Command) { + fn init(core: Core, _input: Self::Flags) -> (Self, Task) { let app = App { core, config: Config { @@ -106,7 +106,7 @@ impl cosmic::Application for App { key_binds: key_binds(), }; - (app, Command::none()) + (app, Task::none()) } fn header_start(&self) -> Vec> { @@ -114,13 +114,13 @@ impl cosmic::Application for App { } /// Handle application events here. - fn update(&mut self, message: Self::Message) -> Command { + fn update(&mut self, message: Self::Message) -> Task { match message { Message::WindowClose => { - return window::close(window::Id::MAIN); + return window::close(self.core.main_window_id().unwrap()); } Message::WindowNew => match env::current_exe() { - Ok(exe) => match process::Command::new(&exe).spawn() { + Ok(exe) => match process::Task::new(&exe).spawn() { Ok(_child) => {} Err(err) => { eprintln!("failed to execute {:?}: {}", exe, err); @@ -132,7 +132,7 @@ impl cosmic::Application for App { }, Message::ToggleHideContent => self.config.hide_content = !self.config.hide_content, } - Command::none() + Task::none() } /// Creates a view after each update. diff --git a/examples/multi-window/src/window.rs b/examples/multi-window/src/window.rs index 3fb843b6..c3df9c7c 100644 --- a/examples/multi-window/src/window.rs +++ b/examples/multi-window/src/window.rs @@ -6,7 +6,7 @@ use cosmic::{ iced_core::{id, Alignment, Length, Point}, iced_widget::{column, container, scrollable, text, text_input}, widget::{button, header_bar}, - ApplicationExt, Command, + ApplicationExt, Task, }; #[derive(Debug, Clone, PartialEq)] @@ -42,10 +42,10 @@ impl cosmic::Application for MultiWindow { &mut self.core } - fn init(core: Core, _input: Self::Flags) -> (Self, cosmic::app::Command) { + fn init(core: Core, _input: Self::Flags) -> (Self, cosmic::app::Task) { let windows = MultiWindow { windows: HashMap::from([( - window::Id::MAIN, + self.core.main_window_id().unwrap(), Window { input_id: id::Id::new("main"), input_value: String::new(), @@ -54,12 +54,12 @@ impl cosmic::Application for MultiWindow { core, }; - (windows, cosmic::app::Command::none()) + (windows, cosmic::app::Task::none()) } fn subscription(&self) -> cosmic::iced_futures::Subscription { - event::listen_with(|event, _| { - if let iced::Event::Window(id, window_event) = event { + event::listen_with(|event, _, id| { + if let iced::Event::Window(window_event) = event { match window_event { window::Event::CloseRequested => Some(Message::CloseWindow(id)), window::Event::Opened { position, .. } => { @@ -77,18 +77,18 @@ impl cosmic::Application for MultiWindow { fn update( &mut self, message: Self::Message, - ) -> iced::Command> { + ) -> iced::Task> { match message { Message::CloseWindow(id) => window::close(id), Message::WindowClosed(id) => { self.windows.remove(&id); - Command::none() + Task::none() } Message::WindowOpened(id, ..) => { if let Some(window) = self.windows.get(&id) { text_input::focus(window.input_id.clone()) } else { - Command::none() + Task::none() } } Message::NewWindow => { @@ -113,13 +113,13 @@ impl cosmic::Application for MultiWindow { spawn_window } Message::Input(id, value) => { - if let Some(w) = self.windows.get_mut(&window::Id::MAIN) { + if let Some(w) = self.windows.get_mut(&self.core.main_window_id().unwrap()) { if id == w.input_id { w.input_value = value; } } - Command::none() + Task::none() } } } @@ -142,17 +142,15 @@ impl cosmic::Application for MultiWindow { column![input, new_window_button] .spacing(50) .width(Length::Fill) - .align_items(Alignment::Center), + .align_x(Alignment::Center), ); - let window_content = container(container(content).width(200).center_x()) + let window_content = container(container(content).center_x(Length::Fixed(200.))) .style(cosmic::style::Container::Background) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y(); + .center_x(Length::Fill) + .center_y(Length::Fill); - if id == window::Id::MAIN { + if id == self.core.main_window_id().unwrap() { window_content.into() } else { column![header_bar().focused(focused), window_content].into() @@ -160,6 +158,6 @@ impl cosmic::Application for MultiWindow { } fn view(&self) -> cosmic::prelude::Element { - self.view_window(window::Id::MAIN) + self.view_window(self.core.main_window_id().unwrap()) } } diff --git a/examples/nav-context/src/main.rs b/examples/nav-context/src/main.rs index 9882284a..2c7b4950 100644 --- a/examples/nav-context/src/main.rs +++ b/examples/nav-context/src/main.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; -use cosmic::app::{Command, Core, Settings}; +use cosmic::app::{Core, Settings, Task}; use cosmic::iced_core::Size; use cosmic::widget::{menu, nav_bar}; use cosmic::{executor, iced, ApplicationExt, Element}; @@ -106,7 +106,7 @@ impl cosmic::Application for App { } /// Creates the application, and optionally emits command on initialize. - fn init(core: Core, input: Self::Flags) -> (Self, Command) { + fn init(core: Core, input: Self::Flags) -> (Self, Task) { let mut nav_model = nav_bar::Model::default(); for (title, content) in input { @@ -143,13 +143,13 @@ impl cosmic::Application for App { } /// Called when a navigation item is selected. - fn on_nav_select(&mut self, id: nav_bar::Id) -> Command { + fn on_nav_select(&mut self, id: nav_bar::Id) -> Task { self.nav_model.activate(id); self.update_title() } /// Handle application events here. - fn update(&mut self, message: Self::Message) -> Command { + fn update(&mut self, message: Self::Message) -> Task { match message { Message::NavMenuAction(message) => match message { NavMenuAction::Delete(id) => self.nav_model.remove(id), @@ -168,7 +168,7 @@ impl cosmic::Application for App { }, } - Command::none() + Task::none() } /// Creates a view after each update. @@ -200,7 +200,7 @@ where .unwrap_or("Unknown Page") } - fn update_title(&mut self) -> Command { + fn update_title(&mut self) -> Task { let header_title = self.active_page_title().to_owned(); let window_title = format!("{header_title} — COSMIC AppDemo"); self.set_header_title(header_title); diff --git a/examples/open-dialog/src/main.rs b/examples/open-dialog/src/main.rs index 5bd2a8bc..f462f0b5 100644 --- a/examples/open-dialog/src/main.rs +++ b/examples/open-dialog/src/main.rs @@ -4,7 +4,7 @@ //! An application which provides an open dialog use apply::Apply; -use cosmic::app::{Command, Core, Settings}; +use cosmic::app::{Task, Core, Settings}; use cosmic::dialog::file_chooser::{self, FileFilter}; use cosmic::iced_core::Length; use cosmic::widget::button; @@ -66,7 +66,7 @@ impl cosmic::Application for App { } /// Creates the application, and optionally emits command on initialize. - fn init(core: Core, _input: Self::Flags) -> (Self, Command) { + fn init(core: Core, _input: Self::Flags) -> (Self, Task) { let mut app = App { core, file_contents: String::new(), @@ -77,7 +77,7 @@ impl cosmic::Application for App { app.set_header_title("Open a file".into()); let cmd = app.set_window_title( "COSMIC OpenDialog Demo".into(), - cosmic::iced::window::Id::MAIN, + cosmic::iced::self.core.main_window_id().unwrap(), ); (app, cmd) @@ -88,7 +88,7 @@ impl cosmic::Application for App { vec![button::suggested("Open").on_press(Message::OpenFile).into()] } - fn update(&mut self, message: Self::Message) -> Command { + fn update(&mut self, message: Self::Message) -> Task { match message { Message::Cancelled => { eprintln!("open file dialog cancelled"); @@ -198,7 +198,7 @@ impl cosmic::Application for App { } } - Command::none() + Task::none() } fn view(&self) -> Element { diff --git a/examples/text-input/src/main.rs b/examples/text-input/src/main.rs index 51404269..252992b2 100644 --- a/examples/text-input/src/main.rs +++ b/examples/text-input/src/main.rs @@ -3,7 +3,7 @@ //! Application API example -use cosmic::app::{Command, Core, Settings}; +use cosmic::app::{Core, Settings, Task}; use cosmic::{executor, iced, ApplicationExt, Element}; /// Runs application with these settings @@ -55,7 +55,7 @@ impl cosmic::Application for App { } /// Creates the application, and optionally emits command on initialize. - fn init(core: Core, _input: Self::Flags) -> (Self, Command) { + fn init(core: Core, _input: Self::Flags) -> (Self, Task) { let mut app = App { core, editing: false, @@ -63,7 +63,7 @@ impl cosmic::Application for App { search_id: cosmic::widget::Id::unique(), }; - let commands = Command::batch(vec![ + let commands = Task::batch(vec![ cosmic::widget::text_input::focus(app.search_id.clone()), app.update_title(), ]); @@ -72,7 +72,7 @@ impl cosmic::Application for App { } /// Handle application events here. - fn update(&mut self, message: Self::Message) -> Command { + fn update(&mut self, message: Self::Message) -> Task { match message { Message::Input(text) => { self.input = text; @@ -83,7 +83,7 @@ impl cosmic::Application for App { } } - Command::none() + Task::none() } /// Creates a view after each update. @@ -115,7 +115,7 @@ impl App where Self: cosmic::Application, { - fn update_title(&mut self) -> Command { + fn update_title(&mut self) -> Task { let window_title = format!("COSMIC TextInputs Demo"); self.set_header_title(window_title.clone()); self.set_window_title(window_title) diff --git a/iced b/iced index 06199508..f2f9dfc6 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 061995084a5775b4fd7df63dda336be01ddf491c +Subproject commit f2f9dfc6c37e14c4f8ff5dafe70d72d3c40eb692 diff --git a/src/app/command.rs b/src/app/command.rs index 80682d11..5fd77fce 100644 --- a/src/app/command.rs +++ b/src/app/command.rs @@ -7,62 +7,88 @@ use iced::window; use super::Message; /// Commands for COSMIC applications. -pub type Command = iced::Command>; +pub type Task = iced::Task>; /// Creates a command which yields a [`crate::app::Message`]. -pub fn message(message: Message) -> Command { +pub fn message(message: Message) -> Task { crate::command::message(message) } /// Convenience methods for building message-based commands. pub mod message { /// Creates a command which yields an application message. - pub fn app(message: M) -> crate::app::Command { + pub fn app(message: M) -> crate::app::Task { super::message(super::Message::App(message)) } /// Creates a command which yields a cosmic message. - pub fn cosmic( - message: crate::app::cosmic::Message, - ) -> crate::app::Command { + pub fn cosmic(message: crate::app::cosmic::Message) -> crate::app::Task { super::message(super::Message::Cosmic(message)) } } -pub fn drag(id: Option) -> iced::Command> { - crate::command::drag(id).map(Message::Cosmic) +impl crate::app::Core { + pub fn drag(&self, id: Option) -> iced::Task> { + let Some(id) = id.or(self.main_window.get().cloned()) else { + return iced::Task::none(); + }; + crate::command::drag(id).map(Message::Cosmic) + } + + pub fn maximize( + &self, + id: Option, + maximized: bool, + ) -> iced::Task> { + let Some(id) = id.or(self.main_window.get().cloned()) else { + return iced::Task::none(); + }; + crate::command::maximize(id, maximized).map(Message::Cosmic) + } + + pub fn minimize(&self, id: Option) -> iced::Task> { + let Some(id) = id.or(self.main_window.get().cloned()) else { + return iced::Task::none(); + }; + crate::command::minimize(id).map(Message::Cosmic) + } + + pub fn set_scaling_factor(&self, factor: f32) -> iced::Task> { + message::cosmic(super::cosmic::Message::ScaleFactor(factor)) + } + + pub fn set_title( + &self, + id: Option, + title: String, + ) -> iced::Task> { + let Some(id) = id.or(self.main_window.get().cloned()) else { + return iced::Task::none(); + }; + crate::command::set_title(id, title).map(Message::Cosmic) + } + + pub fn set_windowed( + &self, + id: Option, + ) -> iced::Task> { + let Some(id) = id.or(self.main_window.get().cloned()) else { + return iced::Task::none(); + }; + crate::command::set_windowed(id).map(Message::Cosmic) + } + + pub fn toggle_maximize( + &self, + id: Option, + ) -> iced::Task> { + let Some(id) = id.or(self.main_window.get().cloned()) else { + return iced::Task::none(); + }; + crate::command::toggle_maximize(id).map(Message::Cosmic) + } } -pub fn maximize( - id: Option, - maximized: bool, -) -> iced::Command> { - crate::command::maximize(id, maximized).map(Message::Cosmic) -} - -pub fn minimize(id: Option) -> iced::Command> { - crate::command::minimize(id).map(Message::Cosmic) -} - -pub fn set_scaling_factor(factor: f32) -> iced::Command> { - message::cosmic(super::cosmic::Message::ScaleFactor(factor)) -} - -pub fn set_theme(theme: crate::Theme) -> iced::Command> { +pub fn set_theme(theme: crate::Theme) -> iced::Task> { message::cosmic(super::cosmic::Message::AppThemeChange(theme)) } - -pub fn set_title( - id: Option, - title: String, -) -> iced::Command> { - crate::command::set_title(id, title).map(Message::Cosmic) -} - -pub fn set_windowed(id: Option) -> iced::Command> { - crate::command::set_windowed(id).map(Message::Cosmic) -} - -pub fn toggle_maximize(id: Option) -> iced::Command> { - crate::command::toggle_maximize(id).map(Message::Cosmic) -} diff --git a/src/app/core.rs b/src/app/core.rs index eee5ae58..f1e63d72 100644 --- a/src/app/core.rs +++ b/src/app/core.rs @@ -1,7 +1,7 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -use std::collections::HashMap; +use std::{cell::OnceCell, collections::HashMap}; use crate::widget::nav_bar; use cosmic_config::CosmicConfigEntry; @@ -40,8 +40,8 @@ pub struct Window { pub show_close: bool, pub show_maximize: bool, pub show_minimize: bool, - height: u32, - width: u32, + height: f32, + width: f32, } /// COSMIC-specific application settings @@ -93,6 +93,10 @@ pub struct Core { #[cfg(feature = "dbus-config")] pub(crate) settings_daemon: Option>, + + pub(crate) main_window: OnceCell, + + pub(crate) exit_on_main_window_closed: bool, } impl Default for Core { @@ -135,10 +139,10 @@ impl Default for Core { show_maximize: true, show_minimize: true, show_window_menu: false, - height: 0, - width: 0, + height: 0., + width: 0., }, - focused_window: Some(window::Id::MAIN), + focused_window: None, #[cfg(feature = "applet")] applet: crate::applet::Context::default(), #[cfg(feature = "single-instance")] @@ -148,6 +152,8 @@ impl Default for Core { portal_is_dark: None, portal_accent: None, portal_is_high_contrast: None, + main_window: OnceCell::new(), + exit_on_main_window_closed: true, } } } @@ -297,12 +303,12 @@ impl Core { } /// Set the height of the main window. - pub(crate) fn set_window_height(&mut self, new_height: u32) { + pub(crate) fn set_window_height(&mut self, new_height: f32) { self.window.height = new_height; } /// Set the width of the main window. - pub(crate) fn set_window_width(&mut self, new_width: u32) { + pub(crate) fn set_window_width(&mut self, new_width: f32) { self.window.width = new_width; self.is_condensed_update(); } @@ -364,4 +370,12 @@ impl Core { self.portal_is_dark .unwrap_or(self.system_theme_mode.is_dark) } + + /// The [`Id`] of the main window + pub fn main_window_id(&self) -> Option { + self.main_window + .get() + .filter(|id| iced::window::Id::NONE != **id) + .cloned() + } } diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 8b437699..295a0050 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -1,32 +1,23 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 +use std::borrow::Borrow; use std::sync::Arc; -use super::{command, Application, ApplicationExt, Core, Subscription}; +use super::{Application, ApplicationExt, Core, Subscription}; use crate::config::CosmicTk; -use crate::theme::{self, Theme, ThemeType, THEME}; +use crate::theme::{Theme, ThemeType, THEME}; use crate::widget::nav_bar; use crate::{keyboard_nav, Element}; #[cfg(feature = "wayland")] use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use cosmic_theme::ThemeMode; #[cfg(feature = "wayland")] -use iced::event::wayland::{self, WindowEvent}; -#[cfg(feature = "wayland")] -use iced::event::PlatformSpecific; -#[cfg(all(feature = "winit", feature = "multi-window"))] -use iced::multi_window::Application as IcedApplication; -#[cfg(feature = "wayland")] -use iced::wayland::Application as IcedApplication; +use iced::event::wayland; #[cfg(not(any(feature = "multi-window", feature = "wayland")))] use iced::Application as IcedApplication; -use iced::{window, Command}; +use iced::{window, Task}; use iced_futures::event::listen_with; -#[cfg(not(feature = "wayland"))] -use iced_runtime::command::Action; -#[cfg(not(feature = "wayland"))] -use iced_runtime::window::Action as WindowAction; use palette::color_difference::EuclideanDistance; /// A message managed internally by COSMIC. @@ -65,7 +56,7 @@ pub enum Message { /// Updates the window maximized state WindowMaximized(window::Id, bool), /// Updates the tracked window geometry. - WindowResize(window::Id, u32, u32), + WindowResize(window::Id, f32, f32), /// Tracks updates to window state. #[cfg(feature = "wayland")] WindowState(window::Id, WindowState), @@ -86,7 +77,9 @@ pub enum Message { Unfocus(window::Id), /// Tracks updates to window suggested size. #[cfg(feature = "applet")] - Configure(cctk::sctk::shell::xdg::window::WindowConfigure), + SuggestedBounds(Option), + /// Window Created + MainWindowCreated(window::Id), } #[derive(Default)] @@ -94,109 +87,104 @@ pub struct Cosmic { pub app: App, } -impl IcedApplication for Cosmic +impl Cosmic where T::Message: Send + 'static, { - type Executor = T::Executor; - type Flags = (Core, T::Flags); - type Message = super::Message; - type Theme = Theme; - - fn new((mut core, flags): Self::Flags) -> (Self, iced::Command) { + pub fn init( + (mut core, flags, window_settings): (Core, T::Flags, iced::window::Settings), + ) -> (Self, iced::Task>) { #[cfg(feature = "dbus-config")] { use iced_futures::futures::executor::block_on; core.settings_daemon = block_on(cosmic_config::dbus::settings_daemon_proxy()).ok(); } - let (model, command) = T::init(core, flags); + let (model, mut command) = T::init(core, flags); (Self::new(model), command) } - #[cfg(not(any(feature = "multi-window", feature = "wayland")))] - fn title(&self) -> String { + #[cfg(not(feature = "multi-window"))] + pub fn title(&self) -> String { self.app.title().to_string() } - #[cfg(any(feature = "multi-window", feature = "wayland"))] - fn title(&self, id: window::Id) -> String { + #[cfg(feature = "multi-window")] + pub fn title(&self, id: window::Id) -> String { self.app.title(id).to_string() } - fn update(&mut self, message: Self::Message) -> iced::Command { + pub fn update( + &mut self, + message: super::Message, + ) -> iced::Task> { match message { super::Message::App(message) => self.app.update(message), super::Message::Cosmic(message) => self.cosmic_update(message), - super::Message::None => iced::Command::none(), + super::Message::None => iced::Task::none(), #[cfg(feature = "single-instance")] super::Message::DbusActivation(message) => self.app.dbus_activation(message), } } - #[cfg(not(any(feature = "multi-window", feature = "wayland")))] - fn scale_factor(&self) -> f64 { + #[cfg(not(feature = "multi-window"))] + pub fn scale_factor(&self) -> f64 { f64::from(self.app.core().scale_factor()) } - #[cfg(any(feature = "multi-window", feature = "wayland"))] - fn scale_factor(&self, _id: window::Id) -> f64 { + #[cfg(feature = "multi-window")] + pub fn scale_factor(&self, _id: window::Id) -> f64 { f64::from(self.app.core().scale_factor()) } - fn style(&self) -> ::Style { + 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 { - theme::Application::default() + let theme = THEME.lock().unwrap(); + crate::style::iced::application::appearance(theme.borrow()) } else { - theme::Application::Custom(Box::new(|theme| iced_style::application::Appearance { + let theme = THEME.lock().unwrap(); + iced_runtime::Appearance { background_color: iced_core::Color::TRANSPARENT, icon_color: theme.cosmic().on_bg_color().into(), text_color: theme.cosmic().on_bg_color().into(), - })) + } } } #[allow(clippy::too_many_lines)] - fn subscription(&self) -> Subscription { - let window_events = listen_with(|event, _| { + pub fn subscription(&self) -> Subscription> { + let window_events = listen_with(|event, _, id| { match event { - iced::Event::Window(id, window::Event::Resized { width, height }) => { + iced::Event::Window(window::Event::Resized(iced::Size { width, height })) => { return Some(Message::WindowResize(id, width, height)); } - iced::Event::Window(id, window::Event::Closed) => { + iced::Event::Window(window::Event::Closed) => { return Some(Message::SurfaceClosed(id)) } - iced::Event::Window(id, window::Event::Focused) => return Some(Message::Focus(id)), - iced::Event::Window(id, window::Event::Unfocused) => { - return Some(Message::Unfocus(id)) + iced::Event::Window(window::Event::Focused) => return Some(Message::Focus(id)), + iced::Event::Window(window::Event::Unfocused) => return Some(Message::Unfocus(id)), + iced::Event::Window(window::Event::Opened { .. }) => { + return Some(Message::MainWindowCreated(id)); } #[cfg(feature = "wayland")] - iced::Event::PlatformSpecific(PlatformSpecific::Wayland(event)) => match event { - wayland::Event::Window(WindowEvent::State(state), _surface, id) => { - return Some(Message::WindowState(id, state)); + iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => { + match event { + wayland::Event::Popup(wayland::PopupEvent::Done, _, id) + | wayland::Event::Layer(wayland::LayerEvent::Done, _, id) => { + return Some(Message::SurfaceClosed(id)); + } + #[cfg(feature = "applet")] + wayland::Event::Window( + iced::event::wayland::WindowEvent::SuggestedBounds(b), + ) => { + return Some(Message::SuggestedBounds(b)); + } + _ => (), } - wayland::Event::Window( - WindowEvent::WmCapabilities(capabilities), - _surface, - id, - ) => { - return Some(Message::WmCapabilities(id, capabilities)); - } - wayland::Event::Popup(wayland::PopupEvent::Done, _, id) - | wayland::Event::Layer(wayland::LayerEvent::Done, _, id) => { - return Some(Message::SurfaceClosed(id)); - } - #[cfg(feature = "applet")] - wayland::Event::Window(WindowEvent::Configure(conf), _surface, id) - if id == window::Id::MAIN => - { - return Some(Message::Configure(conf)); - } - _ => (), - }, + } _ => (), } @@ -275,19 +263,24 @@ where Subscription::batch(subscriptions) } - #[cfg(not(any(feature = "multi-window", feature = "wayland")))] - fn theme(&self) -> Self::Theme { + #[cfg(not(feature = "multi-window"))] + pub fn theme(&self) -> Theme { crate::theme::active() } - #[cfg(any(feature = "multi-window", feature = "wayland"))] - fn theme(&self, _id: window::Id) -> Self::Theme { + #[cfg(feature = "multi-window")] + pub fn theme(&self, _id: window::Id) -> Theme { crate::theme::active() } - #[cfg(any(feature = "multi-window", feature = "wayland"))] - fn view(&self, id: window::Id) -> Element { - if id != self.app.main_window_id() { + #[cfg(feature = "multi-window")] + pub fn view(&self, id: window::Id) -> Element> { + if !self + .app + .core() + .main_window_id() + .is_some_and(|main_id| main_id == id) + { return self.app.view_window(id).map(super::Message::App); } @@ -298,37 +291,43 @@ where } } - #[cfg(not(any(feature = "multi-window", feature = "wayland")))] - fn view(&self) -> Element { + #[cfg(not(feature = "multi-window"))] + pub fn view(&self) -> Element> { self.app.view_main() } } impl Cosmic { - #[cfg(feature = "wayland")] - pub fn close(&mut self) -> iced::Command> { - iced_sctk::commands::window::close_window(self.app.main_window_id()) - } - - #[cfg(not(feature = "wayland"))] #[allow(clippy::unused_self)] - pub fn close(&mut self) -> iced::Command> { - iced::Command::single(Action::Window(WindowAction::Close( - self.app.main_window_id(), - ))) + pub fn close(&mut self) -> iced::Task> { + if let Some(id) = self.app.core().main_window_id() { + iced::window::close(id) + } else { + iced::Task::none() + } } #[allow(clippy::too_many_lines)] - fn cosmic_update(&mut self, message: Message) -> iced::Command> { + fn cosmic_update(&mut self, message: Message) -> iced::Task> { match message { Message::WindowMaximized(id, maximized) => { - if self.app.main_window_id() == id { + if self + .app + .core() + .main_window_id() + .is_some_and(|main_id| main_id == id) + { self.app.core_mut().window.sharp_corners = maximized; } } Message::WindowResize(id, width, height) => { - if self.app.main_window_id() == id { + if self + .app + .core() + .main_window_id() + .is_some_and(|main_id| main_id == id) + { self.app.core_mut().set_window_width(width); self.app.core_mut().set_window_height(height); } @@ -336,15 +335,19 @@ impl Cosmic { self.app.on_window_resize(id, width, height); //TODO: more efficient test of maximized (winit has no event for maximize if set by the OS) - #[cfg(not(feature = "wayland"))] - return iced::window::fetch_maximized(id, move |maximized| { + return iced::window::get_maximized(id).map(move |maximized| { super::Message::Cosmic(Message::WindowMaximized(id, maximized)) }); } #[cfg(feature = "wayland")] Message::WindowState(id, state) => { - if self.app.main_window_id() == id { + if self + .app + .core() + .main_window_id() + .is_some_and(|main_id| main_id == id) + { self.app.core_mut().window.sharp_corners = state.intersects( WindowState::MAXIMIZED | WindowState::FULLSCREEN @@ -359,7 +362,12 @@ impl Cosmic { #[cfg(feature = "wayland")] Message::WmCapabilities(id, capabilities) => { - if self.app.main_window_id() == id { + if self + .app + .core() + .main_window_id() + .is_some_and(|main_id| main_id == id) + { self.app.core_mut().window.show_maximize = capabilities.contains(WindowManagerCapabilities::MAXIMIZE); self.app.core_mut().window.show_minimize = @@ -379,9 +387,7 @@ impl Cosmic { keyboard_nav::Message::Escape => return self.app.on_escape(), keyboard_nav::Message::Search => return self.app.on_search(), - keyboard_nav::Message::Fullscreen => { - return command::toggle_maximize(Some(self.app.main_window_id())) - } + keyboard_nav::Message::Fullscreen => return self.app.core().toggle_maximize(None), }, Message::ContextDrawer(show) => { @@ -389,11 +395,11 @@ impl Cosmic { return self.app.on_context_drawer(); } - Message::Drag => return command::drag(Some(self.app.main_window_id())), + Message::Drag => return self.app.core().drag(None), - Message::Minimize => return command::minimize(Some(self.app.main_window_id())), + Message::Minimize => return self.app.core().minimize(None), - Message::Maximize => return command::toggle_maximize(Some(self.app.main_window_id())), + Message::Maximize => return self.app.core().toggle_maximize(None), Message::NavBar(key) => { self.app.core_mut().nav_bar_set_toggled_condensed(false); @@ -433,7 +439,7 @@ impl Cosmic { let cur_is_dark = THEME.lock().unwrap().theme_type.is_dark(); // Ignore updates if the current theme mode does not match. if cur_is_dark != theme.cosmic().is_dark { - return iced::Command::none(); + return iced::Task::none(); } let cmd = self.app.system_theme_update(&keys, theme.cosmic()); // Record the last-known system theme in event that the current theme is custom. @@ -479,7 +485,7 @@ impl Cosmic { } Message::SystemThemeModeChange(keys, mode) => { if !keys.contains(&"is_dark") { - return iced::Command::none(); + return iced::Task::none(); } if match THEME.lock().unwrap().theme_type { ThemeType::System { @@ -488,7 +494,7 @@ impl Cosmic { } => prefer_dark.is_some(), _ => false, } { - return iced::Command::none(); + return iced::Task::none(); } let mut cmds = vec![self.app.system_theme_mode_update(&keys, &mode)]; @@ -527,23 +533,37 @@ impl Cosmic { } } } - return Command::batch(cmds); + return Task::batch(cmds); } - Message::Activate(_token) => { + Message::Activate(_token) => + { #[cfg(feature = "wayland")] - return iced_sctk::commands::activation::activate( - self.app.main_window_id(), - #[allow(clippy::used_underscore_binding)] - _token, - ); - } - Message::SurfaceClosed(id) => { - if let Some(msg) = self.app.on_close_requested(id) { - return self.app.update(msg); + if let Some(id) = self.app.core().main_window_id() { + return iced_winit::platform_specific::commands::activation::activate( + id, + #[allow(clippy::used_underscore_binding)] + _token, + ); } } + Message::SurfaceClosed(id) => { + let mut ret = if let Some(msg) = self.app.on_close_requested(id) { + self.app.update(msg) + } else { + Task::none() + }; + let core = self.app.core(); + 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::>()]); + } + return ret; + } Message::ShowWindowMenu => { - return window::show_window_menu(window::Id::MAIN); + if let Some(id) = self.app.core().main_window_id() { + return iced::window::show_system_menu(id); + } } #[cfg(feature = "xdg-portal")] Message::DesktopSettings(crate::theme::portal::Desktop::ColorScheme(s)) => { @@ -555,7 +575,7 @@ impl Cosmic { } => prefer_dark.is_some(), _ => false, } { - return iced::Command::none(); + return iced::Task::none(); } let is_dark = match s { ColorScheme::NoPreference => None, @@ -595,7 +615,7 @@ impl Cosmic { if cur_accent.distance_squared(*c) < 0.00001 { // skip calculations if we already have the same color - return iced::Command::none(); + return iced::Task::none(); } { @@ -640,19 +660,20 @@ impl Cosmic { core.focused_window = None; } } - #[cfg(feature = "applet")] - Message::Configure(configure) => { - if let Some(w) = configure.new_size.0 { - self.app.core_mut().set_window_width(w.get()); - } - if let Some(h) = configure.new_size.1 { - self.app.core_mut().set_window_height(h.get()); - } - self.app.core_mut().applet.configure = Some(configure); + Message::MainWindowCreated(id) => { + let core = self.app.core_mut(); + _ = core.main_window.set(id); } + #[cfg(feature = "applet")] + Message::SuggestedBounds(b) => { + tracing::info!("Suggested bounds: {b:?}"); + let core = self.app.core_mut(); + core.applet.suggested_bounds = b; + } + _ => {} } - iced::Command::none() + iced::Task::none() } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 23f9ff2c..5d46d54a 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -9,6 +9,8 @@ pub mod command; mod core; pub mod cosmic; +#[cfg(all(feature = "winit", feature = "multi-window"))] +pub(crate) mod multi_window; pub mod settings; pub mod message { @@ -47,17 +49,14 @@ pub mod message { use std::borrow::Cow; -pub use self::command::Command; +pub use self::command::Task; pub use self::core::Core; pub use self::settings::Settings; use crate::prelude::*; use crate::theme::THEME; use crate::widget::{context_drawer, horizontal_space, id_container, menu, nav_bar, popover}; use apply::Apply; -#[cfg(all(feature = "winit", feature = "multi-window"))] -use iced::{multi_window::Application as IcedApplication, window}; -#[cfg(any(not(feature = "winit"), not(feature = "multi-window")))] -use iced::{window, Application as IcedApplication}; +use iced::window; use iced::{Length, Subscription}; pub use message::Message; use url::Url; @@ -73,15 +72,15 @@ use { pub(crate) fn iced_settings( settings: Settings, flags: App::Flags, -) -> iced::Settings<(Core, App::Flags)> { +) -> (iced::Settings, (Core, App::Flags, iced::window::Settings)) { preload_fonts(); let mut core = Core::default(); core.debug = settings.debug; core.icon_theme_override = settings.default_icon_theme.is_some(); core.set_scale_factor(settings.scale_factor); - core.set_window_width(settings.size.width as u32); - core.set_window_height(settings.size.height as u32); + core.set_window_width(settings.size.width); + core.set_window_height(settings.size.height); if let Some(icon_theme) = settings.default_icon_theme { crate::icon_theme::set_default(icon_theme); @@ -91,65 +90,40 @@ pub(crate) fn iced_settings( THEME.lock().unwrap().set_theme(settings.theme.theme_type); - let mut iced = iced::Settings::with_flags((core, flags)); + if settings.no_main_window { + core.main_window.set(iced::window::Id::NONE).unwrap(); + } + + let mut iced = iced::Settings::default(); iced.antialiasing = settings.antialiasing; iced.default_font = settings.default_font; iced.default_text_size = iced::Pixels(settings.default_text_size); - iced.exit_on_close_request = settings.exit_on_close; - #[cfg(not(feature = "wayland"))] - { - let exit_on_close = settings.exit_on_close; - iced.window.exit_on_close_request = exit_on_close; - } + let exit_on_close = settings.exit_on_close; + iced.exit_on_close_request = exit_on_close; + let mut window_settings = iced::window::Settings::default(); + window_settings.exit_on_close_request = exit_on_close; iced.id = Some(App::APP_ID.to_owned()); - #[cfg(all(not(feature = "wayland"), target_os = "linux"))] - { - iced.window.platform_specific.application_id = App::APP_ID.to_string(); + window_settings.platform_specific.application_id = App::APP_ID.to_string(); + core.exit_on_main_window_closed = exit_on_close; + + if let Some(border_size) = settings.resizable { + window_settings.resize_border = border_size as u32; + window_settings.resizable = true; + } + window_settings.decorations = !settings.client_decorations; + window_settings.size = settings.size; + let min_size = settings.size_limits.min(); + if min_size != iced::Size::ZERO { + window_settings.min_size = Some(min_size); + } + let max_size = settings.size_limits.max(); + if max_size != iced::Size::INFINITY { + window_settings.max_size = Some(max_size); } - #[cfg(feature = "wayland")] - { - use iced::wayland::actions::window::SctkWindowSettings; - use iced_sctk::settings::InitialSurface; - iced.initial_surface = if settings.no_main_window { - InitialSurface::None - } else { - InitialSurface::XdgWindow(SctkWindowSettings { - app_id: Some(App::APP_ID.to_owned()), - autosize: settings.autosize, - client_decorations: settings.client_decorations, - resizable: settings.resizable, - size: (settings.size.width as u32, settings.size.height as u32).into(), - size_limits: settings.size_limits, - title: None, - transparent: settings.transparent, - xdg_activation_token: std::env::var("XDG_ACTIVATION_TOKEN").ok(), - ..SctkWindowSettings::default() - }) - }; - } - - #[cfg(not(feature = "wayland"))] - { - if let Some(border_size) = settings.resizable { - iced.window.resize_border = border_size as u32; - iced.window.resizable = true; - } - iced.window.decorations = !settings.client_decorations; - iced.window.size = settings.size; - let min_size = settings.size_limits.min(); - if min_size != iced::Size::ZERO { - iced.window.min_size = Some(min_size); - } - let max_size = settings.size_limits.max(); - if max_size != iced::Size::INFINITY { - iced.window.max_size = Some(max_size); - } - iced.window.transparent = settings.transparent; - } - - iced + window_settings.transparent = settings.transparent; + (iced, (core, flags, window_settings)) } /// Launch a COSMIC application with the given [`Settings`]. @@ -158,9 +132,40 @@ pub(crate) fn iced_settings( /// /// Returns error on application failure. pub fn run(settings: Settings, flags: App::Flags) -> iced::Result { - let settings = iced_settings::(settings, flags); - - cosmic::Cosmic::::run(settings) + let default_font = settings.default_font; + let (settings, flags) = iced_settings::(settings, flags); + #[cfg(not(feature = "multi-window"))] + { + iced::application( + cosmic::Cosmic::title, + cosmic::Cosmic::update, + cosmic::Cosmic::view, + ) + .subscription(cosmic::Cosmic::subscription) + .style(cosmic::Cosmic::style) + .theme(cosmic::Cosmic::theme) + .window_size((500.0, 800.0)) + .settings(settings) + .window(flags.2.clone()) + .run_with(move || cosmic::Cosmic::::init(flags)) + } + #[cfg(feature = "multi-window")] + { + let mut app = multi_window::multi_window( + cosmic::Cosmic::title, + cosmic::Cosmic::update, + cosmic::Cosmic::view, + ); + if flags.0.main_window.get().is_none() { + app = app.window(flags.2.clone()); + _ = flags.0.main_window.set(iced_core::window::Id::RESERVED); + } + app.subscription(cosmic::Cosmic::subscription) + .style(cosmic::Cosmic::style) + .theme(cosmic::Cosmic::theme) + .settings(settings) + .run_with(move || cosmic::Cosmic::::init(flags)) + } } #[cfg(feature = "single-instance")] @@ -362,9 +367,41 @@ where tracing::info!("Another instance is running"); Ok(()) } else { - let mut settings = iced_settings::(settings, flags); - settings.flags.0.single_instance = true; - cosmic::Cosmic::::run(settings) + let (settings, mut flags) = iced_settings::(settings, flags); + flags.0.single_instance = true; + + #[cfg(not(feature = "multi-window"))] + { + iced::application( + cosmic::Cosmic::title, + cosmic::Cosmic::update, + cosmic::Cosmic::view, + ) + .subscription(cosmic::Cosmic::subscription) + .style(cosmic::Cosmic::style) + .theme(cosmic::Cosmic::theme) + .window_size((500.0, 800.0)) + .settings(settings) + .window(flags.2.clone()) + .run_with(move || cosmic::Cosmic::::init(flags)) + } + #[cfg(feature = "multi-window")] + { + let mut app = multi_window::multi_window( + cosmic::Cosmic::title, + cosmic::Cosmic::update, + cosmic::Cosmic::view, + ); + if flags.0.main_window.get().is_none() { + app = app.window(flags.2.clone()); + _ = flags.0.main_window.set(iced_core::window::Id::RESERVED); + } + app.subscription(cosmic::Cosmic::subscription) + .style(cosmic::Cosmic::style) + .theme(cosmic::Cosmic::theme) + .settings(settings) + .run_with(move || cosmic::Cosmic::::init(flags)) + } } } @@ -409,7 +446,7 @@ where fn core_mut(&mut self) -> &mut Core; /// Creates the application, and optionally emits command on initialize. - fn init(core: Core, flags: Self::Flags) -> (Self, Command); + fn init(core: Core, flags: Self::Flags) -> (Self, Task); /// Displays a context drawer on the side of the application window when `Some`. fn context_drawer(&self) -> Option> { @@ -441,11 +478,6 @@ where Vec::new() } - /// Get the main [`window::Id`], which is [`window::Id::MAIN`] by default - fn main_window_id(&self) -> window::Id { - window::Id::MAIN - } - /// Allows overriding the default nav bar widget. fn nav_bar(&self) -> Option>> { if !self.core().nav_bar_active() { @@ -491,32 +523,32 @@ where } // Called when context drawer is toggled - fn on_context_drawer(&mut self) -> Command { - Command::none() + fn on_context_drawer(&mut self) -> Task { + Task::none() } /// Called when the escape key is pressed. - fn on_escape(&mut self) -> Command { - Command::none() + fn on_escape(&mut self) -> Task { + Task::none() } /// Called when a navigation item is selected. - fn on_nav_select(&mut self, id: nav_bar::Id) -> Command { - Command::none() + fn on_nav_select(&mut self, id: nav_bar::Id) -> Task { + Task::none() } /// Called when a context menu is requested for a navigation item. - fn on_nav_context(&mut self, id: nav_bar::Id) -> Command { - Command::none() + fn on_nav_context(&mut self, id: nav_bar::Id) -> Task { + Task::none() } /// Called when the search function is requested. - fn on_search(&mut self) -> Command { - Command::none() + fn on_search(&mut self) -> Task { + Task::none() } /// Called when a window is resized. - fn on_window_resize(&mut self, id: window::Id, width: u32, height: u32) {} + fn on_window_resize(&mut self, id: window::Id, width: f32, height: f32) {} /// Event sources that are to be listened to. fn subscription(&self) -> Subscription { @@ -524,8 +556,8 @@ where } /// Respond to an application-specific message. - fn update(&mut self, message: Self::Message) -> Command { - Command::none() + fn update(&mut self, message: Self::Message) -> Task { + Task::none() } /// Respond to a system theme change @@ -533,8 +565,8 @@ where &mut self, keys: &[&'static str], new_theme: &cosmic_theme::Theme, - ) -> Command { - Command::none() + ) -> Task { + Task::none() } /// Respond to a system theme mode change @@ -542,8 +574,8 @@ where &mut self, keys: &[&'static str], new_theme: &cosmic_theme::ThemeMode, - ) -> Command { - Command::none() + ) -> Task { + Task::none() } /// Constructs the view for the main window. @@ -555,33 +587,33 @@ where } /// Overrides the default style for applications - fn style(&self) -> Option<::Style> { + fn style(&self) -> Option { None } /// Handles dbus activation messages #[cfg(feature = "single-instance")] - fn dbus_activation(&mut self, msg: DbusActivationMessage) -> Command { - Command::none() + fn dbus_activation(&mut self, msg: DbusActivationMessage) -> Task { + Task::none() } } /// Methods automatically derived for all types implementing [`Application`]. pub trait ApplicationExt: Application { /// Initiates a window drag. - fn drag(&mut self) -> Command; + fn drag(&mut self) -> Task; /// Maximizes the window. - fn maximize(&mut self) -> Command; + fn maximize(&mut self) -> Task; /// Minimizes the window. - fn minimize(&mut self) -> Command; + fn minimize(&mut self) -> Task; /// Get the title of the main window. - #[cfg(not(any(feature = "multi-window", feature = "wayland")))] + #[cfg(not(feature = "multi-window"))] fn title(&self) -> &str; - #[cfg(any(feature = "multi-window", feature = "wayland"))] + #[cfg(feature = "multi-window")] /// Get the title of a window. fn title(&self, id: window::Id) -> &str; @@ -600,56 +632,58 @@ pub trait ApplicationExt: Application { self.core_mut().set_header_title(title); } - #[cfg(not(any(feature = "multi-window", feature = "wayland")))] + #[cfg(not(feature = "multi-window"))] /// Set the title of the main window. - fn set_window_title(&mut self, title: String) -> Command; + fn set_window_title(&mut self, title: String) -> Task; - #[cfg(any(feature = "multi-window", feature = "wayland"))] + #[cfg(feature = "multi-window")] /// Set the title of a window. - fn set_window_title(&mut self, title: String, id: window::Id) -> Command; + fn set_window_title(&mut self, title: String, id: window::Id) -> Task; /// View template for the main window. fn view_main(&self) -> Element>; } impl ApplicationExt for App { - fn drag(&mut self) -> Command { - command::drag(Some(self.main_window_id())) + fn drag(&mut self) -> Task { + self.core().drag(None) } - fn maximize(&mut self) -> Command { - command::maximize(Some(self.main_window_id()), true) + fn maximize(&mut self) -> Task { + self.core().maximize(None, true) } - fn minimize(&mut self) -> Command { - command::minimize(Some(self.main_window_id())) + fn minimize(&mut self) -> Task { + self.core().minimize(None) } - #[cfg(any(feature = "multi-window", feature = "wayland"))] + #[cfg(feature = "multi-window")] fn title(&self, id: window::Id) -> &str { self.core().title.get(&id).map_or("", |s| s.as_str()) } - #[cfg(not(any(feature = "multi-window", feature = "wayland")))] + #[cfg(not(feature = "multi-window"))] fn title(&self) -> &str { self.core() - .title - .get(&self.main_window_id()) - .map_or("", |s| s.as_str()) + .main_window_id() + .and_then(|id| self.core().title.get(&id).map(std::string::String::as_str)) + .unwrap_or("") } - #[cfg(any(feature = "multi-window", feature = "wayland"))] - fn set_window_title(&mut self, title: String, id: window::Id) -> Command { + #[cfg(feature = "multi-window")] + fn set_window_title(&mut self, title: String, id: window::Id) -> Task { self.core_mut().title.insert(id, title.clone()); - command::set_title(Some(id), title) + self.core().set_title(Some(id), title) } - #[cfg(not(any(feature = "multi-window", feature = "wayland")))] - fn set_window_title(&mut self, title: String) -> Command { - let id = self.main_window_id(); + #[cfg(not(feature = "multi-window"))] + fn set_window_title(&mut self, title: String) -> Task { + let Some(id) = self.core().main_window_id() else { + return Task::none(); + }; self.core_mut().title.insert(id, title.clone()); - Command::none() + Task::none() } #[allow(clippy::too_many_lines)] @@ -659,7 +693,7 @@ impl ApplicationExt for App { let is_condensed = core.is_condensed(); let focused = core .focused_window() - .is_some_and(|i| i == self.main_window_id()); + .is_some_and(|i| Some(i) == self.core().main_window_id()); let content_row = crate::widget::row::with_children({ let mut widgets = Vec::with_capacity(4); @@ -678,7 +712,7 @@ impl ApplicationExt for App { if self.nav_model().is_none() || core.show_content() { // Manual spacing must be used due to state workarounds below if has_nav { - widgets.push(horizontal_space(Length::Fixed(8.0)).into()); + widgets.push(horizontal_space().width(Length::Fixed(8.0)).into()); } let main_content = self.view().map(Message::App); @@ -727,7 +761,7 @@ impl ApplicationExt for App { ); } else { //TODO: this element is added to workaround state issues - widgets.push(horizontal_space(Length::Shrink).into()); + widgets.push(horizontal_space().width(Length::Shrink).into()); } } } @@ -744,7 +778,7 @@ impl ApplicationExt for App { .padding([0, 8, 8, 8]) .width(iced::Length::Fill) .height(iced::Length::Fill) - .style(crate::theme::Container::WindowBackground) + .class(crate::theme::Container::WindowBackground) .apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_content_container"))) .into() } else { @@ -770,7 +804,7 @@ impl ApplicationExt for App { } else { Message::Cosmic(cosmic::Message::ToggleNavBar) }) - .style(crate::theme::Button::HeaderBar); + .class(crate::theme::Button::HeaderBar); header = header.start(toggle); } @@ -825,11 +859,9 @@ impl ApplicationExt for App { #[cfg(feature = "single-instance")] fn single_instance_subscription() -> Subscription> { use iced_futures::futures::StreamExt; - - iced::subscription::channel( + iced_futures::Subscription::run_with_id( TypeId::of::(), - 10, - move |mut output| async move { + iced::stream::channel(10, move |mut output| async move { let mut single_instance: DbusActivation = DbusActivation::new(); let mut rx = single_instance.rx(); if let Ok(builder) = zbus::ConnectionBuilder::session() { @@ -888,7 +920,7 @@ fn single_instance_subscription() -> Subscription { + update: Update, + view: View, + _state: PhantomData, + _message: PhantomData, + _theme: PhantomData, + _renderer: PhantomData, +} + +/// Creates an iced [`MultiWindow`] given its title, update, and view logic. +pub fn multi_window( + title: impl Title, + update: impl application::Update, + view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>, +) -> MultiWindow> +where + State: 'static, + Message: Send + std::fmt::Debug + 'static, + Theme: Default + DefaultStyle, + Renderer: program::Renderer, +{ + use std::marker::PhantomData; + + impl Program + for Instance + where + Message: Send + std::fmt::Debug + 'static, + Theme: Default + DefaultStyle, + Renderer: program::Renderer, + Update: application::Update, + View: for<'a> self::View<'a, State, Message, Theme, Renderer>, + { + type State = State; + type Message = Message; + type Theme = Theme; + type Renderer = Renderer; + type Executor = iced_futures::backend::default::Executor; + + fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { + self.update.update(state, message).into() + } + + fn view<'a>( + &self, + state: &'a Self::State, + window: window::Id, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.view.view(state, window).into() + } + } + + MultiWindow { + raw: Instance { + update, + view, + _state: PhantomData, + _message: PhantomData, + _theme: PhantomData, + _renderer: PhantomData, + }, + settings: Settings::default(), + window: None, + } + .title(title) +} + +/// The underlying definition and configuration of an iced daemon. +/// +/// You can use this API to create and run iced applications +/// step by step—without coupling your logic to a trait +/// or a specific type. +/// +/// You can create a [`MultiWindow`] with the [`daemon`] helper. +#[derive(Debug)] +pub struct MultiWindow { + raw: P, + settings: Settings, + window: Option, +} + +impl MultiWindow

{ + #[cfg(any(feature = "winit", feature = "wayland"))] + /// Runs the [`MultiWindow`]. + /// + /// The state of the [`MultiWindow`] must implement [`Default`]. + /// If your state does not implement [`Default`], use [`run_with`] + /// instead. + /// + /// [`run_with`]: Self::run_with + pub fn run(self) -> Result + where + Self: 'static, + P::State: Default, + { + self.raw.run(self.settings, self.window) + } + + #[cfg(any(feature = "winit", feature = "wayland"))] + /// Runs the [`MultiWindow`] with a closure that creates the initial state. + pub fn run_with(self, initialize: I) -> Result + where + Self: 'static, + I: FnOnce() -> (P::State, Task) + 'static, + { + self.raw.run_with(self.settings, self.window, initialize) + } + + /// Sets the [`Settings`] that will be used to run the [`MultiWindow`]. + pub fn settings(self, settings: Settings) -> Self { + Self { settings, ..self } + } + + /// Sets the [`Title`] of the [`MultiWindow`]. + pub(crate) fn title( + self, + title: impl Title, + ) -> MultiWindow> { + MultiWindow { + raw: with_title(self.raw, move |state, window| title.title(state, window)), + settings: self.settings, + window: self.window, + } + } + + /// Sets the subscription logic of the [`MultiWindow`]. + pub fn subscription( + self, + f: impl Fn(&P::State) -> Subscription, + ) -> MultiWindow> { + MultiWindow { + raw: with_subscription(self.raw, f), + settings: self.settings, + window: self.window, + } + } + + /// Sets the theme logic of the [`MultiWindow`]. + pub fn theme( + self, + f: impl Fn(&P::State, window::Id) -> P::Theme, + ) -> MultiWindow> { + MultiWindow { + raw: with_theme(self.raw, f), + settings: self.settings, + window: self.window, + } + } + + /// Sets the style logic of the [`MultiWindow`]. + pub fn style( + self, + f: impl Fn(&P::State, &P::Theme) -> Appearance, + ) -> MultiWindow> { + MultiWindow { + raw: with_style(self.raw, f), + settings: self.settings, + window: self.window, + } + } + + /// Sets the window settings of the [`MultiWindow`]. + pub fn window(self, window: window::Settings) -> Self { + Self { + raw: self.raw, + settings: self.settings, + window: Some(window), + } + } +} + +/// The title logic of some [`MultiWindow`]. +/// +/// This trait is implemented both for `&static str` and +/// any closure `Fn(&State, window::Id) -> String`. +/// +/// This trait allows the [`daemon`] builder to take any of them. +pub trait Title { + /// Produces the title of the [`MultiWindow`]. + fn title(&self, state: &State, window: window::Id) -> String; +} + +impl Title for &'static str { + fn title(&self, _state: &State, _window: window::Id) -> String { + (*self).to_string() + } +} + +impl Title for T +where + T: Fn(&State, window::Id) -> String, +{ + fn title(&self, state: &State, window: window::Id) -> String { + self(state, window) + } +} + +/// The view logic of some [`MultiWindow`]. +/// +/// This trait allows the [`daemon`] builder to take any closure that +/// returns any `Into>`. +pub trait View<'a, State, Message, Theme, Renderer> { + /// Produces the widget of the [`MultiWindow`]. + fn view( + &self, + state: &'a State, + window: window::Id, + ) -> impl Into>; +} + +impl<'a, T, State, Message, Theme, Renderer, Widget> View<'a, State, Message, Theme, Renderer> for T +where + T: Fn(&'a State, window::Id) -> Widget, + State: 'static, + Widget: Into>, +{ + fn view( + &self, + state: &'a State, + window: window::Id, + ) -> impl Into> { + self(state, window) + } +} diff --git a/src/app/settings.rs b/src/app/settings.rs index af976d96..338206e1 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -20,7 +20,6 @@ pub struct Settings { pub(crate) autosize: bool, /// Set the application to not create a main window - #[cfg(feature = "wayland")] pub(crate) no_main_window: bool, /// Whether the window should have a border, a title bar, etc. or not. @@ -77,7 +76,6 @@ impl Default for Settings { antialiasing: true, #[cfg(feature = "wayland")] autosize: false, - #[cfg(feature = "wayland")] no_main_window: false, client_decorations: true, debug: false, diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 2ee82300..4a6db2c4 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -2,7 +2,7 @@ pub mod token; use crate::{ - app::Core, + app::{self, iced_settings, Core}, cctk::sctk, iced::{ self, @@ -10,9 +10,13 @@ use crate::{ widget::Container, window, Color, Length, Limits, Rectangle, }, - iced_style, iced_widget, + iced_widget, theme::{self, system_dark, system_light, Button, THEME}, - widget::{self, layer_container}, + widget::{ + self, + autosize::{autosize, Autosize}, + layer_container, + }, Application, Element, Renderer, }; use cctk::sctk::shell::xdg::window::WindowConfigure; @@ -21,14 +25,16 @@ use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize}; use cosmic_theme::Theme; use iced::Pixels; use iced_core::{Padding, Shadow}; -use iced_style::container::Appearance; -use iced_widget::runtime::command::platform_specific::wayland::popup::{ - SctkPopupSettings, SctkPositioner, -}; +use iced_widget::runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner}; use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity}; -use std::{borrow::Cow, num::NonZeroU32, rc::Rc}; +use std::{borrow::Cow, num::NonZeroU32, rc::Rc, sync::LazyLock}; +use tracing::info; use crate::app::cosmic; +static AUTOSIZE_ID: LazyLock = + LazyLock::new(|| iced::id::Id::new("cosmic-applet-autosize")); +static AUTOSIZE_MAIN_ID: LazyLock = + LazyLock::new(|| iced::id::Id::new("cosmic-applet-autosize-main")); #[derive(Debug, Clone)] pub struct Context { @@ -37,9 +43,9 @@ pub struct Context { pub background: CosmicPanelBackground, pub output_name: String, pub panel_type: PanelType, - /// Includes the suggested size of the window. + /// Includes the configured size of the window. /// This can be used by apples to handle overflow themselves. - pub configure: Option, + pub suggested_bounds: Option<(iced::Size)>, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -94,7 +100,7 @@ impl Default for Context { .unwrap_or(CosmicPanelBackground::ThemeDefault), output_name: std::env::var("COSMIC_PANEL_OUTPUT").unwrap_or_default(), panel_type: PanelType::from(std::env::var("COSMIC_PANEL_NAME").unwrap_or_default()), - configure: None, + suggested_bounds: None, } } } @@ -116,19 +122,21 @@ impl Context { let suggested = self.suggested_size(true); let applet_padding = self.suggested_padding(true); let configured_width = self - .configure + .suggested_bounds .as_ref() - .and_then(|c| c.new_size.0.map(|w| w)) + .and_then(|c| NonZeroU32::new(c.width as u32)) // TODO: should this be physical size instead of logical? .unwrap_or_else(|| { NonZeroU32::new(suggested.0 as u32 + applet_padding as u32 * 2).unwrap() }); + let configured_height = self - .configure + .suggested_bounds .as_ref() - .and_then(|c| c.new_size.1.map(|h| h)) + .and_then(|c| NonZeroU32::new(c.height as u32)) .unwrap_or_else(|| { NonZeroU32::new(suggested.1 as u32 + applet_padding as u32 * 2).unwrap() }); + info!("{configured_height:?}"); (configured_width, configured_height) } @@ -149,18 +157,15 @@ impl Context { #[allow(clippy::cast_precision_loss)] pub fn window_settings(&self) -> crate::app::Settings { let (width, height) = self.suggested_size(true); - let width = f32::from(width); - let height = f32::from(height); let applet_padding = self.suggested_padding(true); + let width = f32::from(width) + applet_padding as f32 * 2.; + let height = f32::from(height) + applet_padding as f32 * 2.; let mut settings = crate::app::Settings::default() - .size(iced_core::Size::new( - width + applet_padding as f32 * 2., - height + applet_padding as f32 * 2., - )) + .size(iced_core::Size::new(width, height)) .size_limits( Limits::NONE - .min_height(height as f32 + applet_padding as f32 * 2.0) - .min_width(width as f32 + applet_padding as f32 * 2.0), + .min_height(height as f32) + .min_width(width as f32), ) .resizable(None) .default_text_size(14.0) @@ -169,6 +174,7 @@ impl Context { if let Some(theme) = self.theme() { settings = settings.theme(theme); } + settings.exit_on_close = true; settings } @@ -182,26 +188,18 @@ impl Context { &self, icon: widget::icon::Handle, ) -> crate::widget::Button<'a, Message> { - let suggested = self.suggested_size(icon.symbolic); + let mut suggested = self.suggested_size(icon.symbolic); let applet_padding = self.suggested_padding(icon.symbolic); - let (mut configured_width, mut configured_height) = self.suggested_window_size(); - // Adjust the width to include padding and force the crosswise dim to match the window size let is_horizontal = self.is_horizontal(); - if is_horizontal { - configured_width = - NonZeroU32::new(suggested.0 as u32 + applet_padding as u32 * 2).unwrap(); - } else { - configured_height = - NonZeroU32::new(suggested.1 as u32 + applet_padding as u32 * 2).unwrap(); - } + let symbolic = icon.symbolic; crate::widget::button::custom( layer_container( widget::icon(icon) - .style(if symbolic { - theme::Svg::Custom(Rc::new(|theme| crate::iced_style::svg::Appearance { + .class(if symbolic { + theme::Svg::Custom(Rc::new(|theme| crate::iced_widget::svg::Style { color: Some(theme.cosmic().background.on.into()), })) } else { @@ -215,9 +213,9 @@ impl Context { .width(Length::Fill) .height(Length::Fill), ) - .width(Length::Fixed(configured_width.get() as f32)) - .height(Length::Fixed(configured_height.get() as f32)) - .style(Button::AppletIcon) + .width(Length::Fixed((suggested.0 + 2 * applet_padding) as f32)) + .height(Length::Fixed((suggested.1 + 2 * applet_padding) as f32)) + .class(Button::AppletIcon) } #[must_use] @@ -225,10 +223,11 @@ impl Context { &self, icon_name: &'a str, ) -> crate::widget::Button<'a, Message> { + let suggested_size = self.suggested_size(true); self.icon_button_from_handle( widget::icon::from_name(icon_name) .symbolic(true) - .size(self.suggested_size(true).0) + .size(suggested_size.0) .into(), ) } @@ -237,7 +236,7 @@ impl Context { pub fn popup_container<'a, Message: 'static>( &self, content: impl Into>, - ) -> Container<'a, Message, crate::Theme, Renderer> { + ) -> Autosize<'a, Message, crate::Theme, Renderer> { let (vertical_align, horizontal_align) = match self.anchor { PanelAnchor::Left => (Vertical::Center, Horizontal::Left), PanelAnchor::Right => (Vertical::Center, Horizontal::Right), @@ -245,12 +244,12 @@ impl Context { PanelAnchor::Bottom => (Vertical::Bottom, Horizontal::Center), }; - Container::::new( - Container::::new(content).style(theme::Container::custom( - |theme| { + autosize( + Container::::new( + Container::::new(content).style(|theme| { let cosmic = theme.cosmic(); let corners = cosmic.corner_radii.clone(); - Appearance { + iced_widget::container::Style { text_color: Some(cosmic.background.on.into()), background: Some(Color::from(cosmic.background.base).into()), border: iced::Border { @@ -261,13 +260,21 @@ impl Context { shadow: Shadow::default(), icon_color: Some(cosmic.background.on.into()), } - }, - )), + }), + ) + .width(Length::Shrink) + .height(Length::Shrink) + .align_x(horizontal_align) + .align_y(vertical_align), + AUTOSIZE_ID.clone(), + ) + .limits( + Limits::NONE + .min_width(1.) + .min_height(1.) + .max_width(500.) + .max_height(1000.), ) - .width(Length::Shrink) - .height(Length::Shrink) - .align_x(horizontal_align) - .align_y(vertical_align) } #[must_use] @@ -282,7 +289,7 @@ impl Context { ) -> SctkPopupSettings { let (width, height) = self.suggested_size(true); let applet_padding = self.suggested_padding(true); - let pixel_offset = 8; + let pixel_offset = 4; let (offset, anchor, gravity) = match self.anchor { PanelAnchor::Left => ((pixel_offset, 0), Anchor::Right, Gravity::Right), PanelAnchor::Right => ((-pixel_offset, 0), Anchor::Left, Gravity::Left), @@ -312,6 +319,35 @@ impl Context { } } + pub fn autosize_window<'a, Message: 'static>( + &self, + content: impl Into>, + ) -> Autosize<'a, Message, crate::Theme, crate::Renderer> { + let force_configured = matches!(&self.panel_type, &PanelType::Other(ref n) if n.is_empty()); + let w = autosize(content, AUTOSIZE_MAIN_ID.clone()); + let mut limits = Limits::NONE; + let suggested_window_size = self.suggested_window_size(); + + if let Some(width) = self + .suggested_bounds + .as_ref() + .filter(|c| c.width as i32 > 0) + .map(|c| c.width) + { + limits = limits.width(width as f32); + } + if let Some(height) = self + .suggested_bounds + .as_ref() + .filter(|c| c.height as i32 > 0) + .map(|c| c.height) + { + limits = limits.height(height as f32); + } + + w.limits(limits) + } + #[must_use] pub fn theme(&self) -> Option { match self.background { @@ -348,77 +384,67 @@ impl Context { /// # Errors /// /// Returns error on application failure. -pub fn run(autosize: bool, flags: App::Flags) -> iced::Result { +pub fn run(flags: App::Flags) -> iced::Result { let helper = Context::default(); - let mut settings = helper.window_settings(); - settings.autosize = autosize; - if autosize { - settings.size_limits = Limits::NONE; - } - if let Some(icon_theme) = settings.default_icon_theme { + let mut settings = helper.window_settings(); + settings.resizable = None; + + if let Some(icon_theme) = settings.default_icon_theme.clone() { crate::icon_theme::set_default(icon_theme); } - let (width, height) = (settings.size.width as u32, settings.size.height as u32); + THEME + .lock() + .unwrap() + .set_theme(settings.theme.theme_type.clone()); - let mut core = Core::default(); - core.window.show_window_menu = false; + let (iced_settings, (mut core, flags, mut window_settings)) = + iced_settings::(settings, flags); core.window.show_headerbar = false; core.window.sharp_corners = true; core.window.show_maximize = false; core.window.show_minimize = false; core.window.use_template = false; - core.debug = settings.debug; - core.set_scale_factor(settings.scale_factor); - core.set_window_width(width); - core.set_window_height(height); + window_settings.decorations = false; + window_settings.exit_on_close_request = true; + window_settings.resizable = false; + window_settings.resize_border = 0; - THEME.lock().unwrap().set_theme(settings.theme.theme_type); + // TODO make multi-window not mandatory - let mut iced = iced::Settings::with_flags((core, flags)); - - iced.antialiasing = settings.antialiasing; - iced.default_font = settings.default_font; - iced.default_text_size = settings.default_text_size.into(); - iced.id = Some(App::APP_ID.to_owned()); - - { - use iced::wayland::actions::window::SctkWindowSettings; - use iced_sctk::settings::InitialSurface; - iced.initial_surface = InitialSurface::XdgWindow(SctkWindowSettings { - app_id: Some(App::APP_ID.to_owned()), - autosize: settings.autosize, - client_decorations: settings.client_decorations, - resizable: settings.resizable, - size: (width, height), - size_limits: settings.size_limits, - title: None, - transparent: settings.transparent, - ..SctkWindowSettings::default() - }); + let mut app = super::app::multi_window::multi_window( + cosmic::Cosmic::title, + cosmic::Cosmic::update, + cosmic::Cosmic::view, + ); + if core.main_window.get().is_none() { + app = app.window(window_settings.clone()); + _ = core.main_window.set(iced_core::window::Id::RESERVED); } - - as iced::Application>::run(iced) + app.subscription(cosmic::Cosmic::subscription) + .style(cosmic::Cosmic::style) + .theme(cosmic::Cosmic::theme) + .settings(iced_settings) + .run_with(move || cosmic::Cosmic::::init((core, flags, window_settings))) } #[must_use] -pub fn style() -> ::Style { - ::Style::Custom(Box::new(|theme| { - iced_style::application::Appearance { - background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0), - text_color: theme.cosmic().on_bg_color().into(), - icon_color: theme.cosmic().on_bg_color().into(), - } - })) +pub fn style() -> iced_runtime::Appearance { + let theme = crate::theme::THEME.lock().unwrap(); + iced_runtime::Appearance { + background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0), + text_color: theme.cosmic().on_bg_color().into(), + icon_color: theme.cosmic().on_bg_color().into(), + } } pub fn menu_button<'a, Message>( content: impl Into>, ) -> crate::widget::Button<'a, Message> { crate::widget::button::custom(content) - .style(Button::AppletMenu) + .class(Button::AppletMenu) .padding(menu_control_padding()) .width(Length::Fill) } diff --git a/src/applet/token/subscription.rs b/src/applet/token/subscription.rs index c48e1250..e3cc6198 100644 --- a/src/applet/token/subscription.rs +++ b/src/applet/token/subscription.rs @@ -1,11 +1,12 @@ use crate::iced; -use crate::iced::subscription; use crate::iced_futures::futures; use cctk::sctk::reexports::calloop; use futures::{ channel::mpsc::{unbounded, UnboundedReceiver}, SinkExt, StreamExt, }; +use iced::Subscription; +use iced_futures::stream; use std::{fmt::Debug, hash::Hash, thread::JoinHandle}; use super::wayland_handler::wayland_handler; @@ -13,13 +14,16 @@ use super::wayland_handler::wayland_handler; pub fn activation_token_subscription( id: I, ) -> iced::Subscription { - subscription::channel(id, 50, move |mut output| async move { - let mut state = State::Ready; + Subscription::run_with_id( + id, + stream::channel(50, move |mut output| async move { + let mut state = State::Ready; - loop { - state = start_listening(state, &mut output).await; - } - }) + loop { + state = start_listening(state, &mut output).await; + } + }), + ) } pub enum State { diff --git a/src/command/mod.rs b/src/command/mod.rs index 10e32a28..2a509bce 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -4,114 +4,55 @@ //! Create asynchronous actions to be performed in the background. use iced::window; -use iced::Command; +use iced::Task; use iced_core::window::Mode; -#[cfg(feature = "wayland")] -use iced_runtime::command::platform_specific::wayland::window::Action as WindowAction; -#[cfg(feature = "wayland")] -use iced_runtime::command::platform_specific::wayland::Action as WaylandAction; -#[cfg(feature = "wayland")] -use iced_runtime::command::platform_specific::Action as PlatformAction; -use iced_runtime::command::Action; +use iced_runtime::{task, Action}; use std::future::Future; /// Yields a command which contains a batch of commands. -pub fn batch, Y: 'static>( - commands: impl IntoIterator>, -) -> Command { - Command::batch(commands).map(Into::into) +pub fn batch, Y: Send + 'static>( + commands: impl IntoIterator>, +) -> Task { + Task::batch(commands).map(Into::into) } /// Yields a command which will run the future on the runtime executor. -pub fn future, Y>(future: impl Future + Send + 'static) -> Command { - Command::single(Action::Future(Box::pin(async move { future.await.into() }))) +pub fn future, Y: 'static>(future: impl Future + Send + 'static) -> Task { + Task::future(async move { future.await.into() }) } /// Yields a command which will return a message. -pub fn message, Y>(message: X) -> Command { +pub fn message, Y: 'static>(message: X) -> Task { future(async move { message.into() }) } /// Initiates a window drag. -#[cfg(feature = "wayland")] -pub fn drag(id: Option) -> Command { - iced_sctk::commands::window::start_drag_window(id.unwrap_or(window::Id::MAIN)) -} - -/// Initiates a window drag. -#[cfg(not(feature = "wayland"))] -pub fn drag(id: Option) -> Command { - iced_runtime::window::drag(id.unwrap_or(window::Id::MAIN)) +pub fn drag(id: window::Id) -> Task { + iced_runtime::window::drag(id) } /// Maximizes the window. -#[cfg(feature = "wayland")] -pub fn maximize(id: Option, maximized: bool) -> Command { - iced_sctk::commands::window::maximize(id.unwrap_or(window::Id::MAIN), maximized) -} - -/// Maximizes the window. -#[cfg(not(feature = "wayland"))] -pub fn maximize(id: Option, maximized: bool) -> Command { - iced_runtime::window::maximize(id.unwrap_or(window::Id::MAIN), maximized) +pub fn maximize(id: window::Id, maximized: bool) -> Task { + iced_runtime::window::maximize(id, maximized) } /// Minimizes the window. -#[cfg(feature = "wayland")] -pub fn minimize(id: Option) -> Command { - iced_sctk::commands::window::set_mode_window(id.unwrap_or(window::Id::MAIN), Mode::Hidden) -} - -/// Minimizes the window. -#[cfg(not(feature = "wayland"))] -pub fn minimize(id: Option) -> Command { - iced_runtime::window::minimize(id.unwrap_or(window::Id::MAIN), true) +pub fn minimize(id: window::Id) -> Task { + iced_runtime::window::minimize(id, true) } /// Sets the title of a window. -#[cfg(feature = "wayland")] -pub fn set_title(id: Option, title: String) -> Command { - window_action(WindowAction::Title { - id: id.unwrap_or(window::Id::MAIN), - title, - }) -} - -/// Sets the title of a window. -#[cfg(not(feature = "wayland"))] #[allow(unused_variables, clippy::needless_pass_by_value)] -pub fn set_title(id: Option, title: String) -> Command { - Command::none() +pub fn set_title(id: window::Id, title: String) -> Task { + Task::none() } /// Sets the window mode to windowed. -#[cfg(feature = "wayland")] -pub fn set_windowed(id: Option) -> Command { - iced_sctk::commands::window::set_mode_window(id.unwrap_or(window::Id::MAIN), Mode::Windowed) -} - -/// Sets the window mode to windowed. -#[cfg(not(feature = "wayland"))] -pub fn set_windowed(id: Option) -> Command { - iced_runtime::window::change_mode(id.unwrap_or(window::Id::MAIN), Mode::Windowed) +pub fn set_windowed(id: window::Id) -> Task { + iced_runtime::window::change_mode(id, Mode::Windowed) } /// Toggles the windows' maximize state. -#[cfg(feature = "wayland")] -pub fn toggle_maximize(id: Option) -> Command { - iced_sctk::commands::window::toggle_maximize(id.unwrap_or(window::Id::MAIN)) -} - -/// Toggles the windows' maximize state. -#[cfg(not(feature = "wayland"))] -pub fn toggle_maximize(id: Option) -> Command { - iced_runtime::window::toggle_maximize(id.unwrap_or(window::Id::MAIN)) -} - -/// Creates a command to apply an action to a window. -#[cfg(feature = "wayland")] -pub fn window_action(action: WindowAction) -> Command { - Command::single(Action::PlatformSpecific(PlatformAction::Wayland( - WaylandAction::Window(action), - ))) +pub fn toggle_maximize(id: window::Id) -> Task { + iced_runtime::window::toggle_maximize(id) } diff --git a/src/ext.rs b/src/ext.rs index beb18c56..215f7b7b 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -64,7 +64,7 @@ impl<'a, Message: 'a> CollectionWidget<'a, Message> for crate::widget::Column<'a where E: Into>, { - self.extend(other.drain(..)) + self.extend(other.drain(..).map(Into::into)) } fn push(self, element: impl Into>) -> Self { @@ -77,7 +77,7 @@ impl<'a, Message: 'a> CollectionWidget<'a, Message> for crate::widget::Row<'a, M where E: Into>, { - self.extend(other.drain(..)) + self.extend(other.drain(..).map(Into::into)) } fn push(self, element: impl Into>) -> Self { diff --git a/src/font.rs b/src/font.rs index fae63d2e..c390cb67 100644 --- a/src/font.rs +++ b/src/font.rs @@ -4,7 +4,7 @@ //! Select preferred fonts. pub use iced::Font; -use iced_core::font::{Family, Weight}; +use iced_core::font::Weight; pub fn default() -> Font { Font::from(crate::config::interface_font()) diff --git a/src/keyboard_nav.rs b/src/keyboard_nav.rs index ae4c0f9d..a45e667a 100644 --- a/src/keyboard_nav.rs +++ b/src/keyboard_nav.rs @@ -17,7 +17,7 @@ pub enum Message { } pub fn subscription() -> Subscription { - listen_raw(|event, status| { + listen_raw(|event, status, _| { if event::Status::Ignored != status { return None; } diff --git a/src/lib.rs b/src/lib.rs index 9d4b6e8f..fc8758c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,28 +4,25 @@ #![allow(clippy::module_name_repetitions)] #![cfg_attr(target_os = "redox", feature(lazy_cell))] -#[cfg(all(feature = "wayland", feature = "winit"))] -compile_error!("cannot use `wayland` feature with `winit`"); - /// Recommended default imports. pub mod prelude { pub use crate::ext::*; - #[cfg(any(feature = "winit", feature = "wayland"))] + #[cfg(feature = "winit")] pub use crate::ApplicationExt; pub use crate::{Also, Apply, Element, Renderer, Theme}; } pub use apply::{Also, Apply}; -#[cfg(any(feature = "winit", feature = "wayland"))] +#[cfg(feature = "winit")] pub mod app; -#[cfg(any(feature = "winit", feature = "wayland"))] +#[cfg(feature = "winit")] pub use app::{Application, ApplicationExt}; #[cfg(feature = "applet")] pub mod applet; -pub use iced::Command; +pub use iced::Task; pub mod command; pub mod config; @@ -62,12 +59,6 @@ pub use iced_renderer; #[doc(inline)] pub use iced_runtime; -#[cfg(feature = "wayland")] -pub use iced_sctk; - -#[doc(inline)] -pub use iced_style; - #[doc(inline)] pub use iced_widget; @@ -96,7 +87,7 @@ pub mod theme; pub use theme::{style, Theme}; pub mod widget; - +type Plain = iced_core::text::paragraph::Plain<::Paragraph>; type Paragraph = ::Paragraph; pub type Renderer = iced::Renderer; pub type Element<'a, Message> = iced::Element<'a, Message, crate::Theme, Renderer>; diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 292c0dcc..30346ec6 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -14,6 +14,7 @@ use cosmic_config::CosmicConfigEntry; use cosmic_theme::Component; use cosmic_theme::LayeredTheme; use iced_futures::Subscription; +use iced_runtime::{Appearance, DefaultStyle}; use std::sync::{Arc, Mutex}; @@ -273,3 +274,14 @@ impl LayeredTheme for Theme { self.layer = layer; } } + +impl DefaultStyle for Theme { + fn default_style(&self) -> Appearance { + let cosmic = self.cosmic(); + Appearance { + icon_color: cosmic.bg_color().into(), + background_color: cosmic.bg_color().into(), + text_color: cosmic.on_bg_color().into(), + } + } +} diff --git a/src/theme/portal.rs b/src/theme/portal.rs index b0fc5f84..17225ccc 100644 --- a/src/theme/portal.rs +++ b/src/theme/portal.rs @@ -1,7 +1,7 @@ use ashpd::desktop::settings::{ColorScheme, Contrast}; use ashpd::desktop::Color; use iced::futures::{self, select, FutureExt, SinkExt, StreamExt}; -use iced_futures::subscription; +use iced_futures::{stream, subscription}; use tracing::error; #[derive(Debug, Clone)] @@ -12,86 +12,92 @@ pub enum Desktop { } pub fn desktop_settings() -> iced_futures::Subscription { - subscription::channel(std::any::TypeId::of::(), 10, |mut tx| { - async move { - let mut attempts = 0; - loop { - let Ok(settings) = ashpd::desktop::settings::Settings::new().await else { - error!("Failed to create the settings proxy"); - #[cfg(feature = "tokio")] - ::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts))) - .await; - #[cfg(not(feature = "tokio"))] - { - pending::<()>().await; - unreachable!(); - } - attempts += 1; - continue; - }; - - match settings.color_scheme().await { - Ok(color_scheme) => { - let _ = tx.send(Desktop::ColorScheme(color_scheme)).await; - } - Err(err) => error!("Failed to get the color scheme {err:?}"), - }; - match settings.contrast().await { - Ok(contrast) => { - let _ = tx.send(Desktop::Contrast(contrast)).await; - } - Err(err) => error!("Failed to get the contrast {err:?}"), - }; - - let mut color_scheme_stream = settings.receive_color_scheme_changed().await.ok(); - if color_scheme_stream.is_none() { - error!("Failed to receive color scheme changes"); - } - - let mut contrast_stream = settings.receive_contrast_changed().await.ok(); - if contrast_stream.is_none() { - error!("Failed to receive contrast changes"); - } - + iced_futures::Subscription::run_with_id( + std::any::TypeId::of::(), + stream::channel(10, |mut tx| { + async move { + let mut attempts = 0; loop { - if color_scheme_stream.is_none() && contrast_stream.is_none() { - break; + let Ok(settings) = ashpd::desktop::settings::Settings::new().await else { + error!("Failed to create the settings proxy"); + #[cfg(feature = "tokio")] + ::tokio::time::sleep(::tokio::time::Duration::from_secs( + 2_u64.pow(attempts), + )) + .await; + #[cfg(not(feature = "tokio"))] + { + pending::<()>().await; + unreachable!(); + } + attempts += 1; + continue; + }; + + match settings.color_scheme().await { + Ok(color_scheme) => { + let _ = tx.send(Desktop::ColorScheme(color_scheme)).await; + } + Err(err) => error!("Failed to get the color scheme {err:?}"), + }; + match settings.contrast().await { + Ok(contrast) => { + let _ = tx.send(Desktop::Contrast(contrast)).await; + } + Err(err) => error!("Failed to get the contrast {err:?}"), + }; + + let mut color_scheme_stream = + settings.receive_color_scheme_changed().await.ok(); + if color_scheme_stream.is_none() { + error!("Failed to receive color scheme changes"); } - let next_color_scheme = async { - if let Some(s) = color_scheme_stream.as_mut() { - return s.next().await; - } - futures::future::pending().await - }; - let next_contrast = async { - if let Some(s) = contrast_stream.as_mut() { - return s.next().await; - } - futures::future::pending().await - }; + let mut contrast_stream = settings.receive_contrast_changed().await.ok(); + if contrast_stream.is_none() { + error!("Failed to receive contrast changes"); + } - select! { - s = next_color_scheme.fuse() => { - if let Some(s) = s { - _ = tx.send(Desktop::ColorScheme(s)).await; - } else { - color_scheme_stream = None; + loop { + if color_scheme_stream.is_none() && contrast_stream.is_none() { + break; + } + let next_color_scheme = async { + if let Some(s) = color_scheme_stream.as_mut() { + return s.next().await; } - }, + futures::future::pending().await + }; - c = next_contrast.fuse() => { - if let Some(c) = c { - _ = tx.send(Desktop::Contrast(c)).await; - } else { - contrast_stream = None; + let next_contrast = async { + if let Some(s) = contrast_stream.as_mut() { + return s.next().await; } - } - }; - // Reset the attempts counter if we successfully received a change - attempts = 0; + futures::future::pending().await + }; + + select! { + s = next_color_scheme.fuse() => { + if let Some(s) = s { + _ = tx.send(Desktop::ColorScheme(s)).await; + } else { + color_scheme_stream = None; + } + }, + + c = next_contrast.fuse() => { + if let Some(c) = c { + _ = tx.send(Desktop::Contrast(c)).await; + } else { + contrast_stream = None; + } + } + }; + // Reset the attempts counter if we successfully received a change + attempts = 0; + } } } - } - }) + }), + ) } diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index c2dc6558..873a390f 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -8,17 +8,17 @@ use iced_core::{Background, Color}; use crate::{ theme::TRANSPARENT_COMPONENT, - widget::button::{Appearance, StyleSheet}, + widget::button::{Catalog, Style}, }; #[derive(Default)] pub enum Button { AppletIcon, Custom { - active: Box Appearance>, - disabled: Box Appearance>, - hovered: Box Appearance>, - pressed: Box Appearance>, + active: Box Style>, + disabled: Box Style>, + hovered: Box Style>, + pressed: Box Style>, }, AppletMenu, Destructive, @@ -44,10 +44,10 @@ pub fn appearance( disabled: bool, style: &Button, color: impl Fn(&Component) -> (Color, Option, Option), -) -> Appearance { +) -> Style { let cosmic = theme.cosmic(); let mut corner_radii = &cosmic.corner_radii.radius_xl; - let mut appearance = Appearance::new(); + let mut appearance = Style::new(); match style { Button::Standard @@ -163,10 +163,10 @@ pub fn appearance( appearance } -impl StyleSheet for crate::Theme { - type Style = Button; +impl Catalog for crate::Theme { + type Class = Button; - fn active(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance { + fn active(&self, focused: bool, selected: bool, style: &Self::Class) -> Style { if let Button::Custom { active, .. } = style { return active(focused, self); } @@ -186,7 +186,7 @@ impl StyleSheet for crate::Theme { }) } - fn disabled(&self, style: &Self::Style) -> Appearance { + fn disabled(&self, style: &Self::Class) -> Style { if let Button::Custom { disabled, .. } = style { return disabled(self); } @@ -202,11 +202,11 @@ impl StyleSheet for crate::Theme { }) } - fn drop_target(&self, style: &Self::Style) -> Appearance { + fn drop_target(&self, style: &Self::Class) -> Style { self.active(false, false, style) } - fn hovered(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance { + fn hovered(&self, focused: bool, selected: bool, style: &Self::Class) -> Style { if let Button::Custom { hovered, .. } = style { return hovered(focused, self); } @@ -233,7 +233,7 @@ impl StyleSheet for crate::Theme { ) } - fn pressed(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance { + fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style { if let Button::Custom { pressed, .. } = style { return pressed(focused, self); } diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 9b4095e7..73be6930 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -5,52 +5,44 @@ use crate::theme::{CosmicComponent, Theme, TRANSPARENT_COMPONENT}; use cosmic_theme::composite::over; +use iced::{ + overlay::menu, + widget::{ + button as iced_button, checkbox as iced_checkbox, container as iced_container, pane_grid, + pick_list, progress_bar, radio, rule, scrollable, + slider::{self, Rail}, + svg, toggler, + }, + Gradient, +}; use iced_core::{Background, Border, Color, Shadow, Vector}; -use iced_style::application; -use iced_style::button as iced_button; -use iced_style::checkbox; -use iced_style::container; -use iced_style::menu; -use iced_style::pane_grid; -use iced_style::pick_list; -use iced_style::progress_bar; -use iced_style::radio; -use iced_style::rule; -use iced_style::scrollable; -use iced_style::slider; -use iced_style::slider::Rail; -use iced_style::svg; -use iced_style::text_input; -use iced_style::toggler; - +use iced_widget::{pane_grid::Highlight, text_editor, text_input}; use std::rc::Rc; -#[derive(Default)] -pub enum Application { - #[default] - Default, - Custom(Box application::Appearance>), -} +pub mod application { + use crate::Theme; + use iced_runtime::Appearance; -impl Application { - pub fn custom application::Appearance + 'static>(f: F) -> Self { - Self::Custom(Box::new(f)) + #[derive(Default)] + pub enum Application { + #[default] + Default, + Custom(Box Appearance>), } -} -impl application::StyleSheet for Theme { - type Style = Application; + impl Application { + pub fn custom Appearance + 'static>(f: F) -> Self { + Self::Custom(Box::new(f)) + } + } - fn appearance(&self, style: &Self::Style) -> application::Appearance { - let cosmic = self.cosmic(); + pub fn appearance(theme: &Theme) -> Appearance { + let cosmic = theme.cosmic(); - match style { - Application::Default => application::Appearance { - icon_color: cosmic.bg_color().into(), - background_color: cosmic.bg_color().into(), - text_color: cosmic.on_bg_color().into(), - }, - Application::Custom(f) => f(self), + Appearance { + icon_color: cosmic.bg_color().into(), + background_color: cosmic.bg_color().into(), + text_color: cosmic.on_bg_color().into(), } } } @@ -69,10 +61,84 @@ pub enum Button { LinkActive, Transparent, Card, - Custom { - active: Box iced_button::Appearance>, - hover: Box iced_button::Appearance>, - }, + Custom(Box iced_button::Style>), +} + +impl iced_button::Catalog for Theme { + type Class<'a> = Button; + + fn default<'a>() -> Self::Class<'a> { + Button::default() + } + + fn style(&self, class: &Self::Class<'_>, status: iced_button::Status) -> iced_button::Style { + if let Button::Custom(f) = class { + return f(self, status); + } + let cosmic = self.cosmic(); + let corner_radii = &cosmic.corner_radii; + let component = class.cosmic(self); + + let mut appearance = iced_button::Style { + border_radius: match class { + Button::Link => corner_radii.radius_0.into(), + Button::Card => corner_radii.radius_xs.into(), + _ => corner_radii.radius_xl.into(), + }, + border: Border { + radius: match class { + Button::Link => corner_radii.radius_0.into(), + Button::Card => corner_radii.radius_xs.into(), + _ => corner_radii.radius_xl.into(), + }, + ..Default::default() + }, + background: match class { + Button::Link | Button::Text => None, + Button::LinkActive => Some(Background::Color(component.divider.into())), + _ => Some(Background::Color(component.base.into())), + }, + text_color: match class { + Button::Link | Button::LinkActive => component.base.into(), + _ => component.on.into(), + }, + ..iced_button::Style::default() + }; + + match status { + iced_button::Status::Active => {} + iced_button::Status::Hovered => { + appearance.background = match class { + Button::Link => None, + Button::LinkActive => Some(Background::Color(component.divider.into())), + _ => Some(Background::Color(component.hover.into())), + }; + } + iced_button::Status::Pressed => { + appearance.background = match class { + Button::Link => None, + Button::LinkActive => Some(Background::Color(component.divider.into())), + _ => Some(Background::Color(component.pressed.into())), + }; + } + iced_button::Status::Disabled => { + appearance.background = appearance.background.map(|background| match background { + Background::Color(color) => Background::Color(Color { + a: color.a * 0.5, + ..color + }), + Background::Gradient(gradient) => { + Background::Gradient(gradient.scale_alpha(0.5)) + } + }); + appearance.text_color = Color { + a: appearance.text_color.a * 0.5, + ..appearance.text_color + }; + } + }; + appearance + } } impl Button { @@ -96,86 +162,6 @@ impl Button { } } -impl iced_button::StyleSheet for Theme { - type Style = Button; - - fn active(&self, style: &Self::Style) -> iced_button::Appearance { - if let Button::Custom { active, .. } = style { - return active(self); - } - - let corner_radii = &self.cosmic().corner_radii; - let component = style.cosmic(self); - iced_button::Appearance { - border_radius: match style { - Button::Link => corner_radii.radius_0.into(), - Button::Card => corner_radii.radius_xs.into(), - _ => corner_radii.radius_xl.into(), - }, - border: Border { - radius: match style { - Button::Link => corner_radii.radius_0.into(), - Button::Card => corner_radii.radius_xs.into(), - _ => corner_radii.radius_xl.into(), - }, - ..Default::default() - }, - background: match style { - Button::Link | Button::Text => None, - Button::LinkActive => Some(Background::Color(component.divider.into())), - _ => Some(Background::Color(component.base.into())), - }, - text_color: match style { - Button::Link | Button::LinkActive => component.base.into(), - _ => component.on.into(), - }, - ..iced_button::Appearance::default() - } - } - - fn hovered(&self, style: &Self::Style) -> iced_button::Appearance { - if let Button::Custom { hover, .. } = style { - return hover(self); - } - - let active = self.active(style); - let component = style.cosmic(self); - - iced_button::Appearance { - background: match style { - Button::Link => None, - Button::LinkActive => Some(Background::Color(component.divider.into())), - _ => Some(Background::Color(component.hover.into())), - }, - ..active - } - } - - fn disabled(&self, style: &Self::Style) -> iced_button::Appearance { - let active = self.active(style); - - if matches!(style, Button::Card) { - return active; - } - - iced_button::Appearance { - shadow_offset: Vector::default(), - background: active.background.map(|background| match background { - Background::Color(color) => Background::Color(Color { - a: color.a * 0.5, - ..color - }), - Background::Gradient(gradient) => Background::Gradient(gradient.mul_alpha(0.5)), - }), - text_color: Color { - a: active.text_color.a * 0.5, - ..active.text_color - }, - ..active - } - } -} - /* * TODO: Checkbox */ @@ -193,169 +179,193 @@ impl Default for Checkbox { } } -impl checkbox::StyleSheet for Theme { - type Style = Checkbox; +impl iced_checkbox::Catalog for Theme { + type Class<'a> = Checkbox; - fn active(&self, style: &Self::Style, is_checked: bool) -> checkbox::Appearance { - let cosmic = self.cosmic(); - - let corners = &cosmic.corner_radii; - match style { - Checkbox::Primary => checkbox::Appearance { - background: Background::Color(if is_checked { - cosmic.accent.base.into() - } else { - cosmic.button.base.into() - }), - icon_color: cosmic.accent.on.into(), - border: Border { - radius: corners.radius_xs.into(), - width: if is_checked { 0.0 } else { 1.0 }, - color: if is_checked { - cosmic.accent.base - } else { - cosmic.button.border - } - .into(), - }, - - text_color: None, - }, - Checkbox::Secondary => checkbox::Appearance { - background: Background::Color(if is_checked { - cosmic.background.component.base.into() - } else { - cosmic.background.base.into() - }), - icon_color: cosmic.background.on.into(), - border: Border { - radius: corners.radius_xs.into(), - width: if is_checked { 0.0 } else { 1.0 }, - color: cosmic.button.border.into(), - }, - text_color: None, - }, - Checkbox::Success => checkbox::Appearance { - background: Background::Color(if is_checked { - cosmic.success.base.into() - } else { - cosmic.button.base.into() - }), - icon_color: cosmic.success.on.into(), - border: Border { - radius: corners.radius_xs.into(), - width: if is_checked { 0.0 } else { 1.0 }, - color: if is_checked { - cosmic.success.base - } else { - cosmic.button.border - } - .into(), - }, - text_color: None, - }, - Checkbox::Danger => checkbox::Appearance { - background: Background::Color(if is_checked { - cosmic.destructive.base.into() - } else { - cosmic.button.base.into() - }), - icon_color: cosmic.destructive.on.into(), - border: Border { - radius: corners.radius_xs.into(), - width: if is_checked { 0.0 } else { 1.0 }, - color: if is_checked { - cosmic.destructive.base - } else { - cosmic.button.border - } - .into(), - }, - text_color: None, - }, - } + fn default<'a>() -> Self::Class<'a> { + Checkbox::default() } - fn hovered(&self, style: &Self::Style, is_checked: bool) -> checkbox::Appearance { + fn style( + &self, + class: &Self::Class<'_>, + status: iced_checkbox::Status, + ) -> iced_checkbox::Style { let cosmic = self.cosmic(); + let corners = &cosmic.corner_radii; - match style { - Checkbox::Primary => checkbox::Appearance { - background: Background::Color(if is_checked { - cosmic.accent.base.into() - } else { - cosmic.button.base.into() - }), - icon_color: cosmic.accent.on.into(), - border: Border { - radius: corners.radius_xs.into(), - width: if is_checked { 0.0 } else { 1.0 }, - color: if is_checked { - cosmic.accent.base - } else { - cosmic.button.border + let disabled = matches!(status, iced_checkbox::Status::Disabled { .. }); + match status { + iced_checkbox::Status::Active { is_checked } + | iced_checkbox::Status::Disabled { is_checked } => { + let mut active = match class { + Checkbox::Primary => iced_checkbox::Style { + background: Background::Color(if is_checked { + cosmic.accent.base.into() + } else { + cosmic.button.base.into() + }), + icon_color: cosmic.accent.on.into(), + border: Border { + radius: corners.radius_xs.into(), + width: if is_checked { 0.0 } else { 1.0 }, + color: if is_checked { + cosmic.accent.base + } else { + cosmic.button.border + } + .into(), + }, + + text_color: None, + }, + Checkbox::Secondary => iced_checkbox::Style { + background: Background::Color(if is_checked { + cosmic.background.component.base.into() + } else { + cosmic.background.base.into() + }), + icon_color: cosmic.background.on.into(), + border: Border { + radius: corners.radius_xs.into(), + width: if is_checked { 0.0 } else { 1.0 }, + color: cosmic.button.border.into(), + }, + text_color: None, + }, + Checkbox::Success => iced_checkbox::Style { + background: Background::Color(if is_checked { + cosmic.success.base.into() + } else { + cosmic.button.base.into() + }), + icon_color: cosmic.success.on.into(), + border: Border { + radius: corners.radius_xs.into(), + width: if is_checked { 0.0 } else { 1.0 }, + color: if is_checked { + cosmic.success.base + } else { + cosmic.button.border + } + .into(), + }, + text_color: None, + }, + Checkbox::Danger => iced_checkbox::Style { + background: Background::Color(if is_checked { + cosmic.destructive.base.into() + } else { + cosmic.button.base.into() + }), + icon_color: cosmic.destructive.on.into(), + border: Border { + radius: corners.radius_xs.into(), + width: if is_checked { 0.0 } else { 1.0 }, + color: if is_checked { + cosmic.destructive.base + } else { + cosmic.button.border + } + .into(), + }, + text_color: None, + }, + }; + if disabled { + match &mut active.background { + Background::Color(color) => { + color.a /= 2.; + } + Background::Gradient(gradient) => { + *gradient = gradient.scale_alpha(0.5); + } } - .into(), - }, - text_color: None, - }, - Checkbox::Secondary => checkbox::Appearance { - background: Background::Color(if is_checked { - self.current_container().base.into() - } else { - cosmic.button.base.into() - }), - icon_color: self.current_container().on.into(), - border: Border { - radius: corners.radius_xs.into(), - width: if is_checked { 0.0 } else { 1.0 }, - color: if is_checked { - self.current_container().base + if let Some(c) = active.text_color.as_mut() { + c.a /= 2. + }; + active.border.color.a /= 2.; + } + active + } + iced_checkbox::Status::Hovered { is_checked } => match class { + Checkbox::Primary => iced_checkbox::Style { + background: Background::Color(if is_checked { + cosmic.accent.base.into() } else { - cosmic.button.border - } - .into(), + cosmic.button.base.into() + }), + icon_color: cosmic.accent.on.into(), + border: Border { + radius: corners.radius_xs.into(), + width: if is_checked { 0.0 } else { 1.0 }, + color: if is_checked { + cosmic.accent.base + } else { + cosmic.button.border + } + .into(), + }, + text_color: None, }, - text_color: None, - }, - Checkbox::Success => checkbox::Appearance { - background: Background::Color(if is_checked { - cosmic.success.base.into() - } else { - cosmic.button.base.into() - }), - icon_color: cosmic.success.on.into(), - border: Border { - radius: corners.radius_xs.into(), - width: if is_checked { 0.0 } else { 1.0 }, - color: if is_checked { - cosmic.success.base + Checkbox::Secondary => iced_checkbox::Style { + background: Background::Color(if is_checked { + self.current_container().base.into() } else { - cosmic.button.border - } - .into(), + cosmic.button.base.into() + }), + icon_color: self.current_container().on.into(), + border: Border { + radius: corners.radius_xs.into(), + width: if is_checked { 0.0 } else { 1.0 }, + color: if is_checked { + self.current_container().base + } else { + cosmic.button.border + } + .into(), + }, + text_color: None, }, - text_color: None, - }, - Checkbox::Danger => checkbox::Appearance { - background: Background::Color(if is_checked { - cosmic.destructive.base.into() - } else { - cosmic.button.base.into() - }), - icon_color: cosmic.destructive.on.into(), - border: Border { - radius: corners.radius_xs.into(), - width: if is_checked { 0.0 } else { 1.0 }, - color: if is_checked { - cosmic.destructive.base + Checkbox::Success => iced_checkbox::Style { + background: Background::Color(if is_checked { + cosmic.success.base.into() } else { - cosmic.button.border - } - .into(), + cosmic.button.base.into() + }), + icon_color: cosmic.success.on.into(), + border: Border { + radius: corners.radius_xs.into(), + width: if is_checked { 0.0 } else { 1.0 }, + color: if is_checked { + cosmic.success.base + } else { + cosmic.button.border + } + .into(), + }, + text_color: None, + }, + Checkbox::Danger => iced_checkbox::Style { + background: Background::Color(if is_checked { + cosmic.destructive.base.into() + } else { + cosmic.button.base.into() + }), + icon_color: cosmic.destructive.on.into(), + border: Border { + radius: corners.radius_xs.into(), + width: if is_checked { 0.0 } else { 1.0 }, + color: if is_checked { + cosmic.destructive.base + } else { + cosmic.button.border + } + .into(), + }, + text_color: None, }, - text_color: None, }, } } @@ -365,12 +375,12 @@ impl checkbox::StyleSheet for Theme { * TODO: Container */ #[derive(Default)] -pub enum Container { +pub enum Container<'a> { WindowBackground, Background, Card, ContextDrawer, - Custom(Box container::Appearance>), + Custom(Box iced_container::Style + 'a>), Dialog, Dropdown, HeaderBar { @@ -384,14 +394,14 @@ pub enum Container { Transparent, } -impl Container { - pub fn custom container::Appearance + 'static>(f: F) -> Self { +impl<'a> Container<'a> { + pub fn custom iced_container::Style + 'a>(f: F) -> Self { Self::Custom(Box::new(f)) } #[must_use] - pub fn background(theme: &cosmic_theme::Theme) -> container::Appearance { - container::Appearance { + pub fn background(theme: &cosmic_theme::Theme) -> iced_container::Style { + iced_container::Style { icon_color: Some(Color::from(theme.background.on)), text_color: Some(Color::from(theme.background.on)), background: Some(iced::Background::Color(theme.background.base.into())), @@ -404,8 +414,8 @@ impl Container { } #[must_use] - pub fn primary(theme: &cosmic_theme::Theme) -> container::Appearance { - container::Appearance { + pub fn primary(theme: &cosmic_theme::Theme) -> iced_container::Style { + iced_container::Style { icon_color: Some(Color::from(theme.primary.on)), text_color: Some(Color::from(theme.primary.on)), background: Some(iced::Background::Color(theme.primary.base.into())), @@ -418,8 +428,8 @@ impl Container { } #[must_use] - pub fn secondary(theme: &cosmic_theme::Theme) -> container::Appearance { - container::Appearance { + pub fn secondary(theme: &cosmic_theme::Theme) -> iced_container::Style { + iced_container::Style { icon_color: Some(Color::from(theme.secondary.on)), text_color: Some(Color::from(theme.secondary.on)), background: Some(iced::Background::Color(theme.secondary.base.into())), @@ -432,19 +442,28 @@ impl Container { } } -impl container::StyleSheet for Theme { - type Style = Container; +impl<'a> From> for Container<'a> { + fn from(value: iced_container::StyleFn<'a, Theme>) -> Self { + Self::custom(value) + } +} - #[allow(clippy::too_many_lines)] - fn appearance(&self, style: &Self::Style) -> container::Appearance { +impl iced_container::Catalog for Theme { + type Class<'a> = Container<'a>; + + fn default<'a>() -> Self::Class<'a> { + Container::default() + } + + fn style(&self, class: &Self::Class<'_>) -> iced_container::Style { let cosmic = self.cosmic(); - match style { - Container::Transparent => container::Appearance::default(), + match class { + Container::Transparent => iced_container::Style::default(), Container::Custom(f) => f(self), - Container::WindowBackground => container::Appearance { + Container::WindowBackground => iced_container::Style { icon_color: Some(Color::from(cosmic.background.on)), text_color: Some(Color::from(cosmic.background.on)), background: Some(iced::Background::Color(cosmic.background.base.into())), @@ -463,7 +482,7 @@ impl container::StyleSheet for Theme { Container::List => { let component = &self.current_container().component; - container::Appearance { + iced_container::Style { icon_color: Some(component.on.into()), text_color: Some(component.on.into()), background: Some(Background::Color(component.base.into())), @@ -488,7 +507,7 @@ impl container::StyleSheet for Theme { (unfocused_color, unfocused_color) }; - container::Appearance { + iced_container::Style { icon_color: Some(icon_color), text_color: Some(text_color), background: Some(iced::Background::Color(cosmic.background.base.into())), @@ -533,7 +552,7 @@ impl container::StyleSheet for Theme { Container::Dropdown => { let theme = self.cosmic(); - container::Appearance { + iced_container::Style { icon_color: None, text_color: None, background: Some(iced::Background::Color(theme.primary.base.into())), @@ -545,7 +564,7 @@ impl container::StyleSheet for Theme { } } - Container::Tooltip => container::Appearance { + Container::Tooltip => iced_container::Style { icon_color: None, text_color: None, background: Some(iced::Background::Color(cosmic.palette.neutral_2.into())), @@ -560,7 +579,7 @@ impl container::StyleSheet for Theme { let cosmic = self.cosmic(); match self.layer { - cosmic_theme::Layer::Background => container::Appearance { + cosmic_theme::Layer::Background => iced_container::Style { icon_color: Some(Color::from(cosmic.background.component.on)), text_color: Some(Color::from(cosmic.background.component.on)), background: Some(iced::Background::Color( @@ -572,7 +591,7 @@ impl container::StyleSheet for Theme { }, shadow: Shadow::default(), }, - cosmic_theme::Layer::Primary => container::Appearance { + cosmic_theme::Layer::Primary => iced_container::Style { icon_color: Some(Color::from(cosmic.primary.component.on)), text_color: Some(Color::from(cosmic.primary.component.on)), background: Some(iced::Background::Color( @@ -584,7 +603,7 @@ impl container::StyleSheet for Theme { }, shadow: Shadow::default(), }, - cosmic_theme::Layer::Secondary => container::Appearance { + cosmic_theme::Layer::Secondary => iced_container::Style { icon_color: Some(Color::from(cosmic.secondary.component.on)), text_color: Some(Color::from(cosmic.secondary.component.on)), background: Some(iced::Background::Color( @@ -599,7 +618,7 @@ impl container::StyleSheet for Theme { } } - Container::Dialog => container::Appearance { + Container::Dialog => iced_container::Style { icon_color: Some(Color::from(cosmic.primary.on)), text_color: Some(Color::from(cosmic.primary.on)), background: Some(iced::Background::Color(cosmic.primary.base.into())), @@ -623,33 +642,39 @@ pub enum Slider { #[default] Standard, Custom { - active: Rc slider::Appearance>, - hovered: Rc slider::Appearance>, - dragging: Rc slider::Appearance>, + active: Rc slider::Style>, + hovered: Rc slider::Style>, + dragging: Rc slider::Style>, }, } /* * Slider */ -impl slider::StyleSheet for Theme { - type Style = Slider; +impl slider::Catalog for Theme { + type Class<'a> = Slider; - fn active(&self, style: &Self::Style) -> slider::Appearance { - match style { + fn default<'a>() -> Self::Class<'a> { + Slider::default() + } + + fn style(&self, class: &Self::Class<'_>, status: slider::Status) -> slider::Style { + let cosmic: &cosmic_theme::Theme = self.cosmic(); + let mut appearance = match class { Slider::Standard => //TODO: no way to set rail thickness { - let cosmic: &cosmic_theme::Theme = self.cosmic(); - - slider::Appearance { + slider::Style { rail: Rail { - colors: slider::RailBackground::Pair( - cosmic.accent.base.into(), - cosmic.palette.neutral_6.into(), + backgrounds: ( + Background::Color(cosmic.accent.base.into()), + Background::Color(cosmic.palette.neutral_6.into()), ), + border: Border { + radius: cosmic.corner_radii.radius_xs.into(), + ..Border::default() + }, width: 4.0, - border_radius: cosmic.corner_radii.radius_xs.into(), }, handle: slider::Handle { @@ -658,9 +683,9 @@ impl slider::StyleSheet for Theme { width: 20, border_radius: cosmic.corner_radii.radius_m.into(), }, - color: cosmic.accent.base.into(), border_color: Color::TRANSPARENT, border_width: 0.0, + background: Background::Color(cosmic.accent.base.into()), }, breakpoint: slider::Breakpoint { @@ -669,55 +694,61 @@ impl slider::StyleSheet for Theme { } } Slider::Custom { active, .. } => active(self), - } - } + }; + match status { + slider::Status::Active => appearance, + slider::Status::Hovered => match class { + Slider::Standard => { + appearance.handle.shape = slider::HandleShape::Rectangle { + height: 26, + width: 26, + border_radius: cosmic.corner_radii.radius_m.into(), + }; + appearance.handle.border_width = 3.0; + let mut border_color = self.cosmic().palette.neutral_10; + border_color.alpha = 0.1; + appearance.handle.border_color = border_color.into(); + appearance + } + Slider::Custom { hovered, .. } => hovered(self), + }, + slider::Status::Dragged => match class { + Slider::Standard => { + let mut style = { + appearance.handle.shape = slider::HandleShape::Rectangle { + height: 26, + width: 26, + border_radius: cosmic.corner_radii.radius_m.into(), + }; + appearance.handle.border_width = 3.0; + let mut border_color = self.cosmic().palette.neutral_10; + border_color.alpha = 0.1; + appearance.handle.border_color = border_color.into(); + appearance + }; + let mut border_color = self.cosmic().palette.neutral_10; + border_color.alpha = 0.2; + style.handle.border_color = border_color.into(); - fn hovered(&self, style: &Self::Style) -> slider::Appearance { - match style { - Slider::Standard => { - let cosmic: &cosmic_theme::Theme = self.cosmic(); - - let mut style = self.active(style); - style.handle.shape = slider::HandleShape::Rectangle { - height: 26, - width: 26, - border_radius: cosmic.corner_radii.radius_m.into(), - }; - style.handle.border_width = 3.0; - let mut border_color = self.cosmic().palette.neutral_10; - border_color.alpha = 0.1; - style.handle.border_color = border_color.into(); - style - } - Slider::Custom { hovered, .. } => hovered(self), - } - } - - fn dragging(&self, style: &Self::Style) -> slider::Appearance { - match style { - Slider::Standard => { - let mut style = self.hovered(style); - let mut border_color = self.cosmic().palette.neutral_10; - border_color.alpha = 0.2; - style.handle.border_color = border_color.into(); - - style - } - Slider::Custom { dragging, .. } => dragging(self), + style + } + Slider::Custom { dragging, .. } => dragging(self), + }, } } } -/* - * TODO: Menu - */ -impl menu::StyleSheet for Theme { - type Style = (); +impl menu::Catalog for Theme { + type Class<'a> = (); - fn appearance(&self, _style: &Self::Style) -> menu::Appearance { + fn default<'a>() -> ::Class<'a> { + () + } + + fn style(&self, class: &::Class<'_>) -> menu::Style { let cosmic = self.cosmic(); - menu::Appearance { + menu::Style { text_color: cosmic.on_bg_color().into(), background: Background::Color(cosmic.background.base.into()), border: Border { @@ -733,13 +764,21 @@ impl menu::StyleSheet for Theme { /* * TODO: Pick List */ -impl pick_list::StyleSheet for Theme { - type Style = (); +impl pick_list::Catalog for Theme { + type Class<'a> = (); - fn active(&self, _style: &()) -> pick_list::Appearance { + fn default<'a>() -> ::Class<'a> { + () + } + + fn style( + &self, + class: &::Class<'_>, + status: pick_list::Status, + ) -> pick_list::Style { let cosmic = &self.cosmic(); - pick_list::Appearance { + let appearance = pick_list::Style { text_color: cosmic.on_bg_color().into(), background: Color::TRANSPARENT.into(), placeholder_color: cosmic.on_bg_color().into(), @@ -749,15 +788,14 @@ impl pick_list::StyleSheet for Theme { }, // icon_size: 0.7, // TODO: how to replace handle_color: cosmic.on_bg_color().into(), - } - } - - fn hovered(&self, style: &()) -> pick_list::Appearance { - let cosmic = &self.cosmic(); - - pick_list::Appearance { - background: Background::Color(cosmic.background.base.into()), - ..self.active(style) + }; + match status { + pick_list::Status::Active => appearance, + pick_list::Status::Hovered => pick_list::Style { + background: Background::Color(cosmic.background.base.into()), + ..appearance + }, + pick_list::Status::Opened => appearance, } } } @@ -765,52 +803,53 @@ impl pick_list::StyleSheet for Theme { /* * TODO: Radio */ -impl radio::StyleSheet for Theme { - type Style = (); +impl radio::Catalog for Theme { + type Class<'a> = (); - fn active(&self, _style: &Self::Style, is_selected: bool) -> radio::Appearance { - let theme = self.cosmic(); - - radio::Appearance { - background: if is_selected { - Color::from(theme.accent.base).into() - } else { - // TODO: this seems to be defined weirdly in FIGMA - Color::from(theme.background.base).into() - }, - dot_color: theme.accent.on.into(), - border_width: 1.0, - border_color: if is_selected { - Color::from(theme.accent.base) - } else { - // TODO: this seems to be defined weirdly in FIGMA - Color::from(theme.palette.neutral_7) - }, - text_color: None, - } + fn default<'a>() -> Self::Class<'a> { + () } - fn hovered(&self, _style: &Self::Style, is_selected: bool) -> radio::Appearance { + fn style(&self, class: &Self::Class<'_>, status: radio::Status) -> radio::Style { let theme = self.cosmic(); let mut neutral_10 = theme.palette.neutral_10; neutral_10.alpha = 0.1; - radio::Appearance { - background: if is_selected { - Color::from(theme.accent.base).into() - } else { - // TODO: this seems to be defined weirdly in FIGMA - Color::from(neutral_10).into() + match status { + radio::Status::Active { is_selected } => radio::Style { + background: if is_selected { + Color::from(theme.accent.base).into() + } else { + // TODO: this seems to be defined weirdly in FIGMA + Color::from(theme.background.base).into() + }, + dot_color: theme.accent.on.into(), + border_width: 1.0, + border_color: if is_selected { + Color::from(theme.accent.base) + } else { + // TODO: this seems to be defined weirdly in FIGMA + Color::from(theme.palette.neutral_7) + }, + text_color: None, }, - dot_color: theme.accent.on.into(), - border_width: 1.0, - border_color: if is_selected { - Color::from(theme.accent.base) - } else { - // TODO: this seems to be defined weirdly in FIGMA - Color::from(theme.palette.neutral_7) + radio::Status::Hovered { is_selected } => radio::Style { + background: if is_selected { + Color::from(theme.accent.base).into() + } else { + // TODO: this seems to be defined weirdly in FIGMA + Color::from(neutral_10).into() + }, + dot_color: theme.accent.on.into(), + border_width: 1.0, + border_color: if is_selected { + Color::from(theme.accent.base) + } else { + // TODO: this seems to be defined weirdly in FIGMA + Color::from(theme.palette.neutral_7) + }, + text_color: None, }, - text_color: None, } } } @@ -818,44 +857,56 @@ impl radio::StyleSheet for Theme { /* * Toggler */ -impl toggler::StyleSheet for Theme { - type Style = (); +impl toggler::Catalog for Theme { + type Class<'a> = (); - fn active(&self, _style: &Self::Style, is_active: bool) -> toggler::Appearance { - let theme = self.cosmic(); + fn default<'a>() -> Self::Class<'a> { + () + } + + fn style(&self, class: &Self::Class<'_>, status: toggler::Status) -> toggler::Style { + let cosmic = self.cosmic(); const HANDLE_MARGIN: f32 = 2.0; - toggler::Appearance { - background: if is_active { - theme.accent.base.into() + let mut neutral_10 = cosmic.palette.neutral_10; + neutral_10.alpha = 0.1; + + let mut active = toggler::Style { + background: if matches!(status, toggler::Status::Active { is_toggled: true }) { + cosmic.accent.base.into() } else { - theme.palette.neutral_5.into() + cosmic.palette.neutral_5.into() }, - background_border: None, - foreground: theme.palette.neutral_2.into(), - foreground_border: None, - border_radius: theme.radius_xl().into(), - handle_radius: theme + foreground: cosmic.palette.neutral_2.into(), + border_radius: cosmic.radius_xl().into(), + handle_radius: cosmic .radius_xl() .map(|x| (x - HANDLE_MARGIN).max(0.0)) .into(), handle_margin: HANDLE_MARGIN, - } - } - - fn hovered(&self, style: &Self::Style, is_active: bool) -> toggler::Appearance { - let cosmic = self.cosmic(); - //TODO: grab colors from palette - let mut neutral_10 = cosmic.palette.neutral_10; - neutral_10.alpha = 0.1; - - toggler::Appearance { - background: if is_active { - over(neutral_10, cosmic.accent_color()) - } else { - over(neutral_10, cosmic.palette.neutral_5) + background_border_width: 0.0, + background_border_color: Color::TRANSPARENT, + foreground_border_width: 0.0, + foreground_border_color: Color::TRANSPARENT, + }; + match status { + toggler::Status::Active { is_toggled } => active, + toggler::Status::Hovered { is_toggled } => { + let is_active = matches!(status, toggler::Status::Hovered { is_toggled: true }); + toggler::Style { + background: if is_active { + over(neutral_10, cosmic.accent_color()) + } else { + over(neutral_10, cosmic.palette.neutral_5) + } + .into(), + ..active + } + } + toggler::Status::Disabled => { + active.background.a /= 2.; + active.foreground.a /= 2.; + active } - .into(), - ..self.active(style, is_active) } } } @@ -863,35 +914,32 @@ impl toggler::StyleSheet for Theme { /* * TODO: Pane Grid */ -impl pane_grid::StyleSheet for Theme { - type Style = (); +impl pane_grid::Catalog for Theme { + type Class<'a> = (); - fn picked_split(&self, _style: &Self::Style) -> Option { - let theme = self.cosmic(); - - Some(pane_grid::Line { - color: theme.accent.base.into(), - width: 2.0, - }) + fn default<'a>() -> ::Class<'a> { + () } - fn hovered_split(&self, _style: &Self::Style) -> Option { + fn style(&self, class: &::Class<'_>) -> pane_grid::Style { let theme = self.cosmic(); - Some(pane_grid::Line { - color: theme.accent.hover.into(), - width: 2.0, - }) - } - - fn hovered_region(&self, _style: &Self::Style) -> pane_grid::Appearance { - let theme = self.cosmic(); - pane_grid::Appearance { - background: Background::Color(theme.bg_color().into()), - border: Border { - radius: theme.corner_radii.radius_0.into(), + pane_grid::Style { + hovered_region: Highlight { + background: Background::Color(theme.bg_color().into()), + border: Border { + radius: theme.corner_radii.radius_0.into(), + width: 2.0, + color: theme.bg_divider().into(), + }, + }, + picked_split: pane_grid::Line { + color: theme.accent.base.into(), + width: 2.0, + }, + hovered_split: pane_grid::Line { + color: theme.accent.hover.into(), width: 2.0, - color: theme.bg_divider().into(), }, } } @@ -906,36 +954,52 @@ pub enum ProgressBar { Primary, Success, Danger, - Custom(Box progress_bar::Appearance>), + Custom(Box progress_bar::Style>), } impl ProgressBar { - pub fn custom progress_bar::Appearance + 'static>(f: F) -> Self { + pub fn custom progress_bar::Style + 'static>(f: F) -> Self { Self::Custom(Box::new(f)) } } -impl progress_bar::StyleSheet for Theme { - type Style = ProgressBar; +impl progress_bar::Catalog for Theme { + type Class<'a> = ProgressBar; - fn appearance(&self, style: &Self::Style) -> progress_bar::Appearance { + fn default<'a>() -> Self::Class<'a> { + ProgressBar::default() + } + + fn style(&self, class: &Self::Class<'_>) -> progress_bar::Style { let theme = self.cosmic(); - match style { - ProgressBar::Primary => progress_bar::Appearance { + match class { + ProgressBar::Primary => progress_bar::Style { background: Color::from(theme.background.divider).into(), bar: Color::from(theme.accent.base).into(), - border_radius: theme.corner_radii.radius_xs.into(), + border: Border { + color: Color::default(), + width: 0.0, + radius: theme.corner_radii.radius_xs.into(), + }, }, - ProgressBar::Success => progress_bar::Appearance { + ProgressBar::Success => progress_bar::Style { background: Color::from(theme.background.divider).into(), bar: Color::from(theme.success.base).into(), - border_radius: theme.corner_radii.radius_xs.into(), + border: Border { + color: Color::default(), + width: 0.0, + radius: theme.corner_radii.radius_xs.into(), + }, }, - ProgressBar::Danger => progress_bar::Appearance { + ProgressBar::Danger => progress_bar::Style { background: Color::from(theme.background.divider).into(), bar: Color::from(theme.destructive.base).into(), - border_radius: theme.corner_radii.radius_xs.into(), + border: Border { + color: Color::default(), + width: 0.0, + radius: theme.corner_radii.radius_xs.into(), + }, }, ProgressBar::Custom(f) => f(self), } @@ -951,33 +1015,37 @@ pub enum Rule { Default, LightDivider, HeavyDivider, - Custom(Box rule::Appearance>), + Custom(Box rule::Style>), } impl Rule { - pub fn custom rule::Appearance + 'static>(f: F) -> Self { + pub fn custom rule::Style + 'static>(f: F) -> Self { Self::Custom(Box::new(f)) } } -impl rule::StyleSheet for Theme { - type Style = Rule; +impl rule::Catalog for Theme { + type Class<'a> = Rule; - fn appearance(&self, style: &Self::Style) -> rule::Appearance { - match style { - Rule::Default => rule::Appearance { + fn default<'a>() -> Self::Class<'a> { + Rule::default() + } + + fn style(&self, class: &Self::Class<'_>) -> rule::Style { + match class { + Rule::Default => rule::Style { color: self.current_container().divider.into(), width: 1, radius: 0.0.into(), fill_mode: rule::FillMode::Full, }, - Rule::LightDivider => rule::Appearance { + Rule::LightDivider => rule::Style { color: self.current_container().divider.into(), width: 1, radius: 0.0.into(), fill_mode: rule::FillMode::Padded(8), }, - Rule::HeavyDivider => rule::Appearance { + Rule::HeavyDivider => rule::Style { color: self.current_container().divider.into(), width: 4, radius: 2.0.into(), @@ -998,99 +1066,148 @@ pub enum Scrollable { /* * TODO: Scrollable */ -impl scrollable::StyleSheet for Theme { - type Style = Scrollable; +impl scrollable::Catalog for Theme { + type Class<'a> = Scrollable; - fn active(&self, style: &Self::Style) -> scrollable::Scrollbar { - let cosmic = self.cosmic(); - let mut neutral_5 = cosmic.palette.neutral_5; - neutral_5.alpha = 0.7; - let mut a = scrollable::Scrollbar { - background: None, - border: Border { - radius: cosmic.corner_radii.radius_s.into(), - ..Default::default() - }, - scroller: scrollable::Scroller { - color: neutral_5.into(), - border: Border { - radius: cosmic.corner_radii.radius_s.into(), - ..Default::default() - }, - }, - }; - - if matches!(style, Scrollable::Permanent) { - let mut neutral_3 = cosmic.palette.neutral_3; - neutral_3.alpha = 0.7; - a.background = Some(Background::Color(neutral_3.into())); - } - - a + fn default<'a>() -> Self::Class<'a> { + Scrollable::default() } - fn hovered(&self, style: &Self::Style, is_mouse_over_scrollbar: bool) -> scrollable::Scrollbar { - let cosmic = self.cosmic(); - let mut neutral_5 = cosmic.palette.neutral_5; - neutral_5.alpha = 0.7; + fn style(&self, class: &Self::Class<'_>, status: scrollable::Status) -> scrollable::Style { + match status { + scrollable::Status::Active => { + let cosmic = self.cosmic(); + let mut neutral_5 = cosmic.palette.neutral_5; + neutral_5.alpha = 0.7; + let mut a = scrollable::Style { + container: iced_container::transparent(self), + vertical_rail: scrollable::Rail { + border: Border { + radius: cosmic.corner_radii.radius_s.into(), + ..Default::default() + }, + background: None, + scroller: scrollable::Scroller { + color: neutral_5.into(), + border: Border { + radius: cosmic.corner_radii.radius_s.into(), + ..Default::default() + }, + }, + }, + horizontal_rail: scrollable::Rail { + border: Border { + radius: cosmic.corner_radii.radius_s.into(), + ..Default::default() + }, + background: None, + scroller: scrollable::Scroller { + color: neutral_5.into(), + border: Border { + radius: cosmic.corner_radii.radius_s.into(), + ..Default::default() + }, + }, + }, + gap: None, + }; - if is_mouse_over_scrollbar { - let mut hover_overlay = cosmic.palette.neutral_0; - hover_overlay.alpha = 0.2; - neutral_5 = over(hover_overlay, neutral_5); - } - let mut a = scrollable::Scrollbar { - background: None, - border: Border { - radius: cosmic.corner_radii.radius_s.into(), - ..Default::default() - }, - scroller: scrollable::Scroller { - color: neutral_5.into(), - border: Border { - radius: cosmic.corner_radii.radius_s.into(), - ..Default::default() - }, - }, - }; - if matches!(style, Scrollable::Permanent) { - let mut neutral_3 = cosmic.palette.neutral_3; - neutral_3.alpha = 0.7; - a.background = Some(Background::Color(neutral_3.into())); - } + if matches!(class, Scrollable::Permanent) { + let mut neutral_3 = cosmic.palette.neutral_3; + neutral_3.alpha = 0.7; + a.horizontal_rail.background = Some(Background::Color(neutral_3.into())); + a.vertical_rail.background = Some(Background::Color(neutral_3.into())); + } - a + a + } + // TODO handle vertical / horizontal + scrollable::Status::Hovered { .. } | scrollable::Status::Dragged { .. } => { + let cosmic = self.cosmic(); + let mut neutral_5 = cosmic.palette.neutral_5; + neutral_5.alpha = 0.7; + + // TODO hover + + // if is_mouse_over_scrollbar { + // let mut hover_overlay = cosmic.palette.neutral_0; + // hover_overlay.alpha = 0.2; + // neutral_5 = over(hover_overlay, neutral_5); + // } + let mut a: scrollable::Style = scrollable::Style { + container: iced_container::Style::default(), + vertical_rail: scrollable::Rail { + border: Border { + radius: cosmic.corner_radii.radius_s.into(), + ..Default::default() + }, + background: None, + scroller: scrollable::Scroller { + color: neutral_5.into(), + border: Border { + radius: cosmic.corner_radii.radius_s.into(), + ..Default::default() + }, + }, + }, + horizontal_rail: scrollable::Rail { + border: Border { + radius: cosmic.corner_radii.radius_s.into(), + ..Default::default() + }, + background: None, + scroller: scrollable::Scroller { + color: neutral_5.into(), + border: Border { + radius: cosmic.corner_radii.radius_s.into(), + ..Default::default() + }, + }, + }, + gap: None, + }; + + if matches!(class, Scrollable::Permanent) { + let mut neutral_3 = cosmic.palette.neutral_3; + neutral_3.alpha = 0.7; + a.horizontal_rail.background = Some(Background::Color(neutral_3.into())); + a.vertical_rail.background = Some(Background::Color(neutral_3.into())); + } + + a + } + } } } #[derive(Clone, Default)] pub enum Svg { /// Apply a custom appearance filter - Custom(Rc svg::Appearance>), + Custom(Rc svg::Style>), /// No filtering is applied #[default] Default, } impl Svg { - pub fn custom svg::Appearance + 'static>(f: F) -> Self { + pub fn custom svg::Style + 'static>(f: F) -> Self { Self::Custom(Rc::new(f)) } } -impl svg::StyleSheet for Theme { - type Style = Svg; +impl svg::Catalog for Theme { + type Class<'a> = Svg; - fn appearance(&self, style: &Self::Style) -> svg::Appearance { - #[allow(clippy::match_same_arms)] - match style { - Svg::Default => svg::Appearance::default(), - Svg::Custom(appearance) => appearance(self), - } + fn default<'a>() -> Self::Class<'a> { + Svg::default() } - fn hovered(&self, style: &Self::Style) -> svg::Appearance { - self.appearance(style) + fn style(&self, class: &Self::Class<'_>, status: svg::Status) -> svg::Style { + #[allow(clippy::match_same_arms)] + match class { + Svg::Default => svg::Style::default(), + Svg::Custom(appearance) => appearance(self), + } } } @@ -1104,7 +1221,7 @@ pub enum Text { Default, Color(Color), // TODO: Can't use dyn Fn since this must be copy - Custom(fn(&Theme) -> iced_widget::text::Appearance), + Custom(fn(&Theme) -> iced_widget::text::Style), } impl From for Text { @@ -1113,16 +1230,20 @@ impl From for Text { } } -impl iced_widget::text::StyleSheet for Theme { - type Style = Text; +impl iced_widget::text::Catalog for Theme { + type Class<'a> = Text; - fn appearance(&self, style: Self::Style) -> iced_widget::text::Appearance { - match style { - Text::Accent => iced_widget::text::Appearance { + fn default<'a>() -> Self::Class<'a> { + Text::default() + } + + fn style(&self, class: &Self::Class<'_>) -> iced_widget::text::Style { + match class { + Text::Accent => iced_widget::text::Style { color: Some(self.cosmic().accent.base.into()), }, - Text::Default => iced_widget::text::Appearance { color: None }, - Text::Color(c) => iced_widget::text::Appearance { color: Some(c) }, + Text::Default => iced_widget::text::Style { color: None }, + Text::Color(c) => iced_widget::text::Style { color: Some(*c) }, Text::Custom(f) => f(self), } } @@ -1138,221 +1259,213 @@ pub enum TextInput { /* * TODO: Text Input */ -impl text_input::StyleSheet for Theme { - type Style = TextInput; +impl text_input::Catalog for Theme { + type Class<'a> = TextInput; - fn active(&self, style: &Self::Style) -> text_input::Appearance { + fn default<'a>() -> Self::Class<'a> { + TextInput::default() + } + + fn style(&self, class: &Self::Class<'_>, status: text_input::Status) -> text_input::Style { let palette = self.cosmic(); let mut bg = palette.palette.neutral_7; bg.alpha = 0.25; - match style { - TextInput::Default => text_input::Appearance { + + let mut neutral_9 = palette.palette.neutral_9; + let value = neutral_9.into(); + neutral_9.alpha = 0.7; + let placeholder = neutral_9.into(); + let selection = palette.accent.base.into(); + + let mut appearance = match class { + TextInput::Default => text_input::Style { background: Color::from(bg).into(), border: Border { radius: palette.corner_radii.radius_s.into(), width: 1.0, color: self.current_container().component.divider.into(), }, - icon_color: self.current_container().on.into(), + icon: self.current_container().on.into(), + placeholder, + value, + selection, }, - TextInput::Search => text_input::Appearance { + TextInput::Search => text_input::Style { background: Color::from(bg).into(), border: Border { radius: palette.corner_radii.radius_m.into(), ..Default::default() }, - icon_color: self.current_container().on.into(), + icon: self.current_container().on.into(), + placeholder, + value, + selection, }, + }; + + match status { + text_input::Status::Active => appearance, + text_input::Status::Hovered => { + let mut bg = palette.palette.neutral_7; + bg.alpha = 0.25; + + match class { + TextInput::Default => text_input::Style { + background: Color::from(bg).into(), + border: Border { + radius: palette.corner_radii.radius_s.into(), + width: 1.0, + color: self.current_container().on.into(), + }, + icon: self.current_container().on.into(), + placeholder, + value, + selection, + }, + TextInput::Search => text_input::Style { + background: Color::from(bg).into(), + border: Border { + radius: palette.corner_radii.radius_m.into(), + ..Default::default() + }, + icon: self.current_container().on.into(), + placeholder, + value, + selection, + }, + } + } + text_input::Status::Focused => { + let mut bg = palette.palette.neutral_7; + bg.alpha = 0.25; + + match class { + TextInput::Default => text_input::Style { + background: Color::from(bg).into(), + border: Border { + radius: palette.corner_radii.radius_s.into(), + width: 1.0, + color: palette.accent.base.into(), + }, + icon: self.current_container().on.into(), + placeholder, + value, + selection, + }, + TextInput::Search => text_input::Style { + background: Color::from(bg).into(), + border: Border { + radius: palette.corner_radii.radius_m.into(), + ..Default::default() + }, + icon: self.current_container().on.into(), + placeholder, + value, + selection, + }, + } + } + text_input::Status::Disabled => { + appearance.background = match appearance.background { + Background::Color(color) => Background::Color(Color { + a: color.a * 0.5, + ..color + }), + Background::Gradient(gradient) => { + Background::Gradient(gradient.scale_alpha(0.5)) + } + }; + appearance.border.color.a /= 2.; + appearance.icon.a /= 2.; + appearance.placeholder.a /= 2.; + appearance.value.a /= 2.; + appearance + } } } - - fn hovered(&self, style: &Self::Style) -> text_input::Appearance { - let palette = self.cosmic(); - let mut bg = palette.palette.neutral_7; - bg.alpha = 0.25; - - match style { - TextInput::Default => text_input::Appearance { - background: Color::from(bg).into(), - border: Border { - radius: palette.corner_radii.radius_s.into(), - width: 1.0, - color: self.current_container().on.into(), - }, - icon_color: self.current_container().on.into(), - }, - TextInput::Search => text_input::Appearance { - background: Color::from(bg).into(), - border: Border { - radius: palette.corner_radii.radius_m.into(), - ..Default::default() - }, - icon_color: self.current_container().on.into(), - }, - } - } - - fn focused(&self, style: &Self::Style) -> text_input::Appearance { - let palette = self.cosmic(); - let mut bg = palette.palette.neutral_7; - bg.alpha = 0.25; - - match style { - TextInput::Default => text_input::Appearance { - background: Color::from(bg).into(), - border: Border { - radius: palette.corner_radii.radius_s.into(), - width: 1.0, - color: palette.accent.base.into(), - }, - icon_color: self.current_container().on.into(), - }, - TextInput::Search => text_input::Appearance { - background: Color::from(bg).into(), - border: Border { - radius: palette.corner_radii.radius_m.into(), - ..Default::default() - }, - icon_color: self.current_container().on.into(), - }, - } - } - - fn placeholder_color(&self, _style: &Self::Style) -> Color { - let palette = self.cosmic(); - let mut neutral_9 = palette.palette.neutral_9; - neutral_9.alpha = 0.7; - neutral_9.into() - } - - fn value_color(&self, _style: &Self::Style) -> Color { - let palette = self.cosmic(); - - palette.palette.neutral_9.into() - } - - fn selection_color(&self, _style: &Self::Style) -> Color { - let palette = self.cosmic(); - - palette.accent.base.into() - } - - fn disabled_color(&self, _style: &Self::Style) -> Color { - let palette = self.cosmic(); - let mut neutral_9 = palette.palette.neutral_9; - neutral_9.alpha = 0.5; - neutral_9.into() - } - - fn disabled(&self, style: &Self::Style) -> text_input::Appearance { - self.active(style) - } } -impl crate::widget::card::style::StyleSheet for Theme { - fn default(&self) -> crate::widget::card::style::Appearance { - let cosmic = self.cosmic(); +// TODO card +// impl crate::widget::card::style::StyleSheet for Theme { +// fn default(&self) -> crate::widget::card::style::Style { +// let cosmic = self.cosmic(); - match self.layer { - cosmic_theme::Layer::Background => crate::widget::card::style::Appearance { - card_1: Background::Color(cosmic.background.component.hover.into()), - card_2: Background::Color(cosmic.background.component.pressed.into()), - }, - cosmic_theme::Layer::Primary => crate::widget::card::style::Appearance { - card_1: Background::Color(cosmic.primary.component.hover.into()), - card_2: Background::Color(cosmic.primary.component.pressed.into()), - }, - cosmic_theme::Layer::Secondary => crate::widget::card::style::Appearance { - card_1: Background::Color(cosmic.secondary.component.hover.into()), - card_2: Background::Color(cosmic.secondary.component.pressed.into()), - }, - } - } -} +// match self.layer { +// cosmic_theme::Layer::Background => crate::widget::card::style::Style { +// card_1: Background::Color(cosmic.background.component.hover.into()), +// card_2: Background::Color(cosmic.background.component.pressed.into()), +// }, +// cosmic_theme::Layer::Primary => crate::widget::card::style::Style { +// card_1: Background::Color(cosmic.primary.component.hover.into()), +// card_2: Background::Color(cosmic.primary.component.pressed.into()), +// }, +// cosmic_theme::Layer::Secondary => crate::widget::card::style::Style { +// card_1: Background::Color(cosmic.secondary.component.hover.into()), +// card_2: Background::Color(cosmic.secondary.component.pressed.into()), +// }, +// } +// } +// } #[derive(Default)] -pub enum TextEditor { +pub enum TextEditor<'a> { #[default] Default, - Custom(Box>), + Custom(text_editor::StyleFn<'a, Theme>), } -impl iced_style::text_editor::StyleSheet for Theme { - type Style = TextEditor; +impl iced_widget::text_editor::Catalog for Theme { + type Class<'a> = TextEditor<'a>; - fn active(&self, style: &Self::Style) -> iced_style::text_editor::Appearance { - if let TextEditor::Custom(style) = style { - return style.active(self); + fn default<'a>() -> Self::Class<'a> { + TextEditor::default() + } + + fn style( + &self, + class: &Self::Class<'_>, + status: iced_widget::text_editor::Status, + ) -> iced_widget::text_editor::Style { + if let TextEditor::Custom(style) = class { + return style(self, status); } let cosmic = self.cosmic(); - iced_style::text_editor::Appearance { - background: iced::Color::from(cosmic.bg_color()).into(), - border: Border { - radius: cosmic.corner_radii.radius_0.into(), - width: f32::from(cosmic.space_xxxs()), - color: iced::Color::from(cosmic.bg_divider()), + + let selection = cosmic.accent.base.into(); + let value = cosmic.palette.neutral_9.into(); + let mut placeholder = cosmic.palette.neutral_9; + placeholder.alpha = 0.7; + let placeholder = placeholder.into(); + let icon = cosmic.background.on.into(); + + match status { + iced_widget::text_editor::Status::Active + | iced_widget::text_editor::Status::Hovered + | iced_widget::text_editor::Status::Disabled => iced_widget::text_editor::Style { + background: iced::Color::from(cosmic.bg_color()).into(), + border: Border { + radius: cosmic.corner_radii.radius_0.into(), + width: f32::from(cosmic.space_xxxs()), + color: iced::Color::from(cosmic.bg_divider()), + }, + icon, + placeholder, + value, + selection, + }, + iced_widget::text_editor::Status::Focused => iced_widget::text_editor::Style { + background: iced::Color::from(cosmic.bg_color()).into(), + border: Border { + radius: cosmic.corner_radii.radius_0.into(), + width: f32::from(cosmic.space_xxxs()), + color: iced::Color::from(cosmic.accent.base), + }, + icon, + placeholder, + value, + selection, }, } } - - fn focused(&self, style: &Self::Style) -> iced_style::text_editor::Appearance { - if let TextEditor::Custom(style) = style { - return style.focused(self); - } - - let cosmic = self.cosmic(); - iced_style::text_editor::Appearance { - background: iced::Color::from(cosmic.bg_color()).into(), - border: Border { - radius: cosmic.corner_radii.radius_0.into(), - width: f32::from(cosmic.space_xxxs()), - color: iced::Color::from(cosmic.accent.base), - }, - } - } - - fn placeholder_color(&self, style: &Self::Style) -> Color { - if let TextEditor::Custom(style) = style { - return style.placeholder_color(self); - } - let palette = self.cosmic(); - let mut neutral_9 = palette.palette.neutral_9; - neutral_9.alpha = 0.7; - neutral_9.into() - } - - fn value_color(&self, style: &Self::Style) -> Color { - if let TextEditor::Custom(style) = style { - return style.value_color(self); - } - let palette = self.cosmic(); - - palette.palette.neutral_9.into() - } - - fn disabled_color(&self, style: &Self::Style) -> Color { - if let TextEditor::Custom(style) = style { - return style.disabled_color(self); - } - let palette = self.cosmic(); - let mut neutral_9 = palette.palette.neutral_9; - neutral_9.alpha = 0.5; - neutral_9.into() - } - - fn selection_color(&self, style: &Self::Style) -> Color { - if let TextEditor::Custom(style) = style { - return style.selection_color(self); - } - let cosmic = self.cosmic(); - cosmic.accent.base.into() - } - - fn disabled(&self, style: &Self::Style) -> iced_style::text_editor::Appearance { - if let TextEditor::Custom(style) = style { - return style.disabled(self); - } - self.active(style) - } } diff --git a/src/theme/style/mod.rs b/src/theme/style/mod.rs index 0469673a..1cbd4ef5 100644 --- a/src/theme/style/mod.rs +++ b/src/theme/style/mod.rs @@ -10,8 +10,6 @@ mod dropdown; pub mod iced; #[doc(inline)] -pub use self::iced::Application; -#[doc(inline)] pub use self::iced::Checkbox; #[doc(inline)] pub use self::iced::Container; diff --git a/src/widget/aspect_ratio.rs b/src/widget/aspect_ratio.rs index c918f8c7..bfc9a78d 100644 --- a/src/widget/aspect_ratio.rs +++ b/src/widget/aspect_ratio.rs @@ -9,9 +9,10 @@ use iced_core::mouse; use iced_core::overlay; use iced_core::renderer; use iced_core::widget::Tree; -use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget}; +use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget}; -pub use iced_style::container::{Appearance, StyleSheet}; +use iced_widget::container; +pub use iced_widget::container::{Catalog, Style}; pub fn aspect_ratio_container<'a, Message: 'static, T>( content: T, @@ -117,22 +118,22 @@ where /// Centers the contents in the horizontal axis of the [`Container`]. #[must_use] - pub fn center_x(mut self) -> Self { - self.container = self.container.center_x(); + pub fn center_x(mut self, width: Length) -> Self { + self.container = self.container.center_x(width); self } /// Centers the contents in the vertical axis of the [`Container`]. #[must_use] - pub fn center_y(mut self) -> Self { - self.container = self.container.center_y(); + pub fn center_y(mut self, height: Length) -> Self { + self.container = self.container.center_y(height); self } /// Sets the style of the [`Container`]. #[must_use] - pub fn style(mut self, style: impl Into<::Style>) -> Self { - self.container = self.container.style(style); + pub fn class(mut self, style: impl Into>) -> Self { + self.container = self.container.class(style); self } } @@ -173,9 +174,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn iced_core::widget::Operation< - iced_core::widget::OperationOutputWrapper, - >, + operation: &mut dyn iced_core::widget::Operation<()>, ) { self.container.operate(tree, layout, renderer, operation); } @@ -241,8 +240,9 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + translation: Vector, ) -> Option> { - self.container.overlay(tree, layout, renderer) + self.container.overlay(tree, layout, renderer, translation) } } diff --git a/src/widget/autosize.rs b/src/widget/autosize.rs new file mode 100644 index 00000000..553f9ea7 --- /dev/null +++ b/src/widget/autosize.rs @@ -0,0 +1,276 @@ +//! Autosize Container, which will resize the window to its contents. + +use cctk::sctk::shell::xdg::window; +use iced_core::event::{self, Event}; +use iced_core::layout; +use iced_core::mouse; +use iced_core::overlay; +use iced_core::renderer; +use iced_core::widget::{Id, Tree}; +use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; +pub use iced_widget::container::{Catalog, Style}; + +pub fn autosize<'a, Message: 'static, Theme, E>( + content: E, + id: Id, +) -> Autosize<'a, Message, Theme, crate::Renderer> +where + E: Into>, + Theme: iced_widget::container::Catalog, + ::Class<'a>: From>, +{ + Autosize::new(content, id) +} + +/// An element decorating some content. +/// +/// It is normally used for alignment purposes. +#[allow(missing_debug_implementations)] +pub struct Autosize<'a, Message, Theme, Renderer> +where + Renderer: iced_core::Renderer, +{ + content: Element<'a, Message, Theme, Renderer>, + id: Id, + limits: layout::Limits, + auto_width: bool, + auto_height: bool, +} + +impl<'a, Message, Theme, Renderer> Autosize<'a, Message, Theme, Renderer> +where + Renderer: iced_core::Renderer, +{ + /// Creates an empty [`IdContainer`]. + pub(crate) fn new(content: T, id: Id) -> Self + where + T: Into>, + { + Autosize { + content: content.into(), + id, + limits: layout::Limits::NONE, + auto_width: true, + auto_height: true, + } + } + + pub fn limits(mut self, limits: layout::Limits) -> Self { + self.limits = limits; + self + } + + pub fn auto_width(mut self, auto_width: bool) -> Self { + self.auto_width = auto_width; + self + } + + pub fn auto_height(mut self, auto_height: bool) -> Self { + self.auto_height = auto_height; + self + } + + pub fn max_width(mut self, v: f32) -> Self { + self.limits = self.limits.max_width(v); + self + } + + pub fn max_height(mut self, v: f32) -> Self { + self.limits = self.limits.max_height(v); + self + } + + pub fn min_width(mut self, v: f32) -> Self { + self.limits = self.limits.min_width(v); + self + } + + pub fn min_height(mut self, v: f32) -> Self { + self.limits = self.limits.min_height(v); + self + } +} + +impl<'a, Message, Theme, Renderer> Widget + for Autosize<'a, Message, Theme, Renderer> +where + Renderer: iced_core::Renderer, +{ + fn children(&self) -> Vec { + vec![Tree::new(&self.content)] + } + + fn diff(&mut self, tree: &mut Tree) { + tree.children[0].diff(&mut self.content); + } + + fn size(&self) -> iced_core::Size { + self.content.as_widget().size() + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + _limits: &layout::Limits, + ) -> layout::Node { + let mut limits = self.limits; + let min = self.limits.min(); + let max = self.limits.max(); + if self.auto_width { + limits.min_width(min.width); + limits.max_width(max.width); + } + if self.auto_height { + limits.min_height(min.height); + limits.max_height(max.height); + } + let node = self + .content + .as_widget() + .layout(&mut tree.children[0], renderer, &self.limits); + let size = node.size(); + layout::Node::with_children(size, vec![node]) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn iced_core::widget::Operation<()>, + ) { + operation.container(Some(&self.id), layout.bounds(), &mut |operation| { + self.content.as_widget().operate( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + operation, + ); + }); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) -> event::Status { + #[cfg(feature = "wayland")] + if matches!( + event, + Event::PlatformSpecific(event::PlatformSpecific::Wayland( + event::wayland::Event::RequestResize + )) + ) { + let bounds = layout.bounds().size(); + clipboard.request_logical_window_size(bounds.width.max(1.), bounds.height.max(1.)); + } + self.content.as_widget_mut().on_event( + &mut tree.children[0], + event.clone(), + layout.children().next().unwrap(), + cursor_position, + renderer, + clipboard, + shell, + viewport, + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + let content_layout = layout.children().next().unwrap(); + self.content.as_widget().mouse_interaction( + &tree.children[0], + content_layout, + cursor_position, + viewport, + renderer, + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + renderer_style: &renderer::Style, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + viewport: &Rectangle, + ) { + let content_layout = layout.children().next().unwrap(); + self.content.as_widget().draw( + &tree.children[0], + renderer, + theme, + renderer_style, + content_layout, + cursor_position, + viewport, + ); + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + translation: Vector, + ) -> Option> { + self.content.as_widget_mut().overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + translation, + ) + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, + ) { + let content_layout = layout.children().next().unwrap(); + self.content.as_widget().drag_destinations( + &state.children[0], + content_layout, + renderer, + dnd_rectangles, + ); + } + + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn set_id(&mut self, id: crate::widget::Id) { + self.id = id; + } +} + +impl<'a, Message, Theme, Renderer> From> + for Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Renderer: 'a + iced_core::Renderer, + Theme: 'a, +{ + fn from(c: Autosize<'a, Message, Theme, Renderer>) -> Element<'a, Message, Theme, Renderer> { + Element::new(c) + } +} diff --git a/src/widget/button/icon.rs b/src/widget/button/icon.rs index 311f8ebc..0322d58c 100644 --- a/src/widget/button/icon.rs +++ b/src/widget/button/icon.rs @@ -1,7 +1,7 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -use super::{Builder, Style}; +use super::{Builder, ButtonClass}; use crate::widget::{ icon::{self, Handle}, tooltip, @@ -48,7 +48,7 @@ impl<'a, Message> Button<'a, Message> { line_height: 20, font_size: 14, font_weight: Weight::Normal, - style: Style::Icon, + class: ButtonClass::Icon, variant: icon, } } @@ -121,7 +121,7 @@ impl<'a, Message> Button<'a, Message> { pub fn vertical(mut self, vertical: bool) -> Self { self.variant.vertical = vertical; - self.style = Style::IconVertical; + self.class = ButtonClass::IconVertical; self } } @@ -157,7 +157,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes crate::widget::column::with_children(content) .padding(builder.padding) .spacing(builder.spacing) - .align_items(Alignment::Center) + .align_x(Alignment::Center) .apply(super::custom) } else { crate::widget::row::with_children(content) @@ -165,7 +165,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .width(builder.width) .height(builder.height) .spacing(builder.spacing) - .align_items(Alignment::Center) + .align_y(Alignment::Center) .apply(super::custom) }; @@ -174,18 +174,22 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .id(builder.id) .on_press_maybe(builder.on_press) .selected(builder.variant.selected) - .style(builder.style); + .class(builder.class); if builder.tooltip.is_empty() { button.into() } else { - tooltip(button, builder.tooltip, tooltip::Position::Top) - .size(builder.font_size) - .font(crate::font::Font { - weight: builder.font_weight, - ..crate::font::default() - }) - .into() + tooltip( + button, + crate::widget::text(builder.tooltip) + .size(builder.font_size) + .font(crate::font::Font { + weight: builder.font_weight, + ..crate::font::default() + }), + tooltip::Position::Top, + ) + .into() } } } diff --git a/src/widget/button/image.rs b/src/widget/button/image.rs index d93844cd..a0508aaf 100644 --- a/src/widget/button/image.rs +++ b/src/widget/button/image.rs @@ -42,7 +42,7 @@ impl<'a, Message> Button<'a, Message> { line_height: 20, font_size: 14, font_weight: Weight::Normal, - style: Style::Image, + class: crate::theme::style::Button::Image, variant, } } @@ -80,7 +80,7 @@ where .selected(builder.variant.selected) .id(builder.id) .on_press_maybe(builder.on_press) - .style(builder.style) + .class(builder.class) .into() } } diff --git a/src/widget/button/link.rs b/src/widget/button/link.rs index e62b05bf..77527ecd 100644 --- a/src/widget/button/link.rs +++ b/src/widget/button/link.rs @@ -4,7 +4,7 @@ //! Hyperlink button widget use super::Builder; -use super::Style; +use super::ButtonClass; use crate::prelude::*; use crate::widget::icon::{self, Handle}; use crate::widget::{button, row, tooltip}; @@ -44,7 +44,7 @@ impl<'a, Message> Button<'a, Message> { line_height: 20, font_size: 14, font_weight: Weight::Normal, - style: Style::Link, + class: ButtonClass::Link, variant: link, } } @@ -81,23 +81,27 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .width(builder.width) .height(builder.height) .spacing(builder.spacing) - .align_items(Alignment::Center) + .align_y(Alignment::Center) .apply(button::custom) .padding(0) .id(builder.id) .on_press_maybe(builder.on_press.take()) - .style(builder.style); + .class(builder.class); if builder.tooltip.is_empty() { button.into() } else { - tooltip(button, builder.tooltip, tooltip::Position::Top) - .size(builder.font_size) - .font(crate::font::Font { - weight: builder.font_weight, - ..crate::font::default() - }) - .into() + tooltip( + button, + crate::widget::text(builder.tooltip) + .size(builder.font_size) + .font(crate::font::Font { + weight: builder.font_weight, + ..crate::font::default() + }), + tooltip::Position::Top, + ) + .into() } } } diff --git a/src/widget/button/mod.rs b/src/widget/button/mod.rs index 4a83b12e..6bf6338a 100644 --- a/src/widget/button/mod.rs +++ b/src/widget/button/mod.rs @@ -3,7 +3,7 @@ //! Button widgets for COSMIC applications. -pub use crate::theme::Button as Style; +pub use crate::theme::Button as ButtonClass; pub mod link; use derive_setters::Setters; @@ -26,7 +26,7 @@ pub use image::Button as ImageButton; mod style; #[doc(inline)] -pub use style::{Appearance, StyleSheet}; +pub use style::{Catalog, Style}; mod text; #[doc(inline)] @@ -105,7 +105,7 @@ pub struct Builder<'a, Message, Variant> { font_weight: Weight, /// The preferred style of the button. - style: Style, + class: ButtonClass, #[setters(skip)] variant: Variant, diff --git a/src/widget/button/style.rs b/src/widget/button/style.rs index 87f27770..36100114 100644 --- a/src/widget/button/style.rs +++ b/src/widget/button/style.rs @@ -9,7 +9,7 @@ use crate::theme::THEME; /// The appearance of a button. #[must_use] #[derive(Debug, Clone, Copy)] -pub struct Appearance { +pub struct Style { /// The amount of offset to apply to the shadow of the button. pub shadow_offset: Vector, @@ -41,7 +41,7 @@ pub struct Appearance { pub text_color: Option, } -impl Appearance { +impl Style { // TODO: `Radius` is not `const fn` compatible. pub fn new() -> Self { let rad_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0; @@ -60,33 +60,34 @@ impl Appearance { } } -impl std::default::Default for Appearance { +impl std::default::Default for Style { fn default() -> Self { Self::new() } } +// TODO update to match other styles /// A set of rules that dictate the style of a button. -pub trait StyleSheet { +pub trait Catalog { /// The supported style of the [`StyleSheet`]. - type Style: Default; + type Class: Default; /// Produces the active [`Appearance`] of a button. - fn active(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance; + fn active(&self, focused: bool, selected: bool, style: &Self::Class) -> Style; /// Produces the disabled [`Appearance`] of a button. - fn disabled(&self, style: &Self::Style) -> Appearance; + fn disabled(&self, style: &Self::Class) -> Style; /// [`Appearance`] when the button is the target of a DND operation. - fn drop_target(&self, style: &Self::Style) -> Appearance { + fn drop_target(&self, style: &Self::Class) -> Style { self.hovered(false, false, style) } /// Produces the hovered [`Appearance`] of a button. - fn hovered(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance; + fn hovered(&self, focused: bool, selected: bool, style: &Self::Class) -> Style; /// Produces the pressed [`Appearance`] of a button. - fn pressed(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance; + fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style; /// Background color of the selection indicator fn selection_background(&self) -> Background; diff --git a/src/widget/button/text.rs b/src/widget/button/text.rs index 0a59976a..30476243 100644 --- a/src/widget/button/text.rs +++ b/src/widget/button/text.rs @@ -1,7 +1,7 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -use super::{Builder, Style}; +use super::{Builder, ButtonClass, Style}; use crate::widget::{icon, row, tooltip}; use crate::{ext::CollectionWidget, Element}; use apply::Apply; @@ -14,14 +14,14 @@ pub type Button<'a, Message> = Builder<'a, Message, Text>; pub fn destructive<'a, Message>(label: impl Into>) -> Button<'a, Message> { Button::new(Text::new()) .label(label) - .style(Style::Destructive) + .class(ButtonClass::Destructive) } /// A text button with the suggested style pub fn suggested<'a, Message>(label: impl Into>) -> Button<'a, Message> { Button::new(Text::new()) .label(label) - .style(Style::Suggested) + .class(ButtonClass::Suggested) } /// A text button with the standard style @@ -31,7 +31,9 @@ pub fn standard<'a, Message>(label: impl Into>) -> Button<'a, Messa /// A text button with the text style pub fn text<'a, Message>(label: impl Into>) -> Button<'a, Message> { - Button::new(Text::new()).label(label).style(Style::Text) + Button::new(Text::new()) + .label(label) + .class(ButtonClass::Text) } /// The text variant of a button. @@ -66,7 +68,7 @@ impl<'a, Message> Button<'a, Message> { line_height: 20, font_size: 14, font_weight: Weight::Normal, - style: Style::Standard, + class: ButtonClass::Standard, variant: text, } } @@ -125,23 +127,27 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .width(builder.width) .height(builder.height) .spacing(builder.spacing) - .align_items(Alignment::Center) + .align_y(Alignment::Center) .apply(super::custom) .padding(0) .id(builder.id) .on_press_maybe(builder.on_press.take()) - .style(builder.style); + .class(builder.class); if builder.tooltip.is_empty() { button.into() } else { - tooltip(button, builder.tooltip, tooltip::Position::Top) - .size(builder.font_size) - .font(crate::font::Font { - weight: builder.font_weight, - ..crate::font::default() - }) - .into() + tooltip( + button, + crate::widget::text(builder.tooltip) + .size(builder.font_size) + .font(crate::font::Font { + weight: builder.font_weight, + ..crate::font::default() + }), + tooltip::Position::Top, + ) + .into() } } } diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 9dd969f0..1c0ef257 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -7,7 +7,7 @@ //! A [`Button`] has some local [`State`]. use iced_runtime::core::widget::Id; -use iced_runtime::{keyboard, Command}; +use iced_runtime::{keyboard, task, Action, Task}; use iced_core::event::{self, Event}; use iced_core::renderer::{self, Quad, Renderer}; @@ -20,11 +20,11 @@ use iced_core::{overlay, Shadow}; use iced_core::{ Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget, }; -use iced_renderer::core::widget::{operation, OperationOutputWrapper}; +use iced_renderer::core::widget::operation; use crate::theme::THEME; -pub use super::style::{Appearance, StyleSheet}; +pub use super::style::{Catalog, Style}; /// Internally defines different button widget variants. enum Variant { @@ -173,7 +173,7 @@ impl<'a, Message> Button<'a, Message> { } /// Sets the style variant of this [`Button`]. - pub fn style(mut self, style: crate::theme::Button) -> Self { + pub fn class(mut self, style: crate::theme::Button) -> Self { self.style = style; self } @@ -257,7 +257,7 @@ impl<'a, Message: 'a + Clone> Widget tree: &mut Tree, layout: Layout<'_>, renderer: &crate::Renderer, - operation: &mut dyn Operation>, + operation: &mut dyn Operation<()>, ) { operation.container(None, layout.bounds(), &mut |operation| { self.content.as_widget().operate( @@ -470,10 +470,11 @@ impl<'a, Message: 'a + Clone> Widget selection_background, ); - iced_core::svg::Renderer::draw( + let svg_handle = svg::Svg::new(crate::widget::common::object_select().clone()) + .color(icon_color); + iced_core::svg::Renderer::draw_svg( renderer, - crate::widget::common::object_select().clone(), - Some(icon_color), + svg_handle, Rectangle { width: 16.0, height: 16.0, @@ -498,11 +499,10 @@ impl<'a, Message: 'a + Clone> Widget }, selection_background, ); - - iced_core::svg::Renderer::draw( + let svg_handle = svg::Svg::new(close_icon.clone()).color(icon_color); + iced_core::svg::Renderer::draw_svg( renderer, - close_icon.clone(), - Some(icon_color), + svg_handle, Rectangle { width: 16.0, height: 16.0, @@ -533,11 +533,16 @@ impl<'a, Message: 'a + Clone> Widget tree: &'b mut Tree, layout: Layout<'_>, renderer: &crate::Renderer, + mut translation: Vector, ) -> Option> { + let mut position = layout.bounds().position(); + translation.x += position.x; + translation.y += position.y; self.content.as_widget_mut().overlay( &mut tree.children[0], layout.children().next().unwrap(), renderer, + translation, ) } @@ -748,11 +753,11 @@ pub fn update<'a, Message: Clone>( pub fn draw( renderer: &mut Renderer, bounds: Rectangle, - styling: &super::style::Appearance, - draw_contents: impl FnOnce(&mut Renderer, &Appearance), + styling: &super::style::Style, + draw_contents: impl FnOnce(&mut Renderer, &Style), is_image: bool, ) where - Theme: super::style::StyleSheet, + Theme: super::style::Catalog, { let doubled_border_width = styling.border_width * 2.0; let doubled_outline_width = styling.outline_width * 2.0; @@ -909,9 +914,9 @@ pub fn mouse_interaction( } } -/// Produces a [`Command`] that focuses the [`Button`] with the given [`Id`]. -pub fn focus(id: Id) -> Command { - Command::widget(operation::focusable::focus(id)) +/// Produces a [`Task`] that focuses the [`Button`] with the given [`Id`]. +pub fn focus(id: Id) -> Task { + task::effect(Action::Widget(Box::new(operation::focusable::focus(id)))) } impl operation::Focusable for State { diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index f86841a9..bcfcda65 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -86,7 +86,7 @@ where text(first_day_of_week.to_string()) .size(12) .width(Length::Fixed(36.0)) - .horizontal_alignment(Horizontal::Center), + .align_x(Horizontal::Center), ); first_day_of_week = first_day_of_week.succ(); @@ -138,17 +138,17 @@ fn date_button( on_select: &dyn Fn(NaiveDate) -> Message, ) -> crate::widget::Button<'static, Message> { let style = if is_day { - button::Style::Suggested + button::ButtonClass::Suggested } else { - button::Style::Text + button::ButtonClass::Text }; let button = button::custom( text(format!("{}", date.day())) - .horizontal_alignment(Horizontal::Center) - .vertical_alignment(Vertical::Center), + .align_x(Horizontal::Center) + .align_y(Vertical::Center), ) - .style(style) + .class(style) .height(Length::Fixed(36.0)) .width(Length::Fixed(36.0)); diff --git a/src/widget/card/style.rs b/src/widget/card/style.rs index 1a3d2935..d73ef8fc 100644 --- a/src/widget/card/style.rs +++ b/src/widget/card/style.rs @@ -5,12 +5,12 @@ use iced_core::{Background, Color}; /// Appearance of the cards. #[derive(Clone, Copy)] -pub struct Appearance { +pub struct Style { pub card_1: Background, pub card_2: Background, } -impl Default for Appearance { +impl Default for Style { fn default() -> Self { Self { card_1: Background::Color(Color::WHITE), @@ -20,7 +20,7 @@ impl Default for Appearance { } /// Defines the [`Appearance`] of a cards. -pub trait StyleSheet { +pub trait Catalog { /// The default [`Appearance`] of the cards. - fn default(&self) -> Appearance; + fn default(&self) -> Style; } diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index d7f42adf..04199590 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -10,10 +10,10 @@ use std::time::{Duration, Instant}; use crate::theme::iced::Slider; use crate::theme::{Button, THEME}; -use crate::widget::{container, segmented_button::Entity, slider}; +use crate::widget::{button::Catalog, container, segmented_button::Entity, slider}; use crate::Element; use derive_setters::Setters; -use iced::Command; +use iced::Task; use iced_core::event::{self, Event}; use iced_core::gradient::{ColorStop, Linear}; use iced_core::renderer::Quad; @@ -23,12 +23,11 @@ use iced_core::{ Rectangle, Renderer, Shadow, Shell, Size, Vector, Widget, }; -use iced_style::slider::{HandleShape, RailBackground}; +use iced_widget::slider::{HandleShape, RailBackground}; use iced_widget::{canvas, column, horizontal_space, row, scrollable, vertical_space, Row}; use lazy_static::lazy_static; use palette::{FromColor, RgbHue}; -use super::button::StyleSheet; use super::divider::horizontal; use super::icon::{self, from_name}; use super::segmented_button::{self, SingleSelect}; @@ -135,7 +134,7 @@ impl ColorPickerModel { ) } - pub fn update(&mut self, update: ColorPickerUpdate) -> Command { + pub fn update(&mut self, update: ColorPickerUpdate) -> Task { match update { ColorPickerUpdate::ActiveColor(c) => { self.must_clear_cache.store(true, Ordering::SeqCst); @@ -206,7 +205,7 @@ impl ColorPickerModel { self.copied_at = None; } }; - Command::none() + Task::none() } #[must_use] @@ -298,7 +297,7 @@ where .width(self.width), // canvas with gradient for the current color // still needs the canvas and the handle to be drawn on it - container(vertical_space(self.height)) + container(vertical_space().height(self.height)) .width(self.width) .height(self.height), slider( @@ -310,16 +309,21 @@ where on_update(ColorPickerUpdate::ActiveColor(new)) } ) - .style(Slider::Custom { + .class(Slider::Custom { active: Rc::new(|t| { let cosmic = t.cosmic(); - let mut a = slider::StyleSheet::active(t, &Slider::default()); - a.rail.colors = RailBackground::Gradient { - gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()), - auto_angle: true, - }; + let mut a = + slider::Catalog::style(t, &Slider::default(), slider::Status::Active); + // a.rail.colors = RailBackground::Gradient { + // gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()), + // auto_angle: true, + // }; + a.rail.backgrounds = ( + Background::Color(Color::TRANSPARENT), + Background::Color(Color::TRANSPARENT), + ); a.rail.width = 8.0; - a.handle.color = Color::TRANSPARENT; + a.handle.background = Color::TRANSPARENT.into(); a.handle.shape = HandleShape::Circle { radius: 8.0 }; a.handle.border_color = cosmic.palette.neutral_10.into(); a.handle.border_width = 4.0; @@ -327,13 +331,15 @@ where }), hovered: Rc::new(|t| { let cosmic = t.cosmic(); - let mut a = slider::StyleSheet::active(t, &Slider::default()); - a.rail.colors = RailBackground::Gradient { - gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()), - auto_angle: true, - }; + let mut a = + slider::Catalog::style(t, &Slider::default(), slider::Status::Active); + // a.rail.colors = RailBackground::Gradient { + // gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()), + // auto_angle: true, + // }; + a.rail.backgrounds = (Color::TRANSPARENT.into(), Color::TRANSPARENT.into()); a.rail.width = 8.0; - a.handle.color = Color::TRANSPARENT; + a.handle.background = Color::TRANSPARENT.into(); a.handle.shape = HandleShape::Circle { radius: 8.0 }; a.handle.border_color = cosmic.palette.neutral_10.into(); a.handle.border_width = 4.0; @@ -341,13 +347,22 @@ where }), dragging: Rc::new(|t| { let cosmic = t.cosmic(); - let mut a = slider::StyleSheet::active(t, &Slider::default()); - a.rail.colors = RailBackground::Gradient { - gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()), - auto_angle: true, - }; + let mut a = + slider::Catalog::style(t, &Slider::default(), slider::Status::Active); + // a.rail.backgrounds = ( + // RailBackground::Gradient(Gradient { + // gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()), + // auto_angle: true, + // }), + // RailBackground::Gradient { + // gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()), + // auto_angle: true, + // }, + // ); + a.rail.backgrounds = + (iced::Color::TRANSPARENT.into(), Color::TRANSPARENT.into()); a.rail.width = 8.0; - a.handle.color = Color::TRANSPARENT; + a.handle.background = Color::TRANSPARENT.into(); a.handle.shape = HandleShape::Circle { radius: 8.0 }; a.handle.border_color = cosmic.palette.neutral_10.into(); a.handle.border_width = 4.0; @@ -373,7 +388,7 @@ where from_name("edit-copy-symbolic").size(spacing.space_s).into(), )) .on_press(on_update(ColorPickerUpdate::Copied(Instant::now()))) - .style(Button::Text); + .class(Button::Text); match self.copied_at.take() { Some(t) if Instant::now().duration_since(t) > Duration::from_secs(2) => { @@ -381,13 +396,13 @@ where } Some(_) => tooltip( button, - copied_to_clipboard_label, + text(copied_to_clipboard_label), iced_widget::tooltip::Position::Bottom, ) .into(), None => tooltip( button, - copy_to_clipboard_label, + text(copy_to_clipboard_label), iced_widget::tooltip::Position::Bottom, ) .into(), @@ -431,7 +446,7 @@ where ) .width(self.width) .direction(iced_widget::scrollable::Direction::Horizontal( - scrollable::Properties::new().alignment(scrollable::Alignment::End), + scrollable::Scrollbar::new().anchor(scrollable::Anchor::End), )) }] .spacing(spacing.space_xxs), @@ -445,7 +460,7 @@ where button::custom( text(reset_to_default) .width(self.width) - .horizontal_alignment(iced_core::alignment::Horizontal::Center) + .align_x(iced_core::alignment::Horizontal::Center) ) .width(self.width) .on_press(on_update(ColorPickerUpdate::Reset)) @@ -461,18 +476,18 @@ where button::custom( text(cancel) .width(self.width) - .horizontal_alignment(iced_core::alignment::Horizontal::Center) + .align_x(iced_core::alignment::Horizontal::Center) ) .width(self.width) .on_press(on_update(ColorPickerUpdate::Cancel)), button::custom( text(save) .width(self.width) - .horizontal_alignment(iced_core::alignment::Horizontal::Center) + .align_x(iced_core::alignment::Horizontal::Center) ) .width(self.width) .on_press(on_update(ColorPickerUpdate::AppliedColor)) - .style(Button::Suggested) + .class(Button::Suggested) ] .spacing(spacing.space_xs) .width(self.width), @@ -589,7 +604,7 @@ where let translation = Vector::new(canvas_layout.bounds().x, canvas_layout.bounds().y); iced_core::Renderer::with_translation(renderer, translation, |renderer| { - canvas::Renderer::draw(renderer, vec![geo]); + iced_renderer::geometry::Renderer::draw_geometry(renderer, geo); }); let bounds = canvas_layout.bounds(); @@ -657,10 +672,11 @@ where state: &'b mut Tree, layout: Layout<'_>, renderer: &crate::Renderer, + translation: Vector, ) -> Option> { self.inner .as_widget_mut() - .overlay(&mut state.children[0], layout, renderer) + .overlay(&mut state.children[0], layout, renderer, translation) } fn on_event( @@ -782,12 +798,12 @@ pub fn color_button<'a, Message: 'static>( let spacing = THEME.lock().unwrap().cosmic().spacing; button::custom(if color.is_some() { - Element::from(vertical_space(Length::Fixed(f32::from(spacing.space_s)))) + Element::from(vertical_space().height(Length::Fixed(f32::from(spacing.space_s)))) } else { Element::from(column![ - vertical_space(Length::FillPortion(6)), + vertical_space().height(Length::FillPortion(6)), row![ - horizontal_space(Length::FillPortion(6)), + horizontal_space().width(Length::FillPortion(6)), Icon::from( icon::from_name("list-add-symbolic") .prefer_svg(true) @@ -797,17 +813,17 @@ pub fn color_button<'a, Message: 'static>( .width(icon_portion) .height(Length::Fill) .content_fit(iced_core::ContentFit::Contain), - horizontal_space(Length::FillPortion(6)), + horizontal_space().width(Length::FillPortion(6)), ] .height(icon_portion) .width(Length::Fill), - vertical_space(Length::FillPortion(6)), + vertical_space().height(Length::FillPortion(6)), ]) }) .width(Length::Fixed(f32::from(spacing.space_s))) .height(Length::Fixed(f32::from(spacing.space_s))) .on_press_maybe(on_press) - .style(crate::theme::Button::Custom { + .class(crate::theme::Button::Custom { active: Box::new(move |focused, theme| { let cosmic = theme.cosmic(); @@ -817,7 +833,7 @@ pub fn color_button<'a, Message: 'static>( (0.0, Color::TRANSPARENT) }; let standard = theme.active(focused, false, &Button::Standard); - button::Appearance { + button::Style { shadow_offset: Vector::default(), background: color.map(Background::from).or(standard.background), border_radius: cosmic.radius_xs().into(), @@ -834,7 +850,7 @@ pub fn color_button<'a, Message: 'static>( let cosmic = theme.cosmic(); let standard = theme.disabled(&Button::Standard); - button::Appearance { + button::Style { shadow_offset: Vector::default(), background: color.map(Background::from).or(standard.background), border_radius: cosmic.radius_xs().into(), @@ -857,7 +873,7 @@ pub fn color_button<'a, Message: 'static>( }; let standard = theme.hovered(focused, false, &Button::Standard); - button::Appearance { + button::Style { shadow_offset: Vector::default(), background: color.map(Background::from).or(standard.background), border_radius: cosmic.radius_xs().into(), @@ -880,7 +896,7 @@ pub fn color_button<'a, Message: 'static>( }; let standard = theme.pressed(focused, false, &Button::Standard); - button::Appearance { + button::Style { shadow_offset: Vector::default(), background: color.map(Background::from).or(standard.background), border_radius: cosmic.radius_xs().into(), diff --git a/src/widget/context_drawer/overlay.rs b/src/widget/context_drawer/overlay.rs index 434bf8db..cc220d10 100644 --- a/src/widget/context_drawer/overlay.rs +++ b/src/widget/context_drawer/overlay.rs @@ -4,13 +4,14 @@ use crate::Element; use iced::advanced::layout::{self, Layout}; -use iced::advanced::widget::{self, Operation, OperationOutputWrapper}; +use iced::advanced::widget::{self, Operation}; use iced::advanced::{overlay, renderer}; use iced::advanced::{Clipboard, Shell}; use iced::{event, mouse, Event, Point, Rectangle, Size}; use iced_core::Renderer; pub(super) struct Overlay<'a, 'b, Message> { + pub(crate) position: Point, pub(super) content: &'b mut Element<'a, Message>, pub(super) tree: &'b mut widget::Tree, pub(super) width: f32, @@ -21,13 +22,8 @@ impl<'a, 'b, Message> overlay::Overlay where Message: Clone, { - fn layout( - &mut self, - renderer: &crate::Renderer, - bounds: Size, - position: Point, - _translation: iced::Vector, - ) -> layout::Node { + fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node { + let position = self.position; let limits = layout::Limits::new(Size::ZERO, bounds) .width(self.width) .height(bounds.height - 8.0 - position.y); @@ -98,7 +94,7 @@ where &mut self, layout: Layout<'_>, renderer: &crate::Renderer, - operation: &mut dyn Operation>, + operation: &mut dyn Operation<()>, ) { self.content .as_widget_mut() @@ -122,8 +118,9 @@ where layout: Layout<'_>, renderer: &crate::Renderer, ) -> Option> { + let translation = iced::Vector::new(self.position.x, self.position.y); self.content .as_widget_mut() - .overlay(self.tree, layout, renderer) + .overlay(self.tree, layout, renderer, translation) } } diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index 417885bc..8e548f49 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -13,11 +13,9 @@ use iced_core::event::{self, Event}; use iced_core::widget::{Operation, Tree}; use iced_core::{ layout, mouse, overlay as iced_overlay, renderer, Clipboard, Layout, Length, Padding, - Rectangle, Shell, Widget, + Rectangle, Shell, Vector, Widget, }; -use iced_renderer::core::widget::OperationOutputWrapper; - #[must_use] pub struct ContextDrawer<'a, Message> { id: Option, @@ -48,19 +46,19 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { text::heading(header) .width(Length::FillPortion(1)) .height(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center) - .vertical_alignment(alignment::Vertical::Center), + .align_x(alignment::Horizontal::Center) + .align_y(alignment::Vertical::Center), ) .push( button::text("Close") .trailing_icon(icon::from_name("go-next-symbolic")) .on_press(on_close) - .style(crate::theme::Button::Link) + .class(crate::theme::Button::Link) .apply(container) .width(Length::FillPortion(1)) .height(Length::Fill) .align_x(alignment::Horizontal::Right) - .center_y(), + .center_y(Length::Fill), ) // XXX must be done after pushing elements or it may be overwritten by size hints from contents .height(Length::Fixed(80.0)) @@ -84,7 +82,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { container( LayerContainer::new(pane) .layer(cosmic_theme::Layer::Primary) - .style(crate::style::Container::ContextDrawer) + .class(crate::style::Container::ContextDrawer) .width(Length::Fill) .height(Length::Fill) .max_width(max_width), @@ -159,7 +157,7 @@ impl<'a, Message: Clone> Widget for ContextDraw tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation>, + operation: &mut dyn Operation<()>, ) { self.content .as_widget() @@ -232,17 +230,20 @@ impl<'a, Message: Clone> Widget for ContextDraw tree: &'b mut Tree, layout: Layout<'_>, _renderer: &Renderer, + translation: Vector, ) -> Option> { let bounds = layout.bounds(); - Some(iced_overlay::Element::new( - layout.position(), - Box::new(Overlay { - content: &mut self.drawer, - tree: &mut tree.children[1], - width: bounds.width, - }), - )) + let mut position = layout.position(); + position.x += translation.x; + position.y += translation.y; + + Some(iced_overlay::Element::new(Box::new(Overlay { + content: &mut self.drawer, + tree: &mut tree.children[1], + width: bounds.width, + position, + }))) } #[cfg(feature = "a11y")] diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 261779f6..ce0c94e7 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -8,7 +8,7 @@ use crate::widget::menu::{ }; use derive_setters::Setters; use iced::touch::Finger; -use iced::Event; +use iced::{Event, Vector}; use iced_core::widget::{tree, Tree, Widget}; use iced_core::{event, mouse, touch, Length, Point, Size}; use std::collections::HashSet; @@ -144,9 +144,7 @@ impl<'a, Message: Clone> Widget tree: &mut Tree, layout: iced_core::Layout<'_>, renderer: &crate::Renderer, - operation: &mut dyn iced_core::widget::Operation< - iced_core::widget::OperationOutputWrapper, - >, + operation: &mut dyn iced_core::widget::Operation<()>, ) { self.content .as_widget() @@ -213,6 +211,7 @@ impl<'a, Message: Clone> Widget tree: &'b mut Tree, layout: iced_core::Layout<'_>, _renderer: &crate::Renderer, + translation: Vector, ) -> Option> { let state = tree.state.downcast_ref::(); @@ -247,6 +246,7 @@ impl<'a, Message: Clone> Widget root_bounds_list: vec![bounds], path_highlight: Some(PathHighlight::MenuActive), style: &crate::theme::menu_bar::MenuBarStyle::Default, + position: Point::new(translation.x, translation.y), } .overlay(), ) diff --git a/src/widget/dialog.rs b/src/widget/dialog.rs index 7e583d2b..014b738b 100644 --- a/src/widget/dialog.rs +++ b/src/widget/dialog.rs @@ -72,11 +72,13 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes let mut content_col = widget::column::with_capacity(3 + dialog.controls.len() * 2); content_col = content_col.push(widget::text::title3(dialog.title)); if let Some(body) = dialog.body { - content_col = content_col.push(widget::vertical_space(Length::Fixed(space_xxs.into()))); + content_col = + content_col.push(widget::vertical_space().height(Length::Fixed(space_xxs.into()))); content_col = content_col.push(widget::text::body(body)); } for control in dialog.controls { - content_col = content_col.push(widget::vertical_space(Length::Fixed(space_s.into()))); + content_col = + content_col.push(widget::vertical_space().height(Length::Fixed(space_s.into()))); content_col = content_col.push(control); } @@ -90,7 +92,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes if let Some(button) = dialog.tertiary_action { button_row = button_row.push(button); } - button_row = button_row.push(widget::horizontal_space(Length::Fill)); + button_row = button_row.push(widget::horizontal_space().width(Length::Fill)); if let Some(button) = dialog.secondary_action { button_row = button_row.push(button); } @@ -103,7 +105,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes widget::column::with_children(vec![content_row.into(), button_row.into()]) .spacing(space_l), ) - .style(style::Container::Dialog) + .class(style::Container::Dialog) .padding(space_m) .width(Length::Fixed(570.0)), ) diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index 5cb32711..6afac4df 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -3,6 +3,8 @@ use std::{ sync::atomic::{AtomicU64, Ordering}, }; +use iced::Vector; + use crate::{ iced::{ clipboard::{ @@ -280,9 +282,7 @@ impl<'a, Message: 'static> Widget tree: &mut Tree, layout: layout::Layout<'_>, renderer: &crate::Renderer, - operation: &mut dyn iced_core::widget::Operation< - iced_core::widget::OperationOutputWrapper, - >, + operation: &mut dyn iced_core::widget::Operation<()>, ) { self.container .as_widget() @@ -496,10 +496,11 @@ impl<'a, Message: 'static> Widget tree: &'b mut Tree, layout: layout::Layout<'_>, renderer: &crate::Renderer, + translation: Vector, ) -> Option> { self.container .as_widget_mut() - .overlay(&mut tree.children[0], layout, renderer) + .overlay(&mut tree.children[0], layout, renderer, translation) } fn drag_destinations( diff --git a/src/widget/dnd_source.rs b/src/widget/dnd_source.rs index 03de37e2..80328120 100644 --- a/src/widget/dnd_source.rs +++ b/src/widget/dnd_source.rs @@ -1,69 +1,77 @@ use std::any::Any; +use iced_core::window; + use crate::{ iced::{ clipboard::dnd::{DndAction, DndEvent, SourceEvent}, - event, mouse, overlay, Event, Length, Point, Rectangle, + event, mouse, overlay, Event, Length, Point, Rectangle, Vector, }, iced_core::{ self, layout, renderer, widget::{tree, Tree}, Clipboard, Shell, }, - iced_style, widget::{container, Id, Widget}, Element, }; pub fn dnd_source< 'a, - Message: 'static, - AppMessage: 'static, + Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + Send + 'static, >( child: impl Into>, -) -> DndSource<'a, Message, AppMessage, D> { +) -> DndSource<'a, Message, D> { DndSource::new(child) } -pub struct DndSource<'a, Message, AppMessage, D> { +pub struct DndSource<'a, Message, D> { id: Id, action: DndAction, container: Element<'a, Message>, + window: Option, drag_content: Option D>>, - drag_icon: Option (Element<'static, AppMessage>, tree::State)>>, + drag_icon: Option (Element<'static, ()>, tree::State)>>, + on_start: Option, + on_cancelled: Option, + on_finish: Option, drag_threshold: f32, - _phantom: std::marker::PhantomData, } impl< 'a, - Message: 'static, - AppMessage: 'static, + Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, - > DndSource<'a, Message, AppMessage, D> + > DndSource<'a, Message, D> { pub fn new(child: impl Into>) -> Self { Self { id: Id::unique(), + window: None, action: DndAction::Copy | DndAction::Move, container: container(child).into(), drag_content: None, drag_icon: None, drag_threshold: 8.0, - _phantom: std::marker::PhantomData, + on_start: None, + on_cancelled: None, + on_finish: None, } } pub fn with_id(child: impl Into>, id: Id) -> Self { Self { id, + window: None, action: DndAction::Copy | DndAction::Move, container: container(child).into(), drag_content: None, drag_icon: None, drag_threshold: 8.0, - _phantom: std::marker::PhantomData, + on_start: None, + on_cancelled: None, + on_finish: None, } } @@ -82,7 +90,7 @@ impl< #[must_use] pub fn drag_icon( mut self, - f: impl Fn() -> (Element<'static, AppMessage>, tree::State) + 'static, + f: impl Fn() -> (Element<'static, ()>, tree::State) + 'static, ) -> Self { self.drag_icon = Some(Box::new(f)); self @@ -98,10 +106,15 @@ impl< let Some(content) = self.drag_content.as_ref().map(|f| f()) else { return; }; + iced_core::clipboard::start_dnd( clipboard, false, - Some(iced_core::clipboard::DndSource::Widget(self.id.clone())), + if let Some(window) = self.window.as_ref() { + Some(iced_core::clipboard::DndSource::Surface(window.clone())) + } else { + Some(iced_core::clipboard::DndSource::Widget(self.id.clone())) + }, self.drag_icon.as_ref().map(|f| { let (icon, state) = f(); ( @@ -116,14 +129,33 @@ impl< self.action, ); } + + pub fn on_start(mut self, on_start: Option) -> Self { + self.on_start = on_start; + self + } + + pub fn on_cancel(mut self, on_cancelled: Option) -> Self { + self.on_cancelled = on_cancelled; + self + } + + pub fn on_finish(mut self, on_finish: Option) -> Self { + self.on_finish = on_finish; + self + } + + pub fn window(mut self, window: window::Id) -> Self { + self.window = Some(window); + self + } } impl< 'a, - Message: 'static, - AppMessage: 'static, + Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, - > Widget for DndSource<'a, Message, AppMessage, D> + > Widget for DndSource<'a, Message, D> { fn children(&self) -> Vec { vec![Tree::new(&self.container)] @@ -165,9 +197,7 @@ impl< tree: &mut Tree, layout: layout::Layout<'_>, renderer: &crate::Renderer, - operation: &mut dyn iced_core::widget::Operation< - iced_core::widget::OperationOutputWrapper, - >, + operation: &mut dyn iced_core::widget::Operation<()>, ) { operation.custom((&mut tree.state) as &mut dyn Any, Some(&self.id)); operation.container(Some(&self.id), layout.bounds(), &mut |operation| { @@ -210,7 +240,6 @@ impl< } state.left_pressed_position = Some(position); - // dbg!(&state, &self.id); return event::Status::Captured; } } @@ -229,8 +258,10 @@ impl< return ret; } if let Some(left_pressed_position) = state.left_pressed_position { - // dbg!(&state); if position.distance(left_pressed_position) > self.drag_threshold { + if let Some(on_start) = self.on_start.as_ref() { + shell.publish(on_start.clone()) + } self.start_dnd(clipboard, state.cached_bounds); state.is_dragging = true; state.left_pressed_position = None; @@ -249,8 +280,21 @@ impl< } _ => return ret, }, - Event::Dnd(DndEvent::Source(SourceEvent::Cancelled | SourceEvent::Finished)) => { + Event::Dnd(DndEvent::Source(SourceEvent::Cancelled)) => { if state.is_dragging { + if let Some(m) = self.on_cancelled.as_ref() { + shell.publish(m.clone()); + } + state.is_dragging = false; + return event::Status::Captured; + } + return ret; + } + Event::Dnd(DndEvent::Source(SourceEvent::Finished)) => { + if state.is_dragging { + if let Some(m) = self.on_finish.as_ref() { + shell.publish(m.clone()); + } state.is_dragging = false; return event::Status::Captured; } @@ -308,10 +352,11 @@ impl< tree: &'b mut Tree, layout: layout::Layout<'_>, renderer: &crate::Renderer, + translation: Vector, ) -> Option> { self.container .as_widget_mut() - .overlay(&mut tree.children[0], layout, renderer) + .overlay(&mut tree.children[0], layout, renderer, translation) } fn drag_destinations( @@ -319,7 +364,7 @@ impl< state: &Tree, layout: layout::Layout<'_>, renderer: &crate::Renderer, - dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { self.container.as_widget().drag_destinations( &state.children[0], @@ -340,12 +385,11 @@ impl< impl< 'a, - Message: 'static, - AppMessage: 'static, + Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, - > From> for Element<'a, Message> + > From> for Element<'a, Message> { - fn from(e: DndSource<'a, Message, AppMessage, D>) -> Element<'a, Message> { + fn from(e: DndSource<'a, Message, D>) -> Element<'a, Message> { Element::new(e) } } diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index e3c056f8..73204f3b 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -97,7 +97,7 @@ impl<'a, S: AsRef, Message: 'a> Menu<'a, S, Message> { position: Point, target_height: f32, ) -> overlay::Element<'a, Message, crate::Theme, crate::Renderer> { - overlay::Element::new(position, Box::new(Overlay::new(self, target_height))) + overlay::Element::new(Box::new(Overlay::new(self, target_height, position))) } } @@ -129,10 +129,15 @@ struct Overlay<'a, Message> { width: f32, target_height: f32, style: (), + position: Point, } impl<'a, Message: 'a> Overlay<'a, Message> { - pub fn new>(menu: Menu<'a, S, Message>, target_height: f32) -> Self { + pub fn new>( + menu: Menu<'a, S, Message>, + target_height: f32, + position: Point, + ) -> Self { let Menu { state, options, @@ -160,7 +165,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> { container = container .padding(padding) - .style(crate::style::Container::Dropdown); + .class(crate::style::Container::Dropdown); state.tree.diff(&mut container as &mut dyn Widget<_, _, _>); @@ -170,6 +175,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> { width, target_height, style, + position, } } } @@ -177,13 +183,8 @@ impl<'a, Message: 'a> Overlay<'a, Message> { impl<'a, Message> iced_core::Overlay for Overlay<'a, Message> { - fn layout( - &mut self, - renderer: &crate::Renderer, - bounds: Size, - position: Point, - _translation: iced::Vector, - ) -> layout::Node { + fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node { + let position = self.position; let space_below = bounds.height - (position.y + self.target_height); let space_above = position.y; @@ -447,10 +448,13 @@ impl<'a, S: AsRef, Message> Widget appearance.selected_background, ); - svg::Renderer::draw( + let svg_handle = + iced_core::Svg::new(crate::widget::common::object_select().clone()) + .color(appearance.selected_text_color) + .border_radius(appearance.border_radius); + svg::Renderer::draw_svg( renderer, - crate::widget::common::object_select().clone(), - Some(appearance.selected_text_color), + svg_handle, Rectangle { x: item_x + item_width - 16.0 - 8.0, y: bounds.y + (bounds.height / 2.0 - 8.0), @@ -494,7 +498,7 @@ impl<'a, S: AsRef, Message> Widget text::Renderer::fill_text( renderer, Text { - content: option.as_ref(), + content: option.as_ref().to_string(), bounds: bounds.size(), size: Pixels(text_size), line_height: self.text_line_height, @@ -502,7 +506,7 @@ impl<'a, S: AsRef, Message> Widget horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, - wrap: text::Wrap::default(), + wrapping: text::Wrapping::default(), }, bounds.position(), color, diff --git a/src/widget/dropdown/multi/menu.rs b/src/widget/dropdown/multi/menu.rs index 3710698d..5e3aa6a6 100644 --- a/src/widget/dropdown/multi/menu.rs +++ b/src/widget/dropdown/multi/menu.rs @@ -97,7 +97,7 @@ where position: Point, target_height: f32, ) -> overlay::Element<'a, Message, crate::Theme, crate::Renderer> { - overlay::Element::new(position, Box::new(Overlay::new(self, target_height))) + overlay::Element::new(Box::new(Overlay::new(self, target_height, position))) } } @@ -129,12 +129,14 @@ struct Overlay<'a, Message> { width: f32, target_height: f32, style: (), + position: Point, } impl<'a, Message: 'a> Overlay<'a, Message> { pub fn new, Item: Clone + PartialEq>( menu: Menu<'a, S, Item, Message>, target_height: f32, + position: Point, ) -> Self { let Menu { state, @@ -163,7 +165,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> { container = container .padding(padding) - .style(crate::style::Container::Dropdown); + .class(crate::style::Container::Dropdown); state.tree.diff(&mut container as &mut dyn Widget<_, _, _>); @@ -173,6 +175,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> { width, target_height, style, + position, } } } @@ -180,13 +183,8 @@ impl<'a, Message: 'a> Overlay<'a, Message> { impl<'a, Message> iced_core::Overlay for Overlay<'a, Message> { - fn layout( - &mut self, - renderer: &crate::Renderer, - bounds: Size, - position: Point, - _translation: iced::Vector, - ) -> layout::Node { + fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node { + let position = self.position; let space_below = bounds.height - (position.y + self.target_height); let space_above = position.y; @@ -537,10 +535,13 @@ where appearance.selected_background, ); - svg::Renderer::draw( + let svg_handle = + svg::Svg::new(crate::widget::common::object_select().clone()) + .color(appearance.selected_text_color) + .border_radius(appearance.border_radius); + svg::Renderer::draw_svg( renderer, - crate::widget::common::object_select().clone(), - Some(appearance.selected_text_color), + svg_handle, Rectangle { x: item_x + item_width - 16.0 - 8.0, y: bounds.y + (bounds.height / 2.0 - 8.0), @@ -587,7 +588,7 @@ where text::Renderer::fill_text( renderer, Text { - content: option.as_ref(), + content: option.as_ref().to_string(), bounds: bounds.size(), size: iced::Pixels(text_size), line_height: self.text_line_height, @@ -595,7 +596,7 @@ where horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, - wrap: text::Wrap::default(), + wrapping: text::Wrapping::default(), }, bounds.position(), color, @@ -636,7 +637,7 @@ where text::Renderer::fill_text( renderer, Text { - content: description.as_ref(), + content: description.as_ref().to_string(), bounds: bounds.size(), size: iced::Pixels(text_size), line_height: text::LineHeight::Absolute(Pixels(text_line_height + 4.0)), @@ -644,7 +645,7 @@ where horizontal_alignment: alignment::Horizontal::Center, vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, - wrap: text::Wrap::default(), + wrapping: text::Wrapping::default(), }, bounds.position(), appearance.description_color, diff --git a/src/widget/dropdown/multi/mod.rs b/src/widget/dropdown/multi/mod.rs index 2f3a68e5..b4963490 100644 --- a/src/widget/dropdown/multi/mod.rs +++ b/src/widget/dropdown/multi/mod.rs @@ -9,7 +9,7 @@ pub mod menu; pub use menu::Menu; mod widget; -pub use widget::{Appearance, Dropdown, StyleSheet}; +pub use widget::{Catalog, Dropdown, Style}; pub fn dropdown<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static>( model: &'a Model, diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index a97f03f2..c40f8760 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -9,10 +9,13 @@ use iced_core::event::{self, Event}; use iced_core::text::{self, Paragraph, Text}; use iced_core::widget::tree::{self, Tree}; use iced_core::{alignment, keyboard, layout, mouse, overlay, renderer, svg, touch, Shadow}; -use iced_core::{Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Widget}; +use iced_core::{ + Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, +}; +use iced_widget::pick_list; use std::ffi::OsStr; -pub use iced_widget::style::pick_list::{Appearance, StyleSheet}; +pub use iced_widget::pick_list::{Catalog, Style}; /// A widget for selecting a single value from a list of selections. #[derive(Setters)] @@ -98,7 +101,7 @@ impl<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static> for (_, item) in &list.options { state .selections - .push((item.clone(), crate::Paragraph::new())); + .push((item.clone(), crate::Plain::default())); } } } @@ -182,6 +185,7 @@ impl<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static> tree: &'b mut Tree, layout: Layout<'_>, renderer: &crate::Renderer, + translation: Vector, ) -> Option> { let state = tree.state.downcast_mut::>(); @@ -216,8 +220,8 @@ pub struct State { keyboard_modifiers: keyboard::Modifiers, is_open: bool, hovered_option: Option, - selections: Vec<(Item, crate::Paragraph)>, - descriptions: Vec, + selections: Vec<(Item, crate::Plain)>, + descriptions: Vec, } impl State { @@ -259,7 +263,7 @@ pub fn layout( text_size: f32, text_line_height: text::LineHeight, font: Option, - selection: Option<(&str, &mut crate::Paragraph)>, + selection: Option<(&str, &mut crate::Plain)>, ) -> layout::Node { use std::f32; @@ -267,7 +271,7 @@ pub fn layout( let max_width = match width { Length::Shrink => { - let measure = move |(label, paragraph): (_, &mut crate::Paragraph)| -> f32 { + let measure = move |(label, paragraph): (_, &mut crate::Plain)| -> f32 { paragraph.update(Text { content: label, bounds: Size::new(f32::MAX, f32::MAX), @@ -277,7 +281,7 @@ pub fn layout( horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, shaping: text::Shaping::Advanced, - wrap: text::Wrap::default(), + wrapping: text::Wrapping::default(), }); paragraph.min_width().round() }; @@ -410,7 +414,7 @@ pub fn overlay<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static ) .width({ let measure = - |label: &str, paragraph: &mut crate::Paragraph, line_height: text::LineHeight| { + |label: &str, paragraph: &mut crate::Plain, line_height: text::LineHeight| { paragraph.update(Text { content: label, bounds: Size::new(f32::MAX, f32::MAX), @@ -420,7 +424,7 @@ pub fn overlay<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, shaping: text::Shaping::Advanced, - wrap: text::Wrap::default(), + wrapping: text::Wrapping::default(), }); paragraph.min_width().round() }; @@ -433,7 +437,7 @@ pub fn overlay<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static let paragraph = if state.descriptions.len() > desc_count { &mut state.descriptions[desc_count] } else { - state.descriptions.push(crate::Paragraph::new()); + state.descriptions.push(crate::Plain::default()); state.descriptions.last_mut().unwrap() }; desc_count += 1; @@ -448,7 +452,7 @@ pub fn overlay<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static None => { state .selections - .push((item.clone(), crate::Paragraph::new())); + .push((item.clone(), crate::Plain::default())); state.selections.len() - 1 } }; @@ -499,9 +503,9 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>( let is_mouse_over = cursor.is_over(bounds); let style = if is_mouse_over { - theme.hovered(&()) + theme.style(&(), pick_list::Status::Hovered) } else { - theme.active(&()) + theme.style(&(), pick_list::Status::Active) }; iced_core::Renderer::fill_quad( @@ -515,10 +519,10 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>( ); if let Some(handle) = state.icon.clone() { - svg::Renderer::draw( + let svg_handle = iced_core::Svg::new(handle).color(style.text_color); + svg::Renderer::draw_svg( renderer, - handle, - Some(style.text_color), + svg_handle, Rectangle { x: bounds.x + bounds.width - gap - 16.0, y: bounds.center_y() - 8.0, @@ -541,7 +545,7 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>( text::Renderer::fill_text( renderer, Text { - content, + content: content.to_string(), size: iced::Pixels(text_size), line_height: text_line_height, font, @@ -549,7 +553,7 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>( horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, - wrap: text::Wrap::default(), + wrapping: text::Wrapping::default(), }, bounds.position(), style.text_color, diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 971072ca..80d5a009 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -5,16 +5,18 @@ use super::menu::{self, Menu}; use crate::widget::icon; use derive_setters::Setters; +use iced::Radians; use iced_core::event::{self, Event}; use iced_core::text::{self, Paragraph, Text}; use iced_core::widget::tree::{self, Tree}; use iced_core::{alignment, keyboard, layout, mouse, overlay, renderer, svg, touch, Shadow}; -use iced_core::{Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Widget}; +use iced_core::{ + Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, +}; +use iced_widget::pick_list::{self, Catalog}; use std::ffi::OsStr; use std::hash::{DefaultHasher, Hash, Hasher}; -pub use iced_widget::style::pick_list::{Appearance, StyleSheet}; - /// A widget for selecting a single value from a list of selections. #[derive(Setters)] pub struct Dropdown<'a, S: AsRef, Message> { @@ -62,6 +64,29 @@ impl<'a, S: AsRef, Message> Dropdown<'a, S, Message> { font: None, } } + + fn update_paragraphs(&self, state: &mut tree::State) { + let state = state.downcast_mut::(); + + state + .selections + .resize_with(self.selections.len(), crate::Plain::default); + for (i, selection) in self.selections.iter().enumerate() { + state.selections[i].update(Text { + content: selection.as_ref(), + bounds: Size::INFINITY, + // TODO use the renderer default size + size: iced::Pixels(self.text_size.unwrap_or(14.0)), + + line_height: self.text_line_height, + font: self.font.unwrap_or(crate::font::default()), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + shaping: text::Shaping::Advanced, + wrapping: text::Wrapping::default(), + }); + } + } } impl<'a, S: AsRef, Message: 'a> Widget @@ -80,7 +105,7 @@ impl<'a, S: AsRef, Message: 'a> Widget, Message: 'a> Widget, Message: 'a> Widget, renderer: &crate::Renderer, + translation: Vector, ) -> Option> { let state = tree.state.downcast_mut::(); @@ -237,8 +263,8 @@ pub struct State { keyboard_modifiers: keyboard::Modifiers, is_open: bool, hovered_option: Option, - selections: Vec, hashes: Vec, + selections: Vec, } impl State { @@ -280,7 +306,7 @@ pub fn layout( text_size: f32, text_line_height: text::LineHeight, font: Option, - selection: Option<(&str, &mut crate::Paragraph)>, + selection: Option<(&str, &mut crate::Plain)>, ) -> layout::Node { use std::f32; @@ -288,7 +314,7 @@ pub fn layout( let max_width = match width { Length::Shrink => { - let measure = move |(label, paragraph): (_, &mut crate::Paragraph)| -> f32 { + let measure = move |(label, paragraph): (_, &mut crate::Plain)| -> f32 { paragraph.update(Text { content: label, bounds: Size::new(f32::MAX, f32::MAX), @@ -298,7 +324,7 @@ pub fn layout( horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, shaping: text::Shaping::Advanced, - wrap: text::Wrap::default(), + wrapping: text::Wrapping::default(), }); paragraph.min_width().round() }; @@ -430,14 +456,14 @@ pub fn overlay<'a, S: AsRef, Message: 'a>( None, ) .width({ - let measure = |_label: &str, selection_paragraph: &mut crate::Paragraph| -> f32 { + let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 { selection_paragraph.min_width().round() }; selections .iter() .zip(state.selections.iter_mut()) - .map(|(label, selection)| measure(label.as_ref(), selection)) + .map(|(label, selection)| measure(label.as_ref(), selection.raw())) .fold(0.0, |next, current| current.max(next)) + gap + 16.0 @@ -477,9 +503,9 @@ pub fn draw<'a, S>( let is_mouse_over = cursor.is_over(bounds); let style = if is_mouse_over { - theme.hovered(&()) + theme.style(&(), pick_list::Status::Hovered) } else { - theme.active(&()) + theme.style(&(), pick_list::Status::Active) }; iced_core::Renderer::fill_quad( @@ -493,10 +519,11 @@ pub fn draw<'a, S>( ); if let Some(handle) = state.icon.clone() { - svg::Renderer::draw( + let svg_handle = svg::Svg::new(handle).color(style.text_color); + + svg::Renderer::draw_svg( renderer, - handle, - Some(style.text_color), + svg_handle, Rectangle { x: bounds.x + bounds.width - gap - 16.0, y: bounds.center_y() - 8.0, @@ -517,7 +544,7 @@ pub fn draw<'a, S>( text::Renderer::fill_text( renderer, Text { - content, + content: content.to_string(), size: iced::Pixels(text_size), line_height: text_line_height, font, @@ -525,7 +552,7 @@ pub fn draw<'a, S>( horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, - wrap: text::Wrap::default(), + wrapping: text::Wrapping::default(), }, bounds.position(), style.text_color, diff --git a/src/widget/flex_row/widget.rs b/src/widget/flex_row/widget.rs index 9f19ca1c..40aee414 100644 --- a/src/widget/flex_row/widget.rs +++ b/src/widget/flex_row/widget.rs @@ -6,9 +6,9 @@ use derive_setters::Setters; use iced_core::event::{self, Event}; use iced_core::widget::{Operation, Tree}; use iced_core::{ - layout, mouse, overlay, renderer, Clipboard, Layout, Length, Padding, Rectangle, Shell, Widget, + layout, mouse, overlay, renderer, Clipboard, Layout, Length, Padding, Rectangle, Shell, Vector, + Widget, }; -use iced_renderer::core::widget::OperationOutputWrapper; /// Responsively generates rows and columns of widgets based on its dimmensions. #[derive(Setters)] @@ -132,7 +132,7 @@ impl<'a, Message: 'static + Clone> Widget tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation>, + operation: &mut dyn Operation<()>, ) { operation.container(None, layout.bounds(), &mut |operation| { self.children @@ -225,8 +225,9 @@ impl<'a, Message: 'static + Clone> Widget tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + translation: Vector, ) -> Option> { - overlay::from_children(&mut self.children, tree, layout, renderer) + overlay::from_children(&mut self.children, tree, layout, renderer, translation) } #[cfg(feature = "a11y")] @@ -252,7 +253,7 @@ impl<'a, Message: 'static + Clone> Widget state: &Tree, layout: Layout<'_>, renderer: &Renderer, - dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { for ((e, layout), state) in self .children diff --git a/src/widget/grid/widget.rs b/src/widget/grid/widget.rs index a043f19d..961ffb90 100644 --- a/src/widget/grid/widget.rs +++ b/src/widget/grid/widget.rs @@ -7,9 +7,8 @@ use iced_core::event::{self, Event}; use iced_core::widget::{Operation, Tree}; use iced_core::{ layout, mouse, overlay, renderer, Alignment, Clipboard, Layout, Length, Padding, Rectangle, - Shell, Widget, + Shell, Vector, Widget, }; -use iced_renderer::core::widget::OperationOutputWrapper; /// Responsively generates rows and columns of widgets based on its dimmensions. #[must_use] @@ -154,7 +153,7 @@ impl<'a, Message: 'static + Clone> Widget for G tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation>, + operation: &mut dyn Operation<()>, ) { operation.container(None, layout.bounds(), &mut |operation| { self.children @@ -247,8 +246,9 @@ impl<'a, Message: 'static + Clone> Widget for G tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + translation: Vector, ) -> Option> { - overlay::from_children(&mut self.children, tree, layout, renderer) + overlay::from_children(&mut self.children, tree, layout, renderer, translation) } #[cfg(feature = "a11y")] @@ -274,7 +274,7 @@ impl<'a, Message: 'static + Clone> Widget for G state: &Tree, layout: Layout<'_>, renderer: &Renderer, - dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { for ((e, layout), state) in self .children diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 1f74510b..d487f6bd 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -6,7 +6,7 @@ use crate::{ext::CollectionWidget, widget, Element}; use apply::Apply; use derive_setters::Setters; use iced::Length; -use iced_core::{widget::tree, Widget}; +use iced_core::{widget::tree, Vector, Widget}; use std::borrow::Cow; #[must_use] @@ -221,9 +221,7 @@ impl<'a, Message: Clone + 'static> Widget, renderer: &crate::Renderer, - operation: &mut dyn iced_core::widget::Operation< - iced_core::widget::OperationOutputWrapper, - >, + operation: &mut dyn iced_core::widget::Operation<()>, ) { let child_tree = &mut state.children[0]; let child_layout = layout.children().next().unwrap(); @@ -237,12 +235,16 @@ impl<'a, Message: Clone + 'static> Widget, renderer: &crate::Renderer, + translation: Vector, ) -> Option> { let child_tree = &mut state.children[0]; let child_layout = layout.children().next().unwrap(); - self.header_bar_inner - .as_widget_mut() - .overlay(child_tree, child_layout, renderer) + self.header_bar_inner.as_widget_mut().overlay( + child_tree, + child_layout, + renderer, + translation, + ) } fn drag_destinations( @@ -250,7 +252,7 @@ impl<'a, Message: Clone + 'static> Widget, renderer: &crate::Renderer, - dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { if let Some((child_tree, child_layout)) = state.children.iter().zip(layout.children()).next() @@ -274,7 +276,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { let mut end = std::mem::take(&mut self.end); // Also packs the window controls at the very end. - end.push(widget::horizontal_space(Length::Fixed(12.0)).into()); + end.push(widget::horizontal_space().width(Length::Fixed(12.0)).into()); end.push(self.window_controls()); let height = match self.density.unwrap_or_else(crate::config::header_size) { @@ -288,7 +290,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { // If elements exist in the start region, append them here. .push( widget::row::with_children(start) - .align_items(iced::Alignment::Center) + .align_y(iced::Alignment::Center) .apply(widget::container) .align_x(iced::alignment::Horizontal::Left) .width(Length::Shrink), @@ -297,32 +299,32 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { // This will otherwise use the title as a widget if a title was defined. .push(if !center.is_empty() { widget::row::with_children(center) - .align_items(iced::Alignment::Center) + .align_y(iced::Alignment::Center) .apply(widget::container) .align_x(iced::alignment::Horizontal::Center) .width(Length::Fill) .into() } else if self.title.is_empty() { - widget::horizontal_space(Length::Fill).into() + widget::horizontal_space().width(Length::Fill).into() } else { self.title_widget() }) .push( widget::row::with_children(end) - .align_items(iced::Alignment::Center) + .align_y(iced::Alignment::Center) .apply(widget::container) .align_x(iced::alignment::Horizontal::Right) .width(Length::Shrink), ) - .align_items(iced::Alignment::Center) + .align_y(iced::Alignment::Center) .height(Length::Fixed(height)) .padding([0, 8]) .spacing(8) .apply(widget::container) - .style(crate::theme::Container::HeaderBar { + .class(crate::theme::Container::HeaderBar { focused: self.focused, }) - .center_y() + .center_y(Length::Shrink) .apply(widget::mouse_area); // Assigns a message to emit when the headerbar is dragged. @@ -350,10 +352,8 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { widget::text::heading(title) .apply(widget::container) - .center_x() - .center_y() - .width(Length::Fill) - .height(Length::Fill) + .center_x(Length::Fill) + .center_y(Length::Fill) .into() } @@ -380,7 +380,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .padding(8) }; - icon.style(crate::theme::Button::HeaderBar) + icon.class(crate::theme::Button::HeaderBar) .selected(self.focused) .icon_size($size) .on_press($on_press) @@ -405,8 +405,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { ) .spacing(8) .apply(widget::container) - .height(Length::Fill) - .center_y() + .center_y(Length::Fill) .into() } } diff --git a/src/widget/icon/handle.rs b/src/widget/icon/handle.rs index 1a138847..fe325908 100644 --- a/src/widget/icon/handle.rs +++ b/src/widget/icon/handle.rs @@ -55,7 +55,10 @@ pub fn from_raster_bytes( ) -> Handle { Handle { symbolic: false, - data: Data::Image(image::Handle::from_memory(bytes)), + data: match bytes.into() { + Cow::Owned(b) => Data::Image(image::Handle::from_bytes(b)), + Cow::Borrowed(b) => Data::Image(image::Handle::from_bytes(b)), + }, } } @@ -66,12 +69,14 @@ pub fn from_raster_pixels( pixels: impl Into> + std::convert::AsRef<[u8]> + std::marker::Send - + std::marker::Sync - + 'static, + + std::marker::Sync, ) -> Handle { Handle { symbolic: false, - data: Data::Image(image::Handle::from_pixels(width, height, pixels)), + data: match pixels.into() { + Cow::Owned(pixels) => Data::Image(image::Handle::from_bytes(pixels)), + Cow::Borrowed(pixels) => Data::Image(image::Handle::from_bytes(pixels)), + }, } } diff --git a/src/widget/icon/mod.rs b/src/widget/icon/mod.rs index 752125fe..5fe630fa 100644 --- a/src/widget/icon/mod.rs +++ b/src/widget/icon/mod.rs @@ -24,7 +24,7 @@ pub fn icon(handle: Handle) -> Icon { handle, height: None, size: 16, - style: crate::theme::Svg::default(), + class: crate::theme::Svg::default(), width: None, } } @@ -40,7 +40,7 @@ pub fn from_name(name: impl Into>) -> Named { pub struct Icon { #[setters(skip)] handle: Handle, - style: crate::theme::Svg, + class: crate::theme::Svg, pub(super) size: u16, content_fit: ContentFit, #[setters(strip_option)] @@ -86,7 +86,7 @@ impl Icon { let from_svg = |handle| { Svg::::new(handle) - .style(self.style.clone()) + .class(self.class.clone()) .width( self.width .unwrap_or_else(|| Length::Fixed(f32::from(self.size))), diff --git a/src/widget/id_container.rs b/src/widget/id_container.rs index 2bd56b39..9be387a5 100644 --- a/src/widget/id_container.rs +++ b/src/widget/id_container.rs @@ -4,8 +4,8 @@ use iced_core::mouse; use iced_core::overlay; use iced_core::renderer; use iced_core::widget::{Id, Tree}; -use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Widget}; -pub use iced_style::container::{Appearance, StyleSheet}; +use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; +pub use iced_widget::container::{Catalog, Style}; pub fn id_container<'a, Message: 'static, Theme, E>( content: E, @@ -13,8 +13,8 @@ pub fn id_container<'a, Message: 'static, Theme, E>( ) -> IdContainer<'a, Message, Theme, crate::Renderer> where E: Into>, - Theme: iced_style::container::StyleSheet, - ::Style: From, + Theme: iced_widget::container::Catalog, + ::Class<'a>: From>, { IdContainer::new(content, id) } @@ -83,9 +83,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn iced_core::widget::Operation< - iced_core::widget::OperationOutputWrapper, - >, + operation: &mut dyn iced_core::widget::Operation<()>, ) { operation.container(Some(&self.id), layout.bounds(), &mut |operation| { self.content.as_widget().operate( @@ -165,11 +163,13 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + translation: Vector, ) -> Option> { self.content.as_widget_mut().overlay( &mut tree.children[0], layout.children().next().unwrap(), renderer, + translation, ) } @@ -178,7 +178,7 @@ where state: &Tree, layout: Layout<'_>, renderer: &Renderer, - dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { let content_layout = layout.children().next().unwrap(); self.content.as_widget().drag_destinations( diff --git a/src/widget/layer_container.rs b/src/widget/layer_container.rs index 7033e342..3f3e4959 100644 --- a/src/widget/layer_container.rs +++ b/src/widget/layer_container.rs @@ -1,3 +1,4 @@ +use crate::Theme; use cosmic_theme::LayeredTheme; use iced::widget::Container; use iced_core::alignment; @@ -7,16 +8,14 @@ use iced_core::mouse; use iced_core::overlay; use iced_core::renderer; use iced_core::widget::Tree; -use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget}; -pub use iced_style::container::{Appearance, StyleSheet}; +use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget}; +pub use iced_widget::container::{Catalog, Style}; -pub fn layer_container<'a, Message: 'static, Theme, E>( +pub fn layer_container<'a, Message: 'static, E>( content: E, -) -> LayerContainer<'a, Message, Theme, crate::Renderer> +) -> LayerContainer<'a, Message, crate::Renderer> where E: Into>, - Theme: iced_style::container::StyleSheet + LayeredTheme, - ::Style: From, { LayerContainer::new(content) } @@ -25,20 +24,18 @@ where /// /// It is normally used for alignment purposes. #[allow(missing_debug_implementations)] -pub struct LayerContainer<'a, Message, Theme, Renderer> +pub struct LayerContainer<'a, Message, Renderer> where Renderer: iced_core::Renderer, - Theme: iced_style::container::StyleSheet + LayeredTheme, { layer: Option, container: Container<'a, Message, Theme, Renderer>, } -impl<'a, Message, Theme, Renderer> LayerContainer<'a, Message, Theme, Renderer> +impl<'a, Message, Renderer> LayerContainer<'a, Message, Renderer> where Renderer: iced_core::Renderer, - Theme: iced_style::container::StyleSheet + LayeredTheme, - ::Style: From, + // iced_widget::container::Style: From, { /// Creates an empty [`Container`]. pub(crate) fn new(content: T) -> Self @@ -55,7 +52,7 @@ where #[must_use] pub fn layer(mut self, layer: cosmic_theme::Layer) -> Self { self.layer = Some(layer); - self.style(match layer { + self.class(match layer { cosmic_theme::Layer::Background => crate::theme::Container::Background, cosmic_theme::Layer::Primary => crate::theme::Container::Primary, cosmic_theme::Layer::Secondary => crate::theme::Container::Secondary, @@ -113,31 +110,30 @@ where /// Centers the contents in the horizontal axis of the [`LayerContainer`]. #[must_use] - pub fn center_x(mut self) -> Self { - self.container = self.container.center_x(); + pub fn center_x(mut self, width: Length) -> Self { + self.container = self.container.center_x(width); self } /// Centers the contents in the vertical axis of the [`LayerContainer`]. #[must_use] - pub fn center_y(mut self) -> Self { - self.container = self.container.center_y(); + pub fn center_y(mut self, height: Length) -> Self { + self.container = self.container.center_y(height); self } /// Sets the style of the [`LayerContainer`]. #[must_use] - pub fn style(mut self, style: impl Into<::Style>) -> Self { - self.container = self.container.style(style); + pub fn class(mut self, style: impl Into>) -> Self { + self.container = self.container.class(style); self } } -impl<'a, Message, Theme, Renderer> Widget - for LayerContainer<'a, Message, Theme, Renderer> +impl<'a, Message, Renderer> Widget + for LayerContainer<'a, Message, Renderer> where Renderer: iced_core::Renderer, - Theme: iced_style::container::StyleSheet + LayeredTheme + Clone, { fn children(&self) -> Vec { self.container.children() @@ -173,9 +169,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn iced_core::widget::Operation< - iced_core::widget::OperationOutputWrapper, - >, + operation: &mut dyn iced_core::widget::Operation<()>, ) { self.container.operate(tree, layout, renderer, operation); } @@ -232,6 +226,7 @@ where } else { theme.clone() }; + self.container.draw( tree, renderer, @@ -248,8 +243,9 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + translation: Vector, ) -> Option> { - self.container.overlay(tree, layout, renderer) + self.container.overlay(tree, layout, renderer, translation) } fn drag_destinations( @@ -257,7 +253,7 @@ where state: &Tree, layout: Layout<'_>, renderer: &Renderer, - dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { self.container .drag_destinations(state, layout, renderer, dnd_rectangles); @@ -272,15 +268,14 @@ where } } -impl<'a, Message, Theme, Renderer> From> +impl<'a, Message, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a, Renderer: 'a + iced_core::Renderer, - Theme: iced_style::container::StyleSheet + LayeredTheme + 'a + Clone, { fn from( - column: LayerContainer<'a, Message, Theme, Renderer>, + column: LayerContainer<'a, Message, Renderer>, ) -> Element<'a, Message, Theme, Renderer> { Element::new(column) } diff --git a/src/widget/list/column.rs b/src/widget/list/column.rs index 082fc8df..18c32f47 100644 --- a/src/widget/list/column.rs +++ b/src/widget/list/column.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 use iced_core::Padding; -use iced_style::container::StyleSheet; +use iced_widget::container::Catalog; use crate::{theme, widget::divider, Apply, Element}; @@ -14,7 +14,7 @@ pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> { pub struct ListColumn<'a, Message> { spacing: u16, padding: Padding, - style: ::Style, + style: crate::theme::Container<'a>, children: Vec>, } @@ -23,7 +23,7 @@ impl<'a, Message: 'static> Default for ListColumn<'a, Message> { Self { spacing: theme::THEME.lock().unwrap().cosmic().spacing.space_xxs, padding: Padding::from(0), - style: ::Style::List, + style: crate::theme::Container::List, children: Vec::with_capacity(4), } } @@ -41,9 +41,11 @@ impl<'a, Message: 'static> ListColumn<'a, Message> { } // Ensure a minimum height of 32. - let container = crate::widget::container(item) - .min_height(32) - .align_y(iced::alignment::Vertical::Center); + let container = iced::widget::row![ + crate::widget::container(item).align_y(iced::alignment::Vertical::Center), + crate::widget::vertical_space().height(iced::Length::Fixed(32.)) + ] + .align_y(iced::alignment::Vertical::Center); self.children.push(container.into()); self @@ -55,7 +57,7 @@ impl<'a, Message: 'static> ListColumn<'a, Message> { } /// Sets the style variant of this [`Circular`]. - pub fn style(mut self, style: ::Style) -> Self { + pub fn style(mut self, style: ::Class<'a>) -> Self { self.style = style; self } @@ -72,7 +74,7 @@ impl<'a, Message: 'static> ListColumn<'a, Message> { .padding(self.padding) .apply(super::container) .padding([self.spacing, 8]) - .style(self.style) + .class(self.style) .into() } } diff --git a/src/widget/list/mod.rs b/src/widget/list/mod.rs index d94da271..9b844e2e 100644 --- a/src/widget/list/mod.rs +++ b/src/widget/list/mod.rs @@ -13,6 +13,6 @@ pub fn container<'a, Message>( ) -> Container<'a, Message, crate::Theme, crate::Renderer> { super::container(content) .padding([16, 6]) - .style(crate::theme::Container::List) + .class(crate::theme::Container::List) .width(iced::Length::Fill) } diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 257ccbfb..1f112925 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -9,6 +9,7 @@ use super::{ }; use crate::style::menu_bar::StyleSheet; +use iced::{Point, Vector}; use iced_core::Border; use iced_widget::core::{ event, @@ -434,6 +435,7 @@ where tree: &'b mut Tree, layout: Layout<'_>, _renderer: &Renderer, + translation: Vector, ) -> Option> { let state = tree.state.downcast_ref::(); if !state.open { @@ -455,6 +457,7 @@ where root_bounds_list: layout.children().map(|lo| lo.bounds()).collect(), path_highlight: self.path_highlight, style: &self.style, + position: Point::new(translation.x, translation.y), } .overlay(), ) diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index bc0b7725..19644b7a 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -276,7 +276,7 @@ impl MenuBounds { let offset_bounds = Rectangle::new(offset_position, offset_size); let children_bounds = Rectangle::new(children_position, children_size); - let check_bounds = pad_rectangle(children_bounds, [bounds_expand; 4].into()); + let check_bounds = pad_rectangle(children_bounds, bounds_expand.into()); Self { child_positions, @@ -445,13 +445,14 @@ where pub(crate) root_bounds_list: Vec, pub(crate) path_highlight: Option, pub(crate) style: &'b ::Style, + pub(crate) position: Point, } impl<'a, 'b, Message, Renderer> Menu<'a, 'b, Message, Renderer> where Renderer: renderer::Renderer, { pub(crate) fn overlay(self) -> overlay::Element<'b, Message, crate::Theme, Renderer> { - overlay::Element::new(Point::ORIGIN, Box::new(self)) + overlay::Element::new(Box::new(self)) } } impl<'a, 'b, Message, Renderer> overlay::Overlay @@ -459,14 +460,9 @@ impl<'a, 'b, Message, Renderer> overlay::Overlay Node { + fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node { // layout children + let position = self.position; let state = self.tree.state.downcast_mut::(); let overlay_offset = Point::ORIGIN - position; let tree_children = &mut self.tree.children; diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index 65ebbd9c..736ba2f4 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -146,14 +146,14 @@ pub fn menu_button<'a, Message: 'a>( ) -> crate::widget::Button<'a, Message> { widget::button::custom( widget::Row::with_children(children) - .align_items(Alignment::Center) + .align_y(Alignment::Center) .height(Length::Fill) .width(Length::Fill), ) .height(Length::Fixed(36.0)) .padding([4, 16]) .width(Length::Fill) - .style(theme::Button::MenuItem) + .class(theme::Button::MenuItem) } /// Represents a menu item that performs an action when selected or a separator between menu items. @@ -197,7 +197,7 @@ where { widget::button::custom(widget::text(label)) .padding([4, 12]) - .style(theme::Button::MenuRoot) + .class(theme::Button::MenuRoot) .into() } @@ -245,7 +245,7 @@ where let key = find_key(&action, key_binds); let menu_button = menu_button(vec![ widget::text(label).into(), - widget::horizontal_space(Length::Fill).into(), + widget::horizontal_space().width(Length::Fill).into(), widget::text(key).into(), ]) .on_press(action.message()); @@ -256,7 +256,7 @@ where let key = find_key(&action, key_binds); let menu_button = menu_button(vec![ widget::text(label).into(), - widget::horizontal_space(Length::Fill).into(), + widget::horizontal_space().width(Length::Fill).into(), widget::text(key).into(), ]); @@ -270,8 +270,8 @@ where widget::icon::from_name("object-select-symbolic") .size(16) .icon() - .style(theme::Svg::Custom(Rc::new(|theme| { - crate::iced_style::svg::Appearance { + .class(theme::Svg::Custom(Rc::new(|theme| { + iced_widget::svg::Style { color: Some(theme.cosmic().accent_color().into()), } }))) @@ -282,9 +282,9 @@ where }, widget::Space::with_width(Length::Fixed(8.0)).into(), widget::text(label) - .horizontal_alignment(iced::alignment::Horizontal::Left) + .align_x(iced::alignment::Horizontal::Left) .into(), - widget::horizontal_space(Length::Fill).into(), + widget::horizontal_space().width(Length::Fill).into(), widget::text(key).into(), ]) .on_press(action.message()), @@ -294,13 +294,13 @@ where trees.push(MenuTree::::with_children( menu_button(vec![ widget::text(label).into(), - widget::horizontal_space(Length::Fill).into(), + widget::horizontal_space().width(Length::Fill).into(), widget::icon::from_name("pan-end-symbolic") .size(16) .icon() .into(), ]) - .style( + .class( // Menu folders have no on_press so they take on the disabled style by default if children.is_empty() { // This will make the folder use the disabled style if it has no children diff --git a/src/widget/min_size_tracker/mod.rs b/src/widget/min_size_tracker/mod.rs new file mode 100644 index 00000000..b190fecd --- /dev/null +++ b/src/widget/min_size_tracker/mod.rs @@ -0,0 +1,319 @@ +mod subscription; + +use iced::futures::channel::mpsc::UnboundedSender; +use iced::widget::Container; +use iced::Vector; +pub use subscription::*; + +use iced_core::alignment; +use iced_core::event::{self, Event}; +use iced_core::layout; +use iced_core::mouse; +use iced_core::overlay; +use iced_core::renderer; +use iced_core::widget::Tree; +use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget}; +use std::{fmt::Debug, hash::Hash}; + +pub use iced_widget::container::{Catalog, Style}; + +pub fn min_size_tracker<'a, Message, I, T>( + content: T, + id: I, + tx: UnboundedSender<(I, Rectangle)>, +) -> MinSizeTrackerContainer<'a, Message, crate::Renderer, I> +where + I: Hash + Copy + Send + Sync + Debug + 'a, + T: Into>, +{ + MinSizeTrackerContainer::new(content, id, tx) +} + +pub fn subscription< + I: 'static + Hash + Copy + Send + Sync + Debug, + R: 'static + Hash + Copy + Send + Sync + Debug + Eq, +>( + id: I, +) -> iced::Subscription<(I, RectangleUpdate)> { + subscription::rectangle_tracker_subscription(id) +} + +#[derive(Clone, Debug)] +pub struct MinSizeTracker { + tx: UnboundedSender<(I, Rectangle)>, +} + +impl MinSizeTracker +where + I: Hash + Copy + Send + Sync + Debug, +{ + pub fn container<'a, Message: 'static, T>( + &self, + id: I, + content: T, + ) -> MinSizeTracker<'a, Message, crate::Renderer, I> + where + I: 'a, + T: Into>, + { + MinSizeTracker::new(content, id, self.tx.clone()) + } +} + +/// An element decorating some content. +/// +/// It is normally used for alignment purposes. +#[allow(missing_debug_implementations)] +pub struct MinSizeTrackerContainer<'a, Message, Renderer, I> +where + Renderer: iced_core::Renderer, +{ + tx: UnboundedSender<(I, Rectangle)>, + id: I, + container: Container<'a, Message, crate::Theme, Renderer>, + ignore_bounds: bool, +} + +impl<'a, Message, Renderer, I> MinSizeTrackerContainer<'a, Message, Renderer, I> +where + Renderer: iced_core::Renderer, + I: 'a + Hash + Copy + Send + Sync + Debug, +{ + /// Creates an empty [`Container`]. + pub(crate) fn new(content: T, id: I, tx: UnboundedSender<(I, Rectangle)>) -> Self + where + T: Into>, + { + MinSizeTrackerContainer { + id, + tx, + container: Container::new(content), + ignore_bounds: false, + } + } + + pub fn diff(&mut self, tree: &mut Tree) { + self.container.diff(tree); + } + + /// Sets the [`Padding`] of the [`Container`]. + #[must_use] + pub fn padding>(mut self, padding: P) -> Self { + self.container = self.container.padding(padding); + self + } + + /// Sets the width of the [`self.`]. + #[must_use] + pub fn width(mut self, width: Length) -> Self { + self.container = self.container.width(width); + self + } + + /// Sets the height of the [`Container`]. + #[must_use] + pub fn height(mut self, height: Length) -> Self { + self.container = self.container.height(height); + self + } + + /// Sets the maximum width of the [`Container`]. + #[must_use] + pub fn max_width(mut self, max_width: f32) -> Self { + self.container = self.container.max_width(max_width); + self + } + + /// Sets the maximum height of the [`Container`] in pixels. + #[must_use] + pub fn max_height(mut self, max_height: f32) -> Self { + self.container = self.container.max_height(max_height); + self + } + + /// Sets the content alignment for the horizontal axis of the [`Container`]. + #[must_use] + pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self { + self.container = self.container.align_x(alignment); + self + } + + /// Sets the content alignment for the vertical axis of the [`Container`]. + #[must_use] + pub fn align_y(mut self, alignment: alignment::Vertical) -> Self { + self.container = self.container.align_y(alignment); + self + } + + /// Centers the contents in the horizontal axis of the [`Container`]. + #[must_use] + pub fn center_x(mut self, width: Length) -> Self { + self.container = self.container.center_x(width); + self + } + + /// Centers the contents in the vertical axis of the [`Container`]. + #[must_use] + pub fn center_y(mut self, height: Length) -> Self { + self.container = self.container.center_y(height); + self + } + + /// Sets the style of the [`Container`]. + #[must_use] + pub fn style(mut self, style: impl Into<::Class<'a>>) -> Self { + self.container = self.container.class(style); + self + } + + /// Set to true to ignore parent container bounds when performing layout. + /// This can be useful for widgets that are in auto-sized surfaces. + #[must_use] + pub fn ignore_bounds(mut self, ignore_bounds: bool) -> Self { + self.ignore_bounds = ignore_bounds; + self + } +} + +impl<'a, Message, Renderer, I> Widget + for MinSizeTrackerContainer<'a, Message, Renderer, I> +where + Renderer: iced_core::Renderer, + I: 'a + Hash + Copy + Send + Sync + Debug, +{ + fn children(&self) -> Vec { + self.container.children() + } + + fn state(&self) -> iced_core::widget::tree::State { + self.container.state() + } + + fn diff(&mut self, tree: &mut Tree) { + self.container.diff(tree); + } + + fn size(&self) -> iced_core::Size { + self.container.size() + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.container.layout( + tree, + renderer, + if self.ignore_bounds { + &layout::Limits::NONE + } else { + limits + }, + ) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn iced_core::widget::Operation<()>, + ) { + self.container.operate(tree, layout, renderer, operation); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &iced_core::Rectangle, + ) -> event::Status { + self.container.on_event( + tree, + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + viewport, + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.container + .mouse_interaction(tree, layout, cursor_position, viewport, renderer) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &crate::Theme, + renderer_style: &renderer::Style, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + viewport: &Rectangle, + ) { + let _ = self.tx.unbounded_send((self.id, layout.bounds())); + + self.container.draw( + tree, + renderer, + theme, + renderer_style, + layout, + cursor_position, + viewport, + ); + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + translation: Vector, + ) -> Option> { + self.container.overlay(tree, layout, renderer, translation) + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, + ) { + self.container + .drag_destinations(state, layout, renderer, dnd_rectangles); + } +} + +impl<'a, Message, Renderer, I> From> + for Element<'a, Message, crate::Theme, Renderer> +where + Message: 'a, + Renderer: 'a + iced_core::Renderer, + I: 'a + Hash + Copy + Send + Sync + Debug, +{ + fn from( + column: MinSizeTrackerContainer<'a, Message, Renderer, I>, + ) -> Element<'a, Message, crate::Theme, Renderer> { + Element::new(column) + } +} diff --git a/src/widget/min_size_tracker/subscription.rs b/src/widget/min_size_tracker/subscription.rs new file mode 100644 index 00000000..c7711b43 --- /dev/null +++ b/src/widget/min_size_tracker/subscription.rs @@ -0,0 +1,88 @@ +use iced::{ + futures::{ + channel::mpsc::{unbounded, UnboundedReceiver}, + stream, StreamExt, + }, + Rectangle, +}; +use iced_futures::Subscription; +use std::{collections::HashMap, fmt::Debug, hash::Hash}; + +use super::MinSizeTrackerContainer; + +pub fn rectangle_tracker_subscription< + I: 'static + Hash + Copy + Send + Sync + Debug, + R: 'static + Hash + Copy + Send + Sync + Debug + Eq, +>( + id: I, +) -> Subscription<(I, RectangleUpdate)> { + Subscription::run_with_id( + id, + stream::unfold(State::Ready, move |state| start_listening(id, state)), + ) +} + +pub enum State { + Ready, + Waiting(UnboundedReceiver<(I, Rectangle)>, HashMap), + Finished, +} + +async fn start_listening( + id: I, + mut state: State, +) -> Option<((I, RectangleUpdate), State)> { + loop { + let (update, new_state) = match state { + State::Ready => { + let (tx, rx) = unbounded(); + + ( + Some((id, RectangleUpdate::Init(MinSizeTracker { tx }))), + State::Waiting(rx, HashMap::new()), + ) + } + State::Waiting(mut rx, mut map) => match rx.next().await { + Some(u) => { + if let Some(prev) = map.get(&u.0) { + let new = u.1; + if (prev.width - new.width).abs() > 0.1 + || (prev.height - new.height).abs() > 0.1 + || (prev.x - new.x).abs() > 0.1 + || (prev.y - new.y).abs() > 0.1 + { + map.insert(u.0, new); + ( + Some((id, RectangleUpdate::Rectangle(u))), + State::Waiting(rx, map), + ) + } else { + (None, State::Waiting(rx, map)) + } + } else { + map.insert(u.0, u.1); + ( + Some((id, RectangleUpdate::Rectangle(u))), + State::Waiting(rx, map), + ) + } + } + None => (None, State::Finished), + }, + State::Finished => return None, + }; + state = new_state; + if let Some(u) = update { + return Some((u, state)); + } + } +} + +#[derive(Clone, Debug)] +pub enum RectangleUpdate +where + I: 'static + Hash + Copy + Send + Sync + Debug, +{ + Rectangle((I, Rectangle)), + Init(MinSizeTrackerContainer), +} diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 60bfcc3d..ecce1a86 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -60,7 +60,7 @@ pub use iced::widget::{combo_box, ComboBox}; pub use iced::widget::{container, Container}; #[doc(inline)] -pub use iced::widget::{horizontal_space, space, vertical_space, Space}; +pub use iced::widget::{horizontal_space, vertical_space, Space}; #[doc(inline)] pub use iced::widget::{image, Image}; @@ -94,6 +94,9 @@ pub use iced_core::widget::{Id, Operation, Widget}; pub mod aspect_ratio; +#[cfg(feature = "autosize")] +pub mod autosize; + pub mod button; #[doc(inline)] pub use button::{Button, IconButton, LinkButton, TextButton}; @@ -166,20 +169,20 @@ pub mod divider { /// Horizontal divider with default thickness #[must_use] - pub fn default() -> Rule { - horizontal_rule(1).style(crate::theme::Rule::Default) + pub fn default<'a>() -> Rule<'a, crate::Theme> { + horizontal_rule(1).class(crate::theme::Rule::Default) } /// Horizontal divider with light thickness #[must_use] - pub fn light() -> Rule { - horizontal_rule(1).style(crate::theme::Rule::LightDivider) + pub fn light<'a>() -> Rule<'a, crate::Theme> { + horizontal_rule(1).class(crate::theme::Rule::LightDivider) } /// Horizontal divider with heavy thickness. #[must_use] - pub fn heavy() -> Rule { - horizontal_rule(4).style(crate::theme::Rule::HeavyDivider) + pub fn heavy<'a>() -> Rule<'a, crate::Theme> { + horizontal_rule(4).class(crate::theme::Rule::HeavyDivider) } } @@ -189,20 +192,20 @@ pub mod divider { /// Vertical divider with default thickness #[must_use] - pub fn default() -> Rule { - vertical_rule(1).style(crate::theme::Rule::Default) + pub fn default<'a>() -> Rule<'a, crate::Theme> { + vertical_rule(1).class(crate::theme::Rule::Default) } /// Vertical divider with light thickness #[must_use] - pub fn light() -> Rule { - vertical_rule(4).style(crate::theme::Rule::LightDivider) + pub fn light<'a>() -> Rule<'a, crate::Theme> { + vertical_rule(4).class(crate::theme::Rule::LightDivider) } /// Vertical divider with heavy thickness. #[must_use] - pub fn heavy() -> Rule { - vertical_rule(10).style(crate::theme::Rule::HeavyDivider) + pub fn heavy<'a>() -> Rule<'a, crate::Theme> { + vertical_rule(10).class(crate::theme::Rule::HeavyDivider) } } } @@ -268,7 +271,7 @@ pub use radio::{radio, Radio}; pub mod rectangle_tracker; #[doc(inline)] -pub use rectangle_tracker::{rectangle_tracker, RectangleTracker}; +pub use rectangle_tracker::{rectangle_tracking_container, RectangleTracker}; #[doc(inline)] pub use row::{row, Row}; @@ -343,13 +346,13 @@ pub mod tooltip { pub fn tooltip<'a, Message>( content: impl Into>, - tooltip: impl Into>, + tooltip: impl Into>, position: Position, ) -> Tooltip<'a, Message> { let xxs = crate::theme::active().cosmic().space_xxs(); Tooltip::new(content, tooltip, position) - .style(crate::theme::Container::Tooltip) + .class(crate::theme::Container::Tooltip) .padding(xxs) .gap(1) } diff --git a/src/widget/nav_bar.rs b/src/widget/nav_bar.rs index 246293cc..cc62d144 100644 --- a/src/widget/nav_bar.rs +++ b/src/widget/nav_bar.rs @@ -147,11 +147,12 @@ impl<'a, Message: Clone + 'static> From> .spacing(space_xxs) .style(crate::theme::SegmentedButton::TabBar) .apply(scrollable) + .class(crate::style::iced::Scrollable::Minimal) .height(Length::Fill) .apply(container) .padding(space_xxs) .height(Length::Fill) - .style(theme::Container::custom(nav_bar_style)) + .class(theme::Container::custom(nav_bar_style)) } } @@ -162,9 +163,9 @@ impl<'a, Message: Clone + 'static> From> for crate::Element< } #[must_use] -pub fn nav_bar_style(theme: &Theme) -> iced_style::container::Appearance { +pub fn nav_bar_style(theme: &Theme) -> iced_widget::container::Style { let cosmic = &theme.cosmic(); - iced_style::container::Appearance { + iced_widget::container::Style { icon_color: Some(cosmic.on_bg_color().into()), text_color: Some(cosmic.on_bg_color().into()), background: Some(Background::Color(cosmic.primary.base.into())), diff --git a/src/widget/nav_bar_toggle.rs b/src/widget/nav_bar_toggle.rs index 68dd528b..bd14cf3f 100644 --- a/src/widget/nav_bar_toggle.rs +++ b/src/widget/nav_bar_toggle.rs @@ -11,7 +11,7 @@ pub struct NavBarToggle { active: bool, #[setters(strip_option)] on_toggle: Option, - style: crate::theme::Button, + class: crate::theme::Button, selected: bool, } @@ -20,7 +20,7 @@ pub fn nav_bar_toggle() -> NavBarToggle { NavBarToggle { active: false, on_toggle: None, - style: crate::theme::Button::Text, + class: crate::theme::Button::Text, selected: false, } } @@ -43,7 +43,7 @@ impl<'a, Message: 'static + Clone> From> for Element<'a, M .padding([8, 16]) .on_press_maybe(nav_bar_toggle.on_toggle) .selected(nav_bar_toggle.selected) - .style(nav_bar_toggle.style) + .class(nav_bar_toggle.class) .into() } } diff --git a/src/widget/popover.rs b/src/widget/popover.rs index cb15da91..4eb50ea2 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -9,12 +9,12 @@ use iced_core::mouse; use iced_core::overlay; use iced_core::renderer; use iced_core::touch; -use iced_core::widget::{tree, Operation, OperationOutputWrapper, Tree}; +use iced_core::widget::{tree, Operation, Tree}; use iced_core::{ Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, }; -pub use iced_style::container::{Appearance, StyleSheet}; +pub use iced_widget::container::{Catalog, Style}; pub fn popover<'a, Message, Renderer>( content: impl Into>, @@ -123,7 +123,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation>, + operation: &mut dyn Operation<()>, ) { self.content .as_widget() @@ -214,6 +214,7 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + mut translation: Vector, ) -> Option> { if !tree.state.downcast_mut::().is_open { return None; @@ -239,19 +240,22 @@ where // Round position to prevent rendering issues overlay_position.x = overlay_position.x.round(); overlay_position.y = overlay_position.y.round(); + translation.x += overlay_position.x; + translation.y += overlay_position.y; - Some(overlay::Element::new( - overlay_position, - Box::new(Overlay { - tree: &mut tree.children[1], - content: popup, - position: self.position, - }), - )) + Some(overlay::Element::new(Box::new(Overlay { + tree: &mut tree.children[1], + content: popup, + position: self.position, + pos: Point::new(translation.x, translation.y), + }))) } else { - self.content - .as_widget_mut() - .overlay(&mut tree.children[0], layout, renderer) + self.content.as_widget_mut().overlay( + &mut tree.children[0], + layout, + renderer, + translation, + ) } } @@ -286,6 +290,7 @@ pub struct Overlay<'a, 'b, Message, Renderer> { tree: &'a mut Tree, content: &'a mut Element<'b, Message, crate::Theme, Renderer>, position: Position, + pos: Point, } impl<'a, 'b, Message, Renderer> overlay::Overlay @@ -294,13 +299,8 @@ where Message: Clone, Renderer: iced_core::Renderer, { - fn layout( - &mut self, - renderer: &Renderer, - bounds: Size, - mut position: Point, - _translation: iced::Vector, - ) -> layout::Node { + fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node { + let mut position = self.pos; let limits = layout::Limits::new(Size::UNIT, bounds); let node = self .content @@ -342,7 +342,7 @@ where &mut self, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation>, + operation: &mut dyn Operation<()>, ) { self.content .as_widget() @@ -413,7 +413,7 @@ where ) -> Option> { self.content .as_widget_mut() - .overlay(&mut self.tree, layout, renderer) + .overlay(&mut self.tree, layout, renderer, Default::default()) } } diff --git a/src/widget/radio.rs b/src/widget/radio.rs index c509b491..1b40a0e0 100644 --- a/src/widget/radio.rs +++ b/src/widget/radio.rs @@ -1,4 +1,6 @@ //! Create choices using radio buttons. +use crate::Theme; +use iced::border; use iced_core::event::{self, Event}; use iced_core::layout; use iced_core::mouse; @@ -7,17 +9,18 @@ use iced_core::renderer; use iced_core::touch; use iced_core::widget::tree::Tree; use iced_core::{ - Border, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, + Border, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Vector, Widget, }; -pub use iced_style::radio::{Appearance, StyleSheet}; +use iced_widget::radio as iced_radio; +pub use iced_widget::radio::Catalog; -pub fn radio<'a, Message: Clone, Theme: StyleSheet, V, F>( +pub fn radio<'a, Message: Clone, V, F>( label: impl Into>, value: V, selected: Option, f: F, -) -> Radio<'a, Message, Theme, crate::Renderer> +) -> Radio<'a, Message, crate::Renderer> where V: Eq + Copy, F: FnOnce(V) -> Message, @@ -83,9 +86,8 @@ where /// let content = column![a, b, c, all]; /// ``` #[allow(missing_debug_implementations)] -pub struct Radio<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> +pub struct Radio<'a, Message, Renderer = crate::Renderer> where - Theme: StyleSheet, Renderer: iced_core::Renderer, { is_selected: bool, @@ -94,13 +96,11 @@ where width: Length, size: f32, spacing: f32, - style: Theme::Style, } -impl<'a, Message, Theme, Renderer> Radio<'a, Message, Theme, Renderer> +impl<'a, Message, Renderer> Radio<'a, Message, Renderer> where Message: Clone, - Theme: StyleSheet, Renderer: iced_core::Renderer, { /// The default size of a [`Radio`] button. @@ -130,7 +130,6 @@ where width: Length::Shrink, size: Self::DEFAULT_SIZE, spacing: Self::DEFAULT_SPACING, - style: Default::default(), } } @@ -154,20 +153,11 @@ where self.spacing = spacing.into().0; self } - - #[must_use] - /// Sets the style of the [`Radio`] button. - pub fn style(mut self, style: impl Into) -> Self { - self.style = style.into(); - self - } } -impl<'a, Message, Theme, Renderer> Widget - for Radio<'a, Message, Theme, Renderer> +impl<'a, Message, Renderer> Widget for Radio<'a, Message, Renderer> where Message: Clone, - Theme: StyleSheet, Renderer: iced_core::Renderer, { fn children(&self) -> Vec { @@ -207,9 +197,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn iced_core::widget::Operation< - iced_core::widget::OperationOutputWrapper, - >, + operation: &mut dyn iced_core::widget::Operation<()>, ) { self.label.as_widget().operate( &mut tree.children[0], @@ -302,9 +290,19 @@ where let mut children = layout.children(); let custom_style = if is_mouse_over { - theme.hovered(&self.style, self.is_selected) + theme.style( + &(), + iced_radio::Status::Hovered { + is_selected: self.is_selected, + }, + ) } else { - theme.active(&self.style, self.is_selected) + theme.style( + &(), + iced_radio::Status::Active { + is_selected: self.is_selected, + }, + ) }; { @@ -336,7 +334,7 @@ where width: dot_size, height: dot_size, }, - border: Border::with_radius(dot_size / 2.0), + border: border::rounded(dot_size / 2.0), ..renderer::Quad::default() }, custom_style.dot_color, @@ -363,11 +361,13 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + translation: Vector, ) -> Option> { self.label.as_widget_mut().overlay( &mut tree.children[0], layout.children().nth(1).unwrap(), renderer, + translation, ) } @@ -376,7 +376,7 @@ where state: &Tree, layout: Layout<'_>, renderer: &Renderer, - dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { self.label.as_widget().drag_destinations( &state.children[0], @@ -387,14 +387,13 @@ where } } -impl<'a, Message, Theme, Renderer> From> +impl<'a, Message, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a + Clone, - Theme: 'a + StyleSheet, Renderer: 'a + iced_core::Renderer, { - fn from(radio: Radio<'a, Message, Theme, Renderer>) -> Element<'a, Message, Theme, Renderer> { + fn from(radio: Radio<'a, Message, Renderer>) -> Element<'a, Message, Theme, Renderer> { Element::new(radio) } } diff --git a/src/widget/rectangle_tracker/mod.rs b/src/widget/rectangle_tracker/mod.rs index 93b861a3..6ce11396 100644 --- a/src/widget/rectangle_tracker/mod.rs +++ b/src/widget/rectangle_tracker/mod.rs @@ -2,6 +2,7 @@ mod subscription; use iced::futures::channel::mpsc::UnboundedSender; use iced::widget::Container; +use iced::Vector; pub use subscription::*; use iced_core::alignment; @@ -14,9 +15,9 @@ use iced_core::widget::Tree; use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget}; use std::{fmt::Debug, hash::Hash}; -pub use iced_style::container::{Appearance, StyleSheet}; +pub use iced_widget::container::{Catalog, Style}; -pub fn rectangle_tracker<'a, Message, I, T>( +pub fn rectangle_tracking_container<'a, Message, I, T>( content: T, id: I, tx: UnboundedSender<(I, Rectangle)>, @@ -71,6 +72,7 @@ where id: I, container: Container<'a, Message, crate::Theme, Renderer>, ignore_bounds: bool, + request_size: bool, } impl<'a, Message, Renderer, I> RectangleTrackingContainer<'a, Message, Renderer, I> @@ -88,6 +90,7 @@ where tx, container: Container::new(content), ignore_bounds: false, + request_size: true, } } @@ -146,22 +149,22 @@ where /// Centers the contents in the horizontal axis of the [`Container`]. #[must_use] - pub fn center_x(mut self) -> Self { - self.container = self.container.center_x(); + pub fn center_x(mut self, width: Length) -> Self { + self.container = self.container.center_x(width); self } /// Centers the contents in the vertical axis of the [`Container`]. #[must_use] - pub fn center_y(mut self) -> Self { - self.container = self.container.center_y(); + pub fn center_y(mut self, height: Length) -> Self { + self.container = self.container.center_y(height); self } /// Sets the style of the [`Container`]. #[must_use] - pub fn style(mut self, style: impl Into<::Style>) -> Self { - self.container = self.container.style(style); + pub fn style(mut self, style: impl Into<::Class<'a>>) -> Self { + self.container = self.container.class(style); self } @@ -202,7 +205,7 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - self.container.layout( + let layout = self.container.layout( tree, renderer, if self.ignore_bounds { @@ -210,7 +213,10 @@ where } else { limits }, - ) + ); + if self.request_size {} + + layout } fn operate( @@ -218,9 +224,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn iced_core::widget::Operation< - iced_core::widget::OperationOutputWrapper, - >, + operation: &mut dyn iced_core::widget::Operation<()>, ) { self.container.operate(tree, layout, renderer, operation); } @@ -271,7 +275,6 @@ where viewport: &Rectangle, ) { let _ = self.tx.unbounded_send((self.id, layout.bounds())); - self.container.draw( tree, renderer, @@ -288,8 +291,9 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + translation: Vector, ) -> Option> { - self.container.overlay(tree, layout, renderer) + self.container.overlay(tree, layout, renderer, translation) } fn drag_destinations( @@ -297,7 +301,7 @@ where state: &Tree, layout: Layout<'_>, renderer: &Renderer, - dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { self.container .drag_destinations(state, layout, renderer, dnd_rectangles); diff --git a/src/widget/rectangle_tracker/subscription.rs b/src/widget/rectangle_tracker/subscription.rs index 224f8d6c..e49946d5 100644 --- a/src/widget/rectangle_tracker/subscription.rs +++ b/src/widget/rectangle_tracker/subscription.rs @@ -1,10 +1,11 @@ use iced::{ futures::{ channel::mpsc::{unbounded, UnboundedReceiver}, - StreamExt, + stream, StreamExt, }, - subscription, Rectangle, + Rectangle, }; +use iced_futures::Subscription; use std::{collections::HashMap, fmt::Debug, hash::Hash}; use super::RectangleTracker; @@ -14,8 +15,11 @@ pub fn rectangle_tracker_subscription< R: 'static + Hash + Copy + Send + Sync + Debug + Eq, >( id: I, -) -> iced::Subscription<(I, RectangleUpdate)> { - subscription::unfold(id, State::Ready, move |state| start_listening(id, state)) +) -> Subscription<(I, RectangleUpdate)> { + Subscription::run_with_id( + id, + stream::unfold(State::Ready, move |state| start_listening(id, state)), + ) } pub enum State { @@ -27,7 +31,7 @@ pub enum State { async fn start_listening( id: I, mut state: State, -) -> ((I, RectangleUpdate), State) { +) -> Option<((I, RectangleUpdate), State)> { loop { let (update, new_state) = match state { State::Ready => { @@ -65,11 +69,11 @@ async fn start_listening (None, State::Finished), }, - State::Finished => iced::futures::future::pending().await, + State::Finished => return None, }; state = new_state; if let Some(u) = update { - return (u, state); + return Some((u, state)); } } } diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index f070c7e7..9fa62d49 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -16,14 +16,15 @@ use iced::clipboard::dnd::{self, DndAction, DndDestinationRectangle, DndEvent, O use iced::clipboard::mime::AllowedMimeTypes; use iced::touch::Finger; use iced::{ - alignment, event, keyboard, mouse, touch, Alignment, Background, Color, Command, Event, Length, - Padding, Rectangle, Size, + alignment, event, keyboard, mouse, touch, Alignment, Background, Color, Event, Length, Padding, + Rectangle, Size, Task, Vector, }; use iced_core::mouse::ScrollDelta; -use iced_core::text::{LineHeight, Paragraph, Renderer as TextRenderer, Shaping, Wrap}; +use iced_core::text::{LineHeight, Paragraph, Renderer as TextRenderer, Shaping, Wrapping}; use iced_core::widget::{self, operation, tree}; use iced_core::{layout, renderer, widget::Tree, Clipboard, Layout, Shell, Widget}; use iced_core::{Border, Gradient, Point, Renderer as IcedRenderer, Shadow, Text}; +use iced_runtime::{task, Action}; use slotmap::{Key, SecondaryMap}; use std::borrow::Cow; use std::collections::hash_map::DefaultHasher; @@ -34,8 +35,8 @@ use std::mem; use std::time::{Duration, Instant}; /// A command that focuses a segmented item stored in a widget. -pub fn focus(id: Id) -> Command { - Command::widget(operation::focusable::focus(id.0)) +pub fn focus(id: Id) -> Task { + task::effect(Action::Widget(Box::new(operation::focusable::focus(id.0)))) } pub enum ItemBounds { @@ -436,15 +437,15 @@ where if !text.is_empty() { icon_spacing = f32::from(self.button_spacing); let paragraph = entry.or_insert_with(|| { - crate::Paragraph::with_text(Text { - content: text, + crate::Plain::new(Text { + content: text.as_ref(), size: iced::Pixels(self.font_size), bounds: Size::INFINITY, font, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: Shaping::Advanced, - wrap: Wrap::default(), + wrapping: Wrapping::default(), line_height: self.line_height, }) }); @@ -622,23 +623,21 @@ where } let text = Text { - content: text, + content: text.as_ref(), size: iced::Pixels(self.font_size), bounds: Size::INFINITY, font, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: Shaping::Advanced, - wrap: Wrap::default(), + wrapping: Wrapping::default(), line_height: self.line_height, }; if let Some(paragraph) = state.paragraphs.get_mut(key) { paragraph.update(text); } else { - state - .paragraphs - .insert(key, crate::Paragraph::with_text(text)); + state.paragraphs.insert(key, crate::Plain::new(text)); } } } @@ -1079,9 +1078,7 @@ where tree: &mut Tree, _layout: Layout<'_>, _renderer: &Renderer, - operation: &mut dyn iced_core::widget::Operation< - iced_core::widget::OperationOutputWrapper, - >, + operation: &mut dyn iced_core::widget::Operation<()>, ) { let state = tree.state.downcast_mut::(); operation.focusable(state, Some(&self.id.0)); @@ -1492,7 +1489,7 @@ where if self.model.text(key).is_some_and(|text| !text.is_empty()) { // Draw the text for this segmented button or tab. renderer.fill_paragraph( - &state.paragraphs[key], + state.paragraphs[key].raw(), bounds.position(), apply_alpha(status_appearance.text_color), Rectangle { @@ -1528,6 +1525,7 @@ where tree: &'b mut Tree, layout: iced_core::Layout<'_>, _renderer: &Renderer, + translation: Vector, ) -> Option> { let state = tree.state.downcast_ref::(); @@ -1575,6 +1573,7 @@ where root_bounds_list: vec![bounds], path_highlight: Some(PathHighlight::MenuActive), style: &crate::theme::menu_bar::MenuBarStyle::Default, + position: Point::new(translation.x, translation.y), } .overlay(), ) @@ -1645,7 +1644,7 @@ pub struct LocalState { /// Dimensions of internal buttons when shrinking pub(super) internal_layout: Vec<(Size, Size)>, /// The paragraphs for each text. - paragraphs: SecondaryMap, + paragraphs: SecondaryMap, /// Used to detect changes in text. text_hashes: SecondaryMap, /// Location of cursor when context menu was opened. diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index b21df312..f277ff02 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -9,7 +9,7 @@ use crate::{ Element, }; use derive_setters::Setters; -use iced_core::{text::Wrap, Length}; +use iced_core::{text::Wrapping, Length}; use taffy::AlignContent; /// A settings item aligned in a row @@ -20,8 +20,8 @@ pub fn item<'a, Message: 'static>( widget: impl Into> + 'a, ) -> Row<'a, Message> { item_row(vec![ - text(title).wrap(Wrap::Word).into(), - horizontal_space(iced::Length::Fill).into(), + text(title).wrapping(Wrapping::Word).into(), + horizontal_space().width(iced::Length::Fill).into(), widget.into(), ]) } @@ -35,7 +35,7 @@ pub fn item_row(children: Vec>) -> Row { } = theme::THEME.lock().unwrap().cosmic().spacing; row::with_children(children) .spacing(space_xs) - .align_items(iced::Alignment::Center) + .align_y(iced::Alignment::Center) .padding([0, space_s]) } @@ -46,7 +46,10 @@ pub fn flex_item<'a, Message: 'static>( widget: impl Into> + 'a, ) -> FlexRow<'a, Message> { flex_item_row(vec![ - text(title).wrap(Wrap::Word).width(Length::Fill).into(), + text(title) + .wrapping(Wrapping::Word) + .width(Length::Fill) + .into(), container(widget).into(), ]) } @@ -111,8 +114,8 @@ impl<'a, Message: 'static> Item<'a, Message> { if let Some(description) = self.description { let column = column::with_capacity(2) .spacing(2) - .push(text(self.title).wrap(Wrap::Word)) - .push(text(description).wrap(Wrap::Word).size(10)) + .push(text(self.title).wrapping(Wrapping::Word)) + .push(text(description).wrapping(Wrapping::Word).size(10)) .width(Length::Fill); contents.push(column.into()); @@ -129,6 +132,6 @@ impl<'a, Message: 'static> Item<'a, Message> { is_checked: bool, message: impl Fn(bool) -> Message + 'static, ) -> Row<'a, Message> { - self.control(crate::widget::toggler(None, is_checked, message)) + self.control(crate::widget::toggler(is_checked, message)) } } diff --git a/src/widget/spin_button/mod.rs b/src/widget/spin_button/mod.rs index d0fbbca2..7ed3e535 100644 --- a/src/widget/spin_button/mod.rs +++ b/src/widget/spin_button/mod.rs @@ -56,11 +56,10 @@ impl<'a, Message: 'static> SpinButton<'a, Message> { .apply(button::custom) .width(Length::Fixed(32.0)) .height(Length::Fixed(32.0)) - .style(theme::Button::Text) + .class(theme::Button::Text) .on_press(model::Message::Decrement) .into(), text::title4(label) - .vertical_alignment(Vertical::Center) .apply(container) .width(Length::Fixed(48.0)) .align_x(Horizontal::Center) @@ -76,18 +75,18 @@ impl<'a, Message: 'static> SpinButton<'a, Message> { .apply(button::custom) .width(Length::Fixed(32.0)) .height(Length::Fixed(32.0)) - .style(theme::Button::Text) + .class(theme::Button::Text) .on_press(model::Message::Increment) .into(), ]) .width(Length::Shrink) .height(Length::Fixed(32.0)) - .align_items(Alignment::Center), + .align_y(Alignment::Center), ) .align_y(Vertical::Center) .width(Length::Shrink) .height(Length::Fixed(32.0)) - .style(theme::Container::custom(container_style)) + .class(theme::Container::custom(container_style)) .apply(Element::from) .map(on_change) } @@ -100,13 +99,13 @@ impl<'a, Message: 'static> From> for Element<'a, Message } #[allow(clippy::trivially_copy_pass_by_ref)] -fn container_style(theme: &crate::Theme) -> iced_style::container::Appearance { +fn container_style(theme: &crate::Theme) -> iced_widget::container::Style { let basic = &theme.cosmic(); let mut neutral_10 = basic.palette.neutral_10; neutral_10.alpha = 0.1; let accent = &basic.accent; let corners = &basic.corner_radii; - iced_style::container::Appearance { + iced_widget::container::Style { icon_color: Some(basic.palette.neutral_10.into()), text_color: Some(basic.palette.neutral_10.into()), background: None, diff --git a/src/widget/text.rs b/src/widget/text.rs index 1aed9500..f1e3cffe 100644 --- a/src/widget/text.rs +++ b/src/widget/text.rs @@ -7,7 +7,7 @@ use std::borrow::Cow; /// /// [`Text`]: widget::Text pub fn text<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text).font(crate::font::default()) + Text::new(text.into()).font(crate::font::default()) } /// Available presets for text typography @@ -26,7 +26,7 @@ pub enum Typography { /// [`Text`] widget with the Title 1 typography preset. pub fn title1<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text) + Text::new(text.into()) .size(32.0) .line_height(LineHeight::Absolute(44.0.into())) .font(crate::font::semibold()) @@ -34,7 +34,7 @@ pub fn title1<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, /// [`Text`] widget with the Title 2 typography preset. pub fn title2<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text) + Text::new(text.into()) .size(28.0) .line_height(LineHeight::Absolute(36.0.into())) .font(crate::font::default()) @@ -42,7 +42,7 @@ pub fn title2<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, /// [`Text`] widget with the Title 3 typography preset. pub fn title3<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text) + Text::new(text.into()) .size(24.0) .line_height(LineHeight::Absolute(32.0.into())) .font(crate::font::default()) @@ -50,7 +50,7 @@ pub fn title3<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, /// [`Text`] widget with the Title 4 typography preset. pub fn title4<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text) + Text::new(text.into()) .size(20.0) .line_height(LineHeight::Absolute(28.0.into())) .font(crate::font::default()) @@ -58,7 +58,7 @@ pub fn title4<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, /// [`Text`] widget with the Heading typography preset. pub fn heading<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text) + Text::new(text.into()) .size(14.0) .line_height(LineHeight::Absolute(iced::Pixels(20.0))) .font(crate::font::semibold()) @@ -66,7 +66,7 @@ pub fn heading<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, /// [`Text`] widget with the Caption Heading typography preset. pub fn caption_heading<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text) + Text::new(text.into()) .size(10.0) .line_height(LineHeight::Absolute(iced::Pixels(14.0))) .font(crate::font::semibold()) @@ -74,7 +74,7 @@ pub fn caption_heading<'a>(text: impl Into> + 'a) -> Text<'a, crate /// [`Text`] widget with the Body typography preset. pub fn body<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text) + Text::new(text.into()) .size(14.0) .line_height(LineHeight::Absolute(20.0.into())) .font(crate::font::default()) @@ -82,7 +82,7 @@ pub fn body<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Re /// [`Text`] widget with the Caption typography preset. pub fn caption<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text) + Text::new(text.into()) .size(10.0) .line_height(LineHeight::Absolute(14.0.into())) .font(crate::font::default()) @@ -90,7 +90,7 @@ pub fn caption<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, /// [`Text`] widget with the Monotext typography preset. pub fn monotext<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text) + Text::new(text.into()) .size(14.0) .line_height(LineHeight::Absolute(20.0.into())) .font(crate::font::mono()) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 90a0f576..3c085628 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -18,6 +18,9 @@ use super::style::StyleSheet; pub use super::value::Value; use apply::Apply; +use cosmic_theme::Theme; +use iced::clipboard::dnd::{DndAction, DndEvent, OfferEvent, SourceEvent}; +use iced::clipboard::mime::AsMimeTypes; use iced::Limits; use iced_core::event::{self, Event}; use iced_core::mouse::{self, click}; @@ -39,15 +42,9 @@ use iced_core::{ }; #[cfg(feature = "wayland")] use iced_renderer::core::event::{wayland, PlatformSpecific}; -use iced_renderer::core::widget::OperationOutputWrapper; #[cfg(feature = "wayland")] -use iced_runtime::command::platform_specific; -use iced_runtime::Command; - -#[cfg(feature = "wayland")] -use cctk::sctk::reexports::client::protocol::wl_data_device_manager::DndAction; -#[cfg(feature = "wayland")] -use iced_runtime::command::platform_specific::wayland::data_device::{DataFromMimeType, DndIcon}; +use iced_runtime::platform_specific; +use iced_runtime::{task, Action, Task}; thread_local! { // Prevents two inputs from being focused at the same time. @@ -146,7 +143,7 @@ where }) .size(16) .apply(crate::widget::button::custom) - .style(crate::theme::Button::Icon) + .class(crate::theme::Button::Icon) .on_press(msg) .padding(8) .into(), @@ -173,7 +170,6 @@ where .padding(spacing) } -#[cfg(feature = "wayland")] pub(crate) const SUPPORTED_TEXT_MIME_TYPES: &[&str; 6] = &[ "text/plain;charset=utf-8", "text/plain;charset=UTF-8", @@ -182,17 +178,12 @@ pub(crate) const SUPPORTED_TEXT_MIME_TYPES: &[&str; 6] = &[ "text/plain", "TEXT", ]; -#[cfg(feature = "wayland")] -pub type DnDCommand = - Box platform_specific::wayland::data_device::ActionInner>; -#[cfg(not(feature = "wayland"))] -pub type DnDCommand = (); /// A field that can be filled with text. #[allow(missing_debug_implementations)] #[must_use] pub struct TextInput<'a, Message> { - id: Option, + id: Id, placeholder: Cow<'a, str>, value: Value, is_secure: bool, @@ -215,7 +206,6 @@ pub struct TextInput<'a, Message> { trailing_icon: Option>, style: ::Style, on_create_dnd_source: Option Message + 'a>>, - on_dnd_command_produced: Option Message + 'a>>, surface_ids: Option<(window::Id, window::Id)>, dnd_icon: bool, line_height: text::LineHeight, @@ -237,7 +227,7 @@ where let v: Cow<'a, str> = value.into(); TextInput { - id: None, + id: Id::unique(), placeholder: placeholder.into(), value: Value::new(v.as_ref()), is_secure: false, @@ -258,7 +248,6 @@ where trailing_icon: None, error: None, style: crate::theme::TextInput::default(), - on_dnd_command_produced: None, on_create_dnd_source: None, surface_ids: None, dnd_icon: false, @@ -269,6 +258,15 @@ where } } + fn dnd_id(&self) -> u128 { + match &self.id.0 { + iced_core::id::Internal::Custom(id, _) | iced_core::id::Internal::Unique(id) => { + *id as u128 + } + _ => unreachable!(), + } + } + /// Sets the input to be always active. /// This makes it behave as if it was always focused. pub fn always_active(mut self) -> Self { @@ -290,7 +288,7 @@ where /// Sets the [`Id`] of the [`TextInput`]. pub fn id(mut self, id: Id) -> Self { - self.id = Some(id); + self.id = id; self } @@ -433,10 +431,12 @@ where value: Option<&Value>, style: &renderer::Style, ) { + let text_layout = self.text_layout(layout.clone()); draw( renderer, theme, layout, + text_layout, cursor_position, tree, value.unwrap_or(&self.value), @@ -467,20 +467,6 @@ where self } - /// Sets the dnd command produced handler of the [`TextInput`]. - /// Commands should be returned in the update function of the application. - #[cfg(feature = "wayland")] - pub fn on_dnd_command_produced( - mut self, - on_dnd_command_produced: impl Fn( - Box platform_specific::wayland::data_device::ActionInner>, - ) -> Message - + 'a, - ) -> Self { - self.on_dnd_command_produced = Some(Box::new(on_dnd_command_produced)); - self - } - /// Sets the window id of the [`TextInput`] and the window id of the drag icon. /// Both ids are required to be unique. /// This is required for the dnd to work. @@ -500,7 +486,7 @@ where crate::widget::icon::from_name("edit-clear-symbolic") .size(16) .apply(crate::widget::button::custom) - .style(crate::theme::Button::Icon) + .class(crate::theme::Button::Icon) .on_press(on_clear) .padding(8) .into(), @@ -550,6 +536,7 @@ where } let old_value = state .value + .raw() .buffer() .lines .iter() @@ -559,6 +546,7 @@ where || old_value != self.value.to_string() || state .label + .raw() .buffer() .lines .iter() @@ -567,6 +555,7 @@ where != self.label.unwrap_or_default() || state .helper_text + .raw() .buffer() .lines .iter() @@ -651,7 +640,7 @@ where let v = self.value.to_string(); value_paragraph.update(Text { content: if self.value.is_empty() { - &self.placeholder + self.placeholder.as_ref() } else { &v }, @@ -662,7 +651,7 @@ where vertical_alignment: alignment::Vertical::Center, line_height: text::LineHeight::default(), shaping: text::Shaping::Advanced, - wrap: text::Wrap::default(), + wrapping: text::Wrapping::default(), }); let Size { width, height } = @@ -717,12 +706,13 @@ where tree: &mut Tree, _layout: Layout<'_>, _renderer: &crate::Renderer, - operation: &mut dyn Operation>, + operation: &mut dyn Operation<()>, ) { let state = tree.state.downcast_mut::(); - operation.focusable(state, self.id.as_ref()); - operation.text_input(state, self.id.as_ref()); + operation.custom(state, Some(&self.id)); + operation.focusable(state, Some(&self.id)); + operation.text_input(state, Some(&self.id)); } fn overlay<'b>( @@ -730,6 +720,7 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &crate::Renderer, + translation: Vector, ) -> Option> { let mut layout_ = Vec::with_capacity(2); if self.leading_icon.is_some() { @@ -752,7 +743,9 @@ where .zip(&mut tree.children) .zip(layout_) .filter_map(|((child, state), layout)| { - child.as_widget_mut().overlay(state, layout, renderer) + child + .as_widget_mut() + .overlay(state, layout, renderer, translation) }) .collect::>(); @@ -814,8 +807,10 @@ where } } } - + let dnd_id = self.dnd_id(); + let id = Widget::id(self); update( + id, event, text_layout.children().next().unwrap(), trailing_icon_layout, @@ -833,9 +828,7 @@ where self.on_toggle_edit.as_deref(), || tree.state.downcast_mut::(), self.on_create_dnd_source.as_deref(), - self.dnd_icon, - self.on_dnd_command_produced.as_deref(), - self.surface_ids, + dnd_id, line_height, layout, ) @@ -851,10 +844,12 @@ where cursor_position: mouse::Cursor, viewport: &Rectangle, ) { + let text_layout = self.text_layout(layout.clone()); draw( renderer, theme, layout, + text_layout, cursor_position, tree, &self.value, @@ -934,11 +929,43 @@ where } fn id(&self) -> Option { - self.id.clone() + Some(self.id.clone()) } fn set_id(&mut self, id: Id) { - self.id = Some(id); + self.id = id; + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &crate::Renderer, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, + ) { + if let Some(input) = layout.children().last() { + let Rectangle { + x, + y, + width, + height, + } = input.bounds(); + dnd_rectangles.push(iced::clipboard::dnd::DndDestinationRectangle { + id: self.dnd_id(), + rectangle: iced::clipboard::dnd::Rectangle { + x: x as f64, + y: y as f64, + width: width as f64, + height: height as f64, + }, + mime_types: SUPPORTED_TEXT_MIME_TYPES + .iter() + .map(|s| Cow::Borrowed(*s)) + .collect(), + actions: DndAction::Move, + preferred: DndAction::Move, + }); + } } } @@ -954,32 +981,38 @@ where } } -/// Produces a [`Command`] that focuses the [`TextInput`] with the given [`Id`]. -pub fn focus(id: Id) -> Command { - Command::widget(operation::focusable::focus(id)) +/// Produces a [`Task`] that focuses the [`TextInput`] with the given [`Id`]. +pub fn focus(id: Id) -> Task { + task::effect(Action::widget(operation::focusable::focus(id))) } -/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the +/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the /// end. -pub fn move_cursor_to_end(id: Id) -> Command { - Command::widget(operation::text_input::move_cursor_to_end(id)) +pub fn move_cursor_to_end(id: Id) -> Task { + task::effect(Action::widget(operation::text_input::move_cursor_to_end( + id, + ))) } -/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the +/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the /// front. -pub fn move_cursor_to_front(id: Id) -> Command { - Command::widget(operation::text_input::move_cursor_to_front(id)) +pub fn move_cursor_to_front(id: Id) -> Task { + task::effect(Action::widget(operation::text_input::move_cursor_to_front( + id, + ))) } -/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the +/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the /// provided position. -pub fn move_cursor_to(id: Id, position: usize) -> Command { - Command::widget(operation::text_input::move_cursor_to(id, position)) +pub fn move_cursor_to(id: Id, position: usize) -> Task { + task::effect(Action::widget(operation::text_input::move_cursor_to( + id, position, + ))) } -/// Produces a [`Command`] that selects all the content of the [`TextInput`] with the given [`Id`]. -pub fn select_all(id: Id) -> Command { - Command::widget(operation::text_input::select_all(id)) +/// Produces a [`Task`] that selects all the content of the [`TextInput`] with the given [`Id`]. +pub fn select_all(id: Id) -> Task { + task::effect(Action::widget(operation::text_input::select_all(id))) } /// Computes the layout of a [`TextInput`]. @@ -1019,7 +1052,7 @@ pub fn layout( vertical_alignment: alignment::Vertical::Center, line_height, shaping: text::Shaping::Advanced, - wrap: text::Wrap::default(), + wrapping: text::Wrapping::default(), }); let label_size = label_paragraph.min_bounds(); @@ -1158,7 +1191,7 @@ pub fn layout( vertical_alignment: alignment::Vertical::Center, line_height: helper_text_line_height, shaping: text::Shaping::Advanced, - wrap: text::Wrap::default(), + wrapping: text::Wrapping::default(), }); let helper_text_size = helper_text_paragraph.min_bounds(); let helper_text_node = layout::Node::new(helper_text_size).translate(helper_pos); @@ -1188,7 +1221,8 @@ pub fn layout( #[allow(clippy::missing_panics_doc)] #[allow(clippy::cast_lossless)] #[allow(clippy::cast_possible_truncation)] -pub fn update<'a, Message>( +pub fn update<'a, Message: 'static>( + id: Option, event: Event, text_layout: Layout<'_>, trailing_icon_layout: Option>, @@ -1206,9 +1240,7 @@ pub fn update<'a, Message>( on_toggle_edit: Option<&dyn Fn(bool) -> Message>, state: impl FnOnce() -> &'a mut State, #[allow(unused_variables)] on_start_dnd_source: Option<&dyn Fn(State) -> Message>, - #[allow(unused_variables)] dnd_icon: bool, - #[allow(unused_variables)] on_dnd_command_produced: Option<&dyn Fn(DnDCommand) -> Message>, - #[allow(unused_variables)] surface_ids: Option<(window::Id, window::Id)>, + #[allow(unused_variables)] dnd_id: u128, line_height: text::LineHeight, layout: Layout<'_>, ) -> event::Status @@ -1253,7 +1285,8 @@ where let text_layout = layout.children().next().unwrap(); let target = cursor_position.x - text_layout.bounds().x; - let click = mouse::Click::new(cursor_position, state.last_click); + let click = + mouse::Click::new(cursor_position, mouse::Button::Left, state.last_click); match ( &state.dragging_state, @@ -1266,28 +1299,18 @@ where // single click that is on top of the selected text // is the click on selected text? - if let ( - Some(on_start_dnd), - Some(on_dnd_command_produced), - Some((window_id, icon_id)), - Some(on_input), - ) = ( - on_start_dnd_source, - on_dnd_command_produced, - surface_ids, - on_input, - ) { + if let Some(on_input) = on_input { let left = start.min(end); let right = end.max(start); let (left_position, _left_offset) = measure_cursor_and_scroll_offset( - &state.value, + state.value.raw(), text_layout.bounds(), left, ); let (right_position, _right_offset) = measure_cursor_and_scroll_offset( - &state.value, + state.value.raw(), text_layout.bounds(), right, ); @@ -1305,10 +1328,12 @@ where if is_secure { return event::Status::Ignored; } - let text = + let input_text = state.selected_text(&value.to_string()).unwrap_or_default(); - state.dragging_state = - Some(DraggingState::Dnd(DndAction::empty(), text.clone())); + state.dragging_state = Some(DraggingState::Dnd( + DndAction::empty(), + input_text.clone(), + )); let mut editor = Editor::new(unsecured_value, &mut state.cursor); editor.delete(); @@ -1316,23 +1341,25 @@ where let unsecured_value = Value::new(&contents); let message = (on_input)(contents); shell.publish(message); - shell.publish(on_start_dnd(state.clone())); + if let Some(on_start_dnd) = on_start_dnd_source { + shell.publish(on_start_dnd(state.clone())); + } let state_clone = state.clone(); - shell.publish(on_dnd_command_produced(Box::new(move || { - platform_specific::wayland::data_device::ActionInner::StartDnd { - mime_types: SUPPORTED_TEXT_MIME_TYPES - .iter() - .map(std::string::ToString::to_string) - .collect(), - actions: DndAction::Move, - origin_id: window_id, - icon_id: Some(( - DndIcon::Widget(icon_id, Box::new(state_clone.clone())), - iced::Vector::ZERO, - )), - data: Box::new(TextInputString(text.clone())), - } - }))); + + iced_core::clipboard::start_dnd( + clipboard, + false, + id.map(|id| iced_core::clipboard::DndSource::Widget(id)), + Some(( + Element::from( + TextInput::<'static, ()>::new("", input_text.clone()) + .dnd_icon(true), + ), + iced_core::widget::tree::State::new(state_clone), + )), + Box::new(TextInputString(input_text)), + DndAction::Move, + ); update_cache(state, &unsecured_value); } else { @@ -1435,7 +1462,7 @@ where return event::Status::Ignored; }; - let click = mouse::Click::new(pos, state.last_click); + let click = mouse::Click::new(pos, mouse::Button::Left, state.last_click); match ( &state.dragging_state, @@ -1621,7 +1648,10 @@ where { if !is_secure { if let Some((start, end)) = state.cursor.selection(value) { - clipboard.write(value.select(start, end).to_string()); + clipboard.write( + iced_core::clipboard::Kind::Primary, + value.select(start, end).to_string(), + ); } } } @@ -1632,7 +1662,10 @@ where { if !is_secure { if let Some((start, end)) = state.cursor.selection(value) { - clipboard.write(value.select(start, end).to_string()); + clipboard.write( + iced_core::clipboard::Kind::Primary, + value.select(start, end).to_string(), + ); } let mut editor = Editor::new(value, &mut state.cursor); @@ -1650,7 +1683,7 @@ where content } else { let content: String = clipboard - .read() + .read(iced_core::clipboard::Kind::Primary) .unwrap_or_default() .chars() .filter(|c| !c.is_control()) @@ -1760,7 +1793,7 @@ where state.keyboard_modifiers = modifiers; } - Event::Window(_, window::Event::RedrawRequested(now)) => { + Event::Window(window::Event::RedrawRequested(now)) => { let state = state(); if let Some(focus) = &mut state.is_focused { @@ -1775,9 +1808,7 @@ where } } #[cfg(feature = "wayland")] - Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DataSource( - wayland::DataSourceEvent::DndFinished | wayland::DataSourceEvent::Cancelled, - ))) => { + Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => { let state = state(); if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) { state.dragging_state = None; @@ -1785,54 +1816,32 @@ where } } #[cfg(feature = "wayland")] - Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DataSource( - wayland::DataSourceEvent::DndActionAccepted(action), - ))) => { - let state = state(); - if let Some(DraggingState::Dnd(_, text)) = state.dragging_state.as_ref() { - state.dragging_state = Some(DraggingState::Dnd(action, text.clone())); - return event::Status::Captured; - } - } - #[cfg(feature = "wayland")] - Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer( - wayland::DndOfferEvent::Enter { x, y, mime_types }, - ))) => { - let Some(on_dnd_command_produced) = on_dnd_command_produced else { - return event::Status::Ignored; - }; - + Event::Dnd(DndEvent::Offer( + rectangle, + OfferEvent::Enter { + x, + y, + mime_types, + surface, + }, + )) if rectangle == Some(dnd_id) => { let state = state(); let is_clicked = text_layout.bounds().contains(Point { x: x as f32, y: y as f32, }); - if !is_clicked { - state.dnd_offer = DndOfferState::OutsideWidget(mime_types, DndAction::None); - return event::Status::Captured; - } let mut accepted = false; for m in &mime_types { if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) { let clone = m.clone(); accepted = true; - shell.publish(on_dnd_command_produced(Box::new(move || { - platform_specific::wayland::data_device::ActionInner::Accept(Some( - clone.clone(), - )) - }))); } } if accepted { - shell.publish(on_dnd_command_produced(Box::new(move || { - platform_specific::wayland::data_device::ActionInner::SetActions { - preferred: DndAction::Move, - accepted: DndAction::Move.union(DndAction::Copy), - } - }))); let target = x as f32 - text_layout.bounds().x; - state.dnd_offer = DndOfferState::HandlingOffer(mime_types.clone(), DndAction::None); + state.dnd_offer = + DndOfferState::HandlingOffer(mime_types.clone(), DndAction::empty()); // existing logic for setting the selection let position = if target > 0.0 { update_cache(state, value); @@ -1846,57 +1855,11 @@ where } } #[cfg(feature = "wayland")] - Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer( - wayland::DndOfferEvent::Motion { x, y }, - ))) => { - let Some(on_dnd_command_produced) = on_dnd_command_produced else { - return event::Status::Ignored; - }; - + Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y })) + if rectangle == Some(dnd_id) => + { let state = state(); - let is_clicked = text_layout.bounds().contains(Point { - x: x as f32, - y: y as f32, - }); - if !is_clicked { - if let DndOfferState::HandlingOffer(mime_types, action) = state.dnd_offer.clone() { - state.dnd_offer = DndOfferState::OutsideWidget(mime_types, action); - shell.publish(on_dnd_command_produced(Box::new(move || { - platform_specific::wayland::data_device::ActionInner::SetActions { - preferred: DndAction::None, - accepted: DndAction::None, - } - }))); - shell.publish(on_dnd_command_produced(Box::new(move || { - platform_specific::wayland::data_device::ActionInner::Accept(None) - }))); - } - return event::Status::Captured; - } else if let DndOfferState::OutsideWidget(mime_types, action) = state.dnd_offer.clone() - { - let mut accepted = false; - for m in &mime_types { - if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) { - accepted = true; - let clone = m.clone(); - shell.publish(on_dnd_command_produced(Box::new(move || { - platform_specific::wayland::data_device::ActionInner::Accept(Some( - clone.clone(), - )) - }))); - } - } - if accepted { - shell.publish(on_dnd_command_produced(Box::new(move || { - platform_specific::wayland::data_device::ActionInner::SetActions { - preferred: DndAction::Move, - accepted: DndAction::Move.union(DndAction::Copy), - } - }))); - state.dnd_offer = DndOfferState::HandlingOffer(mime_types.clone(), action); - } - }; let target = x as f32 - text_layout.bounds().x; // existing logic for setting the selection let position = if target > 0.0 { @@ -1910,13 +1873,7 @@ where return event::Status::Captured; } #[cfg(feature = "wayland")] - Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer( - wayland::DndOfferEvent::DropPerformed, - ))) => { - let Some(on_dnd_command_produced) = on_dnd_command_produced else { - return event::Status::Ignored; - }; - + Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if rectangle == Some(dnd_id) => { let state = state(); if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() { let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES @@ -1927,21 +1884,15 @@ where return event::Status::Captured; }; state.dnd_offer = DndOfferState::Dropped; - shell.publish(on_dnd_command_produced(Box::new(move || { - platform_specific::wayland::data_device::ActionInner::RequestDndData( - (*mime_type).to_string(), - ) - }))); - } else if let DndOfferState::OutsideWidget(..) = &state.dnd_offer { - state.dnd_offer = DndOfferState::None; - return event::Status::Captured; } + return event::Status::Ignored; } #[cfg(feature = "wayland")] - Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer( - wayland::DndOfferEvent::Leave, - ))) => { + Event::Dnd(DndEvent::Offer( + rectangle, + OfferEvent::Leave | OfferEvent::LeaveDestination, + )) if rectangle == Some(dnd_id) => { let state = state(); // ASHLEY TODO we should be able to reset but for now we don't if we are handling a // drop @@ -1954,13 +1905,9 @@ where return event::Status::Captured; } #[cfg(feature = "wayland")] - Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer( - wayland::DndOfferEvent::DndData { mime_type, data }, - ))) => { - let Some(on_dnd_command_produced) = on_dnd_command_produced else { - return event::Status::Ignored; - }; - + Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type })) + if rectangle == Some(dnd_id) => + { let state = state(); if let DndOfferState::Dropped = state.dnd_offer.clone() { state.dnd_offer = DndOfferState::None; @@ -1982,9 +1929,6 @@ where shell.publish(message); } - shell.publish(on_dnd_command_produced(Box::new(move || { - platform_specific::wayland::data_device::ActionInner::DndFinished - }))); let value = if is_secure { unsecured_value.secure() } else { @@ -1995,26 +1939,6 @@ where } return event::Status::Ignored; } - #[cfg(feature = "wayland")] - Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer( - wayland::DndOfferEvent::SourceActions(actions), - ))) => { - let Some(on_dnd_command_produced) = on_dnd_command_produced else { - return event::Status::Ignored; - }; - - let state = state(); - if let DndOfferState::HandlingOffer(..) = state.dnd_offer.clone() { - shell.publish(on_dnd_command_produced(Box::new(move || { - platform_specific::wayland::data_device::ActionInner::SetActions { - preferred: actions.intersection(DndAction::Move), - accepted: actions, - } - }))); - return event::Status::Captured; - } - return event::Status::Ignored; - } _ => {} } @@ -2032,6 +1956,7 @@ pub fn draw<'a, Message>( renderer: &mut crate::Renderer, theme: &crate::Theme, layout: Layout<'_>, + text_layout: Layout<'_>, cursor_position: mouse::Cursor, tree: &Tree, value: &Value, @@ -2083,7 +2008,7 @@ pub fn draw<'a, Message>( let mut children_layout = layout.children(); let bounds = layout.bounds(); - let text_bounds = children_layout.next().unwrap().bounds(); + let text_bounds = text_layout.bounds(); let is_mouse_over = cursor_position.is_over(bounds); @@ -2172,7 +2097,7 @@ pub fn draw<'a, Message>( if let (Some(label_layout), Some(label)) = (label_layout, label) { renderer.fill_text( Text { - content: label, + content: label.to_string(), size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)), font: font.unwrap_or_else(|| renderer.default_font()), bounds: label_layout.bounds().size(), @@ -2180,7 +2105,7 @@ pub fn draw<'a, Message>( vertical_alignment: alignment::Vertical::Top, line_height, shaping: text::Shaping::Advanced, - wrap: text::Wrap::default(), + wrapping: text::Wrapping::default(), }, label_layout.bounds().position(), appearance.label_color, @@ -2214,14 +2139,24 @@ pub fn draw<'a, Message>( let size = size.unwrap_or_else(|| renderer.default_size().0); let radius_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0.into(); - let (cursor, offset) = if let Some(focus) = &state.is_focused { + #[cfg(feature = "wayland")] + let handling_dnd_offer = !matches!(state.dnd_offer, DndOfferState::None); + #[cfg(not(feature = "wayland"))] + let handling_dnd_offer = false; + let (cursor, offset) = if let Some(focus) = &state.is_focused.or_else(|| { + handling_dnd_offer.then(|| Focus { + updated_at: Instant::now(), + now: Instant::now(), + }) + }) { match state.cursor.state(value) { cursor::State::Index(position) => { let (text_value_width, offset) = - measure_cursor_and_scroll_offset(&state.value, text_bounds, position); + measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, position); - let is_cursor_visible = - ((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS) % 2 + let is_cursor_visible = handling_dnd_offer + || ((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS) + % 2 == 0; if is_cursor_visible { @@ -2263,10 +2198,10 @@ pub fn draw<'a, Message>( let value_paragraph = &state.value; let (left_position, left_offset) = - measure_cursor_and_scroll_offset(value_paragraph, text_bounds, left); + measure_cursor_and_scroll_offset(value_paragraph.raw(), text_bounds, left); let (right_position, right_offset) = - measure_cursor_and_scroll_offset(value_paragraph, text_bounds, right); + measure_cursor_and_scroll_offset(value_paragraph.raw(), text_bounds, right); let width = right_position - left_position; @@ -2331,7 +2266,11 @@ pub fn draw<'a, Message>( renderer.fill_text( Text { - content: if text.is_empty() { placeholder } else { &text }, + content: if text.is_empty() { + placeholder.to_string() + } else { + text.clone() + }, font, bounds: bounds.size(), size: iced::Pixels(size), @@ -2339,7 +2278,7 @@ pub fn draw<'a, Message>( vertical_alignment: alignment::Vertical::Center, line_height: text::LineHeight::default(), shaping: text::Shaping::Advanced, - wrap: text::Wrap::default(), + wrapping: text::Wrapping::default(), }, bounds.position(), color, @@ -2374,7 +2313,7 @@ pub fn draw<'a, Message>( if let (Some(helper_text_layout), Some(helper_text)) = (helper_text_layout, helper_text) { renderer.fill_text( Text { - content: helper_text, + content: helper_text.to_string(), // TODO remove to_string? size: iced::Pixels(helper_text_size), font, bounds: helper_text_layout.bounds().size(), @@ -2382,7 +2321,7 @@ pub fn draw<'a, Message>( vertical_alignment: alignment::Vertical::Top, line_height: helper_line_height, shaping: text::Shaping::Advanced, - wrap: text::Wrap::default(), + wrapping: text::Wrapping::default(), }, helper_text_layout.bounds().position(), text_color, @@ -2414,11 +2353,23 @@ pub fn mouse_interaction( pub struct TextInputString(pub String); #[cfg(feature = "wayland")] -impl DataFromMimeType for TextInputString { - fn from_mime_type(&self, mime_type: &str) -> Option> { - SUPPORTED_TEXT_MIME_TYPES - .contains(&mime_type) - .then(|| self.0.as_bytes().to_vec()) +impl AsMimeTypes for TextInputString { + fn available(&self) -> Cow<'static, [String]> { + Cow::Owned( + SUPPORTED_TEXT_MIME_TYPES + .iter() + .cloned() + .map(String::from) + .collect::>(), + ) + } + + 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())) + } else { + None + } } } @@ -2434,7 +2385,6 @@ pub(crate) enum DraggingState { pub(crate) enum DndOfferState { #[default] None, - OutsideWidget(Vec, DndAction), HandlingOffer(Vec, DndAction), Dropped, } @@ -2446,17 +2396,16 @@ pub(crate) struct DndOfferState; #[derive(Debug, Default, Clone)] #[must_use] pub struct State { - pub value: crate::Paragraph, - pub placeholder: crate::Paragraph, - pub label: crate::Paragraph, - pub helper_text: crate::Paragraph, + pub value: crate::Plain, + pub placeholder: crate::Plain, + pub label: crate::Plain, + pub helper_text: crate::Plain, pub dirty: bool, pub is_secure: bool, pub is_read_only: bool, select_on_focus: bool, is_focused: Option, dragging_state: Option, - #[cfg(feature = "wayland")] dnd_offer: DndOfferState, is_pasting: Option, last_click: Option, @@ -2522,15 +2471,14 @@ impl State { pub fn focused(is_secure: bool, is_read_only: bool) -> Self { Self { is_secure, - value: crate::Paragraph::new(), - placeholder: crate::Paragraph::new(), - label: crate::Paragraph::new(), - helper_text: crate::Paragraph::new(), + value: crate::Plain::default(), + placeholder: crate::Plain::default(), + label: crate::Plain::default(), + helper_text: crate::Plain::default(), is_read_only, is_focused: None, select_on_focus: false, dragging_state: None, - #[cfg(feature = "wayland")] dnd_offer: DndOfferState::default(), is_pasting: None, last_click: None, @@ -2654,6 +2602,7 @@ fn find_cursor_position( let char_offset = state .value + .raw() .hit_test(Point::new(x + offset, text_bounds.height / 2.0)) .map(text::Hit::cursor)?; @@ -2677,7 +2626,7 @@ fn replace_paragraph( let mut children_layout = layout.children(); let text_bounds = children_layout.next().unwrap().bounds(); - state.value = crate::Paragraph::with_text(Text { + state.value = crate::Plain::new(Text { font, line_height, content: &value.to_string(), @@ -2686,7 +2635,7 @@ fn replace_paragraph( horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, shaping: text::Shaping::Advanced, - wrap: text::Wrap::default(), + wrapping: text::Wrapping::default(), }); } @@ -2714,7 +2663,7 @@ fn offset(text_bounds: Rectangle, value: &Value, state: &State) -> f32 { }; let (_, offset) = - measure_cursor_and_scroll_offset(&state.value, text_bounds, focus_position); + measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, focus_position); offset } else { diff --git a/src/widget/toaster/mod.rs b/src/widget/toaster/mod.rs index 481d799f..7f7634cf 100644 --- a/src/widget/toaster/mod.rs +++ b/src/widget/toaster/mod.rs @@ -8,7 +8,7 @@ use std::rc::Rc; use crate::widget::container; use crate::widget::Column; -use crate::Command; +use iced::{Padding, Task}; use iced_core::Element; use slotmap::new_key_type; use slotmap::SlotMap; @@ -47,15 +47,15 @@ pub fn toaster<'a, Message: Clone + 'static>( button::icon(icon::from_name("window-close-symbolic")) .on_press((toasts.on_close)(id)), ) - .align_items(iced::Alignment::Center) + .align_y(iced::Alignment::Center) .spacing(space_xxs), ) - .align_items(iced::Alignment::Center) + .align_y(iced::Alignment::Center) .spacing(space_s); container(row) .padding([space_xxs, space_s, space_xxs, space_m]) - .style(crate::style::Container::Tooltip) + .class(crate::style::Container::Tooltip) }; let col = toasts @@ -175,7 +175,7 @@ impl Toasts { } /// Add a new [`Toast`] - pub fn push(&mut self, toast: Toast) -> Command { + pub fn push(&mut self, toast: Toast) -> Task { while self.toasts.len() >= self.limit { self.toasts.remove( self.queue @@ -200,7 +200,7 @@ impl Toasts { } #[cfg(not(feature = "tokio"))] { - Command::none() + Task::none() } } diff --git a/src/widget/toaster/widget.rs b/src/widget/toaster/widget.rs index 7d9a58c9..7a7f6949 100644 --- a/src/widget/toaster/widget.rs +++ b/src/widget/toaster/widget.rs @@ -14,7 +14,6 @@ use iced_core::widget::Operation; use iced_core::Element; use iced_core::Overlay; use iced_core::{Clipboard, Layout, Length, Point, Rectangle, Shell, Vector, Widget}; -use iced_renderer::core::widget::OperationOutputWrapper; pub struct Toaster<'a, Message, Theme, Renderer> { toasts: Element<'a, Message, Theme, Renderer>, @@ -90,7 +89,7 @@ where state: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation>, + operation: &mut dyn Operation<()>, ) { self.content .as_widget() @@ -142,22 +141,23 @@ where state: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + translation: Vector, ) -> Option> { //TODO: this hides the overlay of the content during the toast if self.is_empty { - self.content - .as_widget_mut() - .overlay(&mut state.children[0], layout, renderer) + self.content.as_widget_mut().overlay( + &mut state.children[0], + layout, + renderer, + translation, + ) } else { let bounds = layout.bounds(); - Some(overlay::Element::new( - bounds.position(), - Box::new(ToasterOverlay::new( - &mut state.children[1], - &mut self.toasts, - )), - )) + Some(overlay::Element::new(Box::new(ToasterOverlay::new( + &mut state.children[1], + &mut self.toasts, + )))) } } @@ -166,7 +166,7 @@ where state: &Tree, layout: Layout<'_>, renderer: &Renderer, - dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { self.content.as_widget().drag_destinations( &state.children[0], @@ -196,13 +196,7 @@ impl<'a, 'b, Message, Theme, Renderer> Overlay where Renderer: renderer::Renderer, { - fn layout( - &mut self, - renderer: &Renderer, - bounds: Size, - position: Point, - _translation: Vector, - ) -> Node { + fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node { let limits = Limits::new(Size::ZERO, bounds); let mut node = self @@ -276,7 +270,7 @@ where ) -> Option> { self.element .as_widget_mut() - .overlay(self.state, layout, renderer) + .overlay(self.state, layout, renderer, Default::default()) } } diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index 4e92ffd4..972712ac 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -4,15 +4,15 @@ use iced::{widget, Length}; use iced_core::text; -pub fn toggler<'a, Message, Theme: iced_widget::toggler::StyleSheet, Renderer>( - label: impl Into>, +pub fn toggler<'a, Message, Theme: iced_widget::toggler::Catalog, Renderer>( is_checked: bool, f: impl Fn(bool) -> Message + 'a, ) -> widget::Toggler<'a, Message, Theme, Renderer> where Renderer: iced_core::Renderer + text::Renderer, { - widget::Toggler::new(label, is_checked, f) + widget::Toggler::new(is_checked) + .on_toggle(f) .size(24) .width(Length::Shrink) } diff --git a/src/widget/warning.rs b/src/widget/warning.rs index a6450d8c..77d737a5 100644 --- a/src/widget/warning.rs +++ b/src/widget/warning.rs @@ -41,9 +41,9 @@ impl<'a, Message: 'static + Clone> Warning<'a, Message> { widget::row::with_capacity(2) .push(label) .push(close_button) - .align_items(Alignment::Center) + .align_y(Alignment::Center) .apply(widget::container) - .style(theme::Container::custom(warning_container)) + .class(theme::Container::custom(warning_container)) .padding(10) .align_y(alignment::Vertical::Center) .width(Length::Fill) @@ -57,9 +57,9 @@ impl<'a, Message: 'static + Clone> From> for Element<'a, Me } #[must_use] -pub fn warning_container(theme: &Theme) -> widget::container::Appearance { +pub fn warning_container(theme: &Theme) -> widget::container::Style { let cosmic = theme.cosmic(); - widget::container::Appearance { + widget::container::Style { icon_color: Some(theme.cosmic().warning.on.into()), text_color: Some(theme.cosmic().warning.on.into()), background: Some(Background::Color(theme.cosmic().warning_color().into())), From 33c60ed87a6ade67cad8e7eae185376859babc23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Tue, 8 Oct 2024 23:26:43 +0200 Subject: [PATCH 002/556] fix(theme): update color palette --- cosmic-theme/src/model/dark.ron | 2 +- cosmic-theme/src/model/light.ron | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cosmic-theme/src/model/dark.ron b/cosmic-theme/src/model/dark.ron index 604e4427..559facdb 100644 --- a/cosmic-theme/src/model/dark.ron +++ b/cosmic-theme/src/model/dark.ron @@ -1 +1 @@ -Dark((name:"cosmic-dark",blue:(red:0.5803922,green:0.92156863,blue:0.92156863,alpha:1.0),red:(red:1.0,green:0.70980394,blue:0.70980394,alpha:1.0),green:(red:0.6745098,green:0.96862745,blue:0.8235294,alpha:1.0),yellow:(red:1.0,green:0.94509804,blue:0.61960787,alpha:1.0),gray_1:(red:0.105882354,green:0.105882354,blue:0.105882354,alpha:1.0),gray_2:(red:0.14901961,green:0.14901961,blue:0.14901961,alpha:1.0),gray_3:(red:0.1882353,green:0.1882353,blue:0.1882353,alpha:1.0),neutral_0:(red:0.0,green:0.0,blue:0.0,alpha:1.0),neutral_1:(red:0.105882354,green:0.105882354,blue:0.105882354,alpha:1.0),neutral_2:(red:0.1882353,green:0.1882353,blue:0.1882353,alpha:1.0),neutral_3:(red:0.2784314,green:0.2784314,blue:0.2784314,alpha:1.0),neutral_4:(red:0.36862746,green:0.36862746,blue:0.36862746,alpha:1.0),neutral_5:(red:0.46666667,green:0.46666667,blue:0.46666667,alpha:1.0),neutral_6:(red:0.5686275,green:0.5686275,blue:0.5686275,alpha:1.0),neutral_7:(red:0.67058825,green:0.67058825,blue:0.67058825,alpha:1.0),neutral_8:(red:0.7764706,green:0.7764706,blue:0.7764706,alpha:1.0),neutral_9:(red:0.8862745,green:0.8862745,blue:0.8862745,alpha:1.0),neutral_10:(red:1.0,green:1.0,blue:1.0,alpha:1.0),bright_green:(red:0.36862746,green:0.85882354,blue:0.54901963,alpha:1.0),bright_red:(red:1.0,green:0.627451,blue:0.5647059,alpha:1.0),bright_orange:(red:1.0,green:0.6392157,blue:0.49019608,alpha:1.0),ext_warm_grey:(red:0.60784316,green:0.5568628,blue:0.5411765,alpha:1.0),ext_orange:(red:1.0,green:0.6784314,blue:0.0,alpha:1.0),ext_yellow:(red:0.99607843,green:0.85882354,blue:0.2509804,alpha:1.0),ext_blue:(red:0.28235295,green:0.7254902,blue:0.78039217,alpha:1.0),ext_purple:(red:0.8117647,green:0.49019608,blue:1.0,alpha:1.0),ext_pink:(red:0.9764706,green:0.22745098,blue:0.5137255,alpha:1.0),ext_indigo:(red:0.24313726,green:0.53333336,blue:1.0,alpha:1.0),accent_blue:(red:0.3882353,green:0.8156863,blue:0.8745098,alpha:1.0),accent_red:(red:0.99215686,green:0.6313726,blue:0.627451,alpha:1.0),accent_green:(red:0.57254905,green:0.8117647,blue:0.6117647,alpha:1.0),accent_warm_grey:(red:0.7921569,green:0.7294118,blue:0.7058824,alpha:1.0),accent_orange:(red:1.0,green:0.6784314,blue:0.0,alpha:1.0),accent_yellow:(red:0.96862745,green:0.8784314,blue:0.38431373,alpha:1.0),accent_purple:(red:0.90588236,green:0.6117647,blue:0.99607843,alpha:1.0),accent_pink:(red:1.0,green:0.6117647,blue:0.69411767,alpha:1.0),accent_indigo:(red:0.6313726,green:0.7529412,blue:0.92156863,alpha:1.0))) \ No newline at end of file +Dark((name:"cosmic-dark",blue:(red:0.3882353,green:0.81568627,blue:0.87450981,alpha:1.0),red:(red:0.99215686,green:0.63137255,blue:0.62745098,alpha:1.0),green:(red:0.57254902,green:0.81176471,blue:0.61176471,alpha:1.0),yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),gray_1:(red:0.10588235,green:0.10588235,blue:0.10588235,alpha:1.0),gray_2:(red:0.14901961,green:0.14901961,blue:0.14901961,alpha:1.0),gray_3:(red:0.2,green:0.2,blue:0.2,alpha:1.0),neutral_0:(red:0.0,green:0.0,blue:0.0,alpha:1.0),neutral_1:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_2:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_3:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_4:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_7:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_8:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_9:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_10:(red:1.0,green:1.0,blue:1.0,alpha:1.0),bright_green:(red:0.36862745,green:0.85882353,blue:0.54901961,alpha:1.0),bright_red:(red:1.0,green:0.62745098,blue:0.60392157,alpha:1.0),bright_orange:(red:1.0,green:0.63921569,blue:0.49019608,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),ext_yellow:(red:0.99607843,green:0.85882353,blue:0.25098039,alpha:1.0),ext_blue:(red:0.28235294,green:0.72549020,blue:0.78039216,alpha:1.0),ext_purple:(red:0.81176471,green:0.49019608,blue:1.0,alpha:1.0),ext_pink:(red:0.97647059,green:0.22745098,blue:0.51372549,alpha:1.0),ext_indigo:(red:0.24313725,green:0.53333333,blue:1.0,alpha:1.0),accent_blue:(red:0.3882353,green:0.81568627,blue:0.87450981,alpha:1.0),accent_red:(red:0.99215686,green:0.63137255,blue:0.62745098,alpha:1.0),accent_green:(red:0.57254902,green:0.81176471,blue:0.61176471,alpha:1.0),accent_warm_grey:(red:0.79215686,green:0.72941176,blue:0.70588235,alpha:1.0),accent_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),accent_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),accent_purple:(red:0.90588235,green:0.61176471,blue:0.99607843,alpha:1.0),accent_pink:(red:1.0,green:0.61176471,blue:0.69411765,alpha:1.0),accent_indigo:(red:0.63137255,green:0.75294118,blue:0.92156863,alpha:1.0))) diff --git a/cosmic-theme/src/model/light.ron b/cosmic-theme/src/model/light.ron index 7de84a03..5361c040 100644 --- a/cosmic-theme/src/model/light.ron +++ b/cosmic-theme/src/model/light.ron @@ -1 +1 @@ -Light((name:"cosmic-light",blue:(red:0.0,green:0.28627452,blue:0.42745098,alpha:1.0),red:(red:0.627451,green:0.14509805,blue:0.16862746,alpha:1.0),green:(red:0.23137255,green:0.43137255,blue:0.2627451,alpha:1.0),yellow:(red:0.5882353,green:0.40784314,blue:0.0,alpha:1.0),gray_1:(red:0.8666667,green:0.8666667,blue:0.8666667,alpha:1.0),gray_2:(red:0.9098039,green:0.9098039,blue:0.9098039,alpha:1.0),gray_3:(red:0.9529412,green:0.9529412,blue:0.9529412,alpha:1.0),neutral_0:(red:1.0,green:1.0,blue:1.0,alpha:1.0),neutral_1:(red:0.8862745,green:0.8862745,blue:0.8862745,alpha:1.0),neutral_2:(red:0.7764706,green:0.7764706,blue:0.7764706,alpha:1.0),neutral_3:(red:0.67058825,green:0.67058825,blue:0.67058825,alpha:1.0),neutral_4:(red:0.5686275,green:0.5686275,blue:0.5686275,alpha:1.0),neutral_5:(red:0.46666667,green:0.46666667,blue:0.46666667,alpha:1.0),neutral_6:(red:0.36862746,green:0.36862746,blue:0.36862746,alpha:1.0),neutral_7:(red:0.2784314,green:0.2784314,blue:0.2784314,alpha:1.0),neutral_8:(red:0.1882353,green:0.1882353,blue:0.1882353,alpha:1.0),neutral_9:(red:0.105882354,green:0.105882354,blue:0.105882354,alpha:1.0),neutral_10:(red:0.0,green:0.0,blue:0.0,alpha:1.0),bright_green:(red:0.0,green:0.34117648,blue:0.17254902,alpha:1.0),bright_red:(red:0.5372549,green:0.015686275,blue:0.09411765,alpha:1.0),bright_orange:(red:0.4745098,green:0.17254902,blue:0.0,alpha:1.0),ext_warm_grey:(red:0.60784316,green:0.5568628,blue:0.5411765,alpha:1.0),ext_orange:(red:0.9843137,green:0.72156864,blue:0.42352942,alpha:1.0),ext_yellow:(red:0.96862745,green:0.8784314,blue:0.38431373,alpha:1.0),ext_blue:(red:0.41568628,green:0.7921569,blue:0.84705883,alpha:1.0),ext_purple:(red:0.8352941,green:0.54901963,blue:1.0,alpha:1.0),ext_pink:(red:1.0,green:0.6117647,blue:0.8666667,alpha:1.0),ext_indigo:(red:0.58431375,green:0.76862746,blue:0.9882353,alpha:1.0),accent_blue:(red:0.0,green:0.32156864,blue:0.3529412,alpha:1.0),accent_red:(red:0.47058824,green:0.16078432,blue:0.18039216,alpha:1.0),accent_green:(red:0.09411765,green:0.33333334,blue:0.16078432,alpha:1.0),accent_warm_grey:(red:0.33333334,green:0.2784314,blue:0.25882354,alpha:1.0),accent_orange:(red:0.38431373,green:0.2509804,blue:0.0,alpha:1.0),accent_yellow:(red:0.3254902,green:0.28235295,blue:0.0,alpha:1.0),accent_purple:(red:0.40784314,green:0.12941177,blue:0.4862745,alpha:1.0),accent_pink:(red:0.5254902,green:0.015686275,blue:0.22745098,alpha:1.0),accent_indigo:(red:0.18039216,green:0.28627452,blue:0.42745098,alpha:1.0))) \ No newline at end of file +Light((name:"cosmic-light",blue:(red:0.0,green:0.32156863,blue:0.35294118,alpha:1.0),red:(red:0.47058824,green:0.16078431,blue:0.18039216,alpha:1.0),green:(red:0.09411765,green:0.33333333,blue:0.16078431,alpha:1.0),yellow:(red:0.32549020,green:0.28235294,blue:0.0,alpha:1.0),gray_1:(red:0.84313725,green:0.84313725,blue:0.84313725,alpha:1.0),gray_2:(red:0.89411765,green:0.89411765,blue:0.89411765,alpha:1.0),gray_3:(red:0.96078431,green:0.96078431,blue:0.96078431,alpha:1.0),neutral_0:(red:1.0,green:1.0,blue:1.0,alpha:1.0),neutral_1:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_2:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_3:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_4:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_7:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_8:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_9:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_10:(red:0.0,green:0.0,blue:0.0,alpha:1.0),bright_green:(red:0.0,green:0.34117647,blue:0.17254902,alpha:1.0),bright_red:(red:0.53725490,green:0.01568627,blue:0.09411765,alpha:1.0),bright_orange:(red:0.47450980,green:0.17254902,blue:0.0,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:0.98431373,green:0.72156863,blue:0.42352941,alpha:1.0),ext_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),ext_blue:(red:0.41568627,green:0.79215686,blue:0.84705882,alpha:1.0),ext_purple:(red:0.83529412,green:0.54901961,blue:1.0,alpha:1.0),ext_pink:(red:1.0,green:0.61176471,blue:0.86666667,alpha:1.0),ext_indigo:(red:0.58431373,green:0.76862745,blue:0.98823529,alpha:1.0),accent_blue:(red:0.0,green:0.32156863,blue:0.35294118,alpha:1.0),accent_red:(red:0.47058824,green:0.16078431,blue:0.18039216,alpha:1.0),accent_green:(red:0.09411765,green:0.33333333,blue:0.16078431,alpha:1.0),accent_warm_grey:(red:0.33333333,green:0.27843137,blue:0.25882353,alpha:1.0),accent_orange:(red:0.38431373,green:0.25098039,blue:0.0,alpha:1.0),accent_yellow:(red:0.32549020,green:0.28235294,blue:0.0,alpha:1.0),accent_purple:(red:0.40784314,green:0.12941176,blue:0.48627451,alpha:1.0),accent_pink:(red:0.52549020,green:0.01568627,blue:0.22745098,alpha:1.0),accent_indigo:(red:0.18039216,green:0.28627451,blue:0.42745098,alpha:1.0))) From 478f3ead755da1c504167961def9954cd58a2b23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Wed, 9 Oct 2024 16:10:23 +0200 Subject: [PATCH 003/556] fix(palette): remove deprecated colors --- cosmic-theme/src/model/cosmic_palette.rs | 68 ++++++++++-------------- cosmic-theme/src/model/dark.ron | 2 +- cosmic-theme/src/model/light.ron | 2 +- cosmic-theme/src/model/theme.rs | 8 +-- cosmic-theme/src/output/gtk4_output.rs | 8 +-- 5 files changed, 38 insertions(+), 50 deletions(-) diff --git a/cosmic-theme/src/model/cosmic_palette.rs b/cosmic-theme/src/model/cosmic_palette.rs index 6933c996..3b916c19 100644 --- a/cosmic-theme/src/model/cosmic_palette.rs +++ b/cosmic-theme/src/model/cosmic_palette.rs @@ -88,23 +88,19 @@ pub struct CosmicPaletteInner { /// name of the palette pub name: String, - /// basic palette - /// blue: colors used for various points of emphasis in the UI - pub blue: Srgba, - /// red: colors used for various points of emphasis in the UI - pub red: Srgba, - /// green: colors used for various points of emphasis in the UI - pub green: Srgba, - /// yellow: colors used for various points of emphasis in the UI - pub yellow: Srgba, + /// Utility Colors + /// Colors used for various points of emphasis in the UI. + pub bright_red: Srgba, + /// Colors used for various points of emphasis in the UI. + pub bright_green: Srgba, + /// Colors used for various points of emphasis in the UI. + pub bright_orange: Srgba, - /// surface grays - /// colors used for three levels of surfaces in the UI + /// Surface Grays + /// Colors used for three levels of surfaces in the UI. pub gray_1: Srgba, - /// colors used for three levels of surfaces in the UI + /// Colors used for three levels of surfaces in the UI. pub gray_2: Srgba, - /// colors used for three levels of surfaces in the UI - pub gray_3: Srgba, /// System Neutrals /// A wider spread of dark colors for more general use. @@ -130,13 +126,24 @@ pub struct CosmicPaletteInner { /// A wider spread of dark colors for more general use. pub neutral_10: Srgba, - // Utility Colors - /// Utility bright green - pub bright_green: Srgba, - /// Utility bright red - pub bright_red: Srgba, - /// Utility bright orange - pub bright_orange: Srgba, + /// Potential Accent Color Combos + pub accent_blue: Srgba, + /// Potential Accent Color Combos + pub accent_indigo: Srgba, + /// Potential Accent Color Combos + pub accent_purple: Srgba, + /// Potential Accent Color Combos + pub accent_pink: Srgba, + /// Potential Accent Color Combos + pub accent_red: Srgba, + /// Potential Accent Color Combos + pub accent_orange: Srgba, + /// Potential Accent Color Combos + pub accent_yellow: Srgba, + /// Potential Accent Color Combos + pub accent_green: Srgba, + /// Potential Accent Color Combos + pub accent_warm_grey: Srgba, /// Extended Color Palette /// Colors used for themes, app icons, illustrations, and other brand purposes. @@ -153,25 +160,6 @@ pub struct CosmicPaletteInner { pub ext_pink: Srgba, /// Colors used for themes, app icons, illustrations, and other brand purposes. pub ext_indigo: Srgba, - - /// Potential Accent Color Combos - pub accent_blue: Srgba, - /// Potential Accent Color Combos - pub accent_red: Srgba, - /// Potential Accent Color Combos - pub accent_green: Srgba, - /// Potential Accent Color Combos - pub accent_warm_grey: Srgba, - /// Potential Accent Color Combos - pub accent_orange: Srgba, - /// Potential Accent Color Combos - pub accent_yellow: Srgba, - /// Potential Accent Color Combos - pub accent_purple: Srgba, - /// Potential Accent Color Combos - pub accent_pink: Srgba, - /// Potential Accent Color Combos - pub accent_indigo: Srgba, } impl CosmicPalette { diff --git a/cosmic-theme/src/model/dark.ron b/cosmic-theme/src/model/dark.ron index 559facdb..4453b8bf 100644 --- a/cosmic-theme/src/model/dark.ron +++ b/cosmic-theme/src/model/dark.ron @@ -1 +1 @@ -Dark((name:"cosmic-dark",blue:(red:0.3882353,green:0.81568627,blue:0.87450981,alpha:1.0),red:(red:0.99215686,green:0.63137255,blue:0.62745098,alpha:1.0),green:(red:0.57254902,green:0.81176471,blue:0.61176471,alpha:1.0),yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),gray_1:(red:0.10588235,green:0.10588235,blue:0.10588235,alpha:1.0),gray_2:(red:0.14901961,green:0.14901961,blue:0.14901961,alpha:1.0),gray_3:(red:0.2,green:0.2,blue:0.2,alpha:1.0),neutral_0:(red:0.0,green:0.0,blue:0.0,alpha:1.0),neutral_1:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_2:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_3:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_4:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_7:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_8:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_9:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_10:(red:1.0,green:1.0,blue:1.0,alpha:1.0),bright_green:(red:0.36862745,green:0.85882353,blue:0.54901961,alpha:1.0),bright_red:(red:1.0,green:0.62745098,blue:0.60392157,alpha:1.0),bright_orange:(red:1.0,green:0.63921569,blue:0.49019608,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),ext_yellow:(red:0.99607843,green:0.85882353,blue:0.25098039,alpha:1.0),ext_blue:(red:0.28235294,green:0.72549020,blue:0.78039216,alpha:1.0),ext_purple:(red:0.81176471,green:0.49019608,blue:1.0,alpha:1.0),ext_pink:(red:0.97647059,green:0.22745098,blue:0.51372549,alpha:1.0),ext_indigo:(red:0.24313725,green:0.53333333,blue:1.0,alpha:1.0),accent_blue:(red:0.3882353,green:0.81568627,blue:0.87450981,alpha:1.0),accent_red:(red:0.99215686,green:0.63137255,blue:0.62745098,alpha:1.0),accent_green:(red:0.57254902,green:0.81176471,blue:0.61176471,alpha:1.0),accent_warm_grey:(red:0.79215686,green:0.72941176,blue:0.70588235,alpha:1.0),accent_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),accent_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),accent_purple:(red:0.90588235,green:0.61176471,blue:0.99607843,alpha:1.0),accent_pink:(red:1.0,green:0.61176471,blue:0.69411765,alpha:1.0),accent_indigo:(red:0.63137255,green:0.75294118,blue:0.92156863,alpha:1.0))) +Dark((name:"cosmic-dark",bright_red:(red:1.0,green:0.62745098,blue:0.60392157,alpha:1.0),bright_green:(red:0.36862745,green:0.85882352,blue:0.54901960,alpha:1.0),bright_orange:(red:1.0,green:0.63921569,blue:0.49019608,alpha:1.0),gray_1:(red:0.10588235,green:0.10588235,blue:0.10588235,alpha:1.0),gray_2:(red:0.14901961,green:0.14901961,blue:0.14901961,alpha:1.0),neutral_0:(red:0.0,green:0.0,blue:0.0,alpha:1.0),neutral_1:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_2:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_3:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_4:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_7:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_8:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_9:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_10:(red:1.0,green:1.0,blue:1.0,alpha:1.0),accent_blue:(red:0.3882353,green:0.81568627,blue:0.87450981,alpha:1.0),accent_indigo:(red:0.63137255,green:0.75294118,blue:0.92156863,alpha:1.0),accent_purple:(red:0.90588235,green:0.61176471,blue:0.99607843,alpha:1.0),accent_pink:(red:1.0,green:0.61176471,blue:0.69411765,alpha:1.0),accent_red:(red:0.99215686,green:0.63137255,blue:0.62745098,alpha:1.0),accent_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),accent_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),accent_green:(red:0.57254902,green:0.81176471,blue:0.61176471,alpha:1.0),accent_warm_grey:(red:0.79215686,green:0.72941176,blue:0.70588235,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),ext_yellow:(red:0.99607843,green:0.85882353,blue:0.25098039,alpha:1.0),ext_blue:(red:0.28235294,green:0.72549020,blue:0.78039216,alpha:1.0),ext_purple:(red:0.81176471,green:0.49019608,blue:1.0,alpha:1.0),ext_pink:(red:0.97647059,green:0.22745098,blue:0.51372549,alpha:1.0),ext_indigo:(red:0.24313725,green:0.53333333,blue:1.0,alpha:1.0))) diff --git a/cosmic-theme/src/model/light.ron b/cosmic-theme/src/model/light.ron index 5361c040..29b3ad65 100644 --- a/cosmic-theme/src/model/light.ron +++ b/cosmic-theme/src/model/light.ron @@ -1 +1 @@ -Light((name:"cosmic-light",blue:(red:0.0,green:0.32156863,blue:0.35294118,alpha:1.0),red:(red:0.47058824,green:0.16078431,blue:0.18039216,alpha:1.0),green:(red:0.09411765,green:0.33333333,blue:0.16078431,alpha:1.0),yellow:(red:0.32549020,green:0.28235294,blue:0.0,alpha:1.0),gray_1:(red:0.84313725,green:0.84313725,blue:0.84313725,alpha:1.0),gray_2:(red:0.89411765,green:0.89411765,blue:0.89411765,alpha:1.0),gray_3:(red:0.96078431,green:0.96078431,blue:0.96078431,alpha:1.0),neutral_0:(red:1.0,green:1.0,blue:1.0,alpha:1.0),neutral_1:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_2:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_3:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_4:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_7:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_8:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_9:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_10:(red:0.0,green:0.0,blue:0.0,alpha:1.0),bright_green:(red:0.0,green:0.34117647,blue:0.17254902,alpha:1.0),bright_red:(red:0.53725490,green:0.01568627,blue:0.09411765,alpha:1.0),bright_orange:(red:0.47450980,green:0.17254902,blue:0.0,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:0.98431373,green:0.72156863,blue:0.42352941,alpha:1.0),ext_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),ext_blue:(red:0.41568627,green:0.79215686,blue:0.84705882,alpha:1.0),ext_purple:(red:0.83529412,green:0.54901961,blue:1.0,alpha:1.0),ext_pink:(red:1.0,green:0.61176471,blue:0.86666667,alpha:1.0),ext_indigo:(red:0.58431373,green:0.76862745,blue:0.98823529,alpha:1.0),accent_blue:(red:0.0,green:0.32156863,blue:0.35294118,alpha:1.0),accent_red:(red:0.47058824,green:0.16078431,blue:0.18039216,alpha:1.0),accent_green:(red:0.09411765,green:0.33333333,blue:0.16078431,alpha:1.0),accent_warm_grey:(red:0.33333333,green:0.27843137,blue:0.25882353,alpha:1.0),accent_orange:(red:0.38431373,green:0.25098039,blue:0.0,alpha:1.0),accent_yellow:(red:0.32549020,green:0.28235294,blue:0.0,alpha:1.0),accent_purple:(red:0.40784314,green:0.12941176,blue:0.48627451,alpha:1.0),accent_pink:(red:0.52549020,green:0.01568627,blue:0.22745098,alpha:1.0),accent_indigo:(red:0.18039216,green:0.28627451,blue:0.42745098,alpha:1.0))) +Light((name:"cosmic-light",bright_red:(red:0.53725490,green:0.01568627,blue:0.09411765,alpha:1.0),bright_green:(red:0.0,green:0.34117647,blue:0.17254901,alpha:1.0),bright_orange:(red:0.47450980,green:0.17254902,blue:0.0,alpha:1.0),gray_1:(red:0.84313725,green:0.84313725,blue:0.84313725,alpha:1.0),gray_2:(red:0.89411765,green:0.89411765,blue:0.89411765,alpha:1.0),neutral_0:(red:1.0,green:1.0,blue:1.0,alpha:1.0),neutral_1:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_2:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_3:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_4:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_7:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_8:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_9:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_10:(red:0.0,green:0.0,blue:0.0,alpha:1.0),accent_blue:(red:0.0,green:0.32156863,blue:0.35294118,alpha:1.0),accent_indigo:(red:0.18039216,green:0.28627451,blue:0.42745098,alpha:1.0),accent_purple:(red:0.40784314,green:0.12941176,blue:0.48627451,alpha:1.0),accent_pink:(red:0.52549020,green:0.01568627,blue:0.22745098,alpha:1.0),accent_red:(red:0.47058824,green:0.16078431,blue:0.18039216,alpha:1.0),accent_orange:(red:0.38431373,green:0.25098039,blue:0.0,alpha:1.0),accent_yellow:(red:0.32549020,green:0.28235294,blue:0.0,alpha:1.0),accent_green:(red:0.09411765,green:0.33333333,blue:0.16078431,alpha:1.0),accent_warm_grey:(red:0.33333333,green:0.27843137,blue:0.25882353,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:0.98431373,green:0.72156863,blue:0.42352941,alpha:1.0),ext_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),ext_blue:(red:0.41568627,green:0.79215686,blue:0.84705882,alpha:1.0),ext_purple:(red:0.83529412,green:0.54901961,blue:1.0,alpha:1.0),ext_pink:(red:1.0,green:0.61176471,blue:0.86666667,alpha:1.0),ext_indigo:(red:0.58431373,green:0.76862745,blue:0.98823529,alpha:1.0))) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 192f9756..821450a1 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -758,25 +758,25 @@ impl ThemeBuilder { let accent = if let Some(accent) = accent { accent.into_color() } else { - palette.as_ref().blue + palette.as_ref().accent_blue }; let success = if let Some(success) = success { success.into_color() } else { - palette.as_ref().green + palette.as_ref().accent_green }; let warning = if let Some(warning) = warning { warning.into_color() } else { - palette.as_ref().yellow + palette.as_ref().accent_yellow }; let destructive = if let Some(destructive) = destructive { destructive.into_color() } else { - palette.as_ref().red + palette.as_ref().accent_red }; let text_steps_array = text_tint.map(|c| steps(c, NonZeroUsize::new(100).unwrap())); diff --git a/cosmic-theme/src/output/gtk4_output.rs b/cosmic-theme/src/output/gtk4_output.rs index 21aab991..91795a70 100644 --- a/cosmic-theme/src/output/gtk4_output.rs +++ b/cosmic-theme/src/output/gtk4_output.rs @@ -122,10 +122,10 @@ impl Theme { css.push_str(&component_gtk4_css("accent", accent)); css.push_str(&component_gtk4_css("error", destructive)); - css.push_str(&color_css("blue", palette.blue)); - css.push_str(&color_css("green", palette.green)); - css.push_str(&color_css("yellow", palette.yellow)); - css.push_str(&color_css("red", palette.red)); + css.push_str(&color_css("blue", palette.accent_blue)); + css.push_str(&color_css("green", palette.accent_green)); + css.push_str(&color_css("yellow", palette.accent_yellow)); + css.push_str(&color_css("red", palette.accent_red)); css.push_str(&color_css("orange", palette.ext_orange)); css.push_str(&color_css("purple", palette.ext_purple)); let neutral_steps = steps(palette.neutral_5, NonZeroUsize::new(10).unwrap()); From 7b9cad4d2c06c5b1b11a9e34eaa9d9013d11c89b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 17 Oct 2024 09:58:45 -0400 Subject: [PATCH 004/556] cleanup --- examples/context-menu/src/main.rs | 4 ++-- examples/image-button/src/main.rs | 2 +- examples/menu/src/main.rs | 4 ++-- examples/multi-window/src/window.rs | 22 ++++++++++------------ examples/open-dialog/src/main.rs | 10 +++++++--- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/examples/context-menu/src/main.rs b/examples/context-menu/src/main.rs index 66077226..43866987 100644 --- a/examples/context-menu/src/main.rs +++ b/examples/context-menu/src/main.rs @@ -3,9 +3,9 @@ //! Application API example -use cosmic::app::{Task, Core, Settings}; +use cosmic::app::{Core, Settings, Task}; use cosmic::iced_core::Size; -use cosmic::widget::{menu, segmented_button}; +use cosmic::widget::menu; use cosmic::{executor, iced, ApplicationExt, Element}; use std::collections::HashMap; diff --git a/examples/image-button/src/main.rs b/examples/image-button/src/main.rs index 40cb70b3..2b502689 100644 --- a/examples/image-button/src/main.rs +++ b/examples/image-button/src/main.rs @@ -3,7 +3,7 @@ //! Application API example -use cosmic::app::{Task, Core, Settings}; +use cosmic::app::{Core, Settings, Task}; use cosmic::{executor, iced, ApplicationExt, Element}; /// Runs application with these settings diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs index c7c686db..d9dc7854 100644 --- a/examples/menu/src/main.rs +++ b/examples/menu/src/main.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use std::{env, process}; -use cosmic::app::{Task, Core, Settings}; +use cosmic::app::{Core, Settings, Task}; use cosmic::iced::window; use cosmic::iced_core::alignment::{Horizontal, Vertical}; use cosmic::iced_core::keyboard::Key; @@ -120,7 +120,7 @@ impl cosmic::Application for App { return window::close(self.core.main_window_id().unwrap()); } Message::WindowNew => match env::current_exe() { - Ok(exe) => match process::Task::new(&exe).spawn() { + Ok(exe) => match process::Command::new(&exe).spawn() { Ok(_child) => {} Err(err) => { eprintln!("failed to execute {:?}: {}", exe, err); diff --git a/examples/multi-window/src/window.rs b/examples/multi-window/src/window.rs index c3df9c7c..15663ea3 100644 --- a/examples/multi-window/src/window.rs +++ b/examples/multi-window/src/window.rs @@ -4,7 +4,7 @@ use cosmic::{ app::Core, iced::{self, event, window}, iced_core::{id, Alignment, Length, Point}, - iced_widget::{column, container, scrollable, text, text_input}, + iced_widget::{column, container, scrollable, text}, widget::{button, header_bar}, ApplicationExt, Task, }; @@ -45,7 +45,7 @@ impl cosmic::Application for MultiWindow { fn init(core: Core, _input: Self::Flags) -> (Self, cosmic::app::Task) { let windows = MultiWindow { windows: HashMap::from([( - self.core.main_window_id().unwrap(), + core.main_window_id().unwrap(), Window { input_id: id::Id::new("main"), input_value: String::new(), @@ -86,7 +86,7 @@ impl cosmic::Application for MultiWindow { } Message::WindowOpened(id, ..) => { if let Some(window) = self.windows.get(&id) { - text_input::focus(window.input_id.clone()) + cosmic::widget::text_input::focus(window.input_id.clone()) } else { Task::none() } @@ -94,7 +94,7 @@ impl cosmic::Application for MultiWindow { Message::NewWindow => { let count = self.windows.len() + 1; - let (id, spawn_window) = window::spawn(window::Settings { + let (id, spawn_window) = window::open(window::Settings { position: Default::default(), exit_on_close_request: count % 2 == 0, decorations: false, @@ -110,13 +110,11 @@ impl cosmic::Application for MultiWindow { ); _ = self.set_window_title(format!("window_{}", count), id); - spawn_window + spawn_window.map(|id| cosmic::app::Message::App(Message::WindowOpened(id, None))) } Message::Input(id, value) => { - if let Some(w) = self.windows.get_mut(&self.core.main_window_id().unwrap()) { - if id == w.input_id { - w.input_value = value; - } + if let Some((_, w)) = self.windows.iter_mut().find(|e| e.1.input_id == id) { + w.input_value = value; } Task::none() @@ -128,7 +126,7 @@ impl cosmic::Application for MultiWindow { let w = self.windows.get(&id).unwrap(); let input_id = w.input_id.clone(); - let input = text_input("something", &w.input_value) + let input = cosmic::widget::text_input::text_input("something", &w.input_value) .on_input(move |msg| Message::Input(input_id.clone(), msg)) .id(w.input_id.clone()); let focused = self @@ -136,7 +134,7 @@ impl cosmic::Application for MultiWindow { .focused_window() .map(|i| i == id) .unwrap_or_default(); - let new_window_button = button(text("New Window")).on_press(Message::NewWindow); + let new_window_button = button::custom(text("New Window")).on_press(Message::NewWindow); let content = scrollable( column![input, new_window_button] @@ -146,7 +144,7 @@ impl cosmic::Application for MultiWindow { ); let window_content = container(container(content).center_x(Length::Fixed(200.))) - .style(cosmic::style::Container::Background) + .class(cosmic::style::Container::Background) .center_x(Length::Fill) .center_y(Length::Fill); diff --git a/examples/open-dialog/src/main.rs b/examples/open-dialog/src/main.rs index f462f0b5..2478f9eb 100644 --- a/examples/open-dialog/src/main.rs +++ b/examples/open-dialog/src/main.rs @@ -4,7 +4,7 @@ //! An application which provides an open dialog use apply::Apply; -use cosmic::app::{Task, Core, Settings}; +use cosmic::app::{Core, Settings, Task}; use cosmic::dialog::file_chooser::{self, FileFilter}; use cosmic::iced_core::Length; use cosmic::widget::button; @@ -77,7 +77,7 @@ impl cosmic::Application for App { app.set_header_title("Open a file".into()); let cmd = app.set_window_title( "COSMIC OpenDialog Demo".into(), - cosmic::iced::self.core.main_window_id().unwrap(), + self.core.main_window_id().unwrap(), ); (app, cmd) @@ -211,7 +211,11 @@ impl cosmic::Application for App { .into(), ); - content.push(iced::widget::vertical_space(Length::Fixed(12.0)).into()); + content.push( + iced::widget::vertical_space() + .height(Length::Fixed(12.0)) + .into(), + ); } content.push(if self.selected_file.is_none() { From 69079b94e9ac5812cdcb9fb2a87fbb5e69b3897f Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 18 Oct 2024 10:37:34 -0400 Subject: [PATCH 005/556] update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index f2f9dfc6..79e74f57 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit f2f9dfc6c37e14c4f8ff5dafe70d72d3c40eb692 +Subproject commit 79e74f57120abb3d56331e8a6d847dc98306a383 From 1c1840a7c271d20873ccb09c3185f8a05c253a9f Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 18 Oct 2024 10:45:30 -0400 Subject: [PATCH 006/556] fix: radio example --- src/widget/radio.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/radio.rs b/src/widget/radio.rs index 1b40a0e0..55d96192 100644 --- a/src/widget/radio.rs +++ b/src/widget/radio.rs @@ -33,7 +33,7 @@ where /// # Example /// ```no_run /// # type Radio<'a, Message> = -/// # cosmic::widget::Radio<'a, Message, cosmic::Theme, cosmic::Renderer>; +/// # cosmic::widget::Radio<'a, Message, cosmic::Renderer>; /// # /// # use cosmic::widget::text; /// # use cosmic::iced::widget::column; From 684d33115cbffbc07433dba128d9c42787195f65 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 18 Oct 2024 10:49:51 -0400 Subject: [PATCH 007/556] fix: open-dialog example --- examples/open-dialog/Cargo.toml | 2 +- examples/open-dialog/src/main.rs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/examples/open-dialog/Cargo.toml b/examples/open-dialog/Cargo.toml index 1cd40359..a102f533 100644 --- a/examples/open-dialog/Cargo.toml +++ b/examples/open-dialog/Cargo.toml @@ -16,6 +16,6 @@ tracing-subscriber = "0.3.17" url = "2.4.0" [dependencies.libcosmic] +features = ["debug", "winit", "multi-window", "wayland", "tokio"] path = "../../" default-features = false -features = ["debug", "wayland", "tokio"] diff --git a/examples/open-dialog/src/main.rs b/examples/open-dialog/src/main.rs index 2478f9eb..66c16441 100644 --- a/examples/open-dialog/src/main.rs +++ b/examples/open-dialog/src/main.rs @@ -67,6 +67,7 @@ impl cosmic::Application for App { /// Creates the application, and optionally emits command on initialize. fn init(core: Core, _input: Self::Flags) -> (Self, Task) { + let id = core.main_window_id().unwrap(); let mut app = App { core, file_contents: String::new(), @@ -75,10 +76,7 @@ impl cosmic::Application for App { }; app.set_header_title("Open a file".into()); - let cmd = app.set_window_title( - "COSMIC OpenDialog Demo".into(), - self.core.main_window_id().unwrap(), - ); + let cmd = app.set_window_title("COSMIC OpenDialog Demo".into(), id); (app, cmd) } From 45f3999f9cc3841c792be99abdd1d67649d52058 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 18 Oct 2024 10:59:25 -0400 Subject: [PATCH 008/556] refactor: include winit in the applet feature --- Cargo.toml | 3 ++- examples/applet/Cargo.toml | 12 +----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5e7cb83a..18a9d5ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,13 +17,14 @@ autosize = [] applet = [ "a11y", "autosize", + "winit", "wayland", "tokio", "cosmic-panel-config", "ron", "multi-window", ] -applet-token = [] +applet-token = ["applet"] # Use the cosmic-settings-daemon for config handling dbus-config = ["cosmic-config/dbus", "dep:zbus", "cosmic-settings-daemon"] # Debug features diff --git a/examples/applet/Cargo.toml b/examples/applet/Cargo.toml index ba4ce662..0a9f2b32 100644 --- a/examples/applet/Cargo.toml +++ b/examples/applet/Cargo.toml @@ -15,14 +15,4 @@ log = "0.4.17" [dependencies.libcosmic] git = "https://github.com/pop-os/libcosmic" default-features = false -features = [ - "applet", - "applet-token", - "multi-window", - "tokio", - "wayland", - "winit", - "desktop", - "dbus-config", - "image", -] +features = ["applet-token"] From f0a38aa1ad3559e0a983fedcb24088d85a8f3a15 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 18 Oct 2024 11:16:14 -0400 Subject: [PATCH 009/556] cleanup --- src/widget/min_size_tracker/mod.rs | 319 -------------------- src/widget/min_size_tracker/subscription.rs | 88 ------ src/widget/rectangle_tracker/mod.rs | 3 - 3 files changed, 410 deletions(-) delete mode 100644 src/widget/min_size_tracker/mod.rs delete mode 100644 src/widget/min_size_tracker/subscription.rs diff --git a/src/widget/min_size_tracker/mod.rs b/src/widget/min_size_tracker/mod.rs deleted file mode 100644 index b190fecd..00000000 --- a/src/widget/min_size_tracker/mod.rs +++ /dev/null @@ -1,319 +0,0 @@ -mod subscription; - -use iced::futures::channel::mpsc::UnboundedSender; -use iced::widget::Container; -use iced::Vector; -pub use subscription::*; - -use iced_core::alignment; -use iced_core::event::{self, Event}; -use iced_core::layout; -use iced_core::mouse; -use iced_core::overlay; -use iced_core::renderer; -use iced_core::widget::Tree; -use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget}; -use std::{fmt::Debug, hash::Hash}; - -pub use iced_widget::container::{Catalog, Style}; - -pub fn min_size_tracker<'a, Message, I, T>( - content: T, - id: I, - tx: UnboundedSender<(I, Rectangle)>, -) -> MinSizeTrackerContainer<'a, Message, crate::Renderer, I> -where - I: Hash + Copy + Send + Sync + Debug + 'a, - T: Into>, -{ - MinSizeTrackerContainer::new(content, id, tx) -} - -pub fn subscription< - I: 'static + Hash + Copy + Send + Sync + Debug, - R: 'static + Hash + Copy + Send + Sync + Debug + Eq, ->( - id: I, -) -> iced::Subscription<(I, RectangleUpdate)> { - subscription::rectangle_tracker_subscription(id) -} - -#[derive(Clone, Debug)] -pub struct MinSizeTracker { - tx: UnboundedSender<(I, Rectangle)>, -} - -impl MinSizeTracker -where - I: Hash + Copy + Send + Sync + Debug, -{ - pub fn container<'a, Message: 'static, T>( - &self, - id: I, - content: T, - ) -> MinSizeTracker<'a, Message, crate::Renderer, I> - where - I: 'a, - T: Into>, - { - MinSizeTracker::new(content, id, self.tx.clone()) - } -} - -/// An element decorating some content. -/// -/// It is normally used for alignment purposes. -#[allow(missing_debug_implementations)] -pub struct MinSizeTrackerContainer<'a, Message, Renderer, I> -where - Renderer: iced_core::Renderer, -{ - tx: UnboundedSender<(I, Rectangle)>, - id: I, - container: Container<'a, Message, crate::Theme, Renderer>, - ignore_bounds: bool, -} - -impl<'a, Message, Renderer, I> MinSizeTrackerContainer<'a, Message, Renderer, I> -where - Renderer: iced_core::Renderer, - I: 'a + Hash + Copy + Send + Sync + Debug, -{ - /// Creates an empty [`Container`]. - pub(crate) fn new(content: T, id: I, tx: UnboundedSender<(I, Rectangle)>) -> Self - where - T: Into>, - { - MinSizeTrackerContainer { - id, - tx, - container: Container::new(content), - ignore_bounds: false, - } - } - - pub fn diff(&mut self, tree: &mut Tree) { - self.container.diff(tree); - } - - /// Sets the [`Padding`] of the [`Container`]. - #[must_use] - pub fn padding>(mut self, padding: P) -> Self { - self.container = self.container.padding(padding); - self - } - - /// Sets the width of the [`self.`]. - #[must_use] - pub fn width(mut self, width: Length) -> Self { - self.container = self.container.width(width); - self - } - - /// Sets the height of the [`Container`]. - #[must_use] - pub fn height(mut self, height: Length) -> Self { - self.container = self.container.height(height); - self - } - - /// Sets the maximum width of the [`Container`]. - #[must_use] - pub fn max_width(mut self, max_width: f32) -> Self { - self.container = self.container.max_width(max_width); - self - } - - /// Sets the maximum height of the [`Container`] in pixels. - #[must_use] - pub fn max_height(mut self, max_height: f32) -> Self { - self.container = self.container.max_height(max_height); - self - } - - /// Sets the content alignment for the horizontal axis of the [`Container`]. - #[must_use] - pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self { - self.container = self.container.align_x(alignment); - self - } - - /// Sets the content alignment for the vertical axis of the [`Container`]. - #[must_use] - pub fn align_y(mut self, alignment: alignment::Vertical) -> Self { - self.container = self.container.align_y(alignment); - self - } - - /// Centers the contents in the horizontal axis of the [`Container`]. - #[must_use] - pub fn center_x(mut self, width: Length) -> Self { - self.container = self.container.center_x(width); - self - } - - /// Centers the contents in the vertical axis of the [`Container`]. - #[must_use] - pub fn center_y(mut self, height: Length) -> Self { - self.container = self.container.center_y(height); - self - } - - /// Sets the style of the [`Container`]. - #[must_use] - pub fn style(mut self, style: impl Into<::Class<'a>>) -> Self { - self.container = self.container.class(style); - self - } - - /// Set to true to ignore parent container bounds when performing layout. - /// This can be useful for widgets that are in auto-sized surfaces. - #[must_use] - pub fn ignore_bounds(mut self, ignore_bounds: bool) -> Self { - self.ignore_bounds = ignore_bounds; - self - } -} - -impl<'a, Message, Renderer, I> Widget - for MinSizeTrackerContainer<'a, Message, Renderer, I> -where - Renderer: iced_core::Renderer, - I: 'a + Hash + Copy + Send + Sync + Debug, -{ - fn children(&self) -> Vec { - self.container.children() - } - - fn state(&self) -> iced_core::widget::tree::State { - self.container.state() - } - - fn diff(&mut self, tree: &mut Tree) { - self.container.diff(tree); - } - - fn size(&self) -> iced_core::Size { - self.container.size() - } - - fn layout( - &self, - tree: &mut Tree, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - self.container.layout( - tree, - renderer, - if self.ignore_bounds { - &layout::Limits::NONE - } else { - limits - }, - ) - } - - fn operate( - &self, - tree: &mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn iced_core::widget::Operation<()>, - ) { - self.container.operate(tree, layout, renderer, operation); - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: mouse::Cursor, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - viewport: &iced_core::Rectangle, - ) -> event::Status { - self.container.on_event( - tree, - event, - layout, - cursor_position, - renderer, - clipboard, - shell, - viewport, - ) - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: mouse::Cursor, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.container - .mouse_interaction(tree, layout, cursor_position, viewport, renderer) - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &crate::Theme, - renderer_style: &renderer::Style, - layout: Layout<'_>, - cursor_position: mouse::Cursor, - viewport: &Rectangle, - ) { - let _ = self.tx.unbounded_send((self.id, layout.bounds())); - - self.container.draw( - tree, - renderer, - theme, - renderer_style, - layout, - cursor_position, - viewport, - ); - } - - fn overlay<'b>( - &'b mut self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - translation: Vector, - ) -> Option> { - self.container.overlay(tree, layout, renderer, translation) - } - - fn drag_destinations( - &self, - state: &Tree, - layout: Layout<'_>, - renderer: &Renderer, - dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, - ) { - self.container - .drag_destinations(state, layout, renderer, dnd_rectangles); - } -} - -impl<'a, Message, Renderer, I> From> - for Element<'a, Message, crate::Theme, Renderer> -where - Message: 'a, - Renderer: 'a + iced_core::Renderer, - I: 'a + Hash + Copy + Send + Sync + Debug, -{ - fn from( - column: MinSizeTrackerContainer<'a, Message, Renderer, I>, - ) -> Element<'a, Message, crate::Theme, Renderer> { - Element::new(column) - } -} diff --git a/src/widget/min_size_tracker/subscription.rs b/src/widget/min_size_tracker/subscription.rs deleted file mode 100644 index c7711b43..00000000 --- a/src/widget/min_size_tracker/subscription.rs +++ /dev/null @@ -1,88 +0,0 @@ -use iced::{ - futures::{ - channel::mpsc::{unbounded, UnboundedReceiver}, - stream, StreamExt, - }, - Rectangle, -}; -use iced_futures::Subscription; -use std::{collections::HashMap, fmt::Debug, hash::Hash}; - -use super::MinSizeTrackerContainer; - -pub fn rectangle_tracker_subscription< - I: 'static + Hash + Copy + Send + Sync + Debug, - R: 'static + Hash + Copy + Send + Sync + Debug + Eq, ->( - id: I, -) -> Subscription<(I, RectangleUpdate)> { - Subscription::run_with_id( - id, - stream::unfold(State::Ready, move |state| start_listening(id, state)), - ) -} - -pub enum State { - Ready, - Waiting(UnboundedReceiver<(I, Rectangle)>, HashMap), - Finished, -} - -async fn start_listening( - id: I, - mut state: State, -) -> Option<((I, RectangleUpdate), State)> { - loop { - let (update, new_state) = match state { - State::Ready => { - let (tx, rx) = unbounded(); - - ( - Some((id, RectangleUpdate::Init(MinSizeTracker { tx }))), - State::Waiting(rx, HashMap::new()), - ) - } - State::Waiting(mut rx, mut map) => match rx.next().await { - Some(u) => { - if let Some(prev) = map.get(&u.0) { - let new = u.1; - if (prev.width - new.width).abs() > 0.1 - || (prev.height - new.height).abs() > 0.1 - || (prev.x - new.x).abs() > 0.1 - || (prev.y - new.y).abs() > 0.1 - { - map.insert(u.0, new); - ( - Some((id, RectangleUpdate::Rectangle(u))), - State::Waiting(rx, map), - ) - } else { - (None, State::Waiting(rx, map)) - } - } else { - map.insert(u.0, u.1); - ( - Some((id, RectangleUpdate::Rectangle(u))), - State::Waiting(rx, map), - ) - } - } - None => (None, State::Finished), - }, - State::Finished => return None, - }; - state = new_state; - if let Some(u) = update { - return Some((u, state)); - } - } -} - -#[derive(Clone, Debug)] -pub enum RectangleUpdate -where - I: 'static + Hash + Copy + Send + Sync + Debug, -{ - Rectangle((I, Rectangle)), - Init(MinSizeTrackerContainer), -} diff --git a/src/widget/rectangle_tracker/mod.rs b/src/widget/rectangle_tracker/mod.rs index 6ce11396..d502badb 100644 --- a/src/widget/rectangle_tracker/mod.rs +++ b/src/widget/rectangle_tracker/mod.rs @@ -72,7 +72,6 @@ where id: I, container: Container<'a, Message, crate::Theme, Renderer>, ignore_bounds: bool, - request_size: bool, } impl<'a, Message, Renderer, I> RectangleTrackingContainer<'a, Message, Renderer, I> @@ -90,7 +89,6 @@ where tx, container: Container::new(content), ignore_bounds: false, - request_size: true, } } @@ -214,7 +212,6 @@ where limits }, ); - if self.request_size {} layout } From a73e91a12bbc4318b855d7ab1030820570d6ff5b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 18 Oct 2024 12:10:45 -0400 Subject: [PATCH 010/556] refactor(toggler): remove required on_toggle function --- src/widget/settings/item.rs | 2 +- src/widget/toggler.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index f277ff02..591aa8ac 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -132,6 +132,6 @@ impl<'a, Message: 'static> Item<'a, Message> { is_checked: bool, message: impl Fn(bool) -> Message + 'static, ) -> Row<'a, Message> { - self.control(crate::widget::toggler(is_checked, message)) + self.control(crate::widget::toggler(is_checked).on_toggle(message)) } } diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index 972712ac..27ed6f7b 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -6,13 +6,11 @@ use iced_core::text; pub fn toggler<'a, Message, Theme: iced_widget::toggler::Catalog, Renderer>( is_checked: bool, - f: impl Fn(bool) -> Message + 'a, ) -> widget::Toggler<'a, Message, Theme, Renderer> where Renderer: iced_core::Renderer + text::Renderer, { widget::Toggler::new(is_checked) - .on_toggle(f) .size(24) .width(Length::Shrink) } From c287445ecced91b76ebbad3a2b5fae1b149899c2 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 18 Oct 2024 16:22:56 -0400 Subject: [PATCH 011/556] chore: update iced small scrollable fix --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 79e74f57..2345f29a 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 79e74f57120abb3d56331e8a6d847dc98306a383 +Subproject commit 2345f29a192ca2152ef644008931b0960e6e487c From c70e536b3848642b0acd9a24fb8c4290c2d6b879 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 18 Oct 2024 17:43:09 -0600 Subject: [PATCH 012/556] Update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 2345f29a..29743fc4 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 2345f29a192ca2152ef644008931b0960e6e487c +Subproject commit 29743fc482eebe547ee18fbf67a422dca28ef45b From d718a4a61c5d7e1c5aab34c2bcce42aad06f13b9 Mon Sep 17 00:00:00 2001 From: netengy-dakotaraptor Date: Sat, 19 Oct 2024 22:00:59 -0600 Subject: [PATCH 013/556] fix: remove non-existent cosmic-time feature from cosmic example Removes the "libcosmic" feature of cosmic-time from the cosmic example. This feature appears to no longer exist. --- examples/cosmic/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cosmic/Cargo.toml b/examples/cosmic/Cargo.toml index 194ce885..a7daf0ad 100644 --- a/examples/cosmic/Cargo.toml +++ b/examples/cosmic/Cargo.toml @@ -17,4 +17,4 @@ log = "0.4.17" [dependencies.cosmic-time] git = "https://github.com/pop-os/cosmic-time" default-features = false -features = ["libcosmic", "once_cell"] +features = ["once_cell"] From accb65b7ec1afb9ec1b228a496d83a86dc604361 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 21 Oct 2024 09:19:41 -0400 Subject: [PATCH 014/556] fix(applet): toggler --- examples/applet/src/window.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/applet/src/window.rs b/examples/applet/src/window.rs index 58306573..4f34ddfb 100644 --- a/examples/applet/src/window.rs +++ b/examples/applet/src/window.rs @@ -97,9 +97,9 @@ impl cosmic::Application for Window { fn view_window(&self, _id: Id) -> Element { let content_list = list_column().padding(5).spacing(0).add(settings::item( "Example row", - cosmic::widget::container(toggler(self.example_row, |value| { - Message::ToggleExampleRow(value) - })) + cosmic::widget::container( + toggler(self.example_row).on_toggle(|value| Message::ToggleExampleRow(value)), + ) .height(Length::Fixed(50.)), )); From 533e099cf6259fac14580f5c3881777a43b93932 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 21 Oct 2024 09:12:41 -0400 Subject: [PATCH 015/556] fix: text input icon render --- examples/application/src/main.rs | 22 +++++++++++++++++++++- src/widget/text_input/input.rs | 16 ++++++++++++---- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index 24568f0a..29e2bbf7 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -54,6 +54,8 @@ fn main() -> Result<(), Box> { pub enum Message { Input1(String), Input2(String), + Ignore, + ToggleHide, } /// The [`App`] stores application-specific state. @@ -62,6 +64,7 @@ pub struct App { nav_model: nav_bar::Model, input_1: String, input_2: String, + hidden: bool, } /// Implement [`cosmic::Application`] to integrate with COSMIC. @@ -101,6 +104,7 @@ impl cosmic::Application for App { nav_model, input_1: String::new(), input_2: String::new(), + hidden: true, }; let command = app.update_title(); @@ -128,6 +132,10 @@ impl cosmic::Application for App { Message::Input2(v) => { self.input_2 = v; } + Message::Ignore => {} + Message::ToggleHide => { + self.hidden = !self.hidden; + } } Task::none() } @@ -144,8 +152,20 @@ impl cosmic::Application for App { let centered = cosmic::widget::container( column![ text, + cosmic::widget::text_input::text_input("", &self.input_1) + .on_input(Message::Input1) + .on_clear(Message::Ignore), + cosmic::widget::text_input::secure_input( + "", + &self.input_1, + Some(Message::ToggleHide), + self.hidden + ) + .on_input(Message::Input1), cosmic::widget::text_input::text_input("", &self.input_1).on_input(Message::Input1), - cosmic::widget::text_input::text_input("", &self.input_2).on_input(Message::Input2), + cosmic::widget::text_input::search_input("", &self.input_2) + .on_input(Message::Input2) + .on_clear(Message::Ignore), ] .width(iced::Length::Fill) .height(iced::Length::Shrink) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 3c085628..2b8f424c 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -431,7 +431,7 @@ where value: Option<&Value>, style: &renderer::Style, ) { - let text_layout = self.text_layout(layout.clone()); + let text_layout = self.text_layout(layout); draw( renderer, theme, @@ -2008,7 +2008,7 @@ pub fn draw<'a, Message>( let mut children_layout = layout.children(); let bounds = layout.bounds(); - let text_bounds = text_layout.bounds(); + let text_bounds = children_layout.next().unwrap().bounds(); let is_mouse_over = cursor_position.is_over(bounds); @@ -2115,8 +2115,11 @@ pub fn draw<'a, Message>( let mut child_index = 0; let leading_icon_tree = children.get(child_index); // draw the start icon in the text input + let has_start_icon = icon.is_some(); if let (Some(icon), Some(tree)) = (icon, leading_icon_tree) { - let icon_layout = children_layout.next().unwrap(); + let mut children = text_layout.children(); + let _ = children.next().unwrap(); + let icon_layout = children.next().unwrap(); icon.as_widget().draw( tree, @@ -2292,7 +2295,12 @@ pub fn draw<'a, Message>( // draw the end icon in the text input if let (Some(icon), Some(tree)) = (trailing_icon, trailing_icon_tree) { - let icon_layout = children_layout.next().unwrap(); + let mut children = text_layout.children(); + let mut icon_layout = children.next().unwrap(); + if has_start_icon { + icon_layout = children.next().unwrap(); + } + icon_layout = children.next().unwrap(); icon.as_widget().draw( tree, From 6f5d096be24aca4533d1eaf4237ca40c252aeb73 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 21 Oct 2024 10:57:29 -0400 Subject: [PATCH 016/556] fix: docs --- iced | 2 +- src/applet/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/iced b/iced index 29743fc4..4dd4cfb6 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 29743fc482eebe547ee18fbf67a422dca28ef45b +Subproject commit 4dd4cfb6a389eb42fdf2c8b0fe8d46bd53eaef33 diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 4a6db2c4..67054382 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -45,7 +45,7 @@ pub struct Context { pub panel_type: PanelType, /// Includes the configured size of the window. /// This can be used by apples to handle overflow themselves. - pub suggested_bounds: Option<(iced::Size)>, + pub suggested_bounds: Option, } #[derive(Clone, Debug, PartialEq, Eq)] From e380b2bd814d8bab06d12cf72a2d2abe0c7f00b4 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Mon, 21 Oct 2024 09:47:38 -0600 Subject: [PATCH 017/556] fix(app): only set application_id on Linux --- src/app/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 5d46d54a..2a7483fb 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -104,7 +104,10 @@ pub(crate) fn iced_settings( let mut window_settings = iced::window::Settings::default(); window_settings.exit_on_close_request = exit_on_close; iced.id = Some(App::APP_ID.to_owned()); - window_settings.platform_specific.application_id = App::APP_ID.to_string(); + #[cfg(target_os = "linux")] + { + window_settings.platform_specific.application_id = App::APP_ID.to_string(); + } core.exit_on_main_window_closed = exit_on_close; if let Some(border_size) = settings.resizable { From b44fe2c0b6e881761f2344d2817707725a38f91c Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 21 Oct 2024 16:46:26 -0400 Subject: [PATCH 018/556] update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 4dd4cfb6..894faf6b 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 4dd4cfb6a389eb42fdf2c8b0fe8d46bd53eaef33 +Subproject commit 894faf6b0581c06ee42aa65204e93841293e360b From cf3ba4ca078bcc5238360a2ff448f80b482f451a Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 21 Oct 2024 17:32:16 -0400 Subject: [PATCH 019/556] refactor: allow resetting the main window id --- src/app/command.rs | 12 ++++++------ src/app/core.rs | 14 ++++++++------ src/app/cosmic.rs | 11 +++++++---- src/app/mod.rs | 12 ++++++------ src/applet/mod.rs | 4 ++-- 5 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/app/command.rs b/src/app/command.rs index 5fd77fce..ff51a7d3 100644 --- a/src/app/command.rs +++ b/src/app/command.rs @@ -29,7 +29,7 @@ pub mod message { impl crate::app::Core { pub fn drag(&self, id: Option) -> iced::Task> { - let Some(id) = id.or(self.main_window.get().cloned()) else { + let Some(id) = id.or(self.main_window) else { return iced::Task::none(); }; crate::command::drag(id).map(Message::Cosmic) @@ -40,14 +40,14 @@ impl crate::app::Core { id: Option, maximized: bool, ) -> iced::Task> { - let Some(id) = id.or(self.main_window.get().cloned()) else { + let Some(id) = id.or(self.main_window) else { return iced::Task::none(); }; crate::command::maximize(id, maximized).map(Message::Cosmic) } pub fn minimize(&self, id: Option) -> iced::Task> { - let Some(id) = id.or(self.main_window.get().cloned()) else { + let Some(id) = id.or(self.main_window) else { return iced::Task::none(); }; crate::command::minimize(id).map(Message::Cosmic) @@ -62,7 +62,7 @@ impl crate::app::Core { id: Option, title: String, ) -> iced::Task> { - let Some(id) = id.or(self.main_window.get().cloned()) else { + let Some(id) = id.or(self.main_window) else { return iced::Task::none(); }; crate::command::set_title(id, title).map(Message::Cosmic) @@ -72,7 +72,7 @@ impl crate::app::Core { &self, id: Option, ) -> iced::Task> { - let Some(id) = id.or(self.main_window.get().cloned()) else { + let Some(id) = id.or(self.main_window) else { return iced::Task::none(); }; crate::command::set_windowed(id).map(Message::Cosmic) @@ -82,7 +82,7 @@ impl crate::app::Core { &self, id: Option, ) -> iced::Task> { - let Some(id) = id.or(self.main_window.get().cloned()) else { + let Some(id) = id.or(self.main_window) else { return iced::Task::none(); }; crate::command::toggle_maximize(id).map(Message::Cosmic) diff --git a/src/app/core.rs b/src/app/core.rs index f1e63d72..8ffe8ecd 100644 --- a/src/app/core.rs +++ b/src/app/core.rs @@ -94,7 +94,7 @@ pub struct Core { #[cfg(feature = "dbus-config")] pub(crate) settings_daemon: Option>, - pub(crate) main_window: OnceCell, + pub(crate) main_window: Option, pub(crate) exit_on_main_window_closed: bool, } @@ -152,7 +152,7 @@ impl Default for Core { portal_is_dark: None, portal_accent: None, portal_is_high_contrast: None, - main_window: OnceCell::new(), + main_window: None, exit_on_main_window_closed: true, } } @@ -373,9 +373,11 @@ impl Core { /// The [`Id`] of the main window pub fn main_window_id(&self) -> Option { - self.main_window - .get() - .filter(|id| iced::window::Id::NONE != **id) - .cloned() + self.main_window.filter(|id| iced::window::Id::NONE != *id) + } + + /// Reset the tracked main window to a new value + pub fn set_main_window_id(&mut self, id: window::Id) -> Option { + self.main_window.replace(id) } } diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 295a0050..14dc96d0 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -79,7 +79,7 @@ pub enum Message { #[cfg(feature = "applet")] SuggestedBounds(Option), /// Window Created - MainWindowCreated(window::Id), + WindowCreated(window::Id), } #[derive(Default)] @@ -167,7 +167,7 @@ where iced::Event::Window(window::Event::Focused) => return Some(Message::Focus(id)), iced::Event::Window(window::Event::Unfocused) => return Some(Message::Unfocus(id)), iced::Event::Window(window::Event::Opened { .. }) => { - return Some(Message::MainWindowCreated(id)); + return Some(Message::WindowCreated(id)); } #[cfg(feature = "wayland")] iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => { @@ -660,9 +660,12 @@ impl Cosmic { core.focused_window = None; } } - Message::MainWindowCreated(id) => { + Message::WindowCreated(id) => { let core = self.app.core_mut(); - _ = core.main_window.set(id); + // Set the main window if it was not set to something else + if core.main_window.is_none() { + core.main_window = Some(id); + } } #[cfg(feature = "applet")] Message::SuggestedBounds(b) => { diff --git a/src/app/mod.rs b/src/app/mod.rs index 2a7483fb..1cf419d2 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -91,7 +91,7 @@ pub(crate) fn iced_settings( THEME.lock().unwrap().set_theme(settings.theme.theme_type); if settings.no_main_window { - core.main_window.set(iced::window::Id::NONE).unwrap(); + core.main_window = Some(iced::window::Id::NONE); } let mut iced = iced::Settings::default(); @@ -136,7 +136,7 @@ pub(crate) fn iced_settings( /// Returns error on application failure. pub fn run(settings: Settings, flags: App::Flags) -> iced::Result { let default_font = settings.default_font; - let (settings, flags) = iced_settings::(settings, flags); + let (settings, mut flags) = iced_settings::(settings, flags); #[cfg(not(feature = "multi-window"))] { iced::application( @@ -159,9 +159,9 @@ pub fn run(settings: Settings, flags: App::Flags) -> iced::Res cosmic::Cosmic::update, cosmic::Cosmic::view, ); - if flags.0.main_window.get().is_none() { + if flags.0.main_window.is_none() { app = app.window(flags.2.clone()); - _ = flags.0.main_window.set(iced_core::window::Id::RESERVED); + flags.0.main_window = Some(iced_core::window::Id::RESERVED); } app.subscription(cosmic::Cosmic::subscription) .style(cosmic::Cosmic::style) @@ -395,9 +395,9 @@ where cosmic::Cosmic::update, cosmic::Cosmic::view, ); - if flags.0.main_window.get().is_none() { + if flags.0.main_window.is_none() { app = app.window(flags.2.clone()); - _ = flags.0.main_window.set(iced_core::window::Id::RESERVED); + _ = flags.0.main_window = Some(iced_core::window::Id::RESERVED); } app.subscription(cosmic::Cosmic::subscription) .style(cosmic::Cosmic::style) diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 67054382..bff90489 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -419,9 +419,9 @@ pub fn run(flags: App::Flags) -> iced::Result { cosmic::Cosmic::update, cosmic::Cosmic::view, ); - if core.main_window.get().is_none() { + if core.main_window.is_none() { app = app.window(window_settings.clone()); - _ = core.main_window.set(iced_core::window::Id::RESERVED); + core.main_window = Some(iced_core::window::Id::RESERVED); } app.subscription(cosmic::Cosmic::subscription) .style(cosmic::Cosmic::style) From 953685a88267894ea3fa40f2b99139c3c4e784d6 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 21 Oct 2024 17:37:11 -0400 Subject: [PATCH 020/556] fix(input): fallback on text layout for dnd --- src/widget/text_input/input.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 2b8f424c..bea23db8 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -844,7 +844,7 @@ where cursor_position: mouse::Cursor, viewport: &Rectangle, ) { - let text_layout = self.text_layout(layout.clone()); + let text_layout = self.text_layout(layout); draw( renderer, theme, @@ -2008,7 +2008,8 @@ pub fn draw<'a, Message>( let mut children_layout = layout.children(); let bounds = layout.bounds(); - let text_bounds = children_layout.next().unwrap().bounds(); + // XXX Dnd widget may not have a layout with children, so we just use the text_layout + let text_bounds = children_layout.next().unwrap_or(text_layout).bounds(); let is_mouse_over = cursor_position.is_over(bounds); From 07763aca8e08f7b371aaebd353e145cc62121cd5 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 21 Oct 2024 18:25:25 -0400 Subject: [PATCH 021/556] refactor: allow resetting main window id to None --- src/app/core.rs | 6 ++++-- src/app/cosmic.rs | 16 ++-------------- src/app/mod.rs | 18 +++++++++--------- src/applet/mod.rs | 4 ++-- 4 files changed, 17 insertions(+), 27 deletions(-) diff --git a/src/app/core.rs b/src/app/core.rs index 8ffe8ecd..c805188b 100644 --- a/src/app/core.rs +++ b/src/app/core.rs @@ -372,12 +372,14 @@ impl Core { } /// The [`Id`] of the main window + #[must_use] pub fn main_window_id(&self) -> Option { self.main_window.filter(|id| iced::window::Id::NONE != *id) } /// Reset the tracked main window to a new value - pub fn set_main_window_id(&mut self, id: window::Id) -> Option { - self.main_window.replace(id) + pub fn set_main_window_id(&mut self, mut id: Option) -> Option { + std::mem::swap(&mut self.main_window, &mut id); + id } } diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 14dc96d0..a2b229e6 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -78,8 +78,6 @@ pub enum Message { /// Tracks updates to window suggested size. #[cfg(feature = "applet")] SuggestedBounds(Option), - /// Window Created - WindowCreated(window::Id), } #[derive(Default)] @@ -92,7 +90,7 @@ where T::Message: Send + 'static, { pub fn init( - (mut core, flags, window_settings): (Core, T::Flags, iced::window::Settings), + (mut core, flags): (Core, T::Flags), ) -> (Self, iced::Task>) { #[cfg(feature = "dbus-config")] { @@ -100,7 +98,7 @@ where core.settings_daemon = block_on(cosmic_config::dbus::settings_daemon_proxy()).ok(); } - let (model, mut command) = T::init(core, flags); + let (model, command) = T::init(core, flags); (Self::new(model), command) } @@ -166,9 +164,6 @@ where } iced::Event::Window(window::Event::Focused) => return Some(Message::Focus(id)), iced::Event::Window(window::Event::Unfocused) => return Some(Message::Unfocus(id)), - iced::Event::Window(window::Event::Opened { .. }) => { - return Some(Message::WindowCreated(id)); - } #[cfg(feature = "wayland")] iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => { match event { @@ -660,13 +655,6 @@ impl Cosmic { core.focused_window = None; } } - Message::WindowCreated(id) => { - let core = self.app.core_mut(); - // Set the main window if it was not set to something else - if core.main_window.is_none() { - core.main_window = Some(id); - } - } #[cfg(feature = "applet")] Message::SuggestedBounds(b) => { tracing::info!("Suggested bounds: {b:?}"); diff --git a/src/app/mod.rs b/src/app/mod.rs index 1cf419d2..eb65747b 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -72,7 +72,7 @@ use { pub(crate) fn iced_settings( settings: Settings, flags: App::Flags, -) -> (iced::Settings, (Core, App::Flags, iced::window::Settings)) { +) -> (iced::Settings, (Core, App::Flags), iced::window::Settings) { preload_fonts(); let mut core = Core::default(); @@ -126,7 +126,7 @@ pub(crate) fn iced_settings( } window_settings.transparent = settings.transparent; - (iced, (core, flags, window_settings)) + (iced, (core, flags), window_settings) } /// Launch a COSMIC application with the given [`Settings`]. @@ -136,7 +136,7 @@ pub(crate) fn iced_settings( /// Returns error on application failure. pub fn run(settings: Settings, flags: App::Flags) -> iced::Result { let default_font = settings.default_font; - let (settings, mut flags) = iced_settings::(settings, flags); + let (settings, mut flags, window_settings) = iced_settings::(settings, flags); #[cfg(not(feature = "multi-window"))] { iced::application( @@ -149,7 +149,7 @@ pub fn run(settings: Settings, flags: App::Flags) -> iced::Res .theme(cosmic::Cosmic::theme) .window_size((500.0, 800.0)) .settings(settings) - .window(flags.2.clone()) + .window(window_settings) .run_with(move || cosmic::Cosmic::::init(flags)) } #[cfg(feature = "multi-window")] @@ -160,7 +160,7 @@ pub fn run(settings: Settings, flags: App::Flags) -> iced::Res cosmic::Cosmic::view, ); if flags.0.main_window.is_none() { - app = app.window(flags.2.clone()); + app = app.window(window_settings); flags.0.main_window = Some(iced_core::window::Id::RESERVED); } app.subscription(cosmic::Cosmic::subscription) @@ -370,7 +370,7 @@ where tracing::info!("Another instance is running"); Ok(()) } else { - let (settings, mut flags) = iced_settings::(settings, flags); + let (settings, mut flags, window_settings) = iced_settings::(settings, flags); flags.0.single_instance = true; #[cfg(not(feature = "multi-window"))] @@ -385,7 +385,7 @@ where .theme(cosmic::Cosmic::theme) .window_size((500.0, 800.0)) .settings(settings) - .window(flags.2.clone()) + .window(window_settings) .run_with(move || cosmic::Cosmic::::init(flags)) } #[cfg(feature = "multi-window")] @@ -396,8 +396,8 @@ where cosmic::Cosmic::view, ); if flags.0.main_window.is_none() { - app = app.window(flags.2.clone()); - _ = flags.0.main_window = Some(iced_core::window::Id::RESERVED); + app = app.window(window_settings); + flags.0.main_window = Some(iced_core::window::Id::RESERVED); } app.subscription(cosmic::Cosmic::subscription) .style(cosmic::Cosmic::style) diff --git a/src/applet/mod.rs b/src/applet/mod.rs index bff90489..b6e741fb 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -399,7 +399,7 @@ pub fn run(flags: App::Flags) -> iced::Result { .unwrap() .set_theme(settings.theme.theme_type.clone()); - let (iced_settings, (mut core, flags, mut window_settings)) = + let (iced_settings, (mut core, flags), mut window_settings) = iced_settings::(settings, flags); core.window.show_headerbar = false; core.window.sharp_corners = true; @@ -427,7 +427,7 @@ pub fn run(flags: App::Flags) -> iced::Result { .style(cosmic::Cosmic::style) .theme(cosmic::Cosmic::theme) .settings(iced_settings) - .run_with(move || cosmic::Cosmic::::init((core, flags, window_settings))) + .run_with(move || cosmic::Cosmic::::init((core, flags))) } #[must_use] From f1e52b3aaa60af9e2baa647a11cb9cdb13862af2 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 22 Oct 2024 08:17:11 -0600 Subject: [PATCH 022/556] Update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 894faf6b..57598f21 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 894faf6b0581c06ee42aa65204e93841293e360b +Subproject commit 57598f214c0f78c6796869599009f8aa89380389 From 47ba123f2f4d16a598d546b06392f0139efc2d2a Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 22 Oct 2024 12:29:51 -0600 Subject: [PATCH 023/556] Update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 57598f21..a088df97 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 57598f214c0f78c6796869599009f8aa89380389 +Subproject commit a088df9795e10c94faad2182d83e972023ef9cc0 From fcc0c91dd6d9327a05b36bd0328c5497b8ee4ded Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 22 Oct 2024 14:46:11 -0400 Subject: [PATCH 024/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index a088df97..29ff72bb 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit a088df9795e10c94faad2182d83e972023ef9cc0 +Subproject commit 29ff72bb51c09d9b57ee9126f2629361e29fc2f3 From 59407552b60a62468792f480516ee5c2900cee2a Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 22 Oct 2024 16:04:54 -0400 Subject: [PATCH 025/556] fix: apply translation to dropdown position --- src/widget/dropdown/multi/widget.rs | 5 ++++- src/widget/dropdown/widget.rs | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index c40f8760..b3bb09e2 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -200,6 +200,7 @@ impl<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static> self.text_line_height, self.selections, &self.on_selected, + translation, ) } } @@ -392,6 +393,7 @@ pub fn overlay<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static text_line_height: text::LineHeight, selections: &'a super::Model, on_selected: &'a dyn Fn(Item) -> Message, + translation: Vector, ) -> Option> { if state.is_open { let description_line_height = text::LineHeight::Absolute(Pixels( @@ -474,7 +476,8 @@ pub fn overlay<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static let mut position = layout.position(); position.x -= padding.left; - + position.x += translation.x; + position.y += translation.y; Some(menu.overlay(position, bounds.height)) } else { None diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 80d5a009..3ff76ee1 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -243,6 +243,7 @@ impl<'a, S: AsRef, Message: 'a> Widget, Message: 'a>( selections: &'a [S], selected_option: Option, on_selected: &'a dyn Fn(usize) -> Message, + translation: Vector, ) -> Option> { if state.is_open { let bounds = layout.bounds(); @@ -475,6 +477,8 @@ pub fn overlay<'a, S: AsRef, Message: 'a>( let mut position = layout.position(); position.x -= padding.left; + position.x += translation.x; + position.y += translation.y; Some(menu.overlay(position, bounds.height)) } else { None From 722f30c7241dc9aabd548833ba2a60c340fd25cf Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 22 Oct 2024 16:44:29 -0400 Subject: [PATCH 026/556] fix: bounds for button --- src/widget/button/widget.rs | 39 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 1c0ef257..49c96e1d 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -338,9 +338,12 @@ impl<'a, Message: 'a + Clone> Widget renderer_style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, - _viewport: &Rectangle, + viewport: &Rectangle, ) { let bounds = layout.bounds(); + if !viewport.intersects(&bounds) { + return; + } let content_layout = layout.children().next().unwrap(); let mut headerbar_alpha = None; @@ -391,6 +394,7 @@ impl<'a, Message: 'a + Clone> Widget draw::<_, crate::Theme>( renderer, bounds, + *viewport, &styling, |renderer, _styling| { self.content.as_widget().draw( @@ -404,7 +408,7 @@ impl<'a, Message: 'a + Clone> Widget }, content_layout, cursor, - &bounds, + viewport, ); }, matches!(self.variant, Variant::Image { .. }), @@ -415,12 +419,7 @@ impl<'a, Message: 'a + Clone> Widget on_remove, } = &self.variant { - let mut parent_bounds = bounds; - parent_bounds.y -= 8.0; - parent_bounds.width += 16.0; - parent_bounds.height += 16.0; - - renderer.with_layer(parent_bounds, |renderer| { + renderer.with_layer(*viewport, |renderer| { let selection_background = theme.selection_background(); let c_rad = THEME.lock().unwrap().cosmic().corner_radii; @@ -472,16 +471,15 @@ impl<'a, Message: 'a + Clone> Widget let svg_handle = svg::Svg::new(crate::widget::common::object_select().clone()) .color(icon_color); - iced_core::svg::Renderer::draw_svg( - renderer, - svg_handle, - Rectangle { - width: 16.0, - height: 16.0, - x: bounds.x + 5.0 + styling.border_width, - y: bounds.y + (bounds.height - 18.0 - styling.border_width), - }, - ); + let bounds = Rectangle { + width: 16.0, + height: 16.0, + x: bounds.x + 5.0 + styling.border_width, + y: bounds.y + (bounds.height - 18.0 - styling.border_width), + }; + if bounds.intersects(viewport) { + iced_core::svg::Renderer::draw_svg(renderer, svg_handle, bounds); + } } if on_remove.is_some() { @@ -535,7 +533,7 @@ impl<'a, Message: 'a + Clone> Widget renderer: &crate::Renderer, mut translation: Vector, ) -> Option> { - let mut position = layout.bounds().position(); + let position = layout.bounds().position(); translation.x += position.x; translation.y += position.y; self.content.as_widget_mut().overlay( @@ -753,6 +751,7 @@ pub fn update<'a, Message: Clone>( pub fn draw( renderer: &mut Renderer, bounds: Rectangle, + viewport_bounds: Rectangle, styling: &super::style::Style, draw_contents: impl FnOnce(&mut Renderer, &Style), is_image: bool, @@ -836,7 +835,7 @@ pub fn draw( // Then draw the button contents onto the background. draw_contents(renderer, styling); - let mut clipped_bounds = bounds; + let mut clipped_bounds = viewport_bounds; clipped_bounds.height += styling.border_width; renderer.with_layer(clipped_bounds, |renderer| { From 795654610a2875fe4316add18df6f33a4bc86af1 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 22 Oct 2024 18:52:43 -0400 Subject: [PATCH 027/556] fix: button --- src/widget/button/widget.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 49c96e1d..a51adbb5 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -408,7 +408,7 @@ impl<'a, Message: 'a + Clone> Widget }, content_layout, cursor, - viewport, + &viewport.intersection(&bounds).unwrap_or_default(), ); }, matches!(self.variant, Variant::Image { .. }), @@ -556,13 +556,14 @@ impl<'a, Message: 'a + Clone> Widget accesskit::{Action, DefaultActionVerb, NodeBuilder, NodeId, Rect, Role}, A11yNode, A11yTree, }; + // TODO why is state None sometimes? + if matches!(state.state, iced_core::widget::tree::State::None) { + tracing::info!("Button state is missing."); + return A11yTree::default(); + } let child_layout = layout.children().next().unwrap(); - let child_tree = &state.children[0]; - let child_tree = self - .content - .as_widget() - .a11y_nodes(child_layout, child_tree, p); + let child_tree = state.children.first(); let Rectangle { x, @@ -607,7 +608,15 @@ impl<'a, Message: 'a + Clone> Widget } node.set_default_action_verb(DefaultActionVerb::Click); - A11yTree::node_with_child_tree(A11yNode::new(node, self.id.clone()), child_tree) + if let Some(child_tree) = child_tree.map(|child_tree| { + self.content + .as_widget() + .a11y_nodes(child_layout, child_tree, p) + }) { + A11yTree::node_with_child_tree(A11yNode::new(node, self.id.clone()), child_tree) + } else { + A11yTree::leaf(node, self.id.clone()) + } } fn id(&self) -> Option { @@ -835,7 +844,7 @@ pub fn draw( // Then draw the button contents onto the background. draw_contents(renderer, styling); - let mut clipped_bounds = viewport_bounds; + let mut clipped_bounds = viewport_bounds.intersection(&bounds).unwrap_or_default(); clipped_bounds.height += styling.border_width; renderer.with_layer(clipped_bounds, |renderer| { From 5dfac9748dd989e3cf87265e2abd2af347d60c07 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 23 Oct 2024 18:35:37 -0400 Subject: [PATCH 028/556] update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 29ff72bb..d836b45c 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 29ff72bb51c09d9b57ee9126f2629361e29fc2f3 +Subproject commit d836b45cdf5d4f5c9e964f66439518c95613e95f From d1aabecc160f26f860122dd0e52256085fb24f4b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 23 Oct 2024 18:36:19 -0400 Subject: [PATCH 029/556] fix: card button can't be transparent when disabled --- src/theme/style/iced.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 73be6930..79b7dde2 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -122,6 +122,10 @@ impl iced_button::Catalog for Theme { }; } iced_button::Status::Disabled => { + // Card color is not transparent when it isn't clickable + if matches!(class, Button::Card) { + return appearance; + } appearance.background = appearance.background.map(|background| match background { Background::Color(color) => Background::Color(Color { a: color.a * 0.5, From 3414f62367e78fd594e34d9632e4aba51824aab6 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 23 Oct 2024 18:36:42 -0400 Subject: [PATCH 030/556] refactor: allow a11y to be disabled with applet feature --- Cargo.toml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 18a9d5ff..93cb3fbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,6 @@ animated-image = ["image", "dep:async-fs", "tokio?/io-util", "tokio?/fs"] # XXX autosize should not be used on winit windows unless dialogs autosize = [] applet = [ - "a11y", "autosize", "winit", "wayland", @@ -45,10 +44,7 @@ desktop = [ "tokio?/net", ] # Enables launching desktop files inside systemd scopes -desktop-systemd-scope = [ - "desktop", - "dep:zbus", -] +desktop-systemd-scope = ["desktop", "dep:zbus"] # Enables keycode serialization serde-keycode = ["iced_core/serde"] # Prevents multiple separate process instances. From fde0516484750e0a7d2499e54964c5ed6ba8e16b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 23 Oct 2024 20:34:23 -0400 Subject: [PATCH 031/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index d836b45c..a92abc84 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit d836b45cdf5d4f5c9e964f66439518c95613e95f +Subproject commit a92abc845cea46ae5133648e12283a0782481de4 From d84447aaad96c84f1e59291f14468ba63fb79797 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 29 Oct 2024 15:22:54 -0400 Subject: [PATCH 032/556] fix: apply App::Executor to multi-window instance --- Cargo.toml | 1 + src/app/mod.rs | 4 ++-- src/app/multi_window.rs | 14 +++++++++----- src/applet/mod.rs | 6 ++---- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 93cb3fbb..3f225059 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ rust-version = "1.80" name = "cosmic" [features] +default = ["multi-window"] # Accessibility support a11y = ["iced/a11y", "iced_accessibility"] # Builds support for animated images diff --git a/src/app/mod.rs b/src/app/mod.rs index eb65747b..e413a22c 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -154,7 +154,7 @@ pub fn run(settings: Settings, flags: App::Flags) -> iced::Res } #[cfg(feature = "multi-window")] { - let mut app = multi_window::multi_window( + let mut app = multi_window::multi_window::<_, _, _, _, App::Executor>( cosmic::Cosmic::title, cosmic::Cosmic::update, cosmic::Cosmic::view, @@ -390,7 +390,7 @@ where } #[cfg(feature = "multi-window")] { - let mut app = multi_window::multi_window( + let mut app = multi_window::multi_window::<_, _, _, _, App::Executor>( cosmic::Cosmic::title, cosmic::Cosmic::update, cosmic::Cosmic::view, diff --git a/src/app/multi_window.rs b/src/app/multi_window.rs index f3864cf5..d20739b1 100644 --- a/src/app/multi_window.rs +++ b/src/app/multi_window.rs @@ -13,17 +13,18 @@ use iced::{Element, Result, Settings, Subscription, Task}; use std::marker::PhantomData; -pub(crate) struct Instance { +pub(crate) struct Instance { update: Update, view: View, _state: PhantomData, _message: PhantomData, _theme: PhantomData, _renderer: PhantomData, + _executor: PhantomData, } /// Creates an iced [`MultiWindow`] given its title, update, and view logic. -pub fn multi_window( +pub fn multi_window( title: impl Title, update: impl application::Update, view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>, @@ -33,23 +34,25 @@ where Message: Send + std::fmt::Debug + 'static, Theme: Default + DefaultStyle, Renderer: program::Renderer, + Executor: iced::Executor, { use std::marker::PhantomData; - impl Program - for Instance + impl Program + for Instance where Message: Send + std::fmt::Debug + 'static, Theme: Default + DefaultStyle, Renderer: program::Renderer, Update: application::Update, View: for<'a> self::View<'a, State, Message, Theme, Renderer>, + Executor: iced::Executor, { type State = State; type Message = Message; type Theme = Theme; type Renderer = Renderer; - type Executor = iced_futures::backend::default::Executor; + type Executor = Executor; fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { self.update.update(state, message).into() @@ -72,6 +75,7 @@ where _message: PhantomData, _theme: PhantomData, _renderer: PhantomData, + _executor: PhantomData::, }, settings: Settings::default(), window: None, diff --git a/src/applet/mod.rs b/src/applet/mod.rs index b6e741fb..8a0eb893 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -188,11 +188,9 @@ impl Context { &self, icon: widget::icon::Handle, ) -> crate::widget::Button<'a, Message> { - let mut suggested = self.suggested_size(icon.symbolic); + let suggested = self.suggested_size(icon.symbolic); let applet_padding = self.suggested_padding(icon.symbolic); - let is_horizontal = self.is_horizontal(); - let symbolic = icon.symbolic; crate::widget::button::custom( @@ -414,7 +412,7 @@ pub fn run(flags: App::Flags) -> iced::Result { // TODO make multi-window not mandatory - let mut app = super::app::multi_window::multi_window( + let mut app = super::app::multi_window::multi_window::<_, _, _, _, App::Executor>( cosmic::Cosmic::title, cosmic::Cosmic::update, cosmic::Cosmic::view, From 842cd2ec9418e64911c60588fe70a1ce9c45a1b7 Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Wed, 30 Oct 2024 06:00:30 -0400 Subject: [PATCH 033/556] fix: specify width and height when importing raster images --- src/widget/icon/handle.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widget/icon/handle.rs b/src/widget/icon/handle.rs index fe325908..3a2db2c9 100644 --- a/src/widget/icon/handle.rs +++ b/src/widget/icon/handle.rs @@ -74,8 +74,8 @@ pub fn from_raster_pixels( Handle { symbolic: false, data: match pixels.into() { - Cow::Owned(pixels) => Data::Image(image::Handle::from_bytes(pixels)), - Cow::Borrowed(pixels) => Data::Image(image::Handle::from_bytes(pixels)), + Cow::Owned(pixels) => Data::Image(image::Handle::from_rgba(width, height, pixels)), + Cow::Borrowed(pixels) => Data::Image(image::Handle::from_rgba(width, height, pixels)), }, } } From 4b562867eccb65895953023a19393fa293aafb4b Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Wed, 30 Oct 2024 18:09:30 +0100 Subject: [PATCH 034/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index a92abc84..02b8d087 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit a92abc845cea46ae5133648e12283a0782481de4 +Subproject commit 02b8d08728bb12ea23e82babbd3f05602f7d578f From 0dfaa4d158d85fc9218d5be84cd7a63634940607 Mon Sep 17 00:00:00 2001 From: wiiznokes <78230769+wiiznokes@users.noreply.github.com> Date: Sat, 19 Oct 2024 01:06:10 +0200 Subject: [PATCH 035/556] Update section.rs --- src/widget/settings/section.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/widget/settings/section.rs b/src/widget/settings/section.rs index 3e6c66aa..d42c2318 100644 --- a/src/widget/settings/section.rs +++ b/src/widget/settings/section.rs @@ -37,6 +37,12 @@ pub struct Section<'a, Message> { } impl<'a, Message: 'static> Section<'a, Message> { + /// Define an optional title for the section. + pub fn title(mut self, title: impl Into>) -> Self { + self.title = title.into(); + self + } + /// Add a child element to the section's list column. #[allow(clippy::should_implement_trait)] pub fn add(mut self, item: impl Into>) -> Self { @@ -44,10 +50,21 @@ impl<'a, Message: 'static> Section<'a, Message> { self } - /// Define an optional title for the section. - pub fn title(mut self, title: impl Into>) -> Self { - self.title = title.into(); - self + /// Add a child element to the section's list column, if `Some`. + pub fn add_maybe(self, item: Option>>) -> Self { + if let Some(item) = item { + self.add(item) + } else { + self + } + } + + /// Extends the [`Section`] with the given children. + pub fn extend( + self, + children: impl IntoIterator>>, + ) -> Self { + children.into_iter().fold(self, Self::add) } } From 36b3cfa13aeac6e5cdf7e76ef826ed7d66aaebfe Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 1 Nov 2024 20:51:07 -0400 Subject: [PATCH 036/556] fix: color picker --- src/widget/color_picker/mod.rs | 368 +++++++++++++++++++++------------ 1 file changed, 233 insertions(+), 135 deletions(-) diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 04199590..124bfe61 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -4,6 +4,7 @@ //! Widgets for selecting colors with a color picker. use std::borrow::Cow; +use std::iter; use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{Duration, Instant}; @@ -23,7 +24,7 @@ use iced_core::{ Rectangle, Renderer, Shadow, Shell, Size, Vector, Widget, }; -use iced_widget::slider::{HandleShape, RailBackground}; +use iced_widget::slider::HandleShape; use iced_widget::{canvas, column, horizontal_space, row, scrollable, vertical_space, Row}; use lazy_static::lazy_static; use palette::{FromColor, RgbHue}; @@ -38,15 +39,14 @@ pub use ColorPickerModel as Model; // TODO is this going to look correct enough? lazy_static! { - pub static ref HSV_RAINBOW: Vec = (0u16..8) - .map(|h| ColorStop { - color: iced::Color::from(palette::Srgba::from_color(palette::Hsv::new_srgb_const( + pub static ref HSV_RAINBOW: Vec = (0u16..8) + .map( + |h| iced::Color::from(palette::Srgba::from_color(palette::Hsv::new_srgb_const( RgbHue::new(f32::from(h) * 360.0 / 7.0), 1.0, 1.0 - ))), - offset: f32::from(h) / 7.0 - }) + ))) + ) .collect(); } @@ -287,137 +287,235 @@ where ) -> ColorPicker<'a, Message> { let on_update = self.on_update; let spacing = THEME.lock().unwrap().cosmic().spacing; - let mut inner = column![ - // segmented buttons - segmented_control::horizontal(self.model) - .on_activate(Box::new(move |e| on_update( - ColorPickerUpdate::ActivateSegmented(e) - ))) - .minimum_button_width(0) - .width(self.width), - // canvas with gradient for the current color - // still needs the canvas and the handle to be drawn on it - container(vertical_space().height(self.height)) - .width(self.width) - .height(self.height), - slider( - 0.0..=359.99, - self.active_color.hue.into_positive_degrees(), - move |v| { - let mut new = self.active_color; - new.hue = v.into(); - on_update(ColorPickerUpdate::ActiveColor(new)) - } - ) - .class(Slider::Custom { - active: Rc::new(|t| { - let cosmic = t.cosmic(); - let mut a = - slider::Catalog::style(t, &Slider::default(), slider::Status::Active); - // a.rail.colors = RailBackground::Gradient { - // gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()), - // auto_angle: true, - // }; - a.rail.backgrounds = ( - Background::Color(Color::TRANSPARENT), - Background::Color(Color::TRANSPARENT), - ); - a.rail.width = 8.0; - a.handle.background = Color::TRANSPARENT.into(); - a.handle.shape = HandleShape::Circle { radius: 8.0 }; - a.handle.border_color = cosmic.palette.neutral_10.into(); - a.handle.border_width = 4.0; - a - }), - hovered: Rc::new(|t| { - let cosmic = t.cosmic(); - let mut a = - slider::Catalog::style(t, &Slider::default(), slider::Status::Active); - // a.rail.colors = RailBackground::Gradient { - // gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()), - // auto_angle: true, - // }; - a.rail.backgrounds = (Color::TRANSPARENT.into(), Color::TRANSPARENT.into()); - a.rail.width = 8.0; - a.handle.background = Color::TRANSPARENT.into(); - a.handle.shape = HandleShape::Circle { radius: 8.0 }; - a.handle.border_color = cosmic.palette.neutral_10.into(); - a.handle.border_width = 4.0; - a - }), - dragging: Rc::new(|t| { - let cosmic = t.cosmic(); - let mut a = - slider::Catalog::style(t, &Slider::default(), slider::Status::Active); - // a.rail.backgrounds = ( - // RailBackground::Gradient(Gradient { - // gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()), - // auto_angle: true, - // }), - // RailBackground::Gradient { - // gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()), - // auto_angle: true, - // }, - // ); - a.rail.backgrounds = - (iced::Color::TRANSPARENT.into(), Color::TRANSPARENT.into()); - a.rail.width = 8.0; - a.handle.background = Color::TRANSPARENT.into(); - a.handle.shape = HandleShape::Circle { radius: 8.0 }; - a.handle.border_color = cosmic.palette.neutral_10.into(); - a.handle.border_width = 4.0; - a - }), - }) - .width(self.width), - text_input("", self.input_color) - .on_input(move |s| on_update(ColorPickerUpdate::Input(s))) - .on_paste(move |s| on_update(ColorPickerUpdate::Input(s))) - .on_submit(on_update(ColorPickerUpdate::AppliedColor)) - .leading_icon( - color_button( - None, - Some(Color::from(palette::Srgb::from_color(self.active_color))), - Length::FillPortion(12) - ) - .into() - ) - // TODO copy paste input contents - .trailing_icon({ - let button = button::custom(crate::widget::icon( - from_name("edit-copy-symbolic").size(spacing.space_s).into(), - )) - .on_press(on_update(ColorPickerUpdate::Copied(Instant::now()))) - .class(Button::Text); - - match self.copied_at.take() { - Some(t) if Instant::now().duration_since(t) > Duration::from_secs(2) => { - button.into() - } - Some(_) => tooltip( - button, - text(copied_to_clipboard_label), - iced_widget::tooltip::Position::Bottom, - ) - .into(), - None => tooltip( - button, - text(copy_to_clipboard_label), - iced_widget::tooltip::Position::Bottom, - ) - .into(), + let mut inner = + column![ + // segmented buttons + segmented_control::horizontal(self.model) + .on_activate(Box::new(move |e| on_update( + ColorPickerUpdate::ActivateSegmented(e) + ))) + .minimum_button_width(0) + .width(self.width), + // canvas with gradient for the current color + // still needs the canvas and the handle to be drawn on it + container(vertical_space().height(self.height)) + .width(self.width) + .height(self.height), + slider( + 0.0..=359.99, + self.active_color.hue.into_positive_degrees(), + move |v| { + let mut new = self.active_color; + new.hue = v.into(); + on_update(ColorPickerUpdate::ActiveColor(new)) } + ) + .on_release(on_update(ColorPickerUpdate::ActionFinished)) + .class(Slider::Custom { + active: Rc::new(move |t| { + let cosmic = t.cosmic(); + let mut a = + slider::Catalog::style(t, &Slider::default(), slider::Status::Active); + + let hue = self.active_color.hue.into_positive_degrees(); + let pivot = hue * 7.0 / 360.; + + let low_end = pivot.floor() as usize; + let high_start = pivot.ceil() as usize; + let pivot_color = palette::Hsv::new_srgb(RgbHue::new(hue), 1.0, 1.0); + let low_range = HSV_RAINBOW[0..=low_end] + .iter() + .enumerate() + .map(|(i, color)| ColorStop { + color: *color, + offset: i as f32 / pivot.max(0.0001), + }) + .chain(iter::once(ColorStop { + color: iced::Color::from(palette::Srgba::from_color(pivot_color)), + offset: 1., + })) + .collect::>(); + let high_range = + iter::once(ColorStop { + color: iced::Color::from(palette::Srgba::from_color(pivot_color)), + offset: 0., + }) + .chain(HSV_RAINBOW[high_start..].iter().enumerate().map( + |(i, color)| ColorStop { + color: *color, + offset: (i as f32 + (1. - pivot.fract())) + / (7. - pivot).max(0.0001), + }, + )) + .collect::>(); + a.rail.backgrounds = ( + Background::Gradient(iced::Gradient::Linear( + Linear::new(Radians(90.0)).add_stops(low_range), + )), + Background::Gradient(iced::Gradient::Linear( + Linear::new(Radians(90.0)).add_stops(high_range), + )), + ); + + a.rail.width = 8.0; + a.handle.background = Color::TRANSPARENT.into(); + a.handle.shape = HandleShape::Circle { radius: 8.0 }; + a.handle.border_color = cosmic.palette.neutral_10.into(); + a.handle.border_width = 4.0; + a + }), + hovered: Rc::new(move |t| { + let cosmic = t.cosmic(); + let mut a = + slider::Catalog::style(t, &Slider::default(), slider::Status::Active); + let hue = self.active_color.hue.into_positive_degrees(); + let pivot = hue * 7.0 / 360.; + + let low_end = pivot.floor() as usize; + let high_start = pivot.ceil() as usize; + let pivot_color = palette::Hsv::new_srgb(RgbHue::new(hue), 1.0, 1.0); + let low_range = HSV_RAINBOW[0..=low_end] + .iter() + .enumerate() + .map(|(i, color)| ColorStop { + color: *color, + offset: i as f32 / pivot.max(0.0001), + }) + .chain(iter::once(ColorStop { + color: iced::Color::from(palette::Srgba::from_color(pivot_color)), + offset: 1., + })) + .collect::>(); + let high_range = + iter::once(ColorStop { + color: iced::Color::from(palette::Srgba::from_color(pivot_color)), + offset: 0., + }) + .chain(HSV_RAINBOW[high_start..].iter().enumerate().map( + |(i, color)| ColorStop { + color: *color, + offset: (i as f32 + (1. - pivot.fract())) + / (7. - pivot).max(0.0001), + }, + )) + .collect::>(); + a.rail.backgrounds = ( + Background::Gradient(iced::Gradient::Linear( + Linear::new(Radians(90.0)).add_stops(low_range), + )), + Background::Gradient(iced::Gradient::Linear( + Linear::new(Radians(90.0)).add_stops(high_range), + )), + ); + a.rail.width = 8.0; + a.handle.background = Color::TRANSPARENT.into(); + a.handle.shape = HandleShape::Circle { radius: 8.0 }; + a.handle.border_color = cosmic.palette.neutral_10.into(); + a.handle.border_width = 4.0; + a + }), + dragging: Rc::new(move |t| { + let cosmic = t.cosmic(); + let mut a = + slider::Catalog::style(t, &Slider::default(), slider::Status::Active); + let hue = self.active_color.hue.into_positive_degrees(); + let pivot = hue * 7.0 / 360.; + + let low_end = pivot.floor() as usize; + let high_start = pivot.ceil() as usize; + let pivot_color = palette::Hsv::new_srgb(RgbHue::new(hue), 1.0, 1.0); + let low_range = HSV_RAINBOW[0..=low_end] + .iter() + .enumerate() + .map(|(i, color)| ColorStop { + color: *color, + offset: i as f32 / pivot.max(0.0001), + }) + .chain(iter::once(ColorStop { + color: iced::Color::from(palette::Srgba::from_color(pivot_color)), + offset: 1., + })) + .collect::>(); + let high_range = + iter::once(ColorStop { + color: iced::Color::from(palette::Srgba::from_color(pivot_color)), + offset: 0., + }) + .chain(HSV_RAINBOW[high_start..].iter().enumerate().map( + |(i, color)| ColorStop { + color: *color, + offset: (i as f32 + (1. - pivot.fract())) + / (7. - pivot).max(0.0001), + }, + )) + .collect::>(); + a.rail.backgrounds = ( + Background::Gradient(iced::Gradient::Linear( + Linear::new(Radians(90.0)).add_stops(low_range), + )), + Background::Gradient(iced::Gradient::Linear( + Linear::new(Radians(90.0)).add_stops(high_range), + )), + ); + a.rail.width = 8.0; + a.handle.background = Color::TRANSPARENT.into(); + a.handle.shape = HandleShape::Circle { radius: 8.0 }; + a.handle.border_color = cosmic.palette.neutral_10.into(); + a.handle.border_width = 4.0; + a + }), }) .width(self.width), - ] - // Should we ensure the side padding is at least half the width of the handle? - .padding([ - spacing.space_none, - spacing.space_s, - spacing.space_s, - spacing.space_s, - ]) - .spacing(spacing.space_s); + text_input("", self.input_color) + .on_input(move |s| on_update(ColorPickerUpdate::Input(s))) + .on_paste(move |s| on_update(ColorPickerUpdate::Input(s))) + .on_submit(on_update(ColorPickerUpdate::AppliedColor)) + .leading_icon( + color_button( + None, + Some(Color::from(palette::Srgb::from_color(self.active_color))), + Length::FillPortion(12) + ) + .into() + ) + // TODO copy paste input contents + .trailing_icon({ + let button = button::custom(crate::widget::icon( + from_name("edit-copy-symbolic").size(spacing.space_s).into(), + )) + .on_press(on_update(ColorPickerUpdate::Copied(Instant::now()))) + .class(Button::Text); + + match self.copied_at.take() { + Some(t) + if Instant::now().duration_since(t) > Duration::from_secs(2) => + { + button.into() + } + Some(_) => tooltip( + button, + text(copied_to_clipboard_label), + iced_widget::tooltip::Position::Bottom, + ) + .into(), + None => tooltip( + button, + text(copy_to_clipboard_label), + iced_widget::tooltip::Position::Bottom, + ) + .into(), + } + }) + .width(self.width), + ] + // Should we ensure the side padding is at least half the width of the handle? + .padding([ + spacing.space_none, + spacing.space_s, + spacing.space_s, + spacing.space_s, + ]) + .spacing(spacing.space_s); if !self.recent_colors.is_empty() { inner = inner.push(horizontal::light().width(self.width)); From 127ce17b8539b410591f10605907512ca77b5593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Sun, 3 Nov 2024 19:16:37 +0100 Subject: [PATCH 037/556] improv: add window border --- examples/application/src/main.rs | 6 ++-- examples/calendar/src/main.rs | 4 +-- examples/context-menu/src/main.rs | 4 +-- examples/cosmic/src/window.rs | 9 ++--- examples/image-button/src/main.rs | 4 +-- examples/nav-context/src/main.rs | 4 +-- examples/open-dialog/src/main.rs | 4 +-- examples/text-input/src/main.rs | 4 +-- iced | 2 +- src/app/mod.rs | 56 ++++++++++++++++++++--------- src/applet/mod.rs | 5 +-- src/widget/aspect_ratio.rs | 16 ++++++--- src/widget/calendar.rs | 19 ++++------ src/widget/color_picker/mod.rs | 6 ++-- src/widget/context_drawer/widget.rs | 12 +++---- src/widget/header_bar.rs | 7 ++-- src/widget/layer_container.rs | 16 ++++++--- src/widget/list/column.rs | 4 +-- src/widget/menu/menu_tree.rs | 4 +-- src/widget/nav_bar.rs | 3 +- src/widget/rectangle_tracker/mod.rs | 14 +++++--- src/widget/scrollable.rs | 2 -- src/widget/settings/item.rs | 4 +-- src/widget/settings/mod.rs | 8 ++--- src/widget/spin_button/mod.rs | 23 ++++-------- src/widget/warning.rs | 4 +-- 26 files changed, 130 insertions(+), 114 deletions(-) diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index 29e2bbf7..bf2b5a50 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -169,12 +169,12 @@ impl cosmic::Application for App { ] .width(iced::Length::Fill) .height(iced::Length::Shrink) - .align_x(iced::alignment::Horizontal::Center), + .align_x(iced::Alignment::Center), ) .width(iced::Length::Fill) .height(iced::Length::Shrink) - .align_x(iced::alignment::Horizontal::Center) - .align_y(iced::alignment::Vertical::Center); + .align_x(iced::Alignment::Center) + .align_y(iced::Alignment::Center); Element::from(centered) } diff --git a/examples/calendar/src/main.rs b/examples/calendar/src/main.rs index df36b730..5306d927 100644 --- a/examples/calendar/src/main.rs +++ b/examples/calendar/src/main.rs @@ -88,8 +88,8 @@ impl cosmic::Application for App { let centered = cosmic::widget::container(content) .width(iced::Length::Fill) .height(iced::Length::Shrink) - .align_x(iced::alignment::Horizontal::Center) - .align_y(iced::alignment::Vertical::Center); + .align_x(iced::Alignment::Center) + .align_y(iced::Alignment::Center); Element::from(centered) } diff --git a/examples/context-menu/src/main.rs b/examples/context-menu/src/main.rs index 43866987..9f809dbe 100644 --- a/examples/context-menu/src/main.rs +++ b/examples/context-menu/src/main.rs @@ -96,8 +96,8 @@ impl cosmic::Application for App { let centered = cosmic::widget::container(widget) .width(iced::Length::Fill) .height(iced::Length::Fill) - .align_x(iced::alignment::Horizontal::Center) - .align_y(iced::alignment::Vertical::Center); + .align_x(iced::Alignment::Center) + .align_y(iced::Alignment::Center); Element::from(centered) } diff --git a/examples/cosmic/src/window.rs b/examples/cosmic/src/window.rs index a554772c..da5356b6 100644 --- a/examples/cosmic/src/window.rs +++ b/examples/cosmic/src/window.rs @@ -564,12 +564,9 @@ impl Application for Window { }; widgets.push( - scrollable( - container(content.debug(self.debug)) - .align_x(iced::alignment::Horizontal::Center), - ) - .width(Length::Fill) - .into(), + scrollable(container(content.debug(self.debug)).align_x(iced::Alignment::Center)) + .width(Length::Fill) + .into(), ); } diff --git a/examples/image-button/src/main.rs b/examples/image-button/src/main.rs index 2b502689..406f0d19 100644 --- a/examples/image-button/src/main.rs +++ b/examples/image-button/src/main.rs @@ -95,8 +95,8 @@ impl cosmic::Application for App { let centered = cosmic::widget::container(content) .width(iced::Length::Fill) .height(iced::Length::Shrink) - .align_x(iced::alignment::Horizontal::Center) - .align_y(iced::alignment::Vertical::Center); + .align_x(iced::Alignment::Center) + .align_y(iced::Alignment::Center); Element::from(centered) } diff --git a/examples/nav-context/src/main.rs b/examples/nav-context/src/main.rs index 2c7b4950..07863542 100644 --- a/examples/nav-context/src/main.rs +++ b/examples/nav-context/src/main.rs @@ -183,8 +183,8 @@ impl cosmic::Application for App { let centered = cosmic::widget::container(text) .width(iced::Length::Fill) .height(iced::Length::Shrink) - .align_x(iced::alignment::Horizontal::Center) - .align_y(iced::alignment::Vertical::Center); + .align_x(iced::Alignment::Center) + .align_y(iced::Alignment::Center); Element::from(centered) } diff --git a/examples/open-dialog/src/main.rs b/examples/open-dialog/src/main.rs index 66c16441..a1958880 100644 --- a/examples/open-dialog/src/main.rs +++ b/examples/open-dialog/src/main.rs @@ -233,7 +233,7 @@ fn center<'a>(input: impl Into> + 'a) -> Element<'a, Messag iced::widget::container(input.into()) .width(iced::Length::Fill) .height(iced::Length::Fill) - .align_x(iced::alignment::Horizontal::Center) - .align_y(iced::alignment::Vertical::Center) + .align_x(iced::Alignment::Center) + .align_y(iced::Alignment::Center) .into() } diff --git a/examples/text-input/src/main.rs b/examples/text-input/src/main.rs index 252992b2..de6bbd9d 100644 --- a/examples/text-input/src/main.rs +++ b/examples/text-input/src/main.rs @@ -104,8 +104,8 @@ impl cosmic::Application for App { let centered = cosmic::widget::container(column.width(200)) .width(iced::Length::Fill) .height(iced::Length::Shrink) - .align_x(iced::alignment::Horizontal::Center) - .align_y(iced::alignment::Vertical::Center); + .align_x(iced::Alignment::Center) + .align_y(iced::Alignment::Center); Element::from(centered) } diff --git a/iced b/iced index 02b8d087..a7e035cd 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 02b8d08728bb12ea23e82babbd3f05602f7d578f +Subproject commit a7e035cd852ab13ce0ef04671c1e683fc343aff6 diff --git a/src/app/mod.rs b/src/app/mod.rs index e413a22c..8554c8b3 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -54,7 +54,9 @@ pub use self::core::Core; pub use self::settings::Settings; use crate::prelude::*; use crate::theme::THEME; -use crate::widget::{context_drawer, horizontal_space, id_container, menu, nav_bar, popover}; +use crate::widget::{ + container, context_drawer, horizontal_space, id_container, menu, nav_bar, popover, +}; use apply::Apply; use iced::window; use iced::{Length, Subscription}; @@ -694,6 +696,9 @@ impl ApplicationExt for App { fn view_main(&self) -> Element> { let core = self.core(); let is_condensed = core.is_condensed(); + // TODO: More granularity might be needed for different resize border + // and window border handling of maximized and tiled windows + let sharp_corners = core.window.sharp_corners; let focused = core .focused_window() .is_some_and(|i| Some(i) == self.core().main_window_id()); @@ -706,18 +711,13 @@ impl ApplicationExt for App { .nav_bar() .map(|nav| id_container(nav, iced_core::id::Id::new("COSMIC_nav_bar"))) { - widgets.push(nav.into()); + widgets.push(container(nav).padding([0, 0, 8, 8]).into()); true } else { false }; if self.nav_model().is_none() || core.show_content() { - // Manual spacing must be used due to state workarounds below - if has_nav { - widgets.push(horizontal_space().width(Length::Fixed(8.0)).into()); - } - let main_content = self.view().map(Message::App); //TODO: reduce duplication @@ -737,14 +737,21 @@ impl ApplicationExt for App { drawer, iced_core::id::Id::new("COSMIC_context_drawer"), )) - }), + }) + .apply(container) + .padding([0, 8, 8, 0]) + .into(), ); } else { - widgets.push(main_content); + //TODO: container and padding are temporary, until + //the `resize_border` is moved to not cover window content + widgets.push(container(main_content).padding([0, 8, 8, 8]).into()); } } else { //TODO: hide content when out of space - widgets.push(main_content); + //TODO: container and padding are temporary, until + //the `resize_border` is moved to not cover window content + widgets.push(container(main_content).padding([0, 8, 8, 8]).into()); if let Some(context) = self.context_drawer() { widgets.push( crate::widget::ContextDrawer::new_inner( @@ -753,15 +760,18 @@ impl ApplicationExt for App { Message::Cosmic(cosmic::Message::ContextDrawer(false)), context_width, ) - .apply(crate::widget::container) + .apply(container) .width(context_width) .apply(|drawer| { Element::from(id_container( drawer, iced_core::id::Id::new("COSMIC_context_drawer"), )) - }), - ); + }) + .apply(container) + .padding([0, 8, 8, 0]) + .into(), + ) } else { //TODO: this element is added to workaround state issues widgets.push(horizontal_space().width(Length::Shrink).into()); @@ -774,11 +784,13 @@ impl ApplicationExt for App { let content_col = crate::widget::column::with_capacity(2) .spacing(8) .push(content_row) - .push_maybe(self.footer().map(|footer| footer.map(Message::App))); + .push_maybe( + self.footer() + .map(|footer| container(footer.map(Message::App)).padding([0, 8, 8, 8])), + ); let content: Element<_> = if core.window.content_container { content_col - .apply(crate::widget::container) - .padding([0, 8, 8, 8]) + .apply(container) .width(iced::Length::Fill) .height(iced::Length::Fill) .class(crate::theme::Container::WindowBackground) @@ -842,7 +854,17 @@ impl ApplicationExt for App { None }) // The content element contains every element beneath the header. - .push(content); + .push(content) + .apply(container) + .padding(if sharp_corners { 0 } else { 1 }) + .style(move |theme| container::Style { + border: iced::Border { + color: theme.cosmic().bg_divider().into(), + width: if sharp_corners { 0.0 } else { 1.0 }, + radius: theme.cosmic().radius_s().map(|x| x + 1.0).into(), + }, + ..Default::default() + }); // Show any current dialog on top and centered over the view content // We have to use a popover even without a dialog to keep the tree from changing diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 8a0eb893..4980c91d 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -206,10 +206,7 @@ impl Context { .width(Length::Fixed(suggested.0 as f32)) .height(Length::Fixed(suggested.1 as f32)), ) - .align_x(Horizontal::Center) - .align_y(Vertical::Center) - .width(Length::Fill) - .height(Length::Fill), + .center(Length::Fill), ) .width(Length::Fixed((suggested.0 + 2 * applet_padding) as f32)) .height(Length::Fixed((suggested.1 + 2 * applet_padding) as f32)) diff --git a/src/widget/aspect_ratio.rs b/src/widget/aspect_ratio.rs index bfc9a78d..2bfa702c 100644 --- a/src/widget/aspect_ratio.rs +++ b/src/widget/aspect_ratio.rs @@ -2,14 +2,15 @@ use iced::widget::Container; use iced::Size; -use iced_core::alignment; use iced_core::event::{self, Event}; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; use iced_core::renderer; use iced_core::widget::Tree; -use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget}; +use iced_core::{ + Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, +}; use iced_widget::container; pub use iced_widget::container::{Catalog, Style}; @@ -104,14 +105,14 @@ where /// Sets the content alignment for the horizontal axis of the [`Container`]. #[must_use] - pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self { + pub fn align_x(mut self, alignment: Alignment) -> Self { self.container = self.container.align_x(alignment); self } /// Sets the content alignment for the vertical axis of the [`Container`]. #[must_use] - pub fn align_y(mut self, alignment: alignment::Vertical) -> Self { + pub fn align_y(mut self, alignment: Alignment) -> Self { self.container = self.container.align_y(alignment); self } @@ -130,6 +131,13 @@ where self } + /// Centers the contents in the horizontal and vertical axis of the [`Container`]. + #[must_use] + pub fn center(mut self, length: Length) -> Self { + self.container = self.container.center(length); + self + } + /// Sets the style of the [`Container`]. #[must_use] pub fn class(mut self, style: impl Into>) -> Self { diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index bcfcda65..2fbf9f4d 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -5,10 +5,9 @@ use std::cmp; -use crate::iced_core::{Length, Padding}; +use crate::iced_core::{Alignment, Length, Padding}; use crate::widget::{button, column, grid, icon, row, text, Grid}; use chrono::{Datelike, Days, Months, NaiveDate, Weekday}; -use iced::alignment::{Horizontal, Vertical}; /// A widget that displays an interactive calendar. pub fn calendar( @@ -62,7 +61,7 @@ where { fn from(this: Calendar<'a, Message>) -> Self { let date = text(this.selected.format("%B %-d, %Y").to_string()).size(18); - let day_of_week = text(this.selected.format("%A").to_string()).size(14); + let day_of_week = text::body(this.selected.format("%A").to_string()); let month_controls = row::with_capacity(2) .push( @@ -86,7 +85,7 @@ where text(first_day_of_week.to_string()) .size(12) .width(Length::Fixed(36.0)) - .align_x(Horizontal::Center), + .align_x(Alignment::Center), ); first_day_of_week = first_day_of_week.succ(); @@ -143,14 +142,10 @@ fn date_button( button::ButtonClass::Text }; - let button = button::custom( - text(format!("{}", date.day())) - .align_x(Horizontal::Center) - .align_y(Vertical::Center), - ) - .class(style) - .height(Length::Fixed(36.0)) - .width(Length::Fixed(36.0)); + let button = button::custom(text(format!("{}", date.day())).center()) + .class(style) + .height(Length::Fixed(36.0)) + .width(Length::Fixed(36.0)); if is_month { button.on_press((on_select)(set_day(date, date.day()))) diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 124bfe61..2930aadb 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -558,7 +558,7 @@ where button::custom( text(reset_to_default) .width(self.width) - .align_x(iced_core::alignment::Horizontal::Center) + .align_x(iced_core::Alignment::Center) ) .width(self.width) .on_press(on_update(ColorPickerUpdate::Reset)) @@ -574,14 +574,14 @@ where button::custom( text(cancel) .width(self.width) - .align_x(iced_core::alignment::Horizontal::Center) + .align_x(iced_core::Alignment::Center) ) .width(self.width) .on_press(on_update(ColorPickerUpdate::Cancel)), button::custom( text(save) .width(self.width) - .align_x(iced_core::alignment::Horizontal::Center) + .align_x(iced_core::Alignment::Center) ) .width(self.width) .on_press(on_update(ColorPickerUpdate::AppliedColor)) diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index 8e548f49..1fe6c250 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -8,9 +8,9 @@ use crate::{Apply, Element, Renderer, Theme}; use super::overlay::Overlay; -use iced_core::alignment; use iced_core::event::{self, Event}; use iced_core::widget::{Operation, Tree}; +use iced_core::Alignment; use iced_core::{ layout, mouse, overlay as iced_overlay, renderer, Clipboard, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, @@ -46,18 +46,16 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { text::heading(header) .width(Length::FillPortion(1)) .height(Length::Fill) - .align_x(alignment::Horizontal::Center) - .align_y(alignment::Vertical::Center), + .align_x(Alignment::Center) + .align_y(Alignment::Center), ) .push( button::text("Close") .trailing_icon(icon::from_name("go-next-symbolic")) .on_press(on_close) - .class(crate::theme::Button::Link) .apply(container) .width(Length::FillPortion(1)) - .height(Length::Fill) - .align_x(alignment::Horizontal::Right) + .align_x(Alignment::End) .center_y(Length::Fill), ) // XXX must be done after pushing elements or it may be overwritten by size hints from contents @@ -89,7 +87,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { ) .width(Length::Fill) .height(Length::Fill) - .align_x(alignment::Horizontal::Right) + .align_x(Alignment::End) .into() } diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index d487f6bd..a8dcf949 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -292,7 +292,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { widget::row::with_children(start) .align_y(iced::Alignment::Center) .apply(widget::container) - .align_x(iced::alignment::Horizontal::Left) + .align_x(iced::Alignment::Start) .width(Length::Shrink), ) // If elements exist in the center region, use them here. @@ -301,8 +301,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { widget::row::with_children(center) .align_y(iced::Alignment::Center) .apply(widget::container) - .align_x(iced::alignment::Horizontal::Center) - .width(Length::Fill) + .center_x(Length::Fill) .into() } else if self.title.is_empty() { widget::horizontal_space().width(Length::Fill).into() @@ -313,7 +312,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { widget::row::with_children(end) .align_y(iced::Alignment::Center) .apply(widget::container) - .align_x(iced::alignment::Horizontal::Right) + .align_x(iced::Alignment::End) .width(Length::Shrink), ) .align_y(iced::Alignment::Center) diff --git a/src/widget/layer_container.rs b/src/widget/layer_container.rs index 3f3e4959..3109e666 100644 --- a/src/widget/layer_container.rs +++ b/src/widget/layer_container.rs @@ -1,14 +1,15 @@ use crate::Theme; use cosmic_theme::LayeredTheme; use iced::widget::Container; -use iced_core::alignment; use iced_core::event::{self, Event}; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; use iced_core::renderer; use iced_core::widget::Tree; -use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget}; +use iced_core::{ + Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, +}; pub use iced_widget::container::{Catalog, Style}; pub fn layer_container<'a, Message: 'static, E>( @@ -96,14 +97,14 @@ where /// Sets the content alignment for the horizontal axis of the [`LayerContainer`]. #[must_use] - pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self { + pub fn align_x(mut self, alignment: Alignment) -> Self { self.container = self.container.align_x(alignment); self } /// Sets the content alignment for the vertical axis of the [`LayerContainer`]. #[must_use] - pub fn align_y(mut self, alignment: alignment::Vertical) -> Self { + pub fn align_y(mut self, alignment: Alignment) -> Self { self.container = self.container.align_y(alignment); self } @@ -122,6 +123,13 @@ where self } + /// Centers the contents in the horizontal and vertical axis of the [`Container`]. + #[must_use] + pub fn center(mut self, length: Length) -> Self { + self.container = self.container.center(length); + self + } + /// Sets the style of the [`LayerContainer`]. #[must_use] pub fn class(mut self, style: impl Into>) -> Self { diff --git a/src/widget/list/column.rs b/src/widget/list/column.rs index 18c32f47..cc0b517a 100644 --- a/src/widget/list/column.rs +++ b/src/widget/list/column.rs @@ -42,10 +42,10 @@ impl<'a, Message: 'static> ListColumn<'a, Message> { // Ensure a minimum height of 32. let container = iced::widget::row![ - crate::widget::container(item).align_y(iced::alignment::Vertical::Center), + crate::widget::container(item).align_y(iced::Alignment::Center), crate::widget::vertical_space().height(iced::Length::Fixed(32.)) ] - .align_y(iced::alignment::Vertical::Center); + .align_y(iced::Alignment::Center); self.children.push(container.into()); self diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index 736ba2f4..0c52e58c 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -281,9 +281,7 @@ where widget::Space::with_width(Length::Fixed(16.0)).into() }, widget::Space::with_width(Length::Fixed(8.0)).into(), - widget::text(label) - .align_x(iced::alignment::Horizontal::Left) - .into(), + widget::text(label).align_x(iced::Alignment::Start).into(), widget::horizontal_space().width(Length::Fill).into(), widget::text(key).into(), ]) diff --git a/src/widget/nav_bar.rs b/src/widget/nav_bar.rs index cc62d144..5a32b4a3 100644 --- a/src/widget/nav_bar.rs +++ b/src/widget/nav_bar.rs @@ -146,11 +146,12 @@ impl<'a, Message: Clone + 'static> From> .button_spacing(space_xxs) .spacing(space_xxs) .style(crate::theme::SegmentedButton::TabBar) + .apply(container) + .padding(space_xxs) .apply(scrollable) .class(crate::style::iced::Scrollable::Minimal) .height(Length::Fill) .apply(container) - .padding(space_xxs) .height(Length::Fill) .class(theme::Container::custom(nav_bar_style)) } diff --git a/src/widget/rectangle_tracker/mod.rs b/src/widget/rectangle_tracker/mod.rs index d502badb..30eed770 100644 --- a/src/widget/rectangle_tracker/mod.rs +++ b/src/widget/rectangle_tracker/mod.rs @@ -5,14 +5,13 @@ use iced::widget::Container; use iced::Vector; pub use subscription::*; -use iced_core::alignment; use iced_core::event::{self, Event}; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; use iced_core::renderer; use iced_core::widget::Tree; -use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget}; +use iced_core::{Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget}; use std::{fmt::Debug, hash::Hash}; pub use iced_widget::container::{Catalog, Style}; @@ -133,14 +132,14 @@ where /// Sets the content alignment for the horizontal axis of the [`Container`]. #[must_use] - pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self { + pub fn align_x(mut self, alignment: Alignment) -> Self { self.container = self.container.align_x(alignment); self } /// Sets the content alignment for the vertical axis of the [`Container`]. #[must_use] - pub fn align_y(mut self, alignment: alignment::Vertical) -> Self { + pub fn align_y(mut self, alignment: Alignment) -> Self { self.container = self.container.align_y(alignment); self } @@ -159,6 +158,13 @@ where self } + /// Centers the contents in the horizontal and vertical axis of the [`Container`]. + #[must_use] + pub fn center(mut self, length: Length) -> Self { + self.container = self.container.center(length); + self + } + /// Sets the style of the [`Container`]. #[must_use] pub fn style(mut self, style: impl Into<::Class<'a>>) -> Self { diff --git a/src/widget/scrollable.rs b/src/widget/scrollable.rs index ca6b445e..71f63aad 100644 --- a/src/widget/scrollable.rs +++ b/src/widget/scrollable.rs @@ -8,6 +8,4 @@ pub fn scrollable<'a, Message>( element: impl Into>, ) -> widget::Scrollable<'a, Message, crate::Theme, Renderer> { widget::scrollable(element) - // .scrollbar_width(8) TODO add these back - // .scroller_width(8) } diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index 591aa8ac..8c978f7b 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -114,8 +114,8 @@ impl<'a, Message: 'static> Item<'a, Message> { if let Some(description) = self.description { let column = column::with_capacity(2) .spacing(2) - .push(text(self.title).wrapping(Wrapping::Word)) - .push(text(description).wrapping(Wrapping::Word).size(10)) + .push(text::body(self.title).wrapping(Wrapping::Word)) + .push(text::caption(description).wrapping(Wrapping::Word)) .width(Length::Fill); contents.push(column.into()); diff --git a/src/widget/settings/mod.rs b/src/widget/settings/mod.rs index f4dfbeab..9e2eccf9 100644 --- a/src/widget/settings/mod.rs +++ b/src/widget/settings/mod.rs @@ -5,7 +5,7 @@ pub mod item; pub mod section; pub use self::item::{flex_item, flex_item_row, item, item_row}; -pub use self::section::{section, view_section, Section}; +pub use self::section::{section, Section}; use crate::widget::{column, Column}; use crate::{theme, Element}; @@ -13,8 +13,8 @@ use crate::{theme, Element}; /// A column with a predefined style for creating a settings panel #[must_use] pub fn view_column(children: Vec>) -> Column { - let spacing = theme::THEME.lock().unwrap().cosmic().spacing; + let space_m = theme::THEME.lock().unwrap().cosmic().spacing.space_m; column::with_children(children) - .spacing(spacing.space_m) - .padding([0, spacing.space_m]) + .spacing(space_m) + .padding([0, space_m]) } diff --git a/src/widget/spin_button/mod.rs b/src/widget/spin_button/mod.rs index 7ed3e535..9da29394 100644 --- a/src/widget/spin_button/mod.rs +++ b/src/widget/spin_button/mod.rs @@ -11,10 +11,7 @@ pub use self::model::{Message, Model}; use crate::widget::{button, container, icon, row, text}; use crate::{theme, Element}; use apply::Apply; -use iced::{ - alignment::{Horizontal, Vertical}, - Alignment, Length, -}; +use iced::{Alignment, Length}; use iced_core::{Border, Shadow}; pub struct SpinButton<'a, Message> { @@ -49,10 +46,7 @@ impl<'a, Message: 'static> SpinButton<'a, Message> { icon::from_name("list-remove-symbolic") .size(16) .apply(container) - .width(Length::Fixed(32.0)) - .height(Length::Fixed(32.0)) - .align_x(Horizontal::Center) - .align_y(Vertical::Center) + .center(Length::Fixed(32.0)) .apply(button::custom) .width(Length::Fixed(32.0)) .height(Length::Fixed(32.0)) @@ -61,17 +55,13 @@ impl<'a, Message: 'static> SpinButton<'a, Message> { .into(), text::title4(label) .apply(container) - .width(Length::Fixed(48.0)) - .align_x(Horizontal::Center) - .align_y(Vertical::Center) + .center_x(Length::Fixed(48.0)) + .align_y(Alignment::Center) .into(), icon::from_name("list-add-symbolic") .size(16) .apply(container) - .width(Length::Fixed(32.0)) - .height(Length::Fixed(32.0)) - .align_x(Horizontal::Center) - .align_y(Vertical::Center) + .center(Length::Fixed(32.0)) .apply(button::custom) .width(Length::Fixed(32.0)) .height(Length::Fixed(32.0)) @@ -83,9 +73,8 @@ impl<'a, Message: 'static> SpinButton<'a, Message> { .height(Length::Fixed(32.0)) .align_y(Alignment::Center), ) - .align_y(Vertical::Center) .width(Length::Shrink) - .height(Length::Fixed(32.0)) + .center_y(Length::Fixed(32.0)) .class(theme::Container::custom(container_style)) .apply(Element::from) .map(on_change) diff --git a/src/widget/warning.rs b/src/widget/warning.rs index 77d737a5..f05a08f9 100644 --- a/src/widget/warning.rs +++ b/src/widget/warning.rs @@ -4,7 +4,7 @@ use super::icon; use crate::{theme, widget, Element, Renderer, Theme}; use apply::Apply; -use iced::{alignment, Alignment, Background, Color, Length}; +use iced::{Alignment, Background, Color, Length}; use iced_core::{Border, Shadow}; use std::borrow::Cow; @@ -45,7 +45,7 @@ impl<'a, Message: 'static + Clone> Warning<'a, Message> { .apply(widget::container) .class(theme::Container::custom(warning_container)) .padding(10) - .align_y(alignment::Vertical::Center) + .align_y(Alignment::Center) .width(Length::Fill) } } From d34fd5dc2b8d403d348b772b54e70a470bca37af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Tue, 5 Nov 2024 14:16:45 +0100 Subject: [PATCH 038/556] fix: apply content padding only when content_container is true --- src/app/mod.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 8554c8b3..bfc65c19 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -702,6 +702,11 @@ impl ApplicationExt for App { let focused = core .focused_window() .is_some_and(|i| Some(i) == self.core().main_window_id()); + let main_content_padding = if core.window.content_container { + [0, 8, 8, 8] + } else { + [0, 0, 0, 0] + }; let content_row = crate::widget::row::with_children({ let mut widgets = Vec::with_capacity(4); @@ -745,13 +750,13 @@ impl ApplicationExt for App { } else { //TODO: container and padding are temporary, until //the `resize_border` is moved to not cover window content - widgets.push(container(main_content).padding([0, 8, 8, 8]).into()); + widgets.push(container(main_content).padding(main_content_padding).into()); } } else { //TODO: hide content when out of space //TODO: container and padding are temporary, until //the `resize_border` is moved to not cover window content - widgets.push(container(main_content).padding([0, 8, 8, 8]).into()); + widgets.push(container(main_content).padding(main_content_padding).into()); if let Some(context) = self.context_drawer() { widgets.push( crate::widget::ContextDrawer::new_inner( From bd28ae658161852e51c7746218cd9dd619698998 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 5 Nov 2024 10:32:27 -0500 Subject: [PATCH 039/556] fix: update iced should improve iced diffing and avoid bad interactions with custom id elements having more than 1 child element --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index a7e035cd..57288e5a 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit a7e035cd852ab13ce0ef04671c1e683fc343aff6 +Subproject commit 57288e5a3e1d238572f15153d7c0a7fe5f1e43da From 2ce3e95cf6b3e28f01ac94a79e9f3b7df12aa2fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Tue, 5 Nov 2024 23:08:42 +0100 Subject: [PATCH 040/556] fix(app): border radius Increases the border radius by 1.0 to prevent some corner artifacts. --- src/app/mod.rs | 3 ++- src/theme/style/menu_bar.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index bfc65c19..8771a252 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -866,7 +866,8 @@ impl ApplicationExt for App { border: iced::Border { color: theme.cosmic().bg_divider().into(), width: if sharp_corners { 0.0 } else { 1.0 }, - radius: theme.cosmic().radius_s().map(|x| x + 1.0).into(), + // x + 2.0 is used to prevent corner artifacts + radius: theme.cosmic().radius_s().map(|x| x + 2.0).into(), }, ..Default::default() }); diff --git a/src/theme/style/menu_bar.rs b/src/theme/style/menu_bar.rs index 18b983fd..5acb0d09 100644 --- a/src/theme/style/menu_bar.rs +++ b/src/theme/style/menu_bar.rs @@ -69,7 +69,7 @@ impl StyleSheet for Theme { background: component.base.into(), border_width: 1.0, bar_border_radius: cosmic.corner_radii.radius_xl, - menu_border_radius: cosmic.corner_radii.radius_s, + menu_border_radius: cosmic.corner_radii.radius_s.map(|x| x + 2.0), border_color: component.divider.into(), background_expand: [1; 4], path: component.hover.into(), From 9825e730bc1f8535a763ce054f72fba587e4d12b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Tue, 5 Nov 2024 23:29:44 +0100 Subject: [PATCH 041/556] update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 57288e5a..65c7a3d9 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 57288e5a3e1d238572f15153d7c0a7fe5f1e43da +Subproject commit 65c7a3d9cbb096191cec0c40c39310a54570fdab From a4c1909fc23719e22596a9fc0fa4275e570769c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Wed, 6 Nov 2024 01:27:45 +0100 Subject: [PATCH 042/556] fix(content_col): remove spacing Removes spacing between the footer and content, until the `resize_border` is moved. --- src/app/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 8771a252..fc90c75b 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -787,7 +787,8 @@ impl ApplicationExt for App { widgets }); let content_col = crate::widget::column::with_capacity(2) - .spacing(8) + //TODO: Add back when the `resize_border` is moved to not cover window content + //.spacing(8) .push(content_row) .push_maybe( self.footer() From 8d4afb90da26d1a4526c4146c43856f7bff008b3 Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Wed, 6 Nov 2024 02:36:33 +0000 Subject: [PATCH 043/556] feat(app): add context view method for creating About views --- Cargo.toml | 2 + examples/about/Cargo.toml | 26 ++++++ examples/about/src/main.rs | 165 +++++++++++++++++++++++++++++++++++++ src/app/about.rs | 119 ++++++++++++++++++++++++++ src/app/cosmic.rs | 8 ++ src/app/mod.rs | 93 +++++++++++++++++++++ 6 files changed, 413 insertions(+) create mode 100644 examples/about/Cargo.toml create mode 100644 examples/about/src/main.rs create mode 100644 src/app/about.rs diff --git a/Cargo.toml b/Cargo.toml index 3f225059..3567c2a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ desktop = [ "process", "dep:freedesktop-desktop-entry", "dep:mime", + "dep:open", "dep:shlex", "tokio?/io-util", "tokio?/net", @@ -97,6 +98,7 @@ image = { version = "0.25.1", optional = true } lazy_static = "1.4.0" libc = { version = "0.2.155", optional = true } mime = { version = "0.3.17", optional = true } +open = { version = "5.3.0", optional = true } palette = "0.7.3" rfd = { version = "0.14.0", optional = true } rustix = { version = "0.38.34", features = [ diff --git a/examples/about/Cargo.toml b/examples/about/Cargo.toml new file mode 100644 index 00000000..4dd3aed9 --- /dev/null +++ b/examples/about/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "about" +version = "0.1.0" +edition = "2021" + +[dependencies] +tracing = "0.1.37" +tracing-subscriber = "0.3.17" +tracing-log = "0.2.0" + +[dependencies.libcosmic] +path = "../../" +default-features = false +features = [ + "debug", + "winit", + "tokio", + "xdg-portal", + "dbus-config", + "desktop", + "a11y", + "wayland", + "wgpu", + "single-instance", + "multi-window", +] diff --git a/examples/about/src/main.rs b/examples/about/src/main.rs new file mode 100644 index 00000000..ed061f61 --- /dev/null +++ b/examples/about/src/main.rs @@ -0,0 +1,165 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +//! Application API example + +use cosmic::app::{about::About, Core, Settings, Task}; +use cosmic::iced::widget::column; +use cosmic::iced_core::Size; +use cosmic::widget::{self, nav_bar}; +use cosmic::{executor, iced, ApplicationExt, Element}; + +/// Runs application with these settings +#[rustfmt::skip] +fn main() -> Result<(), Box> { + tracing_subscriber::fmt::init(); + let _ = tracing_log::LogTracer::init(); + + let settings = Settings::default() + .size(Size::new(1024., 768.)); + + cosmic::app::run::(settings, ())?; + + Ok(()) +} + +/// Messages that are used specifically by our [`App`]. +#[derive(Clone, Debug)] +pub enum Message { + ToggleAbout, + Cosmic(cosmic::app::cosmic::Message), +} + +/// The [`App`] stores application-specific state. +pub struct App { + core: Core, + nav_model: nav_bar::Model, + about: About, +} + +/// Implement [`cosmic::Application`] to integrate with COSMIC. +impl cosmic::Application for App { + /// Default async executor to use with the app. + type Executor = executor::Default; + + /// Argument received [`cosmic::Application::new`]. + type Flags = (); + + /// Message type specific to our [`App`]. + type Message = Message; + + /// The unique application ID to supply to the window manager. + const APP_ID: &'static str = "org.cosmic.AboutDemo"; + + fn core(&self) -> &Core { + &self.core + } + + fn core_mut(&mut self) -> &mut Core { + &mut self.core + } + + /// Creates the application, and optionally emits command on initialize. + fn init(core: Core, _flags: Self::Flags) -> (Self, Task) { + let nav_model = nav_bar::Model::default(); + + let about = About::default() + .set_application_name("About Demo") + .set_application_icon(Self::APP_ID) + .set_developer_name("System 76") + .set_license_type("GPL-3.0") + .set_website("https://system76.com/cosmic") + .set_repository_url("https://github.com/pop-os/libcosmic") + .set_support_url("https://github.com/pop-os/libcosmic/issues") + .set_developers([("Michael Murphy".into(), "mmstick@system76.com".into())]); + + let mut app = App { + core, + nav_model, + about, + }; + + let command = app.update_title(); + + (app, command) + } + + /// Allows COSMIC to integrate with your application's [`nav_bar::Model`]. + fn nav_model(&self) -> Option<&nav_bar::Model> { + Some(&self.nav_model) + } + + /// Called when a navigation item is selected. + fn on_nav_select(&mut self, id: nav_bar::Id) -> Task { + self.nav_model.activate(id); + self.update_title() + } + + fn context_drawer(&self) -> Option> { + if !self.core.window.show_context { + return None; + } + + if let Some(abuot_view) = self.about_view() { + Some(abuot_view.map(Message::Cosmic)) + } else { + None + } + } + + /// Handle application events here. + fn update(&mut self, message: Self::Message) -> Task { + match message { + Message::ToggleAbout => { + self.core.window.show_context = !self.core.window.show_context; + self.core.set_show_context(self.core.window.show_context) + } + Message::Cosmic(message) => { + return cosmic::command::message(cosmic::app::Message::Cosmic(message)) + } + } + Task::none() + } + + fn about(&self) -> Option<&About> { + Some(&self.about) + } + + /// Creates a view after each update. + fn view(&self) -> Element { + let centered = cosmic::widget::container( + column![widget::button::text("Show about").on_press(Message::ToggleAbout)] + .width(iced::Length::Fill) + .height(iced::Length::Shrink) + .align_x(iced::alignment::Horizontal::Center), + ) + .width(iced::Length::Fill) + .height(iced::Length::Shrink) + .align_x(iced::alignment::Horizontal::Center) + .align_y(iced::alignment::Vertical::Center); + + Element::from(centered) + } +} + +impl App +where + Self: cosmic::Application, +{ + fn active_page_title(&mut self) -> &str { + self.nav_model + .text(self.nav_model.active()) + .unwrap_or("Unknown Page") + } + + fn update_title(&mut self) -> Task { + let header_title = self.active_page_title().to_owned(); + let window_title = format!("{header_title} — COSMIC AppDemo"); + self.set_header_title(header_title); + if let Some(id) = self.core.main_window_id() { + self.set_window_title(window_title, id) + } else { + Task::none() + } + } +} diff --git a/src/app/about.rs b/src/app/about.rs new file mode 100644 index 00000000..5bb34225 --- /dev/null +++ b/src/app/about.rs @@ -0,0 +1,119 @@ +#[cfg(feature = "desktop")] +use std::collections::BTreeMap; + +#[cfg(feature = "desktop")] +#[derive(Debug, Default, Clone, derive_setters::Setters)] +#[setters(prefix = "set_", into, strip_option)] +pub struct About { + /// The application's name. + pub application_name: Option, + /// The application's icon name. + pub application_icon: Option, + /// Artists who contributed to the application. + #[setters(skip)] + pub artists: BTreeMap, + /// Comments about the application. + pub comments: Option, + /// The application's copyright. + pub copyright: Option, + /// Designers who contributed to the application. + #[setters(skip)] + pub designers: BTreeMap, + /// Name of the application's developer. + pub developer_name: Option, + /// Developers who contributed to the application. + #[setters(skip)] + pub developers: BTreeMap, + /// Documenters who contributed to the application. + #[setters(skip)] + pub documenters: BTreeMap, + /// The license text. + pub license: Option, + /// The license from a list of known licenses. + pub license_type: Option, + /// The URL of the application’s support page. + #[setters(skip)] + pub support_url: Option, + /// The URL of the application’s repository. + #[setters(skip)] + pub repository_url: Option, + /// Translators who contributed to the application. + #[setters(skip)] + pub translators: BTreeMap, + /// Links associated with the application. + #[setters(skip)] + pub links: BTreeMap, + /// The application’s version. + pub version: Option, + /// The application’s website. + #[setters(skip)] + pub website: Option, +} + +impl About { + pub fn set_repository_url(mut self, repository_url: impl Into) -> Self { + let repository_url = repository_url.into(); + self.repository_url = Some(repository_url.clone()); + self.links.insert("Repository".into(), repository_url); + self + } + + pub fn set_support_url(mut self, support_url: impl Into) -> Self { + let support_url = support_url.into(); + self.support_url = Some(support_url.clone()); + self.links.insert("Support".into(), support_url); + self + } + + pub fn set_website(mut self, website: impl Into) -> Self { + let website = website.into(); + self.website = Some(website.clone()); + self.links.insert("Website".into(), website); + self + } + + pub fn set_artists(mut self, artists: impl Into>) -> Self { + let artists: BTreeMap = artists.into(); + self.artists = artists + .into_iter() + .map(|(k, v)| (k, format!("mailto:{v}"))) + .collect(); + self + } + + pub fn set_designers(mut self, designers: impl Into>) -> Self { + let designers: BTreeMap = designers.into(); + self.designers = designers + .into_iter() + .map(|(k, v)| (k, format!("mailto:{v}"))) + .collect(); + self + } + + pub fn set_developers(mut self, developers: impl Into>) -> Self { + let developers: BTreeMap = developers.into(); + self.developers = developers + .into_iter() + .map(|(k, v)| (k, format!("mailto:{v}"))) + .collect(); + self + } + + pub fn set_documenters(mut self, documenters: impl Into>) -> Self { + let documenters: BTreeMap = documenters.into(); + self.documenters = documenters + .into_iter() + .map(|(k, v)| (k, format!("mailto:{v}"))) + .collect(); + self + } + + pub fn set_translators(mut self, translators: impl Into>) -> Self { + let translators: BTreeMap = translators.into(); + self.translators = translators + .into_iter() + .map(|(k, v)| (k, format!("mailto:{v}"))) + .collect(); + self + } +} diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index a2b229e6..969209ce 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -78,6 +78,9 @@ pub enum Message { /// Tracks updates to window suggested size. #[cfg(feature = "applet")] SuggestedBounds(Option), + #[cfg(feature = "desktop")] + /// Opens the provided URL. + OpenUrl(String), } #[derive(Default)] @@ -661,6 +664,11 @@ impl Cosmic { let core = self.app.core_mut(); core.applet.suggested_bounds = b; } + #[cfg(feature = "desktop")] + Message::OpenUrl(url) => match open::that_detached(url) { + Ok(_) => (), + Err(err) => tracing::error!("{err}"), + }, _ => {} } diff --git a/src/app/mod.rs b/src/app/mod.rs index fc90c75b..bdc2433d 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -6,6 +6,9 @@ //! Check out our [application](https://github.com/pop-os/libcosmic/tree/master/examples/application) //! example in our repository. +#[cfg(feature = "desktop")] +pub mod about; + pub mod command; mod core; pub mod cosmic; @@ -71,6 +74,14 @@ use { zbus::{interface, proxy, zvariant::Value}, }; +#[cfg(feature = "desktop")] +use { + crate::app::about::About, + crate::widget, + iced::{alignment::Vertical, Alignment}, + std::collections::BTreeMap, +}; + pub(crate) fn iced_settings( settings: Settings, flags: App::Flags, @@ -591,6 +602,88 @@ where panic!("no view for window {id:?}"); } + #[cfg(feature = "desktop")] + /// Provides information about the application. + fn about(&self) -> Option<&About> { + None + } + + #[cfg(feature = "desktop")] + /// Constructs the view for the about section. + fn about_view<'a>(&'a self) -> Option> { + let about = self.about()?; + + let spacing = crate::theme::active().cosmic().spacing; + + let section = |list: &'a BTreeMap, title: &'a str| { + if list.is_empty() { + None + } else { + let developers: Vec> = list + .into_iter() + .map(|(name, url)| { + widget::button::custom( + widget::row() + .push(widget::text(name)) + .push(horizontal_space()) + .push(crate::widget::icon::from_name("link-symbolic").icon()) + .padding(spacing.space_xxs) + .align_y(Vertical::Center), + ) + .class(crate::theme::Button::Text) + .on_press(crate::app::cosmic::Message::OpenUrl(url.clone())) + .width(Length::Fill) + .into() + }) + .collect(); + Some(widget::settings::section().title(title).extend(developers)) + } + }; + + let application_name = about.application_name.as_ref().map(widget::text::title3); + let application_icon = about + .application_icon + .as_ref() + .map(|icon| crate::desktop::IconSource::Name(icon.clone()).as_cosmic_icon()); + + let links_section = section(&about.links, "Links"); + let developers_section = section(&about.developers, "Developers"); + let designers_section = section(&about.designers, "Designers"); + let artists_section = section(&about.artists, "Artists"); + let translators_section = section(&about.translators, "Translators"); + let documenters_section = section(&about.documenters, "Documenters"); + + let developer_name = about.developer_name.as_ref().map(widget::text); + let version = about.version.as_ref().map(widget::button::standard); + let license = about.license_type.as_ref().map(widget::button::standard); + let copyright = about.copyright.as_ref().map(widget::text::body); + let comments = about.comments.as_ref().map(widget::text::body); + + let about = widget::column() + .push_maybe(application_icon) + .push_maybe(application_name) + .push_maybe(developer_name) + .push( + widget::row() + .push_maybe(version) + .push_maybe(license) + .spacing(spacing.space_xs), + ) + .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(comments) + .push_maybe(copyright) + .align_x(Alignment::Center) + .spacing(spacing.space_xs) + .width(Length::Fill) + .into(); + Some(about) + } + /// Overrides the default style for applications fn style(&self) -> Option { None From 6dfd9d7dd57f67253a2047aab64abac02992520d Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Wed, 6 Nov 2024 17:35:31 +0100 Subject: [PATCH 044/556] fix: right padding for nav bar --- src/app/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index bdc2433d..5f1e5906 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -809,7 +809,7 @@ impl ApplicationExt for App { .nav_bar() .map(|nav| id_container(nav, iced_core::id::Id::new("COSMIC_nav_bar"))) { - widgets.push(container(nav).padding([0, 0, 8, 8]).into()); + widgets.push(container(nav).padding([0, 8, 8, 8]).into()); true } else { false From 3c5a2d9340ca80836ce947cad8cc4c0fb1157355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Wed, 6 Nov 2024 17:53:32 +0100 Subject: [PATCH 045/556] fix(app): conditionally set context drawer padding This fixes the cosmic-term terminal_box becoming cropped when opening a context drawer. --- src/app/mod.rs | 25 +++++++++++++----- src/widget/context_drawer/widget.rs | 40 +++++++++-------------------- 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 5f1e5906..ad4b642c 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -792,11 +792,18 @@ impl ApplicationExt for App { // TODO: More granularity might be needed for different resize border // and window border handling of maximized and tiled windows let sharp_corners = core.window.sharp_corners; + let content_container = core.window.content_container; + let nav_bar_active = core.nav_bar_active(); let focused = core .focused_window() .is_some_and(|i| Some(i) == self.core().main_window_id()); - let main_content_padding = if core.window.content_container { - [0, 8, 8, 8] + + let main_content_padding = if content_container { + if nav_bar_active { + [0, 8, 8, 0] + } else { + [0, 8, 8, 8] + } } else { [0, 0, 0, 0] }; @@ -837,7 +844,11 @@ impl ApplicationExt for App { )) }) .apply(container) - .padding([0, 8, 8, 0]) + .padding(if content_container { + [0, 8, 8, 0] + } else { + [0, 0, 0, 0] + }) .into(), ); } else { @@ -867,7 +878,11 @@ impl ApplicationExt for App { )) }) .apply(container) - .padding([0, 8, 8, 0]) + .padding(if content_container { + [0, 8, 8, 0] + } else { + [0, 0, 0, 0] + }) .into(), ) } else { @@ -880,8 +895,6 @@ impl ApplicationExt for App { widgets }); let content_col = crate::widget::column::with_capacity(2) - //TODO: Add back when the `resize_border` is moved to not cover window content - //.spacing(8) .push(content_row) .push_maybe( self.footer() diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index 1fe6c250..a3c67c0c 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -12,8 +12,8 @@ use iced_core::event::{self, Event}; use iced_core::widget::{Operation, Tree}; use iced_core::Alignment; use iced_core::{ - layout, mouse, overlay as iced_overlay, renderer, Clipboard, Layout, Length, Padding, - Rectangle, Shell, Vector, Widget, + layout, mouse, overlay as iced_overlay, renderer, Clipboard, Layout, Length, Rectangle, Shell, + Vector, Widget, }; #[must_use] @@ -34,46 +34,30 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { where Drawer: Into>, { + let cosmic_theme::Spacing { + space_m, space_l, .. + } = crate::theme::active().cosmic().spacing; + let header = row::with_capacity(3) - .padding(Padding { - top: 0.0, - bottom: 0.0, - left: 32.0, - right: 32.0, - }) + .padding([space_m, space_l]) .push(Space::new(Length::FillPortion(1), Length::Fixed(0.0))) - .push( - text::heading(header) - .width(Length::FillPortion(1)) - .height(Length::Fill) - .align_x(Alignment::Center) - .align_y(Alignment::Center), - ) + .push(text::heading(header).width(Length::FillPortion(1)).center()) .push( button::text("Close") .trailing_icon(icon::from_name("go-next-symbolic")) .on_press(on_close) .apply(container) .width(Length::FillPortion(1)) - .align_x(Alignment::End) - .center_y(Length::Fill), + .align_x(Alignment::End), ) // XXX must be done after pushing elements or it may be overwritten by size hints from contents - .height(Length::Fixed(80.0)) .width(Length::Fixed(480.0)); - let pane = column::with_capacity(2) - .push(header.height(Length::Fixed(80.))) - .push( - scrollable(container(drawer.into()).padding(Padding { - top: 0.0, - left: 32.0, - right: 32.0, - bottom: 32.0, - })) + let pane = column::with_capacity(2).push(header).push( + scrollable(container(drawer.into()).padding([0, space_l, space_l, space_l])) .height(Length::Fill) .width(Length::Shrink), - ); + ); // XXX new limits do not exactly handle the max width well for containers // XXX this is a hack to get around that From 568ff097d67b93eef646d2f86937935438dba4dd Mon Sep 17 00:00:00 2001 From: wiiznokes <78230769+wiiznokes@users.noreply.github.com> Date: Thu, 7 Nov 2024 01:15:04 +0100 Subject: [PATCH 046/556] feat: export markdown iced feature --- Cargo.toml | 1 + src/theme/style/iced.rs | 13 +++++++++++++ src/widget/mod.rs | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 3567c2a9..19cb5532 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,6 +82,7 @@ winit_wgpu = ["winit", "wgpu"] # Enables XDG portal integrations xdg-portal = ["ashpd"] qr_code = ["iced/qr_code"] +markdown = ["iced/markdown"] [dependencies] apply = "0.3.0" diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 79b7dde2..8a49ec09 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -6,6 +6,7 @@ use crate::theme::{CosmicComponent, Theme, TRANSPARENT_COMPONENT}; use cosmic_theme::composite::over; use iced::{ + border, color, overlay::menu, widget::{ button as iced_button, checkbox as iced_checkbox, container as iced_container, pane_grid, @@ -1473,3 +1474,15 @@ impl iced_widget::text_editor::Catalog for Theme { } } } + +#[cfg(feature = "markdown")] +impl iced_widget::markdown::Catalog for Theme { + fn code_block<'a>() -> ::Class<'a> { + Container::custom(|_| iced_container::Style { + background: Some(color!(0x111111).into()), + text_color: Some(Color::WHITE), + border: border::rounded(2), + ..iced_container::Style::default() + }) + } +} diff --git a/src/widget/mod.rs b/src/widget/mod.rs index ecce1a86..6c7fcd08 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -361,3 +361,7 @@ pub mod tooltip { pub mod warning; #[doc(inline)] pub use warning::*; + +#[cfg(feature = "markdown")] +#[doc(inline)] +pub use iced::widget::markdown; From 2704a77aa3506d51eded569c5eec1c04125b1608 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 7 Nov 2024 17:18:47 -0500 Subject: [PATCH 047/556] update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 65c7a3d9..32972639 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 65c7a3d9cbb096191cec0c40c39310a54570fdab +Subproject commit 3297263977365a5d7e4c4cd9bbc13613b674763b From 707f2115eb4d3b4938209ecb6c33f052b880d05f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Fri, 8 Nov 2024 12:26:03 +0100 Subject: [PATCH 048/556] fix(context_drawer): center header elements --- src/app/mod.rs | 2 +- src/widget/context_drawer/widget.rs | 6 +++--- src/widget/header_bar.rs | 5 ++--- src/widget/list/column.rs | 6 +++--- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index ad4b642c..641ca2db 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -809,7 +809,7 @@ impl ApplicationExt for App { }; let content_row = crate::widget::row::with_children({ - let mut widgets = Vec::with_capacity(4); + let mut widgets = Vec::with_capacity(3); // Insert nav bar onto the left side of the window. let has_nav = if let Some(nav) = self diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index a3c67c0c..b9ce7035 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -39,6 +39,8 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { } = crate::theme::active().cosmic().spacing; let header = row::with_capacity(3) + .width(Length::Fixed(480.0)) + .align_y(Alignment::Center) .padding([space_m, space_l]) .push(Space::new(Length::FillPortion(1), Length::Fixed(0.0))) .push(text::heading(header).width(Length::FillPortion(1)).center()) @@ -49,9 +51,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { .apply(container) .width(Length::FillPortion(1)) .align_x(Alignment::End), - ) - // XXX must be done after pushing elements or it may be overwritten by size hints from contents - .width(Length::Fixed(480.0)); + ); let pane = column::with_capacity(2).push(header).push( scrollable(container(drawer.into()).padding([0, space_l, space_l, space_l])) diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index a8dcf949..d9ba249f 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -286,7 +286,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { }; // Creates the headerbar widget. - let mut widget = widget::row::with_capacity(4) + let mut widget = widget::row::with_capacity(3) // If elements exist in the start region, append them here. .push( widget::row::with_children(start) @@ -351,8 +351,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { widget::text::heading(title) .apply(widget::container) - .center_x(Length::Fill) - .center_y(Length::Fill) + .center(Length::Fill) .into() } diff --git a/src/widget/list/column.rs b/src/widget/list/column.rs index cc0b517a..597a4b2f 100644 --- a/src/widget/list/column.rs +++ b/src/widget/list/column.rs @@ -41,13 +41,13 @@ impl<'a, Message: 'static> ListColumn<'a, Message> { } // Ensure a minimum height of 32. - let container = iced::widget::row![ - crate::widget::container(item).align_y(iced::Alignment::Center), + let list_item = iced::widget::row![ + item.into(), crate::widget::vertical_space().height(iced::Length::Fixed(32.)) ] .align_y(iced::Alignment::Center); - self.children.push(container.into()); + self.children.push(list_item.into()); self } From 5fdc2df9cdf6cb001b28e7f655c4e9e30678f7b1 Mon Sep 17 00:00:00 2001 From: wiiznokes <78230769+wiiznokes@users.noreply.github.com> Date: Thu, 7 Nov 2024 18:10:41 +0100 Subject: [PATCH 049/556] feat: optional title on dialog --- src/widget/dialog.rs | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/widget/dialog.rs b/src/widget/dialog.rs index 014b738b..0ee5dcb1 100644 --- a/src/widget/dialog.rs +++ b/src/widget/dialog.rs @@ -1,12 +1,12 @@ use crate::{iced::Length, style, theme, widget, Element}; use std::borrow::Cow; -pub fn dialog<'a, Message>(title: impl Into>) -> Dialog<'a, Message> { - Dialog::new(title) +pub fn dialog<'a, Message>() -> Dialog<'a, Message> { + Dialog::new() } pub struct Dialog<'a, Message> { - title: Cow<'a, str>, + title: Option>, icon: Option>, body: Option>, controls: Vec>, @@ -16,9 +16,9 @@ pub struct Dialog<'a, Message> { } impl<'a, Message> Dialog<'a, Message> { - pub fn new(title: impl Into>) -> Self { + pub fn new() -> Self { Self { - title: title.into(), + title: None, icon: None, body: None, controls: Vec::new(), @@ -28,6 +28,11 @@ impl<'a, Message> Dialog<'a, Message> { } } + pub fn title(mut self, title: impl Into>) -> Self { + self.title = Some(title.into()); + self + } + pub fn icon(mut self, icon: impl Into>) -> Self { self.icon = Some(icon.into()); self @@ -70,16 +75,28 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes } = theme::THEME.lock().unwrap().cosmic().spacing; let mut content_col = widget::column::with_capacity(3 + dialog.controls.len() * 2); - content_col = content_col.push(widget::text::title3(dialog.title)); + + let mut should_space = false; + + if let Some(title) = dialog.title { + content_col = content_col.push(widget::text::title3(title)); + should_space = true; + } if let Some(body) = dialog.body { - content_col = - content_col.push(widget::vertical_space().height(Length::Fixed(space_xxs.into()))); + if should_space { + content_col = content_col + .push(widget::vertical_space().height(Length::Fixed(space_xxs.into()))); + } content_col = content_col.push(widget::text::body(body)); + should_space = true; } for control in dialog.controls { - content_col = - content_col.push(widget::vertical_space().height(Length::Fixed(space_s.into()))); + if should_space { + content_col = content_col + .push(widget::vertical_space().height(Length::Fixed(space_s.into()))); + } content_col = content_col.push(control); + should_space = true; } let mut content_row = widget::row::with_capacity(2).spacing(space_s); From 8c69491f2a324fff53d6e8a6180dd6f4916f64a5 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 8 Nov 2024 16:33:38 +0100 Subject: [PATCH 050/556] fix(context_drawer): remove scrollable --- src/widget/context_drawer/widget.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index b9ce7035..487cee53 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -54,7 +54,8 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { ); let pane = column::with_capacity(2).push(header).push( - scrollable(container(drawer.into()).padding([0, space_l, space_l, space_l])) + container(drawer.into()) + .padding([0, space_l, space_l, space_l]) .height(Length::Fill) .width(Length::Shrink), ); From 6f53b68be595d37312f3058f71d77d7f9dcf1f5e Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Sat, 9 Nov 2024 12:16:00 +0100 Subject: [PATCH 051/556] fix: add scrollable to about view --- src/app/mod.rs | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 641ca2db..12940d7c 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -659,28 +659,30 @@ where let copyright = about.copyright.as_ref().map(widget::text::body); let comments = about.comments.as_ref().map(widget::text::body); - let about = widget::column() - .push_maybe(application_icon) - .push_maybe(application_name) - .push_maybe(developer_name) - .push( - widget::row() - .push_maybe(version) - .push_maybe(license) - .spacing(spacing.space_xs), - ) - .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(comments) - .push_maybe(copyright) - .align_x(Alignment::Center) - .spacing(spacing.space_xs) - .width(Length::Fill) - .into(); + let about = widget::scrollable( + widget::column() + .push_maybe(application_icon) + .push_maybe(application_name) + .push_maybe(developer_name) + .push( + widget::row() + .push_maybe(version) + .push_maybe(license) + .spacing(spacing.space_xs), + ) + .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(comments) + .push_maybe(copyright) + .align_x(Alignment::Center) + .spacing(spacing.space_xs) + .width(Length::Fill), + ) + .into(); Some(about) } From d8357d0ea3a60b28ed7fa115604b97077f5b62b8 Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Sun, 10 Nov 2024 02:42:16 +0100 Subject: [PATCH 052/556] refactor: about page as a widget --- Cargo.toml | 4 +- examples/about/Cargo.toml | 1 + examples/about/src/main.rs | 42 ++++---- src/app/about.rs | 119 --------------------- src/app/cosmic.rs | 8 -- src/app/mod.rs | 88 ---------------- src/widget/about.rs | 211 +++++++++++++++++++++++++++++++++++++ src/widget/mod.rs | 6 ++ 8 files changed, 239 insertions(+), 240 deletions(-) delete mode 100644 src/app/about.rs create mode 100644 src/widget/about.rs diff --git a/Cargo.toml b/Cargo.toml index 19cb5532..57b59b6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ desktop = [ "process", "dep:freedesktop-desktop-entry", "dep:mime", - "dep:open", + "dep:license", "dep:shlex", "tokio?/io-util", "tokio?/net", @@ -98,8 +98,8 @@ fraction = "0.15.3" image = { version = "0.25.1", optional = true } lazy_static = "1.4.0" libc = { version = "0.2.155", optional = true } +license = { version = "3.5.1", optional = true } mime = { version = "0.3.17", optional = true } -open = { version = "5.3.0", optional = true } palette = "0.7.3" rfd = { version = "0.14.0", optional = true } rustix = { version = "0.38.34", features = [ diff --git a/examples/about/Cargo.toml b/examples/about/Cargo.toml index 4dd3aed9..76a35791 100644 --- a/examples/about/Cargo.toml +++ b/examples/about/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" tracing = "0.1.37" tracing-subscriber = "0.3.17" tracing-log = "0.2.0" +open = "5.3.0" [dependencies.libcosmic] path = "../../" diff --git a/examples/about/src/main.rs b/examples/about/src/main.rs index ed061f61..6bc4a4ff 100644 --- a/examples/about/src/main.rs +++ b/examples/about/src/main.rs @@ -3,10 +3,10 @@ //! Application API example -use cosmic::app::{about::About, Core, Settings, Task}; +use cosmic::app::{Core, Settings, Task}; use cosmic::iced::widget::column; use cosmic::iced_core::Size; -use cosmic::widget::{self, nav_bar}; +use cosmic::widget::{self, about::About, nav_bar}; use cosmic::{executor, iced, ApplicationExt, Element}; /// Runs application with these settings @@ -27,7 +27,7 @@ fn main() -> Result<(), Box> { #[derive(Clone, Debug)] pub enum Message { ToggleAbout, - Cosmic(cosmic::app::cosmic::Message), + Open(String), } /// The [`App`] stores application-specific state. @@ -64,14 +64,17 @@ impl cosmic::Application for App { let nav_model = nav_bar::Model::default(); let about = About::default() - .set_application_name("About Demo") - .set_application_icon(Self::APP_ID) - .set_developer_name("System 76") - .set_license_type("GPL-3.0") - .set_website("https://system76.com/cosmic") - .set_repository_url("https://github.com/pop-os/libcosmic") - .set_support_url("https://github.com/pop-os/libcosmic/issues") - .set_developers([("Michael Murphy".into(), "mmstick@system76.com".into())]); + .name("About Demo") + .icon(Self::APP_ID) + .version("0.1.0") + .author("System 76") + .license("GPL-3.0-only") + .developers([("Michael Murphy", "mmstick@system76.com")]) + .links([ + ("Website", "https://system76.com/cosmic"), + ("Repository", "https://github.com/pop-os/libcosmic"), + ("Support", "https://github.com/pop-os/libcosmic/issues"), + ]); let mut app = App { core, @@ -100,11 +103,7 @@ impl cosmic::Application for App { return None; } - if let Some(abuot_view) = self.about_view() { - Some(abuot_view.map(Message::Cosmic)) - } else { - None - } + Some(widget::about(&self.about, Message::Open)) } /// Handle application events here. @@ -114,17 +113,14 @@ impl cosmic::Application for App { self.core.window.show_context = !self.core.window.show_context; self.core.set_show_context(self.core.window.show_context) } - Message::Cosmic(message) => { - return cosmic::command::message(cosmic::app::Message::Cosmic(message)) - } + Message::Open(url) => match open::that_detached(url) { + Ok(_) => (), + Err(err) => tracing::error!("Failed to open URL: {err}"), + }, } Task::none() } - fn about(&self) -> Option<&About> { - Some(&self.about) - } - /// Creates a view after each update. fn view(&self) -> Element { let centered = cosmic::widget::container( diff --git a/src/app/about.rs b/src/app/about.rs deleted file mode 100644 index 5bb34225..00000000 --- a/src/app/about.rs +++ /dev/null @@ -1,119 +0,0 @@ -#[cfg(feature = "desktop")] -use std::collections::BTreeMap; - -#[cfg(feature = "desktop")] -#[derive(Debug, Default, Clone, derive_setters::Setters)] -#[setters(prefix = "set_", into, strip_option)] -pub struct About { - /// The application's name. - pub application_name: Option, - /// The application's icon name. - pub application_icon: Option, - /// Artists who contributed to the application. - #[setters(skip)] - pub artists: BTreeMap, - /// Comments about the application. - pub comments: Option, - /// The application's copyright. - pub copyright: Option, - /// Designers who contributed to the application. - #[setters(skip)] - pub designers: BTreeMap, - /// Name of the application's developer. - pub developer_name: Option, - /// Developers who contributed to the application. - #[setters(skip)] - pub developers: BTreeMap, - /// Documenters who contributed to the application. - #[setters(skip)] - pub documenters: BTreeMap, - /// The license text. - pub license: Option, - /// The license from a list of known licenses. - pub license_type: Option, - /// The URL of the application’s support page. - #[setters(skip)] - pub support_url: Option, - /// The URL of the application’s repository. - #[setters(skip)] - pub repository_url: Option, - /// Translators who contributed to the application. - #[setters(skip)] - pub translators: BTreeMap, - /// Links associated with the application. - #[setters(skip)] - pub links: BTreeMap, - /// The application’s version. - pub version: Option, - /// The application’s website. - #[setters(skip)] - pub website: Option, -} - -impl About { - pub fn set_repository_url(mut self, repository_url: impl Into) -> Self { - let repository_url = repository_url.into(); - self.repository_url = Some(repository_url.clone()); - self.links.insert("Repository".into(), repository_url); - self - } - - pub fn set_support_url(mut self, support_url: impl Into) -> Self { - let support_url = support_url.into(); - self.support_url = Some(support_url.clone()); - self.links.insert("Support".into(), support_url); - self - } - - pub fn set_website(mut self, website: impl Into) -> Self { - let website = website.into(); - self.website = Some(website.clone()); - self.links.insert("Website".into(), website); - self - } - - pub fn set_artists(mut self, artists: impl Into>) -> Self { - let artists: BTreeMap = artists.into(); - self.artists = artists - .into_iter() - .map(|(k, v)| (k, format!("mailto:{v}"))) - .collect(); - self - } - - pub fn set_designers(mut self, designers: impl Into>) -> Self { - let designers: BTreeMap = designers.into(); - self.designers = designers - .into_iter() - .map(|(k, v)| (k, format!("mailto:{v}"))) - .collect(); - self - } - - pub fn set_developers(mut self, developers: impl Into>) -> Self { - let developers: BTreeMap = developers.into(); - self.developers = developers - .into_iter() - .map(|(k, v)| (k, format!("mailto:{v}"))) - .collect(); - self - } - - pub fn set_documenters(mut self, documenters: impl Into>) -> Self { - let documenters: BTreeMap = documenters.into(); - self.documenters = documenters - .into_iter() - .map(|(k, v)| (k, format!("mailto:{v}"))) - .collect(); - self - } - - pub fn set_translators(mut self, translators: impl Into>) -> Self { - let translators: BTreeMap = translators.into(); - self.translators = translators - .into_iter() - .map(|(k, v)| (k, format!("mailto:{v}"))) - .collect(); - self - } -} diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 969209ce..a2b229e6 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -78,9 +78,6 @@ pub enum Message { /// Tracks updates to window suggested size. #[cfg(feature = "applet")] SuggestedBounds(Option), - #[cfg(feature = "desktop")] - /// Opens the provided URL. - OpenUrl(String), } #[derive(Default)] @@ -664,11 +661,6 @@ impl Cosmic { let core = self.app.core_mut(); core.applet.suggested_bounds = b; } - #[cfg(feature = "desktop")] - Message::OpenUrl(url) => match open::that_detached(url) { - Ok(_) => (), - Err(err) => tracing::error!("{err}"), - }, _ => {} } diff --git a/src/app/mod.rs b/src/app/mod.rs index 12940d7c..96a3f5f6 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -6,9 +6,6 @@ //! Check out our [application](https://github.com/pop-os/libcosmic/tree/master/examples/application) //! example in our repository. -#[cfg(feature = "desktop")] -pub mod about; - pub mod command; mod core; pub mod cosmic; @@ -76,7 +73,6 @@ use { #[cfg(feature = "desktop")] use { - crate::app::about::About, crate::widget, iced::{alignment::Vertical, Alignment}, std::collections::BTreeMap, @@ -602,90 +598,6 @@ where panic!("no view for window {id:?}"); } - #[cfg(feature = "desktop")] - /// Provides information about the application. - fn about(&self) -> Option<&About> { - None - } - - #[cfg(feature = "desktop")] - /// Constructs the view for the about section. - fn about_view<'a>(&'a self) -> Option> { - let about = self.about()?; - - let spacing = crate::theme::active().cosmic().spacing; - - let section = |list: &'a BTreeMap, title: &'a str| { - if list.is_empty() { - None - } else { - let developers: Vec> = list - .into_iter() - .map(|(name, url)| { - widget::button::custom( - widget::row() - .push(widget::text(name)) - .push(horizontal_space()) - .push(crate::widget::icon::from_name("link-symbolic").icon()) - .padding(spacing.space_xxs) - .align_y(Vertical::Center), - ) - .class(crate::theme::Button::Text) - .on_press(crate::app::cosmic::Message::OpenUrl(url.clone())) - .width(Length::Fill) - .into() - }) - .collect(); - Some(widget::settings::section().title(title).extend(developers)) - } - }; - - let application_name = about.application_name.as_ref().map(widget::text::title3); - let application_icon = about - .application_icon - .as_ref() - .map(|icon| crate::desktop::IconSource::Name(icon.clone()).as_cosmic_icon()); - - let links_section = section(&about.links, "Links"); - let developers_section = section(&about.developers, "Developers"); - let designers_section = section(&about.designers, "Designers"); - let artists_section = section(&about.artists, "Artists"); - let translators_section = section(&about.translators, "Translators"); - let documenters_section = section(&about.documenters, "Documenters"); - - let developer_name = about.developer_name.as_ref().map(widget::text); - let version = about.version.as_ref().map(widget::button::standard); - let license = about.license_type.as_ref().map(widget::button::standard); - let copyright = about.copyright.as_ref().map(widget::text::body); - let comments = about.comments.as_ref().map(widget::text::body); - - let about = widget::scrollable( - widget::column() - .push_maybe(application_icon) - .push_maybe(application_name) - .push_maybe(developer_name) - .push( - widget::row() - .push_maybe(version) - .push_maybe(license) - .spacing(spacing.space_xs), - ) - .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(comments) - .push_maybe(copyright) - .align_x(Alignment::Center) - .spacing(spacing.space_xs) - .width(Length::Fill), - ) - .into(); - Some(about) - } - /// Overrides the default style for applications fn style(&self) -> Option { None diff --git a/src/widget/about.rs b/src/widget/about.rs new file mode 100644 index 00000000..8e458337 --- /dev/null +++ b/src/widget/about.rs @@ -0,0 +1,211 @@ +#[cfg(feature = "desktop")] +use { + crate::{ + iced::{alignment::Vertical, Alignment, Length}, + widget::{self, horizontal_space}, + Element, + }, + license::License, +}; + +#[derive(Debug, Default, Clone, derive_setters::Setters)] +#[setters(into, strip_option)] +/// Information about the application. +pub struct About { + /// The application's name. + name: Option, + /// The application's icon name. + icon: Option, + /// The application’s version. + version: Option, + /// Name of the application's author. + author: Option, + /// Comments about the application. + comments: Option, + /// The application's copyright. + copyright: Option, + /// The license name. + license: Option, + /// Artists who contributed to the application. + #[setters(skip)] + artists: Vec<(String, String)>, + /// Designers who contributed to the application. + #[setters(skip)] + designers: Vec<(String, String)>, + /// Developers who contributed to the application. + #[setters(skip)] + developers: Vec<(String, String)>, + /// Documenters who contributed to the application. + #[setters(skip)] + documenters: Vec<(String, String)>, + /// Translators who contributed to the application. + #[setters(skip)] + translators: Vec<(String, String)>, + /// Links associated with the application. + #[setters(skip)] + links: Vec<(String, String)>, +} + +impl<'a> About { + /// Artists who contributed to the application. + pub fn artists(mut self, artists: impl Into>) -> Self { + let artists: Vec<(&'a str, &'a str)> = artists.into(); + self.artists = artists + .into_iter() + .map(|(k, v)| (k.to_string(), format!("mailto:{v}"))) + .collect(); + self + } + + /// Designers who contributed to the application. + pub fn designers(mut self, designers: impl Into>) -> Self { + let designers: Vec<(&'a str, &'a str)> = designers.into(); + self.designers = designers + .into_iter() + .map(|(k, v)| (k.to_string(), format!("mailto:{v}"))) + .collect(); + self + } + + /// Developers who contributed to the application. + pub fn developers(mut self, developers: impl Into>) -> Self { + let developers: Vec<(&'a str, &'a str)> = developers.into(); + self.developers = developers + .into_iter() + .map(|(k, v)| (k.to_string(), format!("mailto:{v}"))) + .collect(); + self + } + + /// Documenters who contributed to the application. + pub fn documenters(mut self, documenters: impl Into>) -> Self { + let documenters: Vec<(&'a str, &'a str)> = documenters.into(); + self.documenters = documenters + .into_iter() + .map(|(k, v)| (k.to_string(), format!("mailto:{v}"))) + .collect(); + self + } + + /// Translators who contributed to the application. + pub fn translators(mut self, translators: impl Into>) -> Self { + let translators: Vec<(&'a str, &'a str)> = translators.into(); + self.translators = translators + .into_iter() + .map(|(k, v)| (k.to_string(), format!("mailto:{v}"))) + .collect(); + self + } + + /// Links associated with the application. + pub fn links>(mut self, links: impl Into>) -> Self { + let links: Vec<(T, &'a str)> = links.into(); + self.links = links + .into_iter() + .map(|(k, v)| (k.into(), v.to_string())) + .collect(); + self + } + + fn license_url(&self) -> Option { + let license: &dyn License = match self.license.as_ref() { + Some(license) => license.parse().ok()?, + None => return None, + }; + + self.license + .as_ref() + .map(|_| format!("https://spdx.org/licenses/{}.html", license.id())) + } +} + +/// Constructs the widget for the about section. +pub fn about<'a, Message: Clone + 'static>( + about: &'a About, + on_url_press: impl Fn(String) -> Message, +) -> Element<'a, Message> { + let spacing = crate::theme::active().cosmic().spacing; + + let section = |list: &'a Vec<(String, String)>, title: &'a str| { + (!list.is_empty()).then_some({ + let developers: 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(), + )) + .padding(spacing.space_xxs) + .align_y(Vertical::Center), + ) + .class(crate::theme::Button::Text) + .on_press(on_url_press(url.clone())) + .width(Length::Fill) + .into() + }) + .collect(); + widget::settings::section().title(title).extend(developers) + }) + }; + + let application_name = about.name.as_ref().map(widget::text::title3); + let application_icon = about + .icon + .as_ref() + .map(|icon| crate::desktop::IconSource::Name(icon.clone()).as_cosmic_icon()); + + let links_section = section(&about.links, "Links"); + let developers_section = section(&about.developers, "Developers"); + let designers_section = section(&about.designers, "Designers"); + let artists_section = section(&about.artists, "Artists"); + let translators_section = section(&about.translators, "Translators"); + let documenters_section = section(&about.documenters, "Documenters"); + let author = about.author.as_ref().map(widget::text); + let version = about.version.as_ref().map(widget::button::standard); + let license = about.license.as_ref().map(|license| { + let url = about.license_url(); + widget::settings::section().title("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()), + ) + .padding(spacing.space_xxs) + .align_y(Vertical::Center), + ) + .class(crate::theme::Button::Text) + .on_press(on_url_press(url.unwrap_or(String::new()))) + .width(Length::Fill), + ) + }); + let copyright = about.copyright.as_ref().map(widget::text::body); + let comments = about.comments.as_ref().map(widget::text::body); + + widget::scrollable( + widget::column() + .push_maybe(application_icon) + .push_maybe(application_name) + .push_maybe(author) + .push_maybe(version) + .push_maybe(license) + .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(comments) + .push_maybe(copyright) + .align_x(Alignment::Center) + .spacing(spacing.space_xs) + .width(Length::Fill), + ) + .spacing(spacing.space_xxxs) + .into() +} diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 6c7fcd08..b84e54ef 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -365,3 +365,9 @@ pub use warning::*; #[cfg(feature = "markdown")] #[doc(inline)] pub use iced::widget::markdown; + +#[cfg(feature = "desktop")] +pub mod about; +#[cfg(feature = "desktop")] +#[doc(inline)] +pub use about::about; From c310f4ca244d7be82814d35c4d12fcf9a398f74a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Sat, 9 Nov 2024 20:50:21 +0100 Subject: [PATCH 053/556] feat(context_drawer): add actions to header --- src/app/mod.rs | 7 +++++++ src/widget/context_drawer/mod.rs | 5 +++-- src/widget/context_drawer/widget.rs | 31 ++++++++++++++++++++--------- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 96a3f5f6..b713e7c7 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -465,6 +465,11 @@ where None } + /// App-specific actions at the start of the context drawer header + fn context_header_actions(&self) -> Vec>> { + Vec::new() + } + /// Displays a dialog in the center of the application window when `Some`. fn dialog(&self) -> Option> { None @@ -746,6 +751,7 @@ impl ApplicationExt for App { widgets.push( context_drawer( &core.window.context_title, + self.context_header_actions(), Message::Cosmic(cosmic::Message::ContextDrawer(false)), main_content, context.map(Message::App), @@ -779,6 +785,7 @@ impl ApplicationExt for App { widgets.push( crate::widget::ContextDrawer::new_inner( &core.window.context_title, + self.context_header_actions(), context.map(Message::App), Message::Cosmic(cosmic::Message::ContextDrawer(false)), context_width, diff --git a/src/widget/context_drawer/mod.rs b/src/widget/context_drawer/mod.rs index 52b9a007..0581a21b 100644 --- a/src/widget/context_drawer/mod.rs +++ b/src/widget/context_drawer/mod.rs @@ -12,7 +12,8 @@ use crate::Element; /// An overlayed widget that attaches a toggleable context drawer to the view. pub fn context_drawer<'a, Message: Clone + 'static, Content, Drawer>( - header: &'a str, + title: &'a str, + actions: Vec>, on_close: Message, content: Content, drawer: Drawer, @@ -22,5 +23,5 @@ where Content: Into>, Drawer: Into>, { - ContextDrawer::new(header, content, drawer, on_close, max_width) + ContextDrawer::new(title, actions, content, drawer, on_close, max_width) } diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index 487cee53..5f6560cf 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -1,9 +1,7 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -use crate::widget::{ - button, column, container, icon, row, scrollable, text, LayerContainer, Space, -}; +use crate::widget::{button, column, container, icon, row, text, LayerContainer}; use crate::{Apply, Element, Renderer, Theme}; use super::overlay::Overlay; @@ -26,7 +24,8 @@ pub struct ContextDrawer<'a, Message> { impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { pub fn new_inner( - header: &'a str, + title: &'a str, + actions: Vec>, drawer: Drawer, on_close: Message, max_width: f32, @@ -35,15 +34,28 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { Drawer: Into>, { let cosmic_theme::Spacing { - space_m, space_l, .. + space_xxs, + space_m, + space_l, + .. } = crate::theme::active().cosmic().spacing; + let title_opt = if title.is_empty() { + None + } else { + Some(text::heading(title).width(Length::FillPortion(1)).center()) + }; + let header = row::with_capacity(3) .width(Length::Fixed(480.0)) .align_y(Alignment::Center) .padding([space_m, space_l]) - .push(Space::new(Length::FillPortion(1), Length::Fixed(0.0))) - .push(text::heading(header).width(Length::FillPortion(1)).center()) + .push( + row::with_children(actions) + .spacing(space_xxs) + .width(Length::FillPortion(1)), + ) + .push_maybe(title_opt) .push( button::text("Close") .trailing_icon(icon::from_name("go-next-symbolic")) @@ -78,7 +90,8 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { /// Creates an empty [`ContextDrawer`]. pub fn new( - header: &'a str, + title: &'a str, + actions: Vec>, content: Content, drawer: Drawer, on_close: Message, @@ -88,7 +101,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { Content: Into>, Drawer: Into>, { - let drawer = Self::new_inner(header, drawer, on_close, max_width); + let drawer = Self::new_inner(title, actions, drawer, on_close, max_width); ContextDrawer { id: None, From 2909d37b58afe0907989348b3e487aa21915cfea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Sun, 10 Nov 2024 01:22:27 +0100 Subject: [PATCH 054/556] fix(context_drawer): remove content padding This is so that the scrollbar can be at the edge of the context drawer. Apps will need to specify this padding for everything that goes below the header (if using a scrollable, it should be applied before the scrollable). --- src/widget/context_drawer/widget.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index 5f6560cf..ea4aa3c6 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -67,7 +67,6 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { let pane = column::with_capacity(2).push(header).push( container(drawer.into()) - .padding([0, space_l, space_l, space_l]) .height(Length::Fill) .width(Length::Shrink), ); From 3dcc47d6a7d14423fbd372a0a5ce3c7c9581dd75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Sun, 10 Nov 2024 17:18:58 +0100 Subject: [PATCH 055/556] improv(context_drawer): add optional header and footer element --- src/app/mod.rs | 21 +++++++---- src/widget/about.rs | 55 ++++++++++++++-------------- src/widget/context_drawer/mod.rs | 15 +++++++- src/widget/context_drawer/widget.rs | 57 +++++++++++++++++++++++------ 4 files changed, 101 insertions(+), 47 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index b713e7c7..b9166e68 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -71,13 +71,6 @@ use { zbus::{interface, proxy, zvariant::Value}, }; -#[cfg(feature = "desktop")] -use { - crate::widget, - iced::{alignment::Vertical, Alignment}, - std::collections::BTreeMap, -}; - pub(crate) fn iced_settings( settings: Settings, flags: App::Flags, @@ -470,6 +463,16 @@ where Vec::new() } + /// Non-scrolling elements placed below the context drawer title row + fn context_drawer_header(&self) -> Option>> { + None + } + + /// Elements placed below the context drawer scrollable + fn context_drawer_footer(&self) -> Option>> { + None + } + /// Displays a dialog in the center of the application window when `Some`. fn dialog(&self) -> Option> { None @@ -752,6 +755,8 @@ impl ApplicationExt for App { context_drawer( &core.window.context_title, self.context_header_actions(), + self.context_drawer_header(), + self.context_drawer_footer(), Message::Cosmic(cosmic::Message::ContextDrawer(false)), main_content, context.map(Message::App), @@ -786,6 +791,8 @@ impl ApplicationExt for App { crate::widget::ContextDrawer::new_inner( &core.window.context_title, self.context_header_actions(), + self.context_drawer_header(), + self.context_drawer_footer(), context.map(Message::App), Message::Cosmic(cosmic::Message::ContextDrawer(false)), context_width, diff --git a/src/widget/about.rs b/src/widget/about.rs index 8e458337..a5bd0b33 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -1,7 +1,7 @@ #[cfg(feature = "desktop")] use { crate::{ - iced::{alignment::Vertical, Alignment, Length}, + iced::{Alignment, Length}, widget::{self, horizontal_space}, Element, }, @@ -124,7 +124,11 @@ pub fn about<'a, Message: Clone + 'static>( about: &'a About, on_url_press: impl Fn(String) -> Message, ) -> Element<'a, Message> { - let spacing = crate::theme::active().cosmic().spacing; + let cosmic_theme::Spacing { + space_xxs, + space_xs, + .. + } = crate::theme::active().cosmic().spacing; let section = |list: &'a Vec<(String, String)>, title: &'a str| { (!list.is_empty()).then_some({ @@ -138,8 +142,8 @@ pub fn about<'a, Message: Clone + 'static>( .push_maybe((!url.is_empty()).then_some( crate::widget::icon::from_name("link-symbolic").icon(), )) - .padding(spacing.space_xxs) - .align_y(Vertical::Center), + .padding(space_xxs) + .align_y(Alignment::Center), ) .class(crate::theme::Button::Text) .on_press(on_url_press(url.clone())) @@ -176,8 +180,8 @@ pub fn about<'a, Message: Clone + 'static>( url.is_some() .then_some(crate::widget::icon::from_name("link-symbolic").icon()), ) - .padding(spacing.space_xxs) - .align_y(Vertical::Center), + .padding(space_xxs) + .align_y(Alignment::Center), ) .class(crate::theme::Button::Text) .on_press(on_url_press(url.unwrap_or(String::new()))) @@ -187,25 +191,22 @@ pub fn about<'a, Message: Clone + 'static>( let copyright = about.copyright.as_ref().map(widget::text::body); let comments = about.comments.as_ref().map(widget::text::body); - widget::scrollable( - widget::column() - .push_maybe(application_icon) - .push_maybe(application_name) - .push_maybe(author) - .push_maybe(version) - .push_maybe(license) - .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(comments) - .push_maybe(copyright) - .align_x(Alignment::Center) - .spacing(spacing.space_xs) - .width(Length::Fill), - ) - .spacing(spacing.space_xxxs) - .into() + widget::column() + .push_maybe(application_icon) + .push_maybe(application_name) + .push_maybe(author) + .push_maybe(version) + .push_maybe(license) + .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(comments) + .push_maybe(copyright) + .align_x(Alignment::Center) + .spacing(space_xs) + .width(Length::Fill) + .into() } diff --git a/src/widget/context_drawer/mod.rs b/src/widget/context_drawer/mod.rs index 0581a21b..5a90fc8a 100644 --- a/src/widget/context_drawer/mod.rs +++ b/src/widget/context_drawer/mod.rs @@ -13,7 +13,9 @@ use crate::Element; /// An overlayed widget that attaches a toggleable context drawer to the view. pub fn context_drawer<'a, Message: Clone + 'static, Content, Drawer>( title: &'a str, - actions: Vec>, + header_actions: Vec>, + header_opt: Option>, + footer_opt: Option>, on_close: Message, content: Content, drawer: Drawer, @@ -23,5 +25,14 @@ where Content: Into>, Drawer: Into>, { - ContextDrawer::new(title, actions, content, drawer, on_close, max_width) + ContextDrawer::new( + title, + header_actions, + header_opt, + footer_opt, + content, + drawer, + on_close, + max_width, + ) } diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index ea4aa3c6..759749fe 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -1,7 +1,7 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -use crate::widget::{button, column, container, icon, row, text, LayerContainer}; +use crate::widget::{button, column, container, icon, row, scrollable, text, LayerContainer}; use crate::{Apply, Element, Renderer, Theme}; use super::overlay::Overlay; @@ -25,7 +25,9 @@ pub struct ContextDrawer<'a, Message> { impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { pub fn new_inner( title: &'a str, - actions: Vec>, + header_actions: Vec>, + header_opt: Option>, + footer_opt: Option>, drawer: Drawer, on_close: Message, max_width: f32, @@ -35,6 +37,8 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { { let cosmic_theme::Spacing { space_xxs, + space_xs, + space_s, space_m, space_l, .. @@ -46,12 +50,13 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { Some(text::heading(title).width(Length::FillPortion(1)).center()) }; - let header = row::with_capacity(3) + let horizontal_padding = if max_width < 392.0 { space_s } else { space_l }; + + let header_row = row::with_capacity(3) .width(Length::Fixed(480.0)) .align_y(Alignment::Center) - .padding([space_m, space_l]) .push( - row::with_children(actions) + row::with_children(header_actions) .spacing(space_xxs) .width(Length::FillPortion(1)), ) @@ -64,12 +69,32 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { .width(Length::FillPortion(1)) .align_x(Alignment::End), ); - - let pane = column::with_capacity(2).push(header).push( - container(drawer.into()) + let header = column::with_capacity(2) + .width(Length::Fixed(480.0)) + .align_x(Alignment::Center) + .spacing(space_m) + .padding([space_m, horizontal_padding]) + .push(header_row) + .push_maybe(header_opt); + let footer = footer_opt.map(|element| { + container(element) + .width(Length::Fixed(480.0)) + .align_y(Alignment::Center) + .padding([space_xs, horizontal_padding]) + }); + let pane = column::with_capacity(3) + .push(header) + .push( + scrollable(container(drawer.into()).padding([ + 0, + horizontal_padding, + if footer.is_some() { 0 } else { space_l }, + horizontal_padding, + ])) .height(Length::Fill) .width(Length::Shrink), - ); + ) + .push_maybe(footer); // XXX new limits do not exactly handle the max width well for containers // XXX this is a hack to get around that @@ -90,7 +115,9 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { /// Creates an empty [`ContextDrawer`]. pub fn new( title: &'a str, - actions: Vec>, + header_actions: Vec>, + header_opt: Option>, + footer_opt: Option>, content: Content, drawer: Drawer, on_close: Message, @@ -100,7 +127,15 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { Content: Into>, Drawer: Into>, { - let drawer = Self::new_inner(title, actions, drawer, on_close, max_width); + let drawer = Self::new_inner( + title, + header_actions, + header_opt, + footer_opt, + drawer, + on_close, + max_width, + ); ContextDrawer { id: None, From 01bd999d281cef270742391fdd5f5addf0bc16f1 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 11 Nov 2024 16:39:30 +0100 Subject: [PATCH 056/556] fix(list_column): broken sliders and flex row in cosmic-settings --- src/widget/list/column.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/list/column.rs b/src/widget/list/column.rs index 597a4b2f..eb3b7646 100644 --- a/src/widget/list/column.rs +++ b/src/widget/list/column.rs @@ -42,7 +42,7 @@ impl<'a, Message: 'static> ListColumn<'a, Message> { // Ensure a minimum height of 32. let list_item = iced::widget::row![ - item.into(), + crate::widget::container(item).align_y(iced::Alignment::Center), crate::widget::vertical_space().height(iced::Length::Fixed(32.)) ] .align_y(iced::Alignment::Center); From be4c0a0848fb0bc45002ea443b3871f2b8880e22 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Mon, 11 Nov 2024 11:11:49 -0700 Subject: [PATCH 057/556] Move about widget to new about feature --- Cargo.toml | 3 ++- src/widget/about.rs | 1 - src/widget/mod.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 57b59b6b..21addb80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,8 @@ name = "cosmic" default = ["multi-window"] # Accessibility support a11y = ["iced/a11y", "iced_accessibility"] +# Enable about widget +about = ["desktop", "dep:license"] # Builds support for animated images animated-image = ["image", "dep:async-fs", "tokio?/io-util", "tokio?/fs"] # XXX autosize should not be used on winit windows unless dialogs @@ -40,7 +42,6 @@ desktop = [ "process", "dep:freedesktop-desktop-entry", "dep:mime", - "dep:license", "dep:shlex", "tokio?/io-util", "tokio?/net", diff --git a/src/widget/about.rs b/src/widget/about.rs index a5bd0b33..5026d410 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "desktop")] use { crate::{ iced::{Alignment, Length}, diff --git a/src/widget/mod.rs b/src/widget/mod.rs index b84e54ef..52acf06d 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -366,8 +366,8 @@ pub use warning::*; #[doc(inline)] pub use iced::widget::markdown; -#[cfg(feature = "desktop")] +#[cfg(feature = "about")] pub mod about; -#[cfg(feature = "desktop")] +#[cfg(feature = "about")] #[doc(inline)] pub use about::about; From 2ecef3c7b23d7ef68f319f86bb77e687886e3980 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 11 Nov 2024 16:58:38 -0500 Subject: [PATCH 058/556] feat: better a11y support --- Cargo.toml | 2 +- iced | 2 +- src/widget/aspect_ratio.rs | 11 +++++++++++ src/widget/autosize.rs | 22 ++++++++++++++++------ src/widget/context_drawer/widget.rs | 3 +-- src/widget/context_menu.rs | 12 ++++++++++++ src/widget/dnd_destination.rs | 12 ++++++++++++ src/widget/dnd_source.rs | 12 ++++++++++++ src/widget/dropdown/widget.rs | 11 +++++++++++ src/widget/header_bar.rs | 17 +++++++++++++++++ src/widget/id_container.rs | 22 ++++++++++++++++------ src/widget/layer_container.rs | 11 +++++++++++ src/widget/popover.rs | 12 ++++++++++++ src/widget/rectangle_tracker/mod.rs | 11 +++++++++++ 14 files changed, 144 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 21addb80..1fced16c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ rust-version = "1.80" name = "cosmic" [features] -default = ["multi-window"] +default = ["multi-window", "a11y"] # Accessibility support a11y = ["iced/a11y", "iced_accessibility"] # Enable about widget diff --git a/iced b/iced index 32972639..deb719e1 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 3297263977365a5d7e4c4cd9bbc13613b674763b +Subproject commit deb719e147ddcfee2c2cb52c9ca7a7b04fcd025a diff --git a/src/widget/aspect_ratio.rs b/src/widget/aspect_ratio.rs index 2bfa702c..ec8e2bed 100644 --- a/src/widget/aspect_ratio.rs +++ b/src/widget/aspect_ratio.rs @@ -252,6 +252,17 @@ where ) -> Option> { self.container.overlay(tree, layout, renderer, translation) } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: Layout<'_>, + state: &Tree, + p: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + self.container.a11y_nodes(layout, state, p) + } } impl<'a, Message, Renderer> From> diff --git a/src/widget/autosize.rs b/src/widget/autosize.rs index 553f9ea7..e9ed4d0b 100644 --- a/src/widget/autosize.rs +++ b/src/widget/autosize.rs @@ -230,12 +230,9 @@ where renderer: &Renderer, translation: Vector, ) -> Option> { - self.content.as_widget_mut().overlay( - &mut tree.children[0], - layout.children().next().unwrap(), - renderer, - translation, - ) + self.content + .as_widget_mut() + .overlay(&mut tree.children[0], layout, renderer, translation) } fn drag_destinations( @@ -261,6 +258,19 @@ where fn set_id(&mut self, id: crate::widget::Id) { self.id = id; } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: Layout<'_>, + state: &Tree, + p: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + let c_layout = layout.children().next().unwrap(); + let c_state = &state.children[0]; + self.content.as_widget().a11y_nodes(c_layout, c_state, p) + } } impl<'a, Message, Theme, Renderer> From> diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index 759749fe..6c55d30c 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -284,9 +284,8 @@ impl<'a, Message: Clone> Widget for ContextDraw state: &Tree, p: mouse::Cursor, ) -> iced_accessibility::A11yTree { - let c_layout = layout.children().next().unwrap(); let c_state = &state.children[0]; - self.content.as_widget().a11y_nodes(c_layout, c_state, p) + self.content.as_widget().a11y_nodes(layout, c_state, p) } fn drag_destinations( diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index ce0c94e7..338775b9 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -251,6 +251,18 @@ impl<'a, Message: Clone> Widget .overlay(), ) } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: iced_core::Layout<'_>, + state: &Tree, + p: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + let c_state = &state.children[0]; + self.content.as_widget().a11y_nodes(layout, c_state, p) + } } impl<'a, Message: Clone + 'a> From> for crate::Element<'a, Message> { diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index 6afac4df..f429f12a 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -541,6 +541,18 @@ impl<'a, Message: 'static> Widget fn set_id(&mut self, id: Id) { self.id = id; } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: iced_core::Layout<'_>, + state: &Tree, + p: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + let c_state = &state.children[0]; + self.container.as_widget().a11y_nodes(layout, c_state, p) + } } #[derive(Default)] diff --git a/src/widget/dnd_source.rs b/src/widget/dnd_source.rs index 80328120..38b921ee 100644 --- a/src/widget/dnd_source.rs +++ b/src/widget/dnd_source.rs @@ -381,6 +381,18 @@ impl< fn set_id(&mut self, id: Id) { self.id = id; } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: iced_core::Layout<'_>, + state: &Tree, + p: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + let c_state = &state.children[0]; + self.container.as_widget().a11y_nodes(layout, c_state, p) + } } impl< diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 3ff76ee1..20d46a20 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -246,6 +246,17 @@ impl<'a, S: AsRef, Message: 'a> Widget, + // state: &Tree, + // p: mouse::Cursor, + // ) -> iced_accessibility::A11yTree { + // // TODO + // } } impl<'a, S: AsRef, Message: 'a> From> diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index d9ba249f..972f92a4 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -265,6 +265,23 @@ impl<'a, Message: Clone + 'static> Widget, + state: &tree::Tree, + p: iced::mouse::Cursor, + ) -> iced_accessibility::A11yTree { + let c_layout = layout.children().next().unwrap(); + let c_state = &state.children[0]; + let ret = self + .header_bar_inner + .as_widget() + .a11y_nodes(c_layout, c_state, p); + ret + } } impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { diff --git a/src/widget/id_container.rs b/src/widget/id_container.rs index 9be387a5..ae19d088 100644 --- a/src/widget/id_container.rs +++ b/src/widget/id_container.rs @@ -165,12 +165,9 @@ where renderer: &Renderer, translation: Vector, ) -> Option> { - self.content.as_widget_mut().overlay( - &mut tree.children[0], - layout.children().next().unwrap(), - renderer, - translation, - ) + self.content + .as_widget_mut() + .overlay(&mut tree.children[0], layout, renderer, translation) } fn drag_destinations( @@ -196,6 +193,19 @@ where fn set_id(&mut self, id: crate::widget::Id) { self.id = id; } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: Layout<'_>, + state: &Tree, + p: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + let c_layout = layout.children().next().unwrap(); + let c_state = &state.children[0]; + self.content.as_widget().a11y_nodes(c_layout, c_state, p) + } } impl<'a, Message, Theme, Renderer> From> diff --git a/src/widget/layer_container.rs b/src/widget/layer_container.rs index 3109e666..016932b4 100644 --- a/src/widget/layer_container.rs +++ b/src/widget/layer_container.rs @@ -274,6 +274,17 @@ where fn set_id(&mut self, id: crate::widget::Id) { self.container.set_id(id); } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: iced_core::Layout<'_>, + state: &Tree, + p: iced::mouse::Cursor, + ) -> iced_accessibility::A11yTree { + self.container.a11y_nodes(layout, state, p) + } } impl<'a, Message, Renderer> From> diff --git a/src/widget/popover.rs b/src/widget/popover.rs index 4eb50ea2..974153d3 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -273,6 +273,18 @@ where dnd_rectangles, ); } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: Layout<'_>, + state: &Tree, + p: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + let c_state = &state.children[0]; + self.content.as_widget().a11y_nodes(layout, c_state, p) + } } impl<'a, Message, Renderer> From> diff --git a/src/widget/rectangle_tracker/mod.rs b/src/widget/rectangle_tracker/mod.rs index 30eed770..876b1255 100644 --- a/src/widget/rectangle_tracker/mod.rs +++ b/src/widget/rectangle_tracker/mod.rs @@ -309,6 +309,17 @@ where self.container .drag_destinations(state, layout, renderer, dnd_rectangles); } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: Layout<'_>, + state: &Tree, + p: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + self.container.a11y_nodes(layout, state, p) + } } impl<'a, Message, Renderer, I> From> From 4a97030f524212c8a3386967b80b76d91a660564 Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:56:59 -0500 Subject: [PATCH 059/556] fix(iced): a11y_nodes fix --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index deb719e1..d0c2a037 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit deb719e147ddcfee2c2cb52c9ca7a7b04fcd025a +Subproject commit d0c2a0371b476866cb0f7045fba0215425493d62 From e3fabf7d12e4a7d2662613ce11e5f73f3191dd17 Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:55:30 -0500 Subject: [PATCH 060/556] fix: revert change for overlay layout --- src/widget/autosize.rs | 9 ++++++--- src/widget/id_container.rs | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/widget/autosize.rs b/src/widget/autosize.rs index e9ed4d0b..8c65e5c0 100644 --- a/src/widget/autosize.rs +++ b/src/widget/autosize.rs @@ -230,9 +230,12 @@ where renderer: &Renderer, translation: Vector, ) -> Option> { - self.content - .as_widget_mut() - .overlay(&mut tree.children[0], layout, renderer, translation) + self.content.as_widget_mut().overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + translation, + ) } fn drag_destinations( diff --git a/src/widget/id_container.rs b/src/widget/id_container.rs index ae19d088..ae30289f 100644 --- a/src/widget/id_container.rs +++ b/src/widget/id_container.rs @@ -165,9 +165,12 @@ where renderer: &Renderer, translation: Vector, ) -> Option> { - self.content - .as_widget_mut() - .overlay(&mut tree.children[0], layout, renderer, translation) + self.content.as_widget_mut().overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + translation, + ) } fn drag_destinations( From aaadf7199ebed8a5a04ebed559f62455d622689e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 14 Nov 2024 09:56:03 -0500 Subject: [PATCH 061/556] refactor: add is_daemon setting The app can request to be treated by iced as a daemon so it can perform cleanup when its main window is closed. --- iced | 2 +- src/app/cosmic.rs | 2 +- src/app/mod.rs | 3 ++- src/app/settings.rs | 6 +++++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/iced b/iced index d0c2a037..25686357 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit d0c2a0371b476866cb0f7045fba0215425493d62 +Subproject commit 256863574bacfb1d2797c2a48cba7a3388cbeb59 diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index a2b229e6..30a116f6 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -160,7 +160,7 @@ where return Some(Message::WindowResize(id, width, height)); } iced::Event::Window(window::Event::Closed) => { - return Some(Message::SurfaceClosed(id)) + return Some(Message::SurfaceClosed(id)); } iced::Event::Window(window::Event::Focused) => return Some(Message::Focus(id)), iced::Event::Window(window::Event::Unfocused) => return Some(Message::Unfocus(id)), diff --git a/src/app/mod.rs b/src/app/mod.rs index b9166e68..7a730c8a 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -102,7 +102,8 @@ pub(crate) fn iced_settings( iced.default_font = settings.default_font; iced.default_text_size = iced::Pixels(settings.default_text_size); let exit_on_close = settings.exit_on_close; - iced.exit_on_close_request = exit_on_close; + iced.is_daemon = false; + iced.exit_on_close_request = settings.is_daemon; let mut window_settings = iced::window::Settings::default(); window_settings.exit_on_close_request = exit_on_close; iced.id = Some(App::APP_ID.to_owned()); diff --git a/src/app/settings.rs b/src/app/settings.rs index 338206e1..bd8a5f3d 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -57,8 +57,11 @@ pub struct Settings { /// Whether the window should be transparent. pub(crate) transparent: bool, - /// Whether the application should exit when there are no open windows + /// Whether the application window should close when the exit button is pressed pub(crate) exit_on_close: bool, + + /// Whether the application should act as a daemon + pub(crate) is_daemon: bool, } impl Settings { @@ -92,6 +95,7 @@ impl Default for Settings { theme: crate::theme::system_preference(), transparent: true, exit_on_close: true, + is_daemon: true, } } } From a355a049d917823dafcd6669cad6afa6983bc6c6 Mon Sep 17 00:00:00 2001 From: wiiznokes <78230769+wiiznokes@users.noreply.github.com> Date: Sat, 16 Nov 2024 03:38:29 +0100 Subject: [PATCH 062/556] feat!(app): ContextDrawer return type for context_drawer method --- examples/about/Cargo.toml | 1 + examples/about/src/main.rs | 16 +++--- src/app/context_drawer.rs | 62 +++++++++++++++++++++++ src/app/core.rs | 8 --- src/app/mod.rs | 76 +++++++++++++---------------- src/widget/context_drawer/mod.rs | 4 +- src/widget/context_drawer/widget.rs | 16 +++--- 7 files changed, 115 insertions(+), 68 deletions(-) create mode 100644 src/app/context_drawer.rs diff --git a/examples/about/Cargo.toml b/examples/about/Cargo.toml index 76a35791..b257999c 100644 --- a/examples/about/Cargo.toml +++ b/examples/about/Cargo.toml @@ -24,4 +24,5 @@ features = [ "wgpu", "single-instance", "multi-window", + "about", ] diff --git a/examples/about/src/main.rs b/examples/about/src/main.rs index 6bc4a4ff..0625e152 100644 --- a/examples/about/src/main.rs +++ b/examples/about/src/main.rs @@ -3,6 +3,7 @@ //! Application API example +use cosmic::app::context_drawer::{self, ContextDrawer}; use cosmic::app::{Core, Settings, Task}; use cosmic::iced::widget::column; use cosmic::iced_core::Size; @@ -35,6 +36,7 @@ pub struct App { core: Core, nav_model: nav_bar::Model, about: About, + show_about: bool, } /// Implement [`cosmic::Application`] to integrate with COSMIC. @@ -80,6 +82,7 @@ impl cosmic::Application for App { core, nav_model, about, + show_about: false, }; let command = app.update_title(); @@ -98,20 +101,17 @@ impl cosmic::Application for App { self.update_title() } - fn context_drawer(&self) -> Option> { - if !self.core.window.show_context { - return None; - } - - Some(widget::about(&self.about, Message::Open)) + fn context_drawer(&self) -> Option> { + self.show_about + .then(|| context_drawer::about(&self.about, Message::Open, Message::ToggleAbout)) } /// Handle application events here. fn update(&mut self, message: Self::Message) -> Task { match message { Message::ToggleAbout => { - self.core.window.show_context = !self.core.window.show_context; - self.core.set_show_context(self.core.window.show_context) + self.set_show_context(!self.core.window.show_context); + self.show_about = !self.show_about; } Message::Open(url) => match open::that_detached(url) { Ok(_) => (), diff --git a/src/app/context_drawer.rs b/src/app/context_drawer.rs new file mode 100644 index 00000000..af937168 --- /dev/null +++ b/src/app/context_drawer.rs @@ -0,0 +1,62 @@ +use std::borrow::Cow; + +use crate::Element; + +pub struct ContextDrawer<'a, Message: Clone + 'static> { + pub title: Option>, + pub header_actions: Vec>, + pub header: Option>, + pub content: Element<'a, Message>, + pub footer: Option>, + pub on_close: Message, +} + +#[cfg(feature = "about")] +pub fn about<'a, Message: Clone + 'static>( + about: &'a crate::widget::about::About, + on_url_press: impl Fn(String) -> Message, + on_close: Message, +) -> ContextDrawer<'a, Message> { + context_drawer(crate::widget::about(about, on_url_press), on_close) +} + +pub fn context_drawer<'a, Message: Clone + 'static>( + content: impl Into>, + on_close: Message, +) -> ContextDrawer<'a, Message> { + ContextDrawer { + title: None, + content: content.into(), + header_actions: vec![], + footer: None, + on_close, + header: None, + } +} + +impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { + /// Set a context drawer header title + pub fn title(mut self, title: impl Into>) -> Self { + self.title = Some(title.into()); + self + } + /// App-specific actions at the start of the context drawer header + pub fn header_actions( + mut self, + header_actions: impl IntoIterator>, + ) -> Self { + self.header_actions = header_actions.into_iter().collect(); + self + } + /// Non-scrolling elements placed below the context drawer title row + pub fn header(mut self, header: impl Into>) -> Self { + self.header = Some(header.into()); + self + } + + /// Elements placed below the context drawer scrollable + pub fn footer(mut self, footer: impl Into>) -> Self { + self.footer = Some(footer.into()); + self + } +} diff --git a/src/app/core.rs b/src/app/core.rs index c805188b..f090cdc4 100644 --- a/src/app/core.rs +++ b/src/app/core.rs @@ -26,8 +26,6 @@ pub struct NavBar { #[allow(clippy::struct_excessive_bools)] #[derive(Clone)] pub struct Window { - /// Label to display as context drawer title. - pub context_title: String, /// Label to display as header bar title. pub header_title: String, pub use_template: bool, @@ -127,7 +125,6 @@ impl Default for Core { }) .unwrap_or_default(), window: Window { - context_title: String::new(), header_title: String::new(), use_template: true, content_container: true, @@ -188,11 +185,6 @@ impl Core { self.is_condensed_update(); } - /// Set context drawer header title - pub fn set_context_title(&mut self, title: String) { - self.window.context_title = title; - } - /// Set header bar title pub fn set_header_title(&mut self, title: String) { self.window.header_title = title; diff --git a/src/app/mod.rs b/src/app/mod.rs index 7a730c8a..e4b59d62 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -7,6 +7,7 @@ //! example in our repository. pub mod command; +pub mod context_drawer; mod core; pub mod cosmic; #[cfg(all(feature = "winit", feature = "multi-window"))] @@ -54,10 +55,9 @@ pub use self::core::Core; pub use self::settings::Settings; use crate::prelude::*; use crate::theme::THEME; -use crate::widget::{ - container, context_drawer, horizontal_space, id_container, menu, nav_bar, popover, -}; +use crate::widget::{container, horizontal_space, id_container, menu, nav_bar, popover}; use apply::Apply; +use context_drawer::ContextDrawer; use iced::window; use iced::{Length, Subscription}; pub use message::Message; @@ -455,22 +455,8 @@ where fn init(core: Core, flags: Self::Flags) -> (Self, Task); /// Displays a context drawer on the side of the application window when `Some`. - fn context_drawer(&self) -> Option> { - None - } - - /// App-specific actions at the start of the context drawer header - fn context_header_actions(&self) -> Vec>> { - Vec::new() - } - - /// Non-scrolling elements placed below the context drawer title row - fn context_drawer_header(&self) -> Option>> { - None - } - - /// Elements placed below the context drawer scrollable - fn context_drawer_footer(&self) -> Option>> { + /// Use the [`ApplicationExt::set_show_context`] function for this to take effect. + fn context_drawer(&self) -> Option> { None } @@ -638,11 +624,6 @@ pub trait ApplicationExt: Application { /// Get the title of a window. fn title(&self, id: window::Id) -> &str; - /// Set the context drawer title. - fn set_context_title(&mut self, title: String) { - self.core_mut().set_context_title(title); - } - /// Set the context drawer visibility. fn set_show_context(&mut self, show: bool) { self.core_mut().set_show_context(show); @@ -746,21 +727,21 @@ impl ApplicationExt for App { }; if self.nav_model().is_none() || core.show_content() { - let main_content = self.view().map(Message::App); + let main_content = self.view(); //TODO: reduce duplication let context_width = core.context_width(has_nav); - if core.window.context_is_overlay { + if core.window.context_is_overlay && core.window.show_context { if let Some(context) = self.context_drawer() { widgets.push( - context_drawer( - &core.window.context_title, - self.context_header_actions(), - self.context_drawer_header(), - self.context_drawer_footer(), - Message::Cosmic(cosmic::Message::ContextDrawer(false)), + crate::widget::context_drawer( + context.title, + context.header_actions, + context.header, + context.footer, + context.on_close, main_content, - context.map(Message::App), + context.content, context_width, ) .apply(|drawer| { @@ -775,29 +756,40 @@ impl ApplicationExt for App { } else { [0, 0, 0, 0] }) - .into(), + .apply(Element::from) + .map(Message::App), ); } else { //TODO: container and padding are temporary, until //the `resize_border` is moved to not cover window content - widgets.push(container(main_content).padding(main_content_padding).into()); + widgets.push( + container(main_content.map(Message::App)) + .padding(main_content_padding) + .into(), + ); } } else { //TODO: hide content when out of space //TODO: container and padding are temporary, until //the `resize_border` is moved to not cover window content - widgets.push(container(main_content).padding(main_content_padding).into()); + widgets.push( + container(main_content.map(Message::App)) + .padding(main_content_padding) + .into(), + ); if let Some(context) = self.context_drawer() { widgets.push( crate::widget::ContextDrawer::new_inner( - &core.window.context_title, - self.context_header_actions(), - self.context_drawer_header(), - self.context_drawer_footer(), - context.map(Message::App), - Message::Cosmic(cosmic::Message::ContextDrawer(false)), + context.title, + context.header_actions, + context.header, + context.footer, + context.content, + context.on_close, context_width, ) + .apply(Element::from) + .map(Message::App) .apply(container) .width(context_width) .apply(|drawer| { diff --git a/src/widget/context_drawer/mod.rs b/src/widget/context_drawer/mod.rs index 5a90fc8a..f1621220 100644 --- a/src/widget/context_drawer/mod.rs +++ b/src/widget/context_drawer/mod.rs @@ -6,13 +6,15 @@ mod overlay; mod widget; +use std::borrow::Cow; + pub use widget::ContextDrawer; use crate::Element; /// An overlayed widget that attaches a toggleable context drawer to the view. pub fn context_drawer<'a, Message: Clone + 'static, Content, Drawer>( - title: &'a str, + title: Option>, header_actions: Vec>, header_opt: Option>, footer_opt: Option>, diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index 6c55d30c..866c024c 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -1,6 +1,8 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 +use std::borrow::Cow; + use crate::widget::{button, column, container, icon, row, scrollable, text, LayerContainer}; use crate::{Apply, Element, Renderer, Theme}; @@ -24,7 +26,7 @@ pub struct ContextDrawer<'a, Message> { impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { pub fn new_inner( - title: &'a str, + title: Option>, header_actions: Vec>, header_opt: Option>, footer_opt: Option>, @@ -44,12 +46,6 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { .. } = crate::theme::active().cosmic().spacing; - let title_opt = if title.is_empty() { - None - } else { - Some(text::heading(title).width(Length::FillPortion(1)).center()) - }; - let horizontal_padding = if max_width < 392.0 { space_s } else { space_l }; let header_row = row::with_capacity(3) @@ -60,7 +56,9 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { .spacing(space_xxs) .width(Length::FillPortion(1)), ) - .push_maybe(title_opt) + .push_maybe( + title.map(|title| text::heading(title).width(Length::FillPortion(1)).center()), + ) .push( button::text("Close") .trailing_icon(icon::from_name("go-next-symbolic")) @@ -114,7 +112,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { /// Creates an empty [`ContextDrawer`]. pub fn new( - title: &'a str, + title: Option>, header_actions: Vec>, header_opt: Option>, footer_opt: Option>, From e7b9c6493a7506043e387adfd46dd405b73c8ae7 Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Mon, 18 Nov 2024 12:49:33 +0100 Subject: [PATCH 063/556] improv: icon support for menus --- examples/menu/src/main.rs | 13 +++++- src/widget/menu/menu_tree.rs | 88 ++++++++++++++++++++++-------------- 2 files changed, 65 insertions(+), 36 deletions(-) diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs index d9dc7854..1b564a1b 100644 --- a/examples/menu/src/main.rs +++ b/examples/menu/src/main.rs @@ -159,18 +159,27 @@ pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap) -> El menu::items( key_binds, vec![ - menu::Item::Button("New window", Action::WindowNew), + menu::Item::Button( + "New window", + Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), + Action::WindowNew, + ), menu::Item::Divider, menu::Item::Folder( "View", vec![menu::Item::CheckBox( "Hide content", + Some(cosmic::widget::icon::from_name("view-conceal-symbolic").into()), config.hide_content, Action::ToggleHideContent, )], ), menu::Item::Divider, - menu::Item::Button("Quit", Action::WindowClose), + menu::Item::Button( + "Quit", + Some(cosmic::widget::icon::from_name("window-close-symbolic").into()), + Action::WindowClose, + ), ], ), )]) diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index 0c52e58c..bc2cbb59 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -9,6 +9,7 @@ use std::rc::Rc; use iced_widget::core::{renderer, Element}; use crate::iced_core::{Alignment, Length}; +use crate::widget::icon; use crate::widget::menu::action::MenuAction; use crate::widget::menu::key_bind::KeyBind; use crate::{theme, widget}; @@ -171,11 +172,11 @@ pub fn menu_button<'a, Message: 'a>( /// - `Divider` - Represents a divider between menu items. pub enum MenuItem>> { /// Represents a button menu item. - Button(L, A), + Button(L, Option, A), /// Represents a button menu item that is disabled. - ButtonDisabled(L, A), + ButtonDisabled(L, Option, A), /// Represents a checkbox menu item. - CheckBox(L, bool, A), + CheckBox(L, Option, bool, A), /// Represents a folder menu item. Folder(L, Vec>), /// Represents a divider between menu items. @@ -240,53 +241,72 @@ where .enumerate() .flat_map(|(i, item)| { let mut trees = vec![]; + let spacing = crate::theme::active().cosmic().spacing; + match item { - MenuItem::Button(label, action) => { + MenuItem::Button(label, icon, action) => { let key = find_key(&action, key_binds); - let menu_button = menu_button(vec![ + let mut items = vec![ widget::text(label).into(), widget::horizontal_space().width(Length::Fill).into(), widget::text(key).into(), - ]) - .on_press(action.message()); + ]; + + if let Some(icon) = icon { + items.insert(0, widget::icon::icon(icon).size(16).into()); + items.insert(1, widget::Space::with_width(spacing.space_xxs).into()); + } + + let menu_button = menu_button(items).on_press(action.message()); trees.push(MenuTree::::new(menu_button)); } - MenuItem::ButtonDisabled(label, action) => { + MenuItem::ButtonDisabled(label, icon, action) => { let key = find_key(&action, key_binds); - let menu_button = menu_button(vec![ + + let mut items = vec![ widget::text(label).into(), widget::horizontal_space().width(Length::Fill).into(), widget::text(key).into(), - ]); + ]; + + if let Some(icon) = icon { + items.insert(0, widget::icon::icon(icon).size(16).into()); + items.insert(1, widget::Space::with_width(spacing.space_xxs).into()); + } + + let menu_button = menu_button(items); trees.push(MenuTree::::new(menu_button)); } - MenuItem::CheckBox(label, value, action) => { + MenuItem::CheckBox(label, icon, value, action) => { let key = find_key(&action, key_binds); - trees.push(MenuTree::new( - menu_button(vec![ - if value { - widget::icon::from_name("object-select-symbolic") - .size(16) - .icon() - .class(theme::Svg::Custom(Rc::new(|theme| { - iced_widget::svg::Style { - color: Some(theme.cosmic().accent_color().into()), - } - }))) - .width(Length::Fixed(16.0)) - .into() - } else { - widget::Space::with_width(Length::Fixed(16.0)).into() - }, - widget::Space::with_width(Length::Fixed(8.0)).into(), - widget::text(label).align_x(iced::Alignment::Start).into(), - widget::horizontal_space().width(Length::Fill).into(), - widget::text(key).into(), - ]) - .on_press(action.message()), - )); + let mut items = vec![ + if value { + widget::icon::from_name("object-select-symbolic") + .size(16) + .icon() + .class(theme::Svg::Custom(Rc::new(|theme| { + iced_widget::svg::Style { + color: Some(theme.cosmic().accent_color().into()), + } + }))) + .width(Length::Fixed(16.0)) + .into() + } else { + widget::Space::with_width(Length::Fixed(16.0)).into() + }, + widget::Space::with_width(spacing.space_xxs).into(), + widget::text(label).align_x(iced::Alignment::Start).into(), + widget::horizontal_space().width(Length::Fill).into(), + widget::text(key).into(), + ]; + + if let Some(icon) = icon { + items.insert(1, widget::icon::icon(icon).size(16).into()); + } + + trees.push(MenuTree::new(menu_button(items).on_press(action.message()))); } MenuItem::Folder(label, children) => { trees.push(MenuTree::::with_children( From f3d9e4c0d31f0e70c1fe8c8fcc2846d893952264 Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Mon, 18 Nov 2024 13:00:05 +0100 Subject: [PATCH 064/556] fix: examples --- examples/context-menu/Cargo.toml | 2 +- examples/context-menu/src/main.rs | 11 ++++++++--- examples/menu/Cargo.toml | 2 +- examples/nav-context/Cargo.toml | 2 +- examples/nav-context/src/main.rs | 12 ++++++++---- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/examples/context-menu/Cargo.toml b/examples/context-menu/Cargo.toml index 88a491e0..133c642e 100644 --- a/examples/context-menu/Cargo.toml +++ b/examples/context-menu/Cargo.toml @@ -11,4 +11,4 @@ tracing-log = "0.2.0" [dependencies.libcosmic] path = "../../" default-features = false -features = ["debug", "winit", "tokio", "xdg-portal"] +features = ["debug", "winit", "tokio", "xdg-portal", "multi-window"] diff --git a/examples/context-menu/src/main.rs b/examples/context-menu/src/main.rs index 9f809dbe..5ade4966 100644 --- a/examples/context-menu/src/main.rs +++ b/examples/context-menu/src/main.rs @@ -74,7 +74,11 @@ impl cosmic::Application for App { }; app.set_header_title("COSMIC Context Menu Demo".into()); - let command = app.set_window_title("COSMIC Context Menu Demo".into()); + let command = if let Some(win_id) = app.core.main_window_id() { + app.set_window_title("COSMIC Context Menu Demo".into(), win_id) + } else { + Task::none() + }; (app, command) } @@ -108,18 +112,19 @@ impl App { Some(menu::items( &HashMap::new(), vec![ - menu::Item::Button("New window", ContextMenuAction::WindowNew), + menu::Item::Button("New window", None, ContextMenuAction::WindowNew), menu::Item::Divider, menu::Item::Folder( "View", vec![menu::Item::CheckBox( "Hide content", + None, self.hide_content, ContextMenuAction::ToggleHideContent, )], ), menu::Item::Divider, - menu::Item::Button("Quit", ContextMenuAction::WindowClose), + menu::Item::Button("Quit", None, ContextMenuAction::WindowClose), ], )) } diff --git a/examples/menu/Cargo.toml b/examples/menu/Cargo.toml index 44ece16e..4348ca0b 100644 --- a/examples/menu/Cargo.toml +++ b/examples/menu/Cargo.toml @@ -11,4 +11,4 @@ tracing-log = "0.2.0" [dependencies.libcosmic] path = "../../" default-features = false -features = ["debug", "winit", "tokio", "xdg-portal"] +features = ["debug", "winit", "tokio", "xdg-portal", "multi-window"] diff --git a/examples/nav-context/Cargo.toml b/examples/nav-context/Cargo.toml index 5ddad7fc..d0f3bce5 100644 --- a/examples/nav-context/Cargo.toml +++ b/examples/nav-context/Cargo.toml @@ -11,4 +11,4 @@ tracing-log = "0.2.0" [dependencies.libcosmic] path = "../../" default-features = false -features = ["debug", "winit", "tokio", "xdg-portal"] +features = ["debug", "winit", "tokio", "xdg-portal", "multi-window"] diff --git a/examples/nav-context/src/main.rs b/examples/nav-context/src/main.rs index 07863542..2285292a 100644 --- a/examples/nav-context/src/main.rs +++ b/examples/nav-context/src/main.rs @@ -135,9 +135,9 @@ impl cosmic::Application for App { Some(menu::items( &HashMap::new(), vec![ - menu::Item::Button("Move Up", NavMenuAction::MoveUp(id)), - menu::Item::Button("Move Down", NavMenuAction::MoveDown(id)), - menu::Item::Button("Delete", NavMenuAction::Delete(id)), + menu::Item::Button("Move Up", None, NavMenuAction::MoveUp(id)), + menu::Item::Button("Move Down", None, NavMenuAction::MoveDown(id)), + menu::Item::Button("Delete", None, NavMenuAction::Delete(id)), ], )) } @@ -204,6 +204,10 @@ where let header_title = self.active_page_title().to_owned(); let window_title = format!("{header_title} — COSMIC AppDemo"); self.set_header_title(header_title); - self.set_window_title(window_title) + if let Some(win_id) = self.core.main_window_id() { + self.set_window_title(window_title, win_id) + } else { + Task::none() + } } } From 62b0c8a401c175ab956229b2901f2fdb8b96aeab Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Mon, 18 Nov 2024 13:02:58 +0100 Subject: [PATCH 065/556] fix: icon spacing and size --- src/widget/menu/menu_tree.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index bc2cbb59..85ef8f5e 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -253,7 +253,7 @@ where ]; if let Some(icon) = icon { - items.insert(0, widget::icon::icon(icon).size(16).into()); + items.insert(0, widget::icon::icon(icon).size(14).into()); items.insert(1, widget::Space::with_width(spacing.space_xxs).into()); } @@ -271,7 +271,7 @@ where ]; if let Some(icon) = icon { - items.insert(0, widget::icon::icon(icon).size(16).into()); + items.insert(0, widget::icon::icon(icon).size(14).into()); items.insert(1, widget::Space::with_width(spacing.space_xxs).into()); } @@ -303,7 +303,8 @@ where ]; if let Some(icon) = icon { - items.insert(1, widget::icon::icon(icon).size(16).into()); + items.insert(1, widget::Space::with_width(spacing.space_xxs).into()); + items.insert(2, widget::icon::icon(icon).size(14).into()); } trees.push(MenuTree::new(menu_button(items).on_press(action.message()))); From dd79e900dafed5e11e6306ba887e9b2b906daf91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Fri, 15 Nov 2024 00:56:59 +0100 Subject: [PATCH 066/556] fix(footer): corner radius --- src/theme/style/iced.rs | 16 ++++------------ src/widget/context_drawer/widget.rs | 3 +-- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 8a49ec09..530df55f 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -6,7 +6,6 @@ use crate::theme::{CosmicComponent, Theme, TRANSPARENT_COMPONENT}; use cosmic_theme::composite::over; use iced::{ - border, color, overlay::menu, widget::{ button as iced_button, checkbox as iced_checkbox, container as iced_container, pane_grid, @@ -14,7 +13,6 @@ use iced::{ slider::{self, Rail}, svg, toggler, }, - Gradient, }; use iced_core::{Background, Border, Color, Shadow, Vector}; use iced_widget::{pane_grid::Highlight, text_editor, text_input}; @@ -411,7 +409,7 @@ impl<'a> Container<'a> { text_color: Some(Color::from(theme.background.on)), background: Some(iced::Background::Color(theme.background.base.into())), border: Border { - radius: theme.corner_radii.radius_xs.into(), + radius: theme.corner_radii.radius_s.into(), ..Default::default() }, shadow: Shadow::default(), @@ -425,7 +423,7 @@ impl<'a> Container<'a> { text_color: Some(Color::from(theme.primary.on)), background: Some(iced::Background::Color(theme.primary.base.into())), border: Border { - radius: theme.corner_radii.radius_xs.into(), + radius: theme.corner_radii.radius_s.into(), ..Default::default() }, shadow: Shadow::default(), @@ -439,7 +437,7 @@ impl<'a> Container<'a> { text_color: Some(Color::from(theme.secondary.on)), background: Some(iced::Background::Color(theme.secondary.base.into())), border: Border { - radius: theme.corner_radii.radius_xs.into(), + radius: theme.corner_radii.radius_s.into(), ..Default::default() }, shadow: Shadow::default(), @@ -531,13 +529,7 @@ impl iced_container::Catalog for Theme { } Container::ContextDrawer => { - let mut appearance = crate::style::Container::primary(cosmic); - - appearance.border = Border { - color: cosmic.primary.divider.into(), - width: 0.0, - radius: cosmic.corner_radii.radius_s.into(), - }; + let mut appearance = Container::primary(cosmic); appearance.shadow = Shadow { color: cosmic.shade.into(), diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index 866c024c..fafa8a19 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -39,7 +39,6 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { { let cosmic_theme::Spacing { space_xxs, - space_xs, space_s, space_m, space_l, @@ -78,7 +77,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { container(element) .width(Length::Fixed(480.0)) .align_y(Alignment::Center) - .padding([space_xs, horizontal_padding]) + .padding([space_xxs, horizontal_padding]) }); let pane = column::with_capacity(3) .push(header) From 2b7d171fed2eae791b76fa61c967a6bb9dcca836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Tue, 19 Nov 2024 01:34:46 +0100 Subject: [PATCH 067/556] update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 25686357..d7b189d2 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 256863574bacfb1d2797c2a48cba7a3388cbeb59 +Subproject commit d7b189d2aa2b4b3b2eabed7a796081a521aedacf From a58c73334ed8727918106252652dd9b22ca279ff Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:02:25 -0500 Subject: [PATCH 068/556] fix(iced): autosize fixes --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index d7b189d2..501d7aae 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit d7b189d2aa2b4b3b2eabed7a796081a521aedacf +Subproject commit 501d7aaebe113a785f53e3f139e48be8a6dd4d1a From d79faab7899ebf1c9779415827ff7b629db87589 Mon Sep 17 00:00:00 2001 From: wiiznokes <78230769+wiiznokes@users.noreply.github.com> Date: Tue, 19 Nov 2024 20:03:31 +0100 Subject: [PATCH 069/556] fix(widget): impl Catalog for qr_code --- src/theme/style/iced.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 530df55f..f51e2650 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -1478,3 +1478,19 @@ impl iced_widget::markdown::Catalog for Theme { }) } } + +#[cfg(feature = "qr_code")] +impl iced_widget::qr_code::Catalog for Theme { + type Class<'a> = iced_widget::qr_code::StyleFn<'a, Self>; + + fn default<'a>() -> Self::Class<'a> { + Box::new(|_theme| iced_widget::qr_code::Style { + cell: Color::BLACK, + background: Color::WHITE, + }) + } + + fn style(&self, class: &Self::Class<'_>) -> iced_widget::qr_code::Style { + class(self) + } +} From bc89a8aede2f62b2836bbb008431f1f41c0ac507 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 19 Nov 2024 19:45:11 -0500 Subject: [PATCH 070/556] fix: don't apply drawer position as overlay translation --- src/widget/context_drawer/overlay.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/widget/context_drawer/overlay.rs b/src/widget/context_drawer/overlay.rs index cc220d10..cc62b2db 100644 --- a/src/widget/context_drawer/overlay.rs +++ b/src/widget/context_drawer/overlay.rs @@ -118,9 +118,8 @@ where layout: Layout<'_>, renderer: &crate::Renderer, ) -> Option> { - let translation = iced::Vector::new(self.position.x, self.position.y); self.content .as_widget_mut() - .overlay(self.tree, layout, renderer, translation) + .overlay(self.tree, layout, renderer, iced::Vector::default()) } } From b14fde9033e02694e7fa4be6c49c5fcee6d51200 Mon Sep 17 00:00:00 2001 From: Bryan Hyland Date: Tue, 19 Nov 2024 08:17:40 -0800 Subject: [PATCH 071/556] feat!(spin_button): refactor and support vertical widget variant --- Cargo.toml | 1 - examples/spin-button/Cargo.toml | 12 ++ examples/spin-button/src/main.rs | 201 ++++++++++++++++++++++++ src/widget/mod.rs | 2 +- src/widget/spin_button.rs | 260 +++++++++++++++++++++++++++++++ src/widget/spin_button/mod.rs | 108 ------------- src/widget/spin_button/model.rs | 156 ------------------- 7 files changed, 474 insertions(+), 266 deletions(-) create mode 100644 examples/spin-button/Cargo.toml create mode 100644 examples/spin-button/src/main.rs create mode 100644 src/widget/spin_button.rs delete mode 100644 src/widget/spin_button/mod.rs delete mode 100644 src/widget/spin_button/model.rs diff --git a/Cargo.toml b/Cargo.toml index 1fced16c..bc2d4237 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,7 +95,6 @@ cosmic-config = { path = "cosmic-config" } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } css-color = "0.2.5" derive_setters = "0.1.5" -fraction = "0.15.3" image = { version = "0.25.1", optional = true } lazy_static = "1.4.0" libc = { version = "0.2.155", optional = true } diff --git a/examples/spin-button/Cargo.toml b/examples/spin-button/Cargo.toml new file mode 100644 index 00000000..3088a313 --- /dev/null +++ b/examples/spin-button/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "spin-button" +version = "0.1.0" +edition = "2021" + +[dependencies] +fraction = "0.15.3" + +[dependencies.libcosmic] +features = ["debug", "multi-window", "wayland", "winit", "desktop", "tokio"] +path = "../.." +default-features = false diff --git a/examples/spin-button/src/main.rs b/examples/spin-button/src/main.rs new file mode 100644 index 00000000..602f3f4a --- /dev/null +++ b/examples/spin-button/src/main.rs @@ -0,0 +1,201 @@ +use cosmic::iced::Length; +use cosmic::widget::{column, container, spin_button}; +use cosmic::Apply; +use cosmic::{ + app::{Core, Task}, + iced::{ + self, + alignment::{Horizontal, Vertical}, + Alignment, Size, + }, + Application, Element, +}; +use fraction::Decimal; + +pub struct SpinButtonExamplApp { + core: Core, + i8_num: i8, + i8_str: String, + i16_num: i16, + i16_str: String, + i32_num: i32, + i32_str: String, + i64_num: i64, + i64_str: String, + i128_num: i128, + i128_str: String, + f32_num: f32, + f32_str: String, + f64_num: f64, + f64_str: String, + dec_num: Decimal, + dec_str: String, +} + +#[derive(Debug, Clone)] +pub enum Message { + UpdateI8(i8), + UpdateI16(i16), + UpdateI32(i32), + UpdateI64(i64), + UpdateI128(i128), + UpdateF32(f32), + UpdateF64(f64), + UpdateDec(Decimal), +} + +impl Application for SpinButtonExamplApp { + type Executor = cosmic::executor::Default; + type Flags = (); + type Message = Message; + + const APP_ID: &'static str = "com.system76.SpinButtonExample"; + + fn core(&self) -> &Core { + &self.core + } + + fn core_mut(&mut self) -> &mut Core { + &mut self.core + } + + fn init(core: Core, _flags: Self::Flags) -> (Self, Task) { + ( + Self { + core, + i8_num: 0, + i8_str: 0.to_string(), + i16_num: 0, + i16_str: 0.to_string(), + i32_num: 0, + i32_str: 0.to_string(), + i64_num: 15, + i64_str: 15.to_string(), + i128_num: 0, + i128_str: 0.to_string(), + f32_num: 0., + f32_str: format!("{:.02}", 0.0), + f64_num: 0., + f64_str: format!("{:.02}", 0.0), + dec_num: Decimal::from(0.0), + dec_str: format!("{:.02}", 0.0), + }, + Task::none(), + ) + } + + fn update(&mut self, message: Self::Message) -> Task { + match message { + Message::UpdateI8(value) => { + self.i8_num = value; + self.i8_str = value.to_string(); + } + + Message::UpdateI16(value) => { + self.i16_num = value; + self.i16_str = value.to_string(); + } + + Message::UpdateI32(value) => { + self.i32_num = value; + self.i32_str = value.to_string(); + } + + Message::UpdateI64(value) => { + self.i64_num = value; + self.i64_str = value.to_string(); + } + + Message::UpdateI128(value) => { + self.i128_num = value; + self.i128_str = value.to_string(); + } + + Message::UpdateF32(value) => { + self.f32_num = value; + self.f32_str = format!("{value:.02}"); + } + + Message::UpdateF64(value) => { + self.f64_num = value; + self.f64_str = format!("{value:.02}"); + } + + Message::UpdateDec(value) => { + self.dec_num = value; + self.dec_str = format!("{value:.02}"); + } + } + + Task::none() + } + + fn view(&self) -> Element { + let space_xs = cosmic::theme::active().cosmic().spacing.space_xs; + + let vert_spinner_row = iced::widget::row![ + spin_button::vertical(&self.i8_str, self.i8_num, 1, -5, 5, Message::UpdateI8), + spin_button::vertical(&self.i16_str, self.i16_num, 1, 0, 10, Message::UpdateI16), + spin_button::vertical(&self.i32_str, self.i32_num, 1, 0, 12, Message::UpdateI32), + spin_button::vertical(&self.i64_str, self.i64_num, 10, 15, 35, Message::UpdateI64), + ] + .spacing(space_xs) + .align_y(Vertical::Center); + + let horiz_spinner_row = iced::widget::column![ + spin_button( + &self.i128_str, + self.i128_num, + 100, + -1000, + 500, + Message::UpdateI128 + ), + spin_button( + &self.f32_str, + self.f32_num, + 1.3, + -35.3, + 12.3, + Message::UpdateF32 + ), + spin_button( + &self.f64_str, + self.f64_num, + 1.3, + 0.0, + 3.0, + Message::UpdateF64 + ), + spin_button( + &self.dec_str, + self.dec_num, + Decimal::from(0.25), + Decimal::from(-5.0), + Decimal::from(5.0), + Message::UpdateDec + ), + ] + .spacing(space_xs) + .align_x(Alignment::Center); + + column::with_capacity(3) + .push(vert_spinner_row) + .push(horiz_spinner_row) + .spacing(space_xs) + .align_x(Alignment::Center) + .apply(container) + .width(Length::Fill) + .height(Length::Fill) + .align_x(Horizontal::Center) + .align_y(Vertical::Center) + .into() + } +} + +fn main() -> Result<(), Box> { + let settings = cosmic::app::Settings::default().size(Size::new(550., 1024.)); + cosmic::app::run::(settings, ())?; + + Ok(()) +} diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 52acf06d..06dd4e85 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -311,7 +311,7 @@ pub mod settings; pub mod spin_button; #[doc(inline)] -pub use spin_button::{spin_button, SpinButton}; +pub use spin_button::{spin_button, vertical as vertical_spin_button, SpinButton}; pub mod tab_bar; diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs new file mode 100644 index 00000000..6c0df95f --- /dev/null +++ b/src/widget/spin_button.rs @@ -0,0 +1,260 @@ +// Copyright 2022 System76 +// SPDX-License-Identifier: MPL-2.0 + +//! A control for incremental adjustments of a value. + +use crate::{ + theme, + widget::{button, column, container, icon, row, text}, + Element, +}; +use apply::Apply; +use derive_setters::Setters; +use iced::{alignment::Horizontal, Border, Shadow}; +use iced::{Alignment, Length}; +use std::marker::PhantomData; +use std::ops::{Add, Sub}; +use std::{borrow::Cow, fmt::Display}; + +/// Horizontal spin button widget. +pub fn spin_button<'a, T, M>( + label: impl Into>, + value: T, + step: T, + min: T, + max: T, + on_press: impl Fn(T) -> M + 'static, +) -> SpinButton<'a, T, M> +where + T: Copy + Sub + Add + PartialOrd, +{ + SpinButton::new( + label, + value, + step, + min, + max, + Orientation::Horizontal, + on_press, + ) +} + +/// Vertical spin button widget. +pub fn vertical<'a, T, M>( + label: impl Into>, + value: T, + step: T, + min: T, + max: T, + on_press: impl Fn(T) -> M + 'static, +) -> SpinButton<'a, T, M> +where + T: Copy + Sub + Add + PartialOrd, +{ + SpinButton::new( + label, + value, + step, + min, + max, + Orientation::Vertical, + on_press, + ) +} + +#[derive(Clone, Copy)] +enum Orientation { + Horizontal, + Vertical, +} + +pub struct SpinButton<'a, T, M> +where + T: Copy + Sub + Add + PartialOrd, +{ + /// The formatted value of the spin button. + label: Cow<'a, str>, + /// The current value of the spin button. + value: T, + /// The amount to increment or decrement the value. + step: T, + /// The minimum value permitted. + min: T, + /// The maximum value permitted. + max: T, + orientation: Orientation, + on_press: Box M>, +} + +impl<'a, T, M> SpinButton<'a, T, M> +where + T: Copy + Sub + Add + PartialOrd, +{ + /// Create a new new button + fn new( + label: impl Into>, + value: T, + step: T, + min: T, + max: T, + orientation: Orientation, + on_press: impl Fn(T) -> M + 'static, + ) -> Self { + Self { + label: label.into(), + step, + value: if value < min { + min + } else if value > max { + max + } else { + value + }, + min, + max, + orientation, + on_press: Box::from(on_press), + } + } +} + +fn increment(value: T, step: T, min: T, max: T) -> T +where + T: Copy + Sub + Add + PartialOrd, +{ + if value > max - step { + max + } else { + value + step + } +} + +fn decrement(value: T, step: T, min: T, max: T) -> T +where + T: Copy + Sub + Add + PartialOrd, +{ + if value < min + step { + min + } else { + value - step + } +} + +impl<'a, T, Message> From> for Element<'a, Message> +where + Message: Clone + 'static, + T: Copy + Sub + Add + PartialOrd, +{ + fn from(this: SpinButton<'a, T, Message>) -> Self { + match this.orientation { + Orientation::Horizontal => horizontal_variant(this), + Orientation::Vertical => vertical_variant(this), + } + } +} + +fn horizontal_variant<'a, T, Message>( + spin_button: SpinButton<'a, T, Message>, +) -> Element<'a, Message> +where + Message: Clone + 'static, + T: Copy + Sub + Add + PartialOrd, +{ + let decrement_button = icon::from_name("list-remove-symbolic") + .apply(button::icon) + .on_press((spin_button.on_press)(decrement::( + spin_button.value, + spin_button.step, + spin_button.min, + spin_button.max, + ))); + + let increment_button = icon::from_name("list-add-symbolic") + .apply(button::icon) + .on_press((spin_button.on_press)(increment::( + spin_button.value, + spin_button.step, + spin_button.min, + spin_button.max, + ))); + + let label = text::title4(spin_button.label) + .apply(container) + .center_x(Length::Fixed(48.0)) + .align_y(Alignment::Center); + + row::with_capacity(3) + .push(decrement_button) + .push(label) + .push(increment_button) + .align_y(Alignment::Center) + .apply(container) + .class(theme::Container::custom(container_style)) + .into() +} + +fn vertical_variant<'a, T, Message>(spin_button: SpinButton<'a, T, Message>) -> Element<'a, Message> +where + Message: Clone + 'static, + T: Copy + Sub + Add + PartialOrd, +{ + let decrement_button = icon::from_name("list-remove-symbolic") + .apply(button::icon) + .on_press((spin_button.on_press)(decrement::( + spin_button.value, + spin_button.step, + spin_button.min, + spin_button.max, + ))); + + let increment_button = icon::from_name("list-add-symbolic") + .apply(button::icon) + .on_press((spin_button.on_press)(increment::( + spin_button.value, + spin_button.step, + spin_button.min, + spin_button.max, + ))); + + let label = text::title4(spin_button.label) + .apply(container) + .center_x(Length::Fixed(48.0)) + .align_y(Alignment::Center); + + column::with_capacity(3) + .push(increment_button) + .push(label) + .push(decrement_button) + .align_x(Alignment::Center) + .apply(container) + .class(theme::Container::custom(container_style)) + .into() +} + +#[allow(clippy::trivially_copy_pass_by_ref)] +fn container_style(theme: &crate::Theme) -> iced_widget::container::Style { + let cosmic_theme = &theme.cosmic(); + let mut neutral_10 = cosmic_theme.palette.neutral_10; + neutral_10.alpha = 0.1; + let accent = &cosmic_theme.accent; + let corners = &cosmic_theme.corner_radii; + iced_widget::container::Style { + icon_color: Some(accent.base.into()), + text_color: Some(cosmic_theme.palette.neutral_10.into()), + background: None, + border: Border { + radius: corners.radius_s.into(), + width: 0.0, + color: accent.base.into(), + }, + shadow: Shadow::default(), + } +} + +#[cfg(test)] +mod tests { + #[test] + fn decrement() { + assert_eq!(super::decrement(0i32, 10, 15, 35), 15); + } +} diff --git a/src/widget/spin_button/mod.rs b/src/widget/spin_button/mod.rs deleted file mode 100644 index 9da29394..00000000 --- a/src/widget/spin_button/mod.rs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2022 System76 -// SPDX-License-Identifier: MPL-2.0 - -//! A control for incremental adjustments of a value. - -mod model; -use std::borrow::Cow; - -pub use self::model::{Message, Model}; - -use crate::widget::{button, container, icon, row, text}; -use crate::{theme, Element}; -use apply::Apply; -use iced::{Alignment, Length}; -use iced_core::{Border, Shadow}; - -pub struct SpinButton<'a, Message> { - label: Cow<'a, str>, - on_change: Box Message + 'static>, -} - -/// A control for incremental adjustments of a value. -pub fn spin_button<'a, Message: 'static>( - label: impl Into>, - on_change: impl Fn(model::Message) -> Message + 'static, -) -> SpinButton<'a, Message> { - SpinButton::new(label, on_change) -} - -impl<'a, Message: 'static> SpinButton<'a, Message> { - pub fn new( - label: impl Into>, - on_change: impl Fn(model::Message) -> Message + 'static, - ) -> Self { - Self { - on_change: Box::from(on_change), - label: label.into(), - } - } - - #[must_use] - pub fn into_element(self) -> Element<'a, Message> { - let Self { on_change, label } = self; - container( - row::with_children(vec![ - icon::from_name("list-remove-symbolic") - .size(16) - .apply(container) - .center(Length::Fixed(32.0)) - .apply(button::custom) - .width(Length::Fixed(32.0)) - .height(Length::Fixed(32.0)) - .class(theme::Button::Text) - .on_press(model::Message::Decrement) - .into(), - text::title4(label) - .apply(container) - .center_x(Length::Fixed(48.0)) - .align_y(Alignment::Center) - .into(), - icon::from_name("list-add-symbolic") - .size(16) - .apply(container) - .center(Length::Fixed(32.0)) - .apply(button::custom) - .width(Length::Fixed(32.0)) - .height(Length::Fixed(32.0)) - .class(theme::Button::Text) - .on_press(model::Message::Increment) - .into(), - ]) - .width(Length::Shrink) - .height(Length::Fixed(32.0)) - .align_y(Alignment::Center), - ) - .width(Length::Shrink) - .center_y(Length::Fixed(32.0)) - .class(theme::Container::custom(container_style)) - .apply(Element::from) - .map(on_change) - } -} - -impl<'a, Message: 'static> From> for Element<'a, Message> { - fn from(spin_button: SpinButton<'a, Message>) -> Self { - spin_button.into_element() - } -} - -#[allow(clippy::trivially_copy_pass_by_ref)] -fn container_style(theme: &crate::Theme) -> iced_widget::container::Style { - let basic = &theme.cosmic(); - let mut neutral_10 = basic.palette.neutral_10; - neutral_10.alpha = 0.1; - let accent = &basic.accent; - let corners = &basic.corner_radii; - iced_widget::container::Style { - icon_color: Some(basic.palette.neutral_10.into()), - text_color: Some(basic.palette.neutral_10.into()), - background: None, - border: Border { - radius: corners.radius_s.into(), - width: 0.0, - color: accent.base.into(), - }, - shadow: Shadow::default(), - } -} diff --git a/src/widget/spin_button/model.rs b/src/widget/spin_button/model.rs deleted file mode 100644 index e617bc87..00000000 --- a/src/widget/spin_button/model.rs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2022 System76 -// SPDX-License-Identifier: MPL-2.0 - -use derive_setters::Setters; -use fraction::{Bounded, Decimal}; -use std::hash::Hash; -use std::ops::{Add, Sub}; - -/// A message emitted by the [`SpinButton`](super) widget. -#[derive(Clone, Copy, Debug, Hash)] -pub enum Message { - Increment, - Decrement, -} - -#[derive(Setters)] -pub struct Model { - /// The current value of the spin button. - #[setters(into)] - pub value: T, - /// The amount to increment the value. - #[setters(into)] - pub step: T, - /// The minimum value permitted. - #[setters(into)] - pub min: T, - /// The maximum value permitted. - #[setters(into)] - pub max: T, -} - -impl Model -where - T: Copy + Hash + Sub + Add + Ord, -{ - pub fn update(&mut self, message: Message) { - self.value = match message { - Message::Increment => { - std::cmp::min(std::cmp::max(self.value + self.step, self.min), self.max) - } - Message::Decrement => { - std::cmp::max(std::cmp::min(self.value - self.step, self.max), self.min) - } - } - } -} - -impl Default for Model { - fn default() -> Self { - Self { - value: 0, - step: 1, - min: i8::MIN, - max: i8::MAX, - } - } -} - -impl Default for Model { - fn default() -> Self { - Self { - value: 0, - step: 1, - min: i16::MIN, - max: i16::MAX, - } - } -} - -impl Default for Model { - fn default() -> Self { - Self { - value: 0, - step: 1, - min: i32::MIN, - max: i32::MAX, - } - } -} - -impl Default for Model { - fn default() -> Self { - Self { - value: 0, - step: 1, - min: isize::MIN, - max: isize::MAX, - } - } -} - -impl Default for Model { - fn default() -> Self { - Self { - value: 0, - step: 1, - min: u8::MIN, - max: u8::MAX, - } - } -} - -impl Default for Model { - fn default() -> Self { - Self { - value: 0, - step: 1, - min: u16::MIN, - max: u16::MAX, - } - } -} - -impl Default for Model { - fn default() -> Self { - Self { - value: 0, - step: 1, - min: u32::MIN, - max: u32::MAX, - } - } -} - -impl Default for Model { - fn default() -> Self { - Self { - value: 0, - step: 1, - min: usize::MIN, - max: usize::MAX, - } - } -} - -impl Default for Model { - fn default() -> Self { - Self { - value: 0, - step: 1, - min: u64::MIN, - max: u64::MAX, - } - } -} - -impl Default for Model { - fn default() -> Self { - Self { - value: Decimal::from(0.0), - step: Decimal::from(0.0), - min: Decimal::min_positive_value(), - max: Decimal::max_value(), - } - } -} From 525a14cfb1a735779a9fe970ff1726c8dbf1b217 Mon Sep 17 00:00:00 2001 From: Adam Cosner Date: Tue, 19 Nov 2024 20:35:04 -0800 Subject: [PATCH 072/556] Fixed issue #700 where not using multi-window caused the main window to not be able to be interacted with --- src/app/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/mod.rs b/src/app/mod.rs index e4b59d62..88dee959 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -142,6 +142,7 @@ pub fn run(settings: Settings, flags: App::Flags) -> iced::Res let (settings, mut flags, window_settings) = iced_settings::(settings, flags); #[cfg(not(feature = "multi-window"))] { + flags.0.main_window = Some(iced::window::Id::RESERVED); iced::application( cosmic::Cosmic::title, cosmic::Cosmic::update, From a64529af171529ab94369bc04900c695930059c3 Mon Sep 17 00:00:00 2001 From: Adam Cosner Date: Wed, 20 Nov 2024 16:32:37 -0800 Subject: [PATCH 073/556] Changed cosmic::command module to cosmic::task and changed cosmic::Task to reexport cosmic::app::Task instead of iced::Task --- examples/about/src/main.rs | 2 +- examples/application/src/main.rs | 2 +- examples/calendar/src/main.rs | 2 +- examples/context-menu/src/main.rs | 2 +- examples/image-button/src/main.rs | 2 +- examples/menu/src/main.rs | 2 +- examples/nav-context/src/main.rs | 2 +- examples/open-dialog/src/main.rs | 6 +++--- examples/text-input/src/main.rs | 2 +- src/app/command.rs | 20 ++++++++++---------- src/app/mod.rs | 2 +- src/dialog/file_chooser/mod.rs | 8 ++++---- src/lib.rs | 4 ++-- src/{command => task}/mod.rs | 10 +++++----- src/widget/toaster/mod.rs | 2 +- 15 files changed, 34 insertions(+), 34 deletions(-) rename src/{command => task}/mod.rs (84%) diff --git a/examples/about/src/main.rs b/examples/about/src/main.rs index 0625e152..5450b47e 100644 --- a/examples/about/src/main.rs +++ b/examples/about/src/main.rs @@ -61,7 +61,7 @@ impl cosmic::Application for App { &mut self.core } - /// Creates the application, and optionally emits command on initialize. + /// Creates the application, and optionally emits task on initialize. fn init(core: Core, _flags: Self::Flags) -> (Self, Task) { let nav_model = nav_bar::Model::default(); diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index bf2b5a50..a77b9d74 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -89,7 +89,7 @@ impl cosmic::Application for App { &mut self.core } - /// Creates the application, and optionally emits command on initialize. + /// Creates the application, and optionally emits task on initialize. fn init(core: Core, input: Self::Flags) -> (Self, Task) { let mut nav_model = nav_bar::Model::default(); diff --git a/examples/calendar/src/main.rs b/examples/calendar/src/main.rs index 5306d927..bfad5513 100644 --- a/examples/calendar/src/main.rs +++ b/examples/calendar/src/main.rs @@ -49,7 +49,7 @@ impl cosmic::Application for App { &mut self.core } - /// Creates the application, and optionally emits command on initialize. + /// Creates the application, and optionally emits task on initialize. fn init(core: Core, _input: Self::Flags) -> (Self, Task) { let now = Local::now(); diff --git a/examples/context-menu/src/main.rs b/examples/context-menu/src/main.rs index 5ade4966..840cf865 100644 --- a/examples/context-menu/src/main.rs +++ b/examples/context-menu/src/main.rs @@ -64,7 +64,7 @@ impl cosmic::Application for App { &mut self.core } - /// Creates the application, and optionally emits command on initialize. + /// Creates the application, and optionally emits task on initialize. fn init(core: Core, _input: Self::Flags) -> (Self, Task) { let mut app = App { core, diff --git a/examples/image-button/src/main.rs b/examples/image-button/src/main.rs index 406f0d19..34d907e7 100644 --- a/examples/image-button/src/main.rs +++ b/examples/image-button/src/main.rs @@ -50,7 +50,7 @@ impl cosmic::Application for App { &mut self.core } - /// Creates the application, and optionally emits command on initialize. + /// Creates the application, and optionally emits task on initialize. fn init(core: Core, _input: Self::Flags) -> (Self, Task) { let mut app = App { core, diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs index 1b564a1b..5b65732e 100644 --- a/examples/menu/src/main.rs +++ b/examples/menu/src/main.rs @@ -96,7 +96,7 @@ impl cosmic::Application for App { &mut self.core } - /// Creates the application, and optionally emits command on initialize. + /// Creates the application, and optionally emits task on initialize. fn init(core: Core, _input: Self::Flags) -> (Self, Task) { let app = App { core, diff --git a/examples/nav-context/src/main.rs b/examples/nav-context/src/main.rs index 2285292a..58e20849 100644 --- a/examples/nav-context/src/main.rs +++ b/examples/nav-context/src/main.rs @@ -105,7 +105,7 @@ impl cosmic::Application for App { &mut self.core } - /// Creates the application, and optionally emits command on initialize. + /// Creates the application, and optionally emits task on initialize. fn init(core: Core, input: Self::Flags) -> (Self, Task) { let mut nav_model = nav_bar::Model::default(); diff --git a/examples/open-dialog/src/main.rs b/examples/open-dialog/src/main.rs index a1958880..5ae2df47 100644 --- a/examples/open-dialog/src/main.rs +++ b/examples/open-dialog/src/main.rs @@ -65,7 +65,7 @@ impl cosmic::Application for App { &mut self.core } - /// Creates the application, and optionally emits command on initialize. + /// Creates the application, and optionally emits task on initialize. fn init(core: Core, _input: Self::Flags) -> (Self, Task) { let id = core.main_window_id().unwrap(); let mut app = App { @@ -109,7 +109,7 @@ impl cosmic::Application for App { self.set_header_title(url.to_string()); // Reads the selected file into memory. - return cosmic::command::future(async move { + return cosmic::task::future(async move { // Check if its a valid local file path. let path = match url.scheme() { "file" => url.to_file_path().unwrap(), @@ -145,7 +145,7 @@ impl cosmic::Application for App { // Creates a new open dialog. Message::OpenFile => { - return cosmic::command::future(async move { + return cosmic::task::future(async move { eprintln!("opening new dialog"); #[cfg(feature = "rfd")] diff --git a/examples/text-input/src/main.rs b/examples/text-input/src/main.rs index de6bbd9d..573b9dc1 100644 --- a/examples/text-input/src/main.rs +++ b/examples/text-input/src/main.rs @@ -54,7 +54,7 @@ impl cosmic::Application for App { &mut self.core } - /// Creates the application, and optionally emits command on initialize. + /// Creates the application, and optionally emits task on initialize. fn init(core: Core, _input: Self::Flags) -> (Self, Task) { let mut app = App { core, diff --git a/src/app/command.rs b/src/app/command.rs index ff51a7d3..b39118f5 100644 --- a/src/app/command.rs +++ b/src/app/command.rs @@ -9,19 +9,19 @@ use super::Message; /// Commands for COSMIC applications. pub type Task = iced::Task>; -/// Creates a command which yields a [`crate::app::Message`]. +/// Creates a task which yields a [`crate::app::Message`]. pub fn message(message: Message) -> Task { - crate::command::message(message) + crate::task::message(message) } /// Convenience methods for building message-based commands. pub mod message { - /// Creates a command which yields an application message. + /// Creates a task which yields an application message. pub fn app(message: M) -> crate::app::Task { super::message(super::Message::App(message)) } - /// Creates a command which yields a cosmic message. + /// Creates a task which yields a cosmic message. pub fn cosmic(message: crate::app::cosmic::Message) -> crate::app::Task { super::message(super::Message::Cosmic(message)) } @@ -32,7 +32,7 @@ impl crate::app::Core { let Some(id) = id.or(self.main_window) else { return iced::Task::none(); }; - crate::command::drag(id).map(Message::Cosmic) + crate::task::drag(id).map(Message::Cosmic) } pub fn maximize( @@ -43,14 +43,14 @@ impl crate::app::Core { let Some(id) = id.or(self.main_window) else { return iced::Task::none(); }; - crate::command::maximize(id, maximized).map(Message::Cosmic) + crate::task::maximize(id, maximized).map(Message::Cosmic) } pub fn minimize(&self, id: Option) -> iced::Task> { let Some(id) = id.or(self.main_window) else { return iced::Task::none(); }; - crate::command::minimize(id).map(Message::Cosmic) + crate::task::minimize(id).map(Message::Cosmic) } pub fn set_scaling_factor(&self, factor: f32) -> iced::Task> { @@ -65,7 +65,7 @@ impl crate::app::Core { let Some(id) = id.or(self.main_window) else { return iced::Task::none(); }; - crate::command::set_title(id, title).map(Message::Cosmic) + crate::task::set_title(id, title).map(Message::Cosmic) } pub fn set_windowed( @@ -75,7 +75,7 @@ impl crate::app::Core { let Some(id) = id.or(self.main_window) else { return iced::Task::none(); }; - crate::command::set_windowed(id).map(Message::Cosmic) + crate::task::set_windowed(id).map(Message::Cosmic) } pub fn toggle_maximize( @@ -85,7 +85,7 @@ impl crate::app::Core { let Some(id) = id.or(self.main_window) else { return iced::Task::none(); }; - crate::command::toggle_maximize(id).map(Message::Cosmic) + crate::task::toggle_maximize(id).map(Message::Cosmic) } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 88dee959..eb53a67e 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -452,7 +452,7 @@ where /// Grants access to the COSMIC Core. fn core_mut(&mut self) -> &mut Core; - /// Creates the application, and optionally emits command on initialize. + /// Creates the application, and optionally emits task on initialize. fn init(core: Core, flags: Self::Flags) -> (Self, Task); /// Displays a context drawer on the side of the application window when `Some`. diff --git a/src/dialog/file_chooser/mod.rs b/src/dialog/file_chooser/mod.rs index 0f328e32..186f7625 100644 --- a/src/dialog/file_chooser/mod.rs +++ b/src/dialog/file_chooser/mod.rs @@ -11,7 +11,7 @@ //! # Open a file //! //! ```no_run -//! cosmic::command::future(async { +//! cosmic::task::future(async { //! use cosmic::dialog::file_chooser; //! //! let dialog = file_chooser::open::Dialog::new() @@ -30,7 +30,7 @@ //! # Open multiple files //! //! ```no_run -//! cosmic::command::future(async { +//! cosmic::task::future(async { //! use cosmic::dialog::file_chooser; //! //! let dialog = file_chooser::open::Dialog::new() @@ -49,7 +49,7 @@ //! # Open a folder //! //! ```no_run -//! cosmic::command::future(async { +//! cosmic::task::future(async { //! use cosmic::dialog::file_chooser; //! //! let dialog = file_chooser::open::Dialog::new() @@ -68,7 +68,7 @@ //! # Open multiple folders //! //! ```no_run -//! cosmic::command::future(async { +//! cosmic::task::future(async { //! use cosmic::dialog::file_chooser; //! //! let dialog = file_chooser::open::Dialog::new() diff --git a/src/lib.rs b/src/lib.rs index fc8758c8..cc396144 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,8 +22,8 @@ pub use app::{Application, ApplicationExt}; #[cfg(feature = "applet")] pub mod applet; -pub use iced::Task; -pub mod command; +pub use app::Task; +pub mod task; pub mod config; diff --git a/src/command/mod.rs b/src/task/mod.rs similarity index 84% rename from src/command/mod.rs rename to src/task/mod.rs index 2a509bce..379ea19b 100644 --- a/src/command/mod.rs +++ b/src/task/mod.rs @@ -9,19 +9,19 @@ use iced_core::window::Mode; use iced_runtime::{task, Action}; use std::future::Future; -/// Yields a command which contains a batch of commands. +/// Yields a task which contains a batch of tasks. pub fn batch, Y: Send + 'static>( - commands: impl IntoIterator>, + tasks: impl IntoIterator>, ) -> Task { - Task::batch(commands).map(Into::into) + Task::batch(tasks).map(Into::into) } -/// Yields a command which will run the future on the runtime executor. +/// Yields a task which will run the future on the runtime executor. pub fn future, Y: 'static>(future: impl Future + Send + 'static) -> Task { Task::future(async move { future.await.into() }) } -/// Yields a command which will return a message. +/// Yields a task which will return a message. pub fn message, Y: 'static>(message: X) -> Task { future(async move { message.into() }) } diff --git a/src/widget/toaster/mod.rs b/src/widget/toaster/mod.rs index 7f7634cf..2ed8289e 100644 --- a/src/widget/toaster/mod.rs +++ b/src/widget/toaster/mod.rs @@ -193,7 +193,7 @@ impl Toasts { #[cfg(feature = "tokio")] { let on_close = self.on_close; - crate::command::future(async move { + crate::task::future(async move { tokio::time::sleep(duration).await; on_close(id) }) From 2ca8961b9feed53274017b78a5be8d68dabb43f6 Mon Sep 17 00:00:00 2001 From: Adam Cosner Date: Wed, 20 Nov 2024 16:42:31 -0800 Subject: [PATCH 074/556] Made app::Task a winit-only feature Not exactly sure why, but the wayland feature disables cosmic::app so this is required --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index cc396144..2b2c9b43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,9 @@ pub use app::{Application, ApplicationExt}; #[cfg(feature = "applet")] pub mod applet; +#[cfg(feature = "winit")] pub use app::Task; + pub mod task; pub mod config; From 0ef3cced6ae9822c7844a6d0c2295d6ff1424980 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 22 Nov 2024 12:48:06 -0700 Subject: [PATCH 075/556] fix(text): make title 1 text use light weight --- src/widget/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/text.rs b/src/widget/text.rs index f1e3cffe..0e3febbd 100644 --- a/src/widget/text.rs +++ b/src/widget/text.rs @@ -29,7 +29,7 @@ pub fn title1<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Text::new(text.into()) .size(32.0) .line_height(LineHeight::Absolute(44.0.into())) - .font(crate::font::semibold()) + .font(crate::font::light()) } /// [`Text`] widget with the Title 2 typography preset. From 637d932669af46e6f2eff77026c2b6f9111c5654 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 25 Nov 2024 06:36:02 +0100 Subject: [PATCH 076/556] fix: revert change of `cosmic::Task` to `cosmic::app::Task` This change caused all uses of `cosmic::Task` to be coerced into a message type specific to `cosmic::app`. Thus, users were forced to create messages that are wrapped in `cosmic::app::Message` enums. --- src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2b2c9b43..886ae559 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,9 +22,7 @@ pub use app::{Application, ApplicationExt}; #[cfg(feature = "applet")] pub mod applet; -#[cfg(feature = "winit")] -pub use app::Task; - +pub use iced::Task; pub mod task; pub mod config; From 52ab37c1ebe74a64771a18571ad0c6bcc7e600e5 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 25 Nov 2024 06:50:59 +0100 Subject: [PATCH 077/556] feat(icon): add draw method for renderer --- src/widget/icon/mod.rs | 45 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/widget/icon/mod.rs b/src/widget/icon/mod.rs index 5fe630fa..eb91fc00 100644 --- a/src/widget/icon/mod.rs +++ b/src/widget/icon/mod.rs @@ -15,7 +15,7 @@ pub use handle::{from_path, from_raster_bytes, from_raster_pixels, from_svg_byte use crate::Element; use derive_setters::Setters; use iced::widget::{Image, Svg}; -use iced::{ContentFit, Length}; +use iced::{ContentFit, Length, Rectangle}; /// Create an [`Icon`] from a pre-existing [`Handle`] pub fn icon(handle: Handle) -> Icon { @@ -125,3 +125,46 @@ impl<'a, Message: 'a> From for Element<'a, Message> { icon.view::() } } + +/// Draw an icon in the given bounds via the runtime's renderer. +pub fn draw(renderer: &mut crate::Renderer, handle: &Handle, icon_bounds: Rectangle) { + enum IcedHandle { + Svg(iced_core::svg::Handle), + Image(iced_core::image::Handle), + } + + let iced_handle = match handle.clone().data { + Data::Name(named) => named.path().map(|path| { + if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) { + IcedHandle::Svg(iced_core::svg::Handle::from_path(path)) + } else { + IcedHandle::Image(iced_core::image::Handle::from_path(path)) + } + }), + + Data::Image(handle) => Some(IcedHandle::Image(handle)), + Data::Svg(handle) => Some(IcedHandle::Svg(handle)), + }; + + match iced_handle { + Some(IcedHandle::Svg(handle)) => iced_core::svg::Renderer::draw_svg( + renderer, + iced_core::svg::Svg::new(handle), + icon_bounds, + ), + + Some(IcedHandle::Image(handle)) => { + iced_core::image::Renderer::draw_image( + renderer, + handle, + iced_core::image::FilterMethod::Linear, + icon_bounds, + iced_core::Radians::from(0), + 1.0, + [0.0; 4], + ); + } + + None => {} + } +} From 9a8a56952da8ad25a7482a6d554dd49041dc6718 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 25 Nov 2024 06:51:16 +0100 Subject: [PATCH 078/556] feat(dropdown): optional icons for dropdowns --- src/widget/dropdown/menu/mod.rs | 30 +++++++++++++--- src/widget/dropdown/widget.rs | 62 ++++++++++++++++++--------------- 2 files changed, 59 insertions(+), 33 deletions(-) diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index 73204f3b..66574420 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -5,7 +5,7 @@ mod appearance; pub use appearance::{Appearance, StyleSheet}; -use crate::widget::Container; +use crate::widget::{icon, Container}; use iced_core::event::{self, Event}; use iced_core::layout::{self, Layout}; use iced_core::text::{self, Text}; @@ -24,6 +24,7 @@ where { state: &'a mut State, options: &'a [S], + icons: &'a [icon::Handle], hovered_option: &'a mut Option, selected_option: Option, on_selected: Box Message + 'a>, @@ -41,6 +42,7 @@ impl<'a, S: AsRef, Message: 'a> Menu<'a, S, Message> { pub fn new( state: &'a mut State, options: &'a [S], + icons: &'a [icon::Handle], hovered_option: &'a mut Option, selected_option: Option, on_selected: impl FnMut(usize) -> Message + 'a, @@ -49,6 +51,7 @@ impl<'a, S: AsRef, Message: 'a> Menu<'a, S, Message> { Menu { state, options, + icons, hovered_option, selected_option, on_selected: Box::new(on_selected), @@ -141,6 +144,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> { let Menu { state, options, + icons, hovered_option, selected_option, on_selected, @@ -154,6 +158,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> { let mut container = Container::new(Scrollable::new(List { options, + icons, hovered_option, selected_option, on_selected, @@ -268,6 +273,7 @@ impl<'a, Message> iced_core::Overlay struct List<'a, S: AsRef, Message> { options: &'a [S], + icons: &'a [icon::Handle], hovered_option: &'a mut Option, selected_option: Option, on_selected: Box Message + 'a>, @@ -395,12 +401,12 @@ impl<'a, S: AsRef, Message> Widget fn draw( &self, - _state: &Tree, + state: &Tree, renderer: &mut crate::Renderer, theme: &crate::Theme, - _style: &renderer::Style, + style: &renderer::Style, layout: Layout<'_>, - _cursor: mouse::Cursor, + cursor: mouse::Cursor, viewport: &Rectangle, ) { let appearance = theme.appearance(&()); @@ -452,6 +458,7 @@ impl<'a, S: AsRef, Message> Widget iced_core::Svg::new(crate::widget::common::object_select().clone()) .color(appearance.selected_text_color) .border_radius(appearance.border_radius); + svg::Renderer::draw_svg( renderer, svg_handle, @@ -489,12 +496,25 @@ impl<'a, S: AsRef, Message> Widget (appearance.text_color, crate::font::default()) }; - let bounds = Rectangle { + let mut bounds = Rectangle { x: bounds.x + self.padding.left, y: bounds.center_y(), width: f32::INFINITY, ..bounds }; + + if let Some(handle) = self.icons.get(i) { + let icon_bounds = Rectangle { + x: bounds.x, + y: bounds.y + 8.0 - (bounds.height / 2.0), + width: 20.0, + height: 20.0, + }; + + bounds.x += 24.0; + icon::draw(renderer, handle, icon_bounds); + } + text::Renderer::fill_text( renderer, Text { diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 20d46a20..26f69cce 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -24,6 +24,8 @@ pub struct Dropdown<'a, S: AsRef, Message> { on_selected: Box Message + 'a>, #[setters(skip)] selections: &'a [S], + #[setters] + icons: &'a [icon::Handle], #[setters(skip)] selected: Option, #[setters(into)] @@ -55,6 +57,7 @@ impl<'a, S: AsRef, Message> Dropdown<'a, S, Message> { Self { on_selected: Box::new(on_selected), selections, + icons: &[], selected, width: Length::Shrink, gap: Self::DEFAULT_GAP, @@ -64,29 +67,6 @@ impl<'a, S: AsRef, Message> Dropdown<'a, S, Message> { font: None, } } - - fn update_paragraphs(&self, state: &mut tree::State) { - let state = state.downcast_mut::(); - - state - .selections - .resize_with(self.selections.len(), crate::Plain::default); - for (i, selection) in self.selections.iter().enumerate() { - state.selections[i].update(Text { - content: selection.as_ref(), - bounds: Size::INFINITY, - // TODO use the renderer default size - size: iced::Pixels(self.text_size.unwrap_or(14.0)), - - line_height: self.text_line_height, - font: self.font.unwrap_or(crate::font::default()), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - shaping: text::Shaping::Advanced, - wrapping: text::Wrapping::default(), - }); - } - } } impl<'a, S: AsRef, Message: 'a> Widget @@ -158,6 +138,7 @@ impl<'a, S: AsRef, Message: 'a> Widget().selections.get_mut(id)) }), + !self.icons.is_empty(), ) } @@ -217,6 +198,7 @@ impl<'a, S: AsRef, Message: 'a> Widget(), viewport, ); @@ -241,6 +223,7 @@ impl<'a, S: AsRef, Message: 'a> Widget, selection: Option<(&str, &mut crate::Plain)>, + has_icons: bool, ) -> layout::Node { use std::f32; @@ -346,9 +330,11 @@ pub fn layout( _ => 0.0, }; + let icon_size = if has_icons { 24.0 } else { 0.0 }; + let size = { let intrinsic = Size::new( - max_width + gap + 16.0, + max_width + icon_size + gap + 16.0, f32::from(text_line_height.to_absolute(Pixels(text_size))), ); @@ -449,6 +435,7 @@ pub fn overlay<'a, S: AsRef, Message: 'a>( _text_line_height: text::LineHeight, _font: Option, selections: &'a [S], + icons: &'a [icon::Handle], selected_option: Option, on_selected: &'a dyn Fn(usize) -> Message, translation: Vector, @@ -459,6 +446,7 @@ pub fn overlay<'a, S: AsRef, Message: 'a>( let menu = Menu::new( &mut state.menu, selections, + icons, &mut state.hovered_option, selected_option, |option| { @@ -473,15 +461,18 @@ pub fn overlay<'a, S: AsRef, Message: 'a>( selection_paragraph.min_width().round() }; + let pad_width = padding.horizontal().mul_add(2.0, 16.0); + + let icon_width = if icons.is_empty() { 0.0 } else { 24.0 }; + selections .iter() .zip(state.selections.iter_mut()) .map(|(label, selection)| measure(label.as_ref(), selection.raw())) .fold(0.0, |next, current| current.max(next)) + gap - + 16.0 - + padding.horizontal() - + padding.horizontal() + + pad_width + + icon_width }) .padding(padding) .text_size(text_size); @@ -509,6 +500,7 @@ pub fn draw<'a, S>( text_line_height: text::LineHeight, font: crate::font::Font, selected: Option<&'a S>, + icon: Option<&'a icon::Handle>, state: &'a State, viewport: &Rectangle, ) where @@ -550,12 +542,26 @@ pub fn draw<'a, S>( if let Some(content) = selected.map(AsRef::as_ref) { let text_size = text_size.unwrap_or_else(|| text::Renderer::default_size(renderer).0); - let bounds = Rectangle { + + let mut bounds = Rectangle { x: bounds.x + padding.left, y: bounds.center_y(), width: bounds.width - padding.horizontal(), height: f32::from(text_line_height.to_absolute(Pixels(text_size))), }; + + if let Some(handle) = icon { + let icon_bounds = Rectangle { + x: bounds.x, + y: bounds.y - (bounds.height / 2.0) - 2.0, + width: 20.0, + height: 20.0, + }; + + bounds.x += 24.0; + icon::draw(renderer, handle, icon_bounds); + } + text::Renderer::fill_text( renderer, Text { From a9c7c3cdbfd586b090437647e3d380977780d520 Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Mon, 25 Nov 2024 00:52:32 -0500 Subject: [PATCH 079/556] fix(iced): a11y tree focus --- iced | 2 +- src/widget/button/text.rs | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/iced b/iced index 501d7aae..630612a7 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 501d7aaebe113a785f53e3f139e48be8a6dd4d1a +Subproject commit 630612a7b1cb4fb236edabb2dee88b1754976549 diff --git a/src/widget/button/text.rs b/src/widget/button/text.rs index 30476243..2070fb16 100644 --- a/src/widget/button/text.rs +++ b/src/widget/button/text.rs @@ -116,7 +116,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .into() }); - let button: super::Button<'a, Message> = row::with_capacity(3) + let mut button: super::Button<'a, Message> = row::with_capacity(3) // Optional icon to place before label. .push_maybe(leading_icon) // Optional label between icons. @@ -134,6 +134,13 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .on_press_maybe(builder.on_press.take()) .class(builder.class); + #[cfg(feature = "a11y")] + { + if !builder.label.is_empty() { + button = button.name(builder.label); + } + } + if builder.tooltip.is_empty() { button.into() } else { From af7157b45a9efe58b7185a5fe7957cb51b94dbc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Mon, 25 Nov 2024 02:36:11 +0100 Subject: [PATCH 080/556] improv: window border corner appearance --- src/app/mod.rs | 10 +++++++--- src/theme/style/iced.rs | 26 ++++---------------------- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index eb53a67e..b137ecb7 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -892,13 +892,17 @@ impl ApplicationExt for App { .apply(container) .padding(if sharp_corners { 0 } else { 1 }) .style(move |theme| container::Style { + icon_color: Some(iced::Color::from(theme.cosmic().background.on)), + text_color: Some(iced::Color::from(theme.cosmic().background.on)), + background: Some(iced::Background::Color( + theme.cosmic().background.base.into(), + )), border: iced::Border { color: theme.cosmic().bg_divider().into(), width: if sharp_corners { 0.0 } else { 1.0 }, - // x + 2.0 is used to prevent corner artifacts - radius: theme.cosmic().radius_s().map(|x| x + 2.0).into(), + radius: theme.cosmic().radius_s().into(), }, - ..Default::default() + shadow: iced::Shadow::default(), }); // Show any current dialog on top and centered over the view content diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index f51e2650..1bf20524 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -469,17 +469,8 @@ impl iced_container::Catalog for Theme { Container::WindowBackground => iced_container::Style { icon_color: Some(Color::from(cosmic.background.on)), text_color: Some(Color::from(cosmic.background.on)), - background: Some(iced::Background::Color(cosmic.background.base.into())), - border: Border { - radius: [ - cosmic.corner_radii.radius_0[0], - cosmic.corner_radii.radius_0[1], - cosmic.corner_radii.radius_s[2], - cosmic.corner_radii.radius_s[3], - ] - .into(), - ..Default::default() - }, + background: None, + border: Border::default(), shadow: Shadow::default(), }, @@ -513,17 +504,8 @@ impl iced_container::Catalog for Theme { iced_container::Style { icon_color: Some(icon_color), text_color: Some(text_color), - background: Some(iced::Background::Color(cosmic.background.base.into())), - border: Border { - radius: [ - cosmic.corner_radii.radius_s[0], - cosmic.corner_radii.radius_s[1], - cosmic.corner_radii.radius_0[2], - cosmic.corner_radii.radius_0[3], - ] - .into(), - ..Default::default() - }, + background: None, + border: Border::default(), shadow: Shadow::default(), } } From 8e823f622fdc65d9fc4f4a7638100fa6d13bbe35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Mon, 25 Nov 2024 03:06:34 +0100 Subject: [PATCH 081/556] fix(header_bar): match spacing to designs --- src/app/mod.rs | 39 ++++++++++++++++++++++++++++++++------- src/widget/header_bar.rs | 15 ++++++++++++--- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index b137ecb7..6051a2e9 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -882,7 +882,30 @@ impl ApplicationExt for App { header = header.end(element.map(Message::App)); } - header.apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_header"))) + header + // Needed for apps without a content container, but with a header bar + .apply(container) + .style(move |theme| container::Style { + background: if content_container { + None + } else { + Some(iced::Background::Color( + theme.cosmic().background.base.into(), + )) + }, + border: iced::Border { + radius: [ + theme.cosmic().radius_s()[0] - 1.0, + theme.cosmic().radius_s()[1] - 1.0, + theme.cosmic().radius_0()[2], + theme.cosmic().radius_0()[3], + ] + .into(), + ..Default::default() + }, + ..Default::default() + }) + .apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_header"))) }) } else { None @@ -892,17 +915,19 @@ impl ApplicationExt for App { .apply(container) .padding(if sharp_corners { 0 } else { 1 }) .style(move |theme| container::Style { - icon_color: Some(iced::Color::from(theme.cosmic().background.on)), - text_color: Some(iced::Color::from(theme.cosmic().background.on)), - background: Some(iced::Background::Color( - theme.cosmic().background.base.into(), - )), + background: if content_container { + Some(iced::Background::Color( + theme.cosmic().background.base.into(), + )) + } else { + None + }, border: iced::Border { color: theme.cosmic().bg_divider().into(), width: if sharp_corners { 0.0 } else { 1.0 }, radius: theme.cosmic().radius_s().into(), }, - shadow: iced::Shadow::default(), + ..Default::default() }); // Show any current dialog on top and centered over the view content diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 972f92a4..7108e652 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -1,8 +1,8 @@ // Copyright 2022 System76 // SPDX-License-Identifier: MPL-2.0 -use crate::cosmic_theme::Density; -use crate::{ext::CollectionWidget, widget, Element}; +use crate::cosmic_theme::{Density, Spacing}; +use crate::{theme, widget, Element}; use apply::Apply; use derive_setters::Setters; use iced::Length; @@ -287,6 +287,12 @@ impl<'a, Message: Clone + 'static> Widget HeaderBar<'a, Message> { /// Converts the headerbar builder into an Iced element. pub fn view(mut self) -> Element<'a, Message> { + let Spacing { + space_xxxs, + space_xxs, + .. + } = theme::active().cosmic().spacing; + // Take ownership of the regions to be packed. let start = std::mem::take(&mut self.start); let center = std::mem::take(&mut self.center); @@ -307,6 +313,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { // If elements exist in the start region, append them here. .push( widget::row::with_children(start) + .spacing(space_xxxs) .align_y(iced::Alignment::Center) .apply(widget::container) .align_x(iced::Alignment::Start) @@ -316,6 +323,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { // This will otherwise use the title as a widget if a title was defined. .push(if !center.is_empty() { widget::row::with_children(center) + .spacing(space_xxxs) .align_y(iced::Alignment::Center) .apply(widget::container) .center_x(Length::Fill) @@ -327,6 +335,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { }) .push( widget::row::with_children(end) + .spacing(space_xxs) .align_y(iced::Alignment::Center) .apply(widget::container) .align_x(iced::Alignment::End) @@ -418,7 +427,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .take() .map(|m| icon!("window-close-symbolic", 16, m)), ) - .spacing(8) + .spacing(theme::active().cosmic().space_xxs()) .apply(widget::container) .center_y(Length::Fill) .into() From 96c67e29a496a930ec40502a9fa3ea81af15da40 Mon Sep 17 00:00:00 2001 From: wiiznokes <78230769+wiiznokes@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:30:56 +0100 Subject: [PATCH 082/556] fix(iced): correct event types for Windows --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 630612a7..aee94ca1 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 630612a7b1cb4fb236edabb2dee88b1754976549 +Subproject commit aee94ca1ffa74c65c4e0e936bc850d1ff4d97e49 From a6db807c1bbffc90b68513171348cad0b4469eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Tue, 26 Nov 2024 19:34:43 +0100 Subject: [PATCH 083/556] fix --- src/app/mod.rs | 25 +------------------------ src/theme/style/iced.rs | 26 ++++++++++++++++++++++---- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 6051a2e9..6e85ff73 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -882,30 +882,7 @@ impl ApplicationExt for App { header = header.end(element.map(Message::App)); } - header - // Needed for apps without a content container, but with a header bar - .apply(container) - .style(move |theme| container::Style { - background: if content_container { - None - } else { - Some(iced::Background::Color( - theme.cosmic().background.base.into(), - )) - }, - border: iced::Border { - radius: [ - theme.cosmic().radius_s()[0] - 1.0, - theme.cosmic().radius_s()[1] - 1.0, - theme.cosmic().radius_0()[2], - theme.cosmic().radius_0()[3], - ] - .into(), - ..Default::default() - }, - ..Default::default() - }) - .apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_header"))) + header.apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_header"))) }) } else { None diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 1bf20524..f51e2650 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -469,8 +469,17 @@ impl iced_container::Catalog for Theme { Container::WindowBackground => iced_container::Style { icon_color: Some(Color::from(cosmic.background.on)), text_color: Some(Color::from(cosmic.background.on)), - background: None, - border: Border::default(), + background: Some(iced::Background::Color(cosmic.background.base.into())), + border: Border { + radius: [ + cosmic.corner_radii.radius_0[0], + cosmic.corner_radii.radius_0[1], + cosmic.corner_radii.radius_s[2], + cosmic.corner_radii.radius_s[3], + ] + .into(), + ..Default::default() + }, shadow: Shadow::default(), }, @@ -504,8 +513,17 @@ impl iced_container::Catalog for Theme { iced_container::Style { icon_color: Some(icon_color), text_color: Some(text_color), - background: None, - border: Border::default(), + background: Some(iced::Background::Color(cosmic.background.base.into())), + border: Border { + radius: [ + cosmic.corner_radii.radius_s[0], + cosmic.corner_radii.radius_s[1], + cosmic.corner_radii.radius_0[2], + cosmic.corner_radii.radius_0[3], + ] + .into(), + ..Default::default() + }, shadow: Shadow::default(), } } From a6c08d68f9cf94107d3aea5872b3ae6fa05a2836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Thu, 28 Nov 2024 00:23:03 +0100 Subject: [PATCH 084/556] fix(toggler): remove extra padding --- src/app/mod.rs | 25 ++++++++++++++++++++++++- src/widget/toggler.rs | 1 + 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 6e85ff73..3f77a78b 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -882,7 +882,30 @@ impl ApplicationExt for App { header = header.end(element.map(Message::App)); } - header.apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_header"))) + if content_container { + header.apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_header"))) + } else { + // Needed to avoid header bar corner gaps for apps without a content container + header + .apply(container) + .style(move |theme| container::Style { + background: Some(iced::Background::Color( + theme.cosmic().background.base.into(), + )), + border: iced::Border { + radius: [ + theme.cosmic().radius_s()[0] - 1.0, + theme.cosmic().radius_s()[1] - 1.0, + theme.cosmic().radius_0()[2], + theme.cosmic().radius_0()[3], + ] + .into(), + ..Default::default() + }, + ..Default::default() + }) + .apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_header"))) + } }) } else { None diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index 27ed6f7b..47656a01 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -12,5 +12,6 @@ where { widget::Toggler::new(is_checked) .size(24) + .spacing(0) .width(Length::Shrink) } From de0c1921f71a2cbd26a653fa2bbf7e5f948a36fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Thu, 28 Nov 2024 15:24:15 +0100 Subject: [PATCH 085/556] fix(list_column): match padding/spacing to designs --- examples/cosmic/src/window.rs | 26 ++++++++++++------------ src/app/mod.rs | 36 ++++++++++++++++++---------------- src/widget/dialog.rs | 2 +- src/widget/flex_row/widget.rs | 2 +- src/widget/header_bar.rs | 2 +- src/widget/list/column.rs | 32 +++++++++++++++++++++--------- src/widget/list/mod.rs | 12 ------------ src/widget/menu/menu_tree.rs | 8 ++++---- src/widget/settings/item.rs | 14 +++---------- src/widget/settings/mod.rs | 5 +---- src/widget/settings/section.rs | 1 - 11 files changed, 65 insertions(+), 75 deletions(-) diff --git a/examples/cosmic/src/window.rs b/examples/cosmic/src/window.rs index da5356b6..9fce8767 100644 --- a/examples/cosmic/src/window.rs +++ b/examples/cosmic/src/window.rs @@ -17,7 +17,7 @@ use cosmic::{ prelude::*, theme::{self, Theme}, widget::{ - button, header_bar, icon, list, nav_bar, nav_bar_toggle, scrollable, segmented_button, + button, container, header_bar, icon, nav_bar, nav_bar_toggle, scrollable, segmented_button, settings, warning, }, Element, @@ -231,7 +231,7 @@ impl Window { } fn page_title(&self, page: Page) -> Element { - row!(text(page.title()).size(28), horizontal_space(Length::Fill),).into() + row!(text(page.title()).size(28), horizontal_space(),).into() } fn is_condensed(&self) -> bool { @@ -253,10 +253,7 @@ impl Window { .label(page.title()) .padding(0) .on_press(Message::from(page)), - row!( - text(sub_page.title()).size(28), - horizontal_space(Length::Fill), - ), + row!(text(sub_page.title()).size(28), horizontal_space(),), ) .spacing(10) .into() @@ -272,7 +269,7 @@ impl Window { sub_page: impl SubPage, ) -> Element { iced::widget::Button::new( - list::container( + container( settings::item_row(vec![ icon::from_name(sub_page.icon_name()).size(20).icon().into(), column!( @@ -281,12 +278,14 @@ impl Window { ) .spacing(2) .into(), - horizontal_space(iced::Length::Fill).into(), + horizontal_space().into(), icon::from_name("go-next-symbolic").size(20).icon().into(), ]) .spacing(16), ) - .padding([20, 24]), + .padding([20, 24]) + .class(theme::Container::List) + .width(Length::Fill), ) .width(Length::Fill) .padding(0) @@ -361,10 +360,7 @@ impl Application for Window { fn subscription(&self) -> Subscription { let window_break = listen_raw(|event, _| match event { - cosmic::iced::Event::Window( - _window_id, - window::Event::Resized { width, height: _ }, - ) => { + cosmic::iced::Event::Window(window::Event::Resized { width, height: _ }) => { let old_width = WINDOW_WIDTH.load(Ordering::Relaxed); if old_width == 0 || old_width < BREAK_POINT && width > BREAK_POINT @@ -584,7 +580,9 @@ impl Application for Window { header, container(column(vec![ warning, - iced::widget::vertical_space(Length::Fixed(12.0)).into(), + iced::widget::vertical_space() + .width(Length::Fixed(12.0)) + .into(), content, ])) .style(theme::Container::Background) diff --git a/src/app/mod.rs b/src/app/mod.rs index 3f77a78b..426f92f8 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -888,7 +888,7 @@ impl ApplicationExt for App { // Needed to avoid header bar corner gaps for apps without a content container header .apply(container) - .style(move |theme| container::Style { + .class(crate::theme::Container::custom(|theme| container::Style { background: Some(iced::Background::Color( theme.cosmic().background.base.into(), )), @@ -903,7 +903,7 @@ impl ApplicationExt for App { ..Default::default() }, ..Default::default() - }) + })) .apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_header"))) } }) @@ -914,21 +914,23 @@ impl ApplicationExt for App { .push(content) .apply(container) .padding(if sharp_corners { 0 } else { 1 }) - .style(move |theme| container::Style { - background: if content_container { - Some(iced::Background::Color( - theme.cosmic().background.base.into(), - )) - } else { - None - }, - border: iced::Border { - color: theme.cosmic().bg_divider().into(), - width: if sharp_corners { 0.0 } else { 1.0 }, - radius: theme.cosmic().radius_s().into(), - }, - ..Default::default() - }); + .class(crate::theme::Container::custom(move |theme| { + container::Style { + background: if content_container { + Some(iced::Background::Color( + theme.cosmic().background.base.into(), + )) + } else { + None + }, + border: iced::Border { + color: theme.cosmic().bg_divider().into(), + width: if sharp_corners { 0.0 } else { 1.0 }, + radius: theme.cosmic().radius_s().into(), + }, + ..Default::default() + } + })); // Show any current dialog on top and centered over the view content // We have to use a popover even without a dialog to keep the tree from changing diff --git a/src/widget/dialog.rs b/src/widget/dialog.rs index 0ee5dcb1..0c7907e4 100644 --- a/src/widget/dialog.rs +++ b/src/widget/dialog.rs @@ -109,7 +109,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes if let Some(button) = dialog.tertiary_action { button_row = button_row.push(button); } - button_row = button_row.push(widget::horizontal_space().width(Length::Fill)); + button_row = button_row.push(widget::horizontal_space()); if let Some(button) = dialog.secondary_action { button_row = button_row.push(button); } diff --git a/src/widget/flex_row/widget.rs b/src/widget/flex_row/widget.rs index 40aee414..49955217 100644 --- a/src/widget/flex_row/widget.rs +++ b/src/widget/flex_row/widget.rs @@ -10,7 +10,7 @@ use iced_core::{ Widget, }; -/// Responsively generates rows and columns of widgets based on its dimmensions. +/// Responsively generates rows and columns of widgets based on its dimensions. #[derive(Setters)] #[must_use] pub struct FlexRow<'a, Message> { diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 7108e652..fd010603 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -329,7 +329,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .center_x(Length::Fill) .into() } else if self.title.is_empty() { - widget::horizontal_space().width(Length::Fill).into() + widget::horizontal_space().into() } else { self.title_widget() }) diff --git a/src/widget/list/column.rs b/src/widget/list/column.rs index eb3b7646..9a908fb7 100644 --- a/src/widget/list/column.rs +++ b/src/widget/list/column.rs @@ -4,7 +4,11 @@ use iced_core::Padding; use iced_widget::container::Catalog; -use crate::{theme, widget::divider, Apply, Element}; +use crate::{ + theme, + widget::{container, divider, vertical_space}, + Apply, Element, +}; pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> { ListColumn::default() @@ -14,16 +18,16 @@ pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> { pub struct ListColumn<'a, Message> { spacing: u16, padding: Padding, - style: crate::theme::Container<'a>, + style: theme::Container<'a>, children: Vec>, } impl<'a, Message: 'static> Default for ListColumn<'a, Message> { fn default() -> Self { Self { - spacing: theme::THEME.lock().unwrap().cosmic().spacing.space_xxs, + spacing: 0, padding: Padding::from(0), - style: crate::theme::Container::List, + style: theme::Container::List, children: Vec::with_capacity(4), } } @@ -36,15 +40,24 @@ impl<'a, Message: 'static> ListColumn<'a, Message> { #[allow(clippy::should_implement_trait)] pub fn add(mut self, item: impl Into>) -> Self { + let cosmic_theme::Spacing { + space_xxs, space_m, .. + } = theme::active().cosmic().spacing; + if !self.children.is_empty() { - self.children.push(divider::horizontal::light().into()); + self.children.push( + container(divider::horizontal::default()) + .padding([0, 16]) + .into(), + ); } // Ensure a minimum height of 32. let list_item = iced::widget::row![ - crate::widget::container(item).align_y(iced::Alignment::Center), - crate::widget::vertical_space().height(iced::Length::Fixed(32.)) + container(item).align_y(iced::Alignment::Center), + vertical_space().height(iced::Length::Fixed(32.)) ] + .padding([space_xxs, space_m]) .align_y(iced::Alignment::Center); self.children.push(list_item.into()); @@ -72,9 +85,10 @@ impl<'a, Message: 'static> ListColumn<'a, Message> { crate::widget::column::with_children(self.children) .spacing(self.spacing) .padding(self.padding) - .apply(super::container) - .padding([self.spacing, 8]) + .apply(container) + .padding([self.spacing, 0]) .class(self.style) + .width(iced::Length::Fill) .into() } } diff --git a/src/widget/list/mod.rs b/src/widget/list/mod.rs index 9b844e2e..7ec5d107 100644 --- a/src/widget/list/mod.rs +++ b/src/widget/list/mod.rs @@ -4,15 +4,3 @@ pub mod column; pub use self::column::{list_column, ListColumn}; - -use crate::widget::Container; -use crate::Element; - -pub fn container<'a, Message>( - content: impl Into>, -) -> Container<'a, Message, crate::Theme, crate::Renderer> { - super::container(content) - .padding([16, 6]) - .class(crate::theme::Container::List) - .width(iced::Length::Fill) -} diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index 85ef8f5e..01ca3076 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -248,7 +248,7 @@ where let key = find_key(&action, key_binds); let mut items = vec![ widget::text(label).into(), - widget::horizontal_space().width(Length::Fill).into(), + widget::horizontal_space().into(), widget::text(key).into(), ]; @@ -266,7 +266,7 @@ where let mut items = vec![ widget::text(label).into(), - widget::horizontal_space().width(Length::Fill).into(), + widget::horizontal_space().into(), widget::text(key).into(), ]; @@ -298,7 +298,7 @@ where }, widget::Space::with_width(spacing.space_xxs).into(), widget::text(label).align_x(iced::Alignment::Start).into(), - widget::horizontal_space().width(Length::Fill).into(), + widget::horizontal_space().into(), widget::text(key).into(), ]; @@ -313,7 +313,7 @@ where trees.push(MenuTree::::with_children( menu_button(vec![ widget::text(label).into(), - widget::horizontal_space().width(Length::Fill).into(), + widget::horizontal_space().into(), widget::icon::from_name("pan-end-symbolic") .size(16) .icon() diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index 8c978f7b..36cc555f 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -21,7 +21,7 @@ pub fn item<'a, Message: 'static>( ) -> Row<'a, Message> { item_row(vec![ text(title).wrapping(Wrapping::Word).into(), - horizontal_space().width(iced::Length::Fill).into(), + horizontal_space().into(), widget.into(), ]) } @@ -30,13 +30,9 @@ pub fn item<'a, Message: 'static>( #[must_use] #[allow(clippy::module_name_repetitions)] pub fn item_row(children: Vec>) -> Row { - let cosmic_theme::Spacing { - space_s, space_xs, .. - } = theme::THEME.lock().unwrap().cosmic().spacing; row::with_children(children) - .spacing(space_xs) + .spacing(theme::active().cosmic().space_xs()) .align_y(iced::Alignment::Center) - .padding([0, space_s]) } /// A settings item aligned in a flex row @@ -57,12 +53,8 @@ pub fn flex_item<'a, Message: 'static>( /// A settings item aligned in a flex row #[allow(clippy::module_name_repetitions)] pub fn flex_item_row(children: Vec>) -> FlexRow { - let cosmic_theme::Spacing { - space_s, space_xs, .. - } = theme::THEME.lock().unwrap().cosmic().spacing; flex_row(children) - .padding([0, space_s]) - .spacing(space_xs) + .spacing(theme::active().cosmic().space_xs()) .min_item_width(200.0) .justify_items(iced::Alignment::Center) .justify_content(AlignContent::SpaceBetween) diff --git a/src/widget/settings/mod.rs b/src/widget/settings/mod.rs index 9e2eccf9..805e58c8 100644 --- a/src/widget/settings/mod.rs +++ b/src/widget/settings/mod.rs @@ -13,8 +13,5 @@ use crate::{theme, Element}; /// A column with a predefined style for creating a settings panel #[must_use] pub fn view_column(children: Vec>) -> Column { - let space_m = theme::THEME.lock().unwrap().cosmic().spacing.space_m; - column::with_children(children) - .spacing(space_m) - .padding([0, space_m]) + column::with_children(children).spacing(theme::active().cosmic().space_m()) } diff --git a/src/widget/settings/section.rs b/src/widget/settings/section.rs index d42c2318..e32251ef 100644 --- a/src/widget/settings/section.rs +++ b/src/widget/settings/section.rs @@ -1,7 +1,6 @@ // Copyright 2022 System76 // SPDX-License-Identifier: MPL-2.0 -use crate::ext::CollectionWidget; use crate::widget::{column, text, ListColumn}; use crate::Element; use std::borrow::Cow; From 931165050d202c7af180cd7687351ec62d94c396 Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Mon, 2 Dec 2024 23:13:27 -0500 Subject: [PATCH 086/556] chore: update iced --- iced | 2 +- src/widget/text_input/input.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/iced b/iced index aee94ca1..23869067 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit aee94ca1ffa74c65c4e0e936bc850d1ff4d97e49 +Subproject commit 238690672d5e12e4290479698bb1bc357d629728 diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index bea23db8..f440b59c 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -1649,7 +1649,7 @@ where if !is_secure { if let Some((start, end)) = state.cursor.selection(value) { clipboard.write( - iced_core::clipboard::Kind::Primary, + iced_core::clipboard::Kind::Standard, value.select(start, end).to_string(), ); } @@ -1663,7 +1663,7 @@ where if !is_secure { if let Some((start, end)) = state.cursor.selection(value) { clipboard.write( - iced_core::clipboard::Kind::Primary, + iced_core::clipboard::Kind::Standard, value.select(start, end).to_string(), ); } @@ -1683,7 +1683,7 @@ where content } else { let content: String = clipboard - .read(iced_core::clipboard::Kind::Primary) + .read(iced_core::clipboard::Kind::Standard) .unwrap_or_default() .chars() .filter(|c| !c.is_control()) From b80d90e5ce6bc88195a830b7c3b6c3d88b50f788 Mon Sep 17 00:00:00 2001 From: wiiznokes <78230769+wiiznokes@users.noreply.github.com> Date: Tue, 3 Dec 2024 05:13:54 +0100 Subject: [PATCH 087/556] fix: compile for markdown feature --- src/theme/style/iced.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index f51e2650..1b6a156d 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -1471,9 +1471,9 @@ impl iced_widget::text_editor::Catalog for Theme { impl iced_widget::markdown::Catalog for Theme { fn code_block<'a>() -> ::Class<'a> { Container::custom(|_| iced_container::Style { - background: Some(color!(0x111111).into()), + background: Some(iced::color!(0x111111).into()), text_color: Some(Color::WHITE), - border: border::rounded(2), + border: iced::border::rounded(2), ..iced_container::Style::default() }) } From d536341234c928f1c3815c53aee7d3803f11773d Mon Sep 17 00:00:00 2001 From: Tony4dev <78384793+Tony4dev@users.noreply.github.com> Date: Tue, 3 Dec 2024 05:14:33 +0100 Subject: [PATCH 088/556] fix(segmented_button): model index out of bounds when setting position --- src/widget/segmented_button/model/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/widget/segmented_button/model/mod.rs b/src/widget/segmented_button/model/mod.rs index f790bff5..449c829f 100644 --- a/src/widget/segmented_button/model/mod.rs +++ b/src/widget/segmented_button/model/mod.rs @@ -355,9 +355,10 @@ where return None; }; + self.order.remove(index as usize); + let position = self.order.len().min(position as usize); - self.order.remove(index as usize); self.order.insert(position, id); Some(position) } From b524ccb0a46b1ca585db09638c33e39e79829716 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 2 Dec 2024 23:33:04 -0500 Subject: [PATCH 089/556] chore: update cctk --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bc2d4237..5d62c75f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,7 +89,7 @@ markdown = ["iced/markdown"] apply = "0.3.0" ashpd = { version = "0.9.1", default-features = false, optional = true } async-fs = { version = "2.1", optional = true } -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "c8d3a1c", optional = true } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d218c76", optional = true } chrono = "0.4.35" cosmic-config = { path = "cosmic-config" } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } From ff0ba4860c9ba732e601485f1952fde5fe5f6952 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 3 Dec 2024 16:46:03 -0500 Subject: [PATCH 090/556] fix: autosize layout limits --- src/widget/autosize.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/widget/autosize.rs b/src/widget/autosize.rs index 8c65e5c0..a792c6ca 100644 --- a/src/widget/autosize.rs +++ b/src/widget/autosize.rs @@ -112,23 +112,21 @@ where &self, tree: &mut Tree, renderer: &Renderer, - _limits: &layout::Limits, + limits: &layout::Limits, ) -> layout::Node { - let mut limits = self.limits; - let min = self.limits.min(); - let max = self.limits.max(); - if self.auto_width { - limits.min_width(min.width); - limits.max_width(max.width); + let mut my_limits = self.limits; + let min = limits.min(); + let max = limits.max(); + if !self.auto_width { + my_limits = limits.min_width(min.width).max_width(max.width); } - if self.auto_height { - limits.min_height(min.height); - limits.max_height(max.height); + if !self.auto_height { + my_limits = limits.min_height(min.height).max_height(max.height); } let node = self .content .as_widget() - .layout(&mut tree.children[0], renderer, &self.limits); + .layout(&mut tree.children[0], renderer, &my_limits); let size = node.size(); layout::Node::with_children(size, vec![node]) } From 43e7213b705be39a213ab9b18a353912a7bc1e48 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 5 Dec 2024 12:21:26 +0100 Subject: [PATCH 091/556] fix: switch to cosmic fork of freedesktop-icons Switch to a fork that we maintain, which containss a few fixes that haven't been merged upstream yet. --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5d62c75f..2be8600b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -111,14 +111,14 @@ slotmap = "1.0.6" smol = { version = "2.0.0", optional = true } thiserror = "1.0.44" tokio = { version = "1.24.2", optional = true } -tracing = "0.1" +tracing = "0.1.41" unicode-segmentation = "1.6" url = "2.4.0" ustr = { version = "1.0.0", features = ["serde"] } zbus = { version = "4.2.1", default-features = false, optional = true } [target.'cfg(unix)'.dependencies] -freedesktop-icons = "0.2.5" +freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } freedesktop-desktop-entry = { version = "0.5.1", optional = true } shlex = { version = "1.3.0", optional = true } From a02fa21d36652ad12d448329dd8167822bbef2ad Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 10 Dec 2024 16:35:24 +0100 Subject: [PATCH 092/556] feat(button): add ListItem style --- src/theme/style/button.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index 873a390f..68145b4b 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -27,6 +27,7 @@ pub enum Button { IconVertical, Image, Link, + ListItem, MenuFolder, MenuItem, MenuRoot, @@ -137,6 +138,21 @@ pub fn appearance( appearance.text_color = Some(component.on.into()); corner_radii = &cosmic.corner_radii.radius_s; } + Button::ListItem => { + corner_radii = &[0.0; 4]; + let (background, text, icon) = color(&cosmic.background.component); + + if selected { + appearance.background = + Some(Background::Color(cosmic.primary.component.hover.into())); + appearance.icon_color = Some(cosmic.accent.base.into()); + appearance.text_color = Some(cosmic.accent.base.into()); + } else { + appearance.background = Some(Background::Color(background)); + appearance.icon_color = icon; + appearance.text_color = text; + } + } Button::MenuItem => { let (background, text, icon) = color(&cosmic.background.component); appearance.background = Some(Background::Color(background)); From 5422ab3130a0f943c71fda558d61c815086e6f40 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 10 Dec 2024 16:35:57 +0100 Subject: [PATCH 093/556] feat(list_column): configurable list item and divider padding --- src/widget/list/column.rs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/widget/list/column.rs b/src/widget/list/column.rs index 9a908fb7..39eb96a1 100644 --- a/src/widget/list/column.rs +++ b/src/widget/list/column.rs @@ -18,15 +18,23 @@ pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> { pub struct ListColumn<'a, Message> { spacing: u16, padding: Padding, + list_item_padding: Padding, + divider_padding: u16, style: theme::Container<'a>, children: Vec>, } impl<'a, Message: 'static> Default for ListColumn<'a, Message> { fn default() -> Self { + let cosmic_theme::Spacing { + space_xxs, space_m, .. + } = theme::active().cosmic().spacing; + Self { spacing: 0, padding: Padding::from(0), + divider_padding: 16, + list_item_padding: [space_xxs, space_m].into(), style: theme::Container::List, children: Vec::with_capacity(4), } @@ -40,14 +48,10 @@ impl<'a, Message: 'static> ListColumn<'a, Message> { #[allow(clippy::should_implement_trait)] pub fn add(mut self, item: impl Into>) -> Self { - let cosmic_theme::Spacing { - space_xxs, space_m, .. - } = theme::active().cosmic().spacing; - if !self.children.is_empty() { self.children.push( container(divider::horizontal::default()) - .padding([0, 16]) + .padding([0, self.divider_padding]) .into(), ); } @@ -57,7 +61,7 @@ impl<'a, Message: 'static> ListColumn<'a, Message> { container(item).align_y(iced::Alignment::Center), vertical_space().height(iced::Length::Fixed(32.)) ] - .padding([space_xxs, space_m]) + .padding(self.list_item_padding) .align_y(iced::Alignment::Center); self.children.push(list_item.into()); @@ -80,6 +84,16 @@ impl<'a, Message: 'static> ListColumn<'a, Message> { self } + pub fn divider_padding(mut self, padding: u16) -> Self { + self.divider_padding = padding; + self + } + + pub fn list_item_padding(mut self, padding: impl Into) -> Self { + self.list_item_padding = padding.into(); + self + } + #[must_use] pub fn into_element(self) -> Element<'a, Message> { crate::widget::column::with_children(self.children) From 2c29a7b158d07f4479e20268ddae4e4860903a2d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 11 Dec 2024 14:47:00 -0500 Subject: [PATCH 094/556] update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 23869067..2cc6865c 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 238690672d5e12e4290479698bb1bc357d629728 +Subproject commit 2cc6865c908d025e43d7fc937f6b67defd51ea18 From aeb87f88865a92fee6b96e061f618d20abe59aaa Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 13 Dec 2024 15:15:16 -0500 Subject: [PATCH 095/556] refactor: backup non-cosmic gtk css files --- cosmic-theme/src/output/gtk4_output.rs | 78 +++++++++++++++++++------- 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/cosmic-theme/src/output/gtk4_output.rs b/cosmic-theme/src/output/gtk4_output.rs index 91795a70..d0ab0c0e 100644 --- a/cosmic-theme/src/output/gtk4_output.rs +++ b/cosmic-theme/src/output/gtk4_output.rs @@ -2,8 +2,9 @@ use crate::{composite::over, steps::steps, Component, Theme}; use palette::{rgb::Rgba, Darken, IntoColor, Lighten, Srgba}; use std::{ fs::{self, File}, - io::Write, + io::{self, Write}, num::NonZeroUsize, + path::Path, }; use super::{to_rgba, OutputError}; @@ -77,7 +78,7 @@ impl Theme { inverted_bg_divider.alpha = 0.5; let scrollbar_outline = to_rgba(inverted_bg_divider); - let mut css = format! {r#" + let mut css = format! {r#"/* GENERATED BY COSMIC */ @define-color window_bg_color {window_bg}; @define-color window_fg_color {window_fg}; @@ -169,6 +170,10 @@ impl Theme { } /// Apply gtk color variable settings + /// + /// # Errors + /// + /// Returns an `OutputError` if there is an error applying the CSS file. pub fn apply_gtk(is_dark: bool) -> Result<(), OutputError> { let Some(config_dir) = dirs::config_dir() else { return Err(OutputError::MissingConfigDir); @@ -180,31 +185,22 @@ impl Theme { fs::create_dir_all(>k4).map_err(OutputError::Io)?; fs::create_dir_all(>k3).map_err(OutputError::Io)?; - let cosmic_css = gtk4 - .join("cosmic") - .join(if is_dark { "dark.css" } else { "light.css" }); + let cosmic_css_dir = gtk4.join("cosmic"); + let cosmic_css = + cosmic_css_dir + .clone() + .join(if is_dark { "dark.css" } else { "light.css" }); let gtk4_dest = gtk4.join("gtk.css"); let gtk3_dest = gtk3.join("gtk.css"); #[cfg(target_family = "unix")] for gtk_dest in [>k4_dest, >k3_dest] { - use std::fs::metadata; use std::os::unix::fs::symlink; - - let mut gtk_dest_bak = gtk_dest.clone(); - gtk_dest_bak.set_extension("css.bak"); + Self::backup_non_cosmic_css(gtk_dest, &cosmic_css_dir).map_err(OutputError::Io)?; if gtk_dest.exists() { - if metadata(>k_dest) - .map_err(OutputError::Io)? - .file_type() - .is_symlink() - { - fs::remove_file(>k_dest).map_err(OutputError::Io)?; - } else { - fs::rename(>k_dest, gtk_dest_bak).map_err(OutputError::Io)?; - } + fs::remove_file(gtk_dest).map_err(OutputError::Io)?; } symlink(&cosmic_css, gtk_dest).map_err(OutputError::Io)?; @@ -213,6 +209,10 @@ impl Theme { } /// Reset the applied gtk css + /// + /// # Errors + /// + /// Returns an `OutputError` if there is an error resetting the CSS file. pub fn reset_gtk() -> Result<(), OutputError> { let Some(config_dir) = dirs::config_dir() else { return Err(OutputError::MissingConfigDir); @@ -221,11 +221,47 @@ impl Theme { let gtk4 = config_dir.join("gtk-4.0"); let gtk3 = config_dir.join("gtk-3.0"); let gtk4_dest = gtk4.join("gtk.css"); + let cosmic_css = gtk4.join("cosmic"); let gtk3_dest = gtk3.join("gtk.css"); - let res = fs::remove_file(gtk3_dest); - fs::remove_file(gtk4_dest).map_err(OutputError::Io)?; - Ok(res.map_err(OutputError::Io)?) + let res = Self::reset_cosmic_css(>k3_dest, &cosmic_css).map_err(OutputError::Io); + Self::reset_cosmic_css(>k4_dest, &cosmic_css).map_err(OutputError::Io)?; + res + } + + fn backup_non_cosmic_css(path: &Path, cosmic_css: &Path) -> io::Result<()> { + if !Self::is_cosmic_css(path, cosmic_css)?.unwrap_or(true) { + let backup_path = path.with_extension("css.bak"); + fs::rename(path, &backup_path)?; + } + Ok(()) + } + + fn reset_cosmic_css(path: &Path, cosmic_css: &Path) -> io::Result<()> { + if Self::is_cosmic_css(path, cosmic_css)?.unwrap_or_default() { + fs::remove_file(path)?; + } + Ok(()) + } + + fn is_cosmic_css(path: &Path, cosmic_css: &Path) -> io::Result> { + if !path.exists() { + return Ok(None); + } + + if let Ok(metadata) = fs::symlink_metadata(path) { + if metadata.file_type().is_symlink() { + if let Ok(actual_cosmic_css) = fs::read_link(path) { + let canonical_target = fs::canonicalize(&actual_cosmic_css)?; + let canonical_base = fs::canonicalize(cosmic_css)?; + return Ok(Some( + canonical_target == canonical_base + || canonical_target.starts_with(&canonical_base), + )); + } + } + } + Ok(Some(false)) } } From 75a11b3c84f44757f6096bf9b062d8d7e0ce5e83 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 19 Dec 2024 15:23:17 -0800 Subject: [PATCH 096/556] Don't require `'static` child in `dnd_destination_for_data` Matches `dnd_destination`. --- src/widget/dnd_destination.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index f429f12a..4fcca830 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -31,10 +31,10 @@ pub fn dnd_destination<'a, Message: 'static>( DndDestination::new(child, mimes) } -pub fn dnd_destination_for_data( - child: impl Into>, +pub fn dnd_destination_for_data<'a, T: AllowedMimeTypes, Message: 'static>( + child: impl Into>, on_finish: impl Fn(Option, DndAction) -> Message + 'static, -) -> DndDestination<'static, Message> { +) -> DndDestination<'a, Message> { DndDestination::for_data(child, on_finish) } From 2d06ec4226f3ca9b280b74357a69273bdeefc18e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Thu, 5 Dec 2024 21:16:10 +0100 Subject: [PATCH 097/556] fix(style): use `radius_s` for nav bar toggle --- src/app/mod.rs | 33 ++++++++++++++++++++++----------- src/theme/style/button.rs | 8 ++++++-- src/theme/style/iced.rs | 12 +----------- src/widget/header_bar.rs | 7 +++++-- src/widget/nav_bar_toggle.rs | 2 +- 5 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 426f92f8..d6864a50 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -703,11 +703,14 @@ impl ApplicationExt for App { .focused_window() .is_some_and(|i| Some(i) == self.core().main_window_id()); + // Offset for the window border + let window_padding = if sharp_corners { 8 } else { 7 }; + let main_content_padding = if content_container { if nav_bar_active { - [0, 8, 8, 0] + [0, window_padding, window_padding, 0] } else { - [0, 8, 8, 8] + [0, window_padding, window_padding, window_padding] } } else { [0, 0, 0, 0] @@ -721,7 +724,11 @@ impl ApplicationExt for App { .nav_bar() .map(|nav| id_container(nav, iced_core::id::Id::new("COSMIC_nav_bar"))) { - widgets.push(container(nav).padding([0, 8, 8, 8]).into()); + widgets.push( + container(nav) + .padding([0, window_padding, window_padding, window_padding]) + .into(), + ); true } else { false @@ -753,7 +760,7 @@ impl ApplicationExt for App { }) .apply(container) .padding(if content_container { - [0, 8, 8, 0] + [0, window_padding, window_padding, 0] } else { [0, 0, 0, 0] }) @@ -801,7 +808,7 @@ impl ApplicationExt for App { }) .apply(container) .padding(if content_container { - [0, 8, 8, 0] + [0, window_padding, window_padding, 0] } else { [0, 0, 0, 0] }) @@ -818,10 +825,14 @@ impl ApplicationExt for App { }); let content_col = crate::widget::column::with_capacity(2) .push(content_row) - .push_maybe( - self.footer() - .map(|footer| container(footer.map(Message::App)).padding([0, 8, 8, 8])), - ); + .push_maybe(self.footer().map(|footer| { + container(footer.map(Message::App)).padding([ + 0, + window_padding, + window_padding, + window_padding, + ]) + })); let content: Element<_> = if core.window.content_container { content_col .apply(container) @@ -840,6 +851,7 @@ impl ApplicationExt for App { let mut header = crate::widget::header_bar() .focused(focused) .title(&core.window.header_title) + .horizontal_padding(window_padding) .on_drag(Message::Cosmic(cosmic::Message::Drag)) .on_right_click(Message::Cosmic(cosmic::Message::ShowWindowMenu)) .on_double_click(Message::Cosmic(cosmic::Message::Maximize)); @@ -852,8 +864,7 @@ impl ApplicationExt for App { Message::Cosmic(cosmic::Message::ToggleNavBarCondensed) } else { Message::Cosmic(cosmic::Message::ToggleNavBar) - }) - .class(crate::theme::Button::HeaderBar); + }); header = header.start(toggle); } diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index 68145b4b..9f32b72b 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -14,13 +14,13 @@ use crate::{ #[derive(Default)] pub enum Button { AppletIcon, + AppletMenu, Custom { active: Box Style>, disabled: Box Style>, hovered: Box Style>, pressed: Box Style>, }, - AppletMenu, Destructive, HeaderBar, Icon, @@ -31,6 +31,7 @@ pub enum Button { MenuFolder, MenuItem, MenuRoot, + NavToggle, #[default] Standard, Suggested, @@ -73,7 +74,7 @@ pub fn appearance( } } - Button::Icon | Button::IconVertical | Button::HeaderBar => { + Button::Icon | Button::IconVertical | Button::HeaderBar | Button::NavToggle => { if matches!(style, Button::IconVertical) { corner_radii = &cosmic.corner_radii.radius_m; if selected { @@ -82,6 +83,9 @@ pub fn appearance( ))); } } + if matches!(style, Button::NavToggle) { + corner_radii = &cosmic.corner_radii.radius_s; + } let (background, text, icon) = color(&cosmic.icon_button); appearance.background = Some(Background::Color(background)); diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 1b6a156d..b6e866b7 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -528,17 +528,7 @@ impl iced_container::Catalog for Theme { } } - Container::ContextDrawer => { - let mut appearance = Container::primary(cosmic); - - appearance.shadow = Shadow { - color: cosmic.shade.into(), - offset: Vector::new(0.0, 0.0), - blur_radius: 16.0, - }; - - appearance - } + Container::ContextDrawer => Container::primary(cosmic), Container::Background => Container::background(cosmic), diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index fd010603..07b2593b 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -22,6 +22,7 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { center: Vec::new(), end: Vec::new(), density: None, + horizontal_padding: 8, focused: false, on_double_click: None, } @@ -74,6 +75,9 @@ pub struct HeaderBar<'a, Message> { #[setters(strip_option)] density: Option, + /// Horizontal padding of the headerbar + horizontal_padding: u16, + /// Focused state of the window focused: bool, } @@ -299,7 +303,6 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { let mut end = std::mem::take(&mut self.end); // Also packs the window controls at the very end. - end.push(widget::horizontal_space().width(Length::Fixed(12.0)).into()); end.push(self.window_controls()); let height = match self.density.unwrap_or_else(crate::config::header_size) { @@ -343,7 +346,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { ) .align_y(iced::Alignment::Center) .height(Length::Fixed(height)) - .padding([0, 8]) + .padding([0, self.horizontal_padding]) .spacing(8) .apply(widget::container) .class(crate::theme::Container::HeaderBar { diff --git a/src/widget/nav_bar_toggle.rs b/src/widget/nav_bar_toggle.rs index bd14cf3f..1d807060 100644 --- a/src/widget/nav_bar_toggle.rs +++ b/src/widget/nav_bar_toggle.rs @@ -20,7 +20,7 @@ pub fn nav_bar_toggle() -> NavBarToggle { NavBarToggle { active: false, on_toggle: None, - class: crate::theme::Button::Text, + class: crate::theme::Button::NavToggle, selected: false, } } From 58fc0344595262a3557362ce880489cef7e97841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Wed, 25 Dec 2024 01:09:05 +0100 Subject: [PATCH 098/556] fix(dropdown): styling --- src/app/mod.rs | 30 +++++++++--------------------- src/theme/style/iced.rs | 25 +++++++++++-------------- src/widget/dropdown/menu/mod.rs | 30 +++++++++++++++--------------- src/widget/dropdown/multi/menu.rs | 28 ++++++++++++++-------------- src/widget/header_bar.rs | 6 +----- 5 files changed, 50 insertions(+), 69 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index d6864a50..45b53fc6 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -703,14 +703,11 @@ impl ApplicationExt for App { .focused_window() .is_some_and(|i| Some(i) == self.core().main_window_id()); - // Offset for the window border - let window_padding = if sharp_corners { 8 } else { 7 }; - let main_content_padding = if content_container { if nav_bar_active { - [0, window_padding, window_padding, 0] + [0, 8, 8, 0] } else { - [0, window_padding, window_padding, window_padding] + [0, 8, 8, 8] } } else { [0, 0, 0, 0] @@ -724,11 +721,7 @@ impl ApplicationExt for App { .nav_bar() .map(|nav| id_container(nav, iced_core::id::Id::new("COSMIC_nav_bar"))) { - widgets.push( - container(nav) - .padding([0, window_padding, window_padding, window_padding]) - .into(), - ); + widgets.push(container(nav).padding([0, 8, 8, 8]).into()); true } else { false @@ -760,7 +753,7 @@ impl ApplicationExt for App { }) .apply(container) .padding(if content_container { - [0, window_padding, window_padding, 0] + [0, 8, 8, 0] } else { [0, 0, 0, 0] }) @@ -808,7 +801,7 @@ impl ApplicationExt for App { }) .apply(container) .padding(if content_container { - [0, window_padding, window_padding, 0] + [0, 8, 8, 0] } else { [0, 0, 0, 0] }) @@ -825,14 +818,10 @@ impl ApplicationExt for App { }); let content_col = crate::widget::column::with_capacity(2) .push(content_row) - .push_maybe(self.footer().map(|footer| { - container(footer.map(Message::App)).padding([ - 0, - window_padding, - window_padding, - window_padding, - ]) - })); + .push_maybe( + self.footer() + .map(|footer| container(footer.map(Message::App)).padding([0, 8, 8, 8])), + ); let content: Element<_> = if core.window.content_container { content_col .apply(container) @@ -851,7 +840,6 @@ impl ApplicationExt for App { let mut header = crate::widget::header_bar() .focused(focused) .title(&core.window.header_title) - .horizontal_padding(window_padding) .on_drag(Message::Cosmic(cosmic::Message::Drag)) .on_right_click(Message::Cosmic(cosmic::Message::ShowWindowMenu)) .on_double_click(Message::Cosmic(cosmic::Message::Maximize)); diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index b6e866b7..b12d77f4 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -536,20 +536,17 @@ impl iced_container::Catalog for Theme { Container::Secondary => Container::secondary(cosmic), - Container::Dropdown => { - let theme = self.cosmic(); - - iced_container::Style { - icon_color: None, - text_color: None, - background: Some(iced::Background::Color(theme.primary.base.into())), - border: Border { - radius: cosmic.corner_radii.radius_xs.into(), - ..Default::default() - }, - shadow: Shadow::default(), - } - } + Container::Dropdown => iced_container::Style { + icon_color: None, + text_color: None, + background: Some(iced::Background::Color(cosmic.bg_component_color().into())), + border: Border { + color: cosmic.bg_component_divider().into(), + width: 1.0, + radius: cosmic.corner_radii.radius_s.into(), + }, + shadow: Shadow::default(), + }, Container::Tooltip => iced_container::Style { icon_color: None, diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index 66574420..045a5ef0 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -156,21 +156,21 @@ impl<'a, Message: 'a> Overlay<'a, Message> { style, } = menu; - let mut container = Container::new(Scrollable::new(List { - options, - icons, - hovered_option, - selected_option, - on_selected, - on_option_hovered, - text_size, - text_line_height, - padding, - })); - - container = container - .padding(padding) - .class(crate::style::Container::Dropdown); + let mut container = Container::new(Scrollable::new( + Container::new(List { + options, + icons, + hovered_option, + selected_option, + on_selected, + on_option_hovered, + text_size, + text_line_height, + padding, + }) + .padding(padding), + )) + .class(crate::style::Container::Dropdown); state.tree.diff(&mut container as &mut dyn Widget<_, _, _>); diff --git a/src/widget/dropdown/multi/menu.rs b/src/widget/dropdown/multi/menu.rs index 5e3aa6a6..f5ee5f5b 100644 --- a/src/widget/dropdown/multi/menu.rs +++ b/src/widget/dropdown/multi/menu.rs @@ -152,20 +152,20 @@ impl<'a, Message: 'a> Overlay<'a, Message> { style, } = menu; - let mut container = Container::new(Scrollable::new(InnerList { - options, - hovered_option, - selected_option, - on_selected, - on_option_hovered, - padding, - text_size, - text_line_height, - })); - - container = container - .padding(padding) - .class(crate::style::Container::Dropdown); + let mut container = Container::new(Scrollable::new( + Container::new(InnerList { + options, + hovered_option, + selected_option, + on_selected, + on_option_hovered, + padding, + text_size, + text_line_height, + }) + .padding(padding), + )) + .class(crate::style::Container::Dropdown); state.tree.diff(&mut container as &mut dyn Widget<_, _, _>); diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 07b2593b..1f70220f 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -22,7 +22,6 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { center: Vec::new(), end: Vec::new(), density: None, - horizontal_padding: 8, focused: false, on_double_click: None, } @@ -75,9 +74,6 @@ pub struct HeaderBar<'a, Message> { #[setters(strip_option)] density: Option, - /// Horizontal padding of the headerbar - horizontal_padding: u16, - /// Focused state of the window focused: bool, } @@ -346,7 +342,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { ) .align_y(iced::Alignment::Center) .height(Length::Fixed(height)) - .padding([0, self.horizontal_padding]) + .padding([0, 8]) .spacing(8) .apply(widget::container) .class(crate::theme::Container::HeaderBar { From b2ce4ccea2b0e4c8aa6ee319d1cb580856fcea5a Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Thu, 26 Dec 2024 12:14:41 +0100 Subject: [PATCH 099/556] fix: add cfg for unix only packages --- src/desktop.rs | 20 +++++++++++++++----- src/lib.rs | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/desktop.rs b/src/desktop.rs index 21b50aca..c8a7ab9e 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -1,10 +1,10 @@ +#[cfg(not(windows))] pub use freedesktop_desktop_entry::DesktopEntry; +#[cfg(not(windows))] pub use mime::Mime; -use std::{ - borrow::Cow, - ffi::OsStr, - path::{Path, PathBuf}, -}; +use std::path::{Path, PathBuf}; +#[cfg(not(windows))] +use std::{borrow::Cow, ffi::OsStr}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum IconSource { @@ -42,12 +42,14 @@ impl Default for IconSource { } } +#[cfg(not(windows))] #[derive(Debug, Clone, PartialEq)] pub struct DesktopAction { pub name: String, pub exec: String, } +#[cfg(not(windows))] #[derive(Debug, Clone, PartialEq, Default)] pub struct DesktopEntryData { pub id: String, @@ -62,6 +64,7 @@ pub struct DesktopEntryData { pub prefers_dgpu: bool, } +#[cfg(not(windows))] pub fn load_applications<'a>( locale: impl Into>, include_no_display: bool, @@ -69,6 +72,7 @@ pub fn load_applications<'a>( load_applications_filtered(locale, |de| include_no_display || !de.no_display()) } +#[cfg(not(windows))] pub fn app_id_or_fallback_matches(app_id: &str, entry: &DesktopEntryData) -> bool { let lowercase_wm_class = match entry.wm_class.as_ref() { Some(s) => Some(s.to_lowercase()), @@ -80,6 +84,7 @@ pub fn app_id_or_fallback_matches(app_id: &str, entry: &DesktopEntryData) -> boo || app_id.to_lowercase() == entry.name.to_lowercase() } +#[cfg(not(windows))] pub fn load_applications_for_app_ids<'a, 'b>( locale: impl Into>, app_ids: impl Iterator, @@ -123,6 +128,7 @@ pub fn load_applications_for_app_ids<'a, 'b>( applications } +#[cfg(not(windows))] pub fn load_applications_filtered<'a, F: FnMut(&DesktopEntry) -> bool>( locale: impl Into>, mut filter: F, @@ -148,6 +154,7 @@ pub fn load_applications_filtered<'a, F: FnMut(&DesktopEntry) -> bool>( .collect() } +#[cfg(not(windows))] pub fn load_desktop_file<'a>( locale: impl Into>, path: impl AsRef, @@ -160,6 +167,7 @@ pub fn load_desktop_file<'a>( }) } +#[cfg(not(windows))] impl DesktopEntryData { fn from_desktop_entry<'a>( locale: impl Into>, @@ -229,6 +237,7 @@ impl DesktopEntryData { } } +#[cfg(not(windows))] pub async fn spawn_desktop_exec(exec: S, env_vars: I, app_id: Option<&str>) where S: AsRef, @@ -293,6 +302,7 @@ where } } +#[cfg(not(windows))] #[cfg(feature = "desktop-systemd-scope")] #[zbus::proxy( interface = "org.freedesktop.systemd1.Manager", diff --git a/src/lib.rs b/src/lib.rs index 886ae559..3e583d13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,7 +75,7 @@ pub mod keyboard_nav; #[cfg(feature = "desktop")] pub mod desktop; -#[cfg(feature = "process")] +#[cfg(all(feature = "process", not(windows)))] pub mod process; #[cfg(feature = "wayland")] From 3f2ba11d56817dbcea2d62b8afd462aa9f8a3a3e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 27 Dec 2024 20:45:13 -0500 Subject: [PATCH 100/556] refactor: send initial config after watching for changes --- cosmic-config/src/dbus.rs | 44 ++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/cosmic-config/src/dbus.rs b/cosmic-config/src/dbus.rs index dd0612c8..c1808e35 100644 --- a/cosmic-config/src/dbus.rs +++ b/cosmic-config/src/dbus.rs @@ -86,27 +86,6 @@ fn watcher_stream config, - Err((errors, default)) => { - if !errors.is_empty() { - eprintln!("Error getting config: {config_id} {errors:?}"); - } - default - } - }; - - if let Err(err) = tx - .send(Update { - errors: Vec::new(), - keys: Vec::new(), - config: config.clone(), - }) - .await - { - eprintln!("Failed to send config: {err}"); - } - let mut attempts = 0; loop { @@ -168,6 +147,29 @@ fn watcher_stream config, + Err((errors, default)) => { + if !errors.is_empty() { + eprintln!("Error getting config: {config_id} {errors:?}"); + } + default + } + }; + + if let Err(err) = tx + .send(Update { + errors: Vec::new(), + keys: Vec::new(), + config: config.clone(), + }) + .await + { + eprintln!("Failed to send config: {err}"); + } + loop { let change: Changed = futures::select! { c = changes.next() => { From 51ede4bce64b5add7d162260751a4d4e705f9138 Mon Sep 17 00:00:00 2001 From: Jason Hansen Date: Tue, 31 Dec 2024 16:37:04 -0700 Subject: [PATCH 101/556] fix(segmented_button): close context menu when clicked outside --- src/widget/menu/menu_inner.rs | 10 ++++++++-- src/widget/segmented_button/widget.rs | 15 ++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 19644b7a..b64fba2c 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -579,7 +579,7 @@ where .merge(menu_status) } - Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. }) => { + Mouse(ButtonReleased(_)) | Touch(FingerLifted { .. }) => { let state = self.tree.state.downcast_mut::(); state.pressed = false; @@ -596,7 +596,13 @@ where .iter() .any(|ms| ms.menu_bounds.check_bounds.contains(overlay_cursor)); - if self.close_condition.click_inside && is_inside { + if self.close_condition.click_inside + && is_inside + && matches!( + event, + Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. }) + ) + { state.reset(); return Captured; } diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 9fa62d49..7841b3ae 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -910,6 +910,13 @@ where } } + if let Event::Mouse(mouse::Event::ButtonReleased(_)) + | Event::Touch(touch::Event::FingerLifted { .. }) = event + { + state.focused = false; + state.focused_item = Item::None; + } + if let Some(on_activate) = self.on_activate.as_ref() { if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerLifted { .. }) = event @@ -928,8 +935,6 @@ where state.show_context = Some(key); state.context_cursor = cursor_position.position().unwrap_or_default(); - state.focused = true; - state.focused_item = Item::Tab(key); let menu_state = tree.children[0].state.downcast_mut::(); @@ -1321,13 +1326,17 @@ where let center_y = bounds.center_y(); + let menu_open = !tree.children.is_empty() + && tree.children[0].state.downcast_ref::().open; + let key_is_active = self.model.is_active(key); let key_is_hovered = self.button_is_hovered(state, key); + let key_has_context_menu_open = menu_open && state.show_context == Some(key); let status_appearance = if self.button_is_focused(state, key) { appearance.focus } else if key_is_active { appearance.active - } else if key_is_hovered { + } else if key_is_hovered || key_has_context_menu_open { appearance.hover } else { appearance.inactive From fdefc5860b8150ed663727e9247ae8b97d3f5748 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 3 Jan 2025 01:18:39 +0100 Subject: [PATCH 102/556] perf: avoid duplicate Ustr for default font names --- src/config/mod.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 14cf419e..91529596 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -4,12 +4,12 @@ //! Configurations available to libcosmic applications. use crate::cosmic_theme::Density; +use crate::icon_theme::DEFAULT; use cosmic_config::cosmic_config_derive::CosmicConfigEntry; use cosmic_config::{Config, CosmicConfigEntry}; -use iced::font::Family; use serde::{Deserialize, Serialize}; use std::sync::{LazyLock, RwLock}; -use ustr::Ustr; +use ustr::{existing_ustr, ustr, Ustr}; /// ID for the `CosmicTk` config. pub const ID: &str = "com.system76.CosmicTk"; @@ -103,6 +103,9 @@ pub struct CosmicTk { pub monospace_font: FontConfig, } +const DEFAULT_FONT_FAMILIES: LazyLock<[Ustr; 2]> = + LazyLock::new(|| [ustr("Fira Mono"), ustr("Fira Sans")]); + impl Default for CosmicTk { fn default() -> Self { Self { @@ -113,13 +116,13 @@ impl Default for CosmicTk { header_size: Density::Standard, interface_density: Density::Standard, interface_font: FontConfig { - family: Ustr::from("Fira Sans"), + family: DEFAULT_FONT_FAMILIES[1], weight: iced::font::Weight::Normal, stretch: iced::font::Stretch::Normal, style: iced::font::Style::Normal, }, monospace_font: FontConfig { - family: Ustr::from("Fira Mono"), + family: DEFAULT_FONT_FAMILIES[0], weight: iced::font::Weight::Normal, stretch: iced::font::Stretch::Normal, style: iced::font::Style::Normal, From e162c59160c8dd95aa3a85ec7ff203353d3ebb43 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 3 Jan 2025 21:56:27 +0100 Subject: [PATCH 103/556] perf: reduce memory usage by dropping ustr dependency The string cache used by ustr pre-allocates 12 MB, even if we're only using it for a few font family names. We can therefore manage our own set of leaked strings to reduce memory usage by 12 MB. --- Cargo.toml | 1 - src/config/mod.rs | 39 ++++++++++++++++++++++++++------------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2be8600b..b2afe43f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,7 +114,6 @@ tokio = { version = "1.24.2", optional = true } tracing = "0.1.41" unicode-segmentation = "1.6" url = "2.4.0" -ustr = { version = "1.0.0", features = ["serde"] } zbus = { version = "4.2.1", default-features = false, optional = true } [target.'cfg(unix)'.dependencies] diff --git a/src/config/mod.rs b/src/config/mod.rs index 91529596..5e264aed 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -4,16 +4,21 @@ //! Configurations available to libcosmic applications. use crate::cosmic_theme::Density; -use crate::icon_theme::DEFAULT; use cosmic_config::cosmic_config_derive::CosmicConfigEntry; use cosmic_config::{Config, CosmicConfigEntry}; use serde::{Deserialize, Serialize}; -use std::sync::{LazyLock, RwLock}; -use ustr::{existing_ustr, ustr, Ustr}; +use std::collections::BTreeSet; +use std::sync::{LazyLock, Mutex, RwLock}; /// ID for the `CosmicTk` config. pub const ID: &str = "com.system76.CosmicTk"; +const MONO_FAMILY_DEFAULT: &str = "Fira Mono"; +const SANS_FAMILY_DEFAULT: &str = "Fira Sans"; + +/// Stores static strings of the family names for `iced::Font` compatibility. +pub static FAMILY_MAP: LazyLock>> = LazyLock::new(|| Mutex::default()); + pub static COSMIC_TK: LazyLock> = LazyLock::new(|| { RwLock::new( CosmicTk::config() @@ -67,12 +72,12 @@ pub fn interface_density() -> Density { #[allow(clippy::missing_panics_doc)] pub fn interface_font() -> FontConfig { - COSMIC_TK.read().unwrap().interface_font + COSMIC_TK.read().unwrap().interface_font.clone() } #[allow(clippy::missing_panics_doc)] pub fn monospace_font() -> FontConfig { - COSMIC_TK.read().unwrap().monospace_font + COSMIC_TK.read().unwrap().monospace_font.clone() } #[derive(Clone, CosmicConfigEntry, Debug, Eq, PartialEq)] @@ -103,9 +108,6 @@ pub struct CosmicTk { pub monospace_font: FontConfig, } -const DEFAULT_FONT_FAMILIES: LazyLock<[Ustr; 2]> = - LazyLock::new(|| [ustr("Fira Mono"), ustr("Fira Sans")]); - impl Default for CosmicTk { fn default() -> Self { Self { @@ -116,13 +118,13 @@ impl Default for CosmicTk { header_size: Density::Standard, interface_density: Density::Standard, interface_font: FontConfig { - family: DEFAULT_FONT_FAMILIES[1], + family: SANS_FAMILY_DEFAULT.to_owned(), weight: iced::font::Weight::Normal, stretch: iced::font::Stretch::Normal, style: iced::font::Style::Normal, }, monospace_font: FontConfig { - family: DEFAULT_FONT_FAMILIES[0], + family: MONO_FAMILY_DEFAULT.to_owned(), weight: iced::font::Weight::Normal, stretch: iced::font::Stretch::Normal, style: iced::font::Style::Normal, @@ -137,9 +139,9 @@ impl CosmicTk { } } -#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] pub struct FontConfig { - pub family: Ustr, + pub family: String, pub weight: iced::font::Weight, pub stretch: iced::font::Stretch, pub style: iced::font::Style, @@ -147,8 +149,19 @@ pub struct FontConfig { impl From for iced::Font { fn from(font: FontConfig) -> Self { + let mut family_map = FAMILY_MAP.lock().unwrap(); + + let name: &'static str = family_map + .get(font.family.as_str()) + .map(|&x| x) + .unwrap_or_else(|| { + let value = font.family.clone().leak(); + family_map.insert(value); + value + }); + Self { - family: iced::font::Family::Name(font.family.as_str()), + family: iced::font::Family::Name(name), weight: font.weight, stretch: font.stretch, style: font.style, From aaa2ba3ad4239cb44ba01c10cbca57174d52a7da Mon Sep 17 00:00:00 2001 From: Jason Rodney Hansen Date: Sun, 5 Jan 2025 15:29:17 -0700 Subject: [PATCH 104/556] Fix entering text with compose key Previously entering text in text inputs with the compose key would insert one or more NUL bytes before the inserted character. --- src/widget/text_input/input.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index f440b59c..80bd8b54 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -1742,9 +1742,11 @@ where { let mut editor = Editor::new(unsecured_value, &mut state.cursor); - editor.insert( - text.unwrap_or_default().chars().next().unwrap_or_default(), - ); + let character = + text.unwrap_or_default().chars().next().unwrap_or_default(); + if !character.is_control() { + editor.insert(character); + } let contents = editor.contents(); let unsecured_value = Value::new(&contents); let message = (on_input)(contents); From af9e353f5003cb323a46340acf1939123147a041 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 7 Jan 2025 13:56:58 -0800 Subject: [PATCH 105/556] dnd_source: Add suppport for surface offset The `drag_icon` callback is passed the offset of the cursor within the widget at the start of the drag, and can return an offset the drag surface should be placed relative to the cursor. --- iced | 2 +- src/widget/dnd_source.rs | 17 +++++++++++------ src/widget/text_input/input.rs | 3 ++- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/iced b/iced index 2cc6865c..9ad04dad 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 2cc6865c908d025e43d7fc937f6b67defd51ea18 +Subproject commit 9ad04dadde75ac4b445602fc6f31e05d7b7003c3 diff --git a/src/widget/dnd_source.rs b/src/widget/dnd_source.rs index 38b921ee..d57e099b 100644 --- a/src/widget/dnd_source.rs +++ b/src/widget/dnd_source.rs @@ -32,7 +32,7 @@ pub struct DndSource<'a, Message, D> { container: Element<'a, Message>, window: Option, drag_content: Option D>>, - drag_icon: Option (Element<'static, ()>, tree::State)>>, + drag_icon: Option (Element<'static, ()>, tree::State, Vector)>>, on_start: Option, on_cancelled: Option, on_finish: Option, @@ -90,7 +90,7 @@ impl< #[must_use] pub fn drag_icon( mut self, - f: impl Fn() -> (Element<'static, ()>, tree::State) + 'static, + f: impl Fn(Vector) -> (Element<'static, ()>, tree::State, Vector) + 'static, ) -> Self { self.drag_icon = Some(Box::new(f)); self @@ -102,7 +102,7 @@ impl< self } - pub fn start_dnd(&self, clipboard: &mut dyn Clipboard, bounds: Rectangle) { + pub fn start_dnd(&self, clipboard: &mut dyn Clipboard, bounds: Rectangle, offset: Vector) { let Some(content) = self.drag_content.as_ref().map(|f| f()) else { return; }; @@ -116,13 +116,14 @@ impl< Some(iced_core::clipboard::DndSource::Widget(self.id.clone())) }, self.drag_icon.as_ref().map(|f| { - let (icon, state) = f(); - ( + let (icon, state, offset) = f(offset); + iced_core::clipboard::IconSurface::new( container(icon) .width(Length::Fixed(bounds.width)) .height(Length::Fixed(bounds.height)) .into(), state, + offset, ) }), Box::new(content), @@ -262,7 +263,11 @@ impl< if let Some(on_start) = self.on_start.as_ref() { shell.publish(on_start.clone()) } - self.start_dnd(clipboard, state.cached_bounds); + let offset = Vector::new( + left_pressed_position.x - layout.bounds().x, + left_pressed_position.y - layout.bounds().y, + ); + self.start_dnd(clipboard, state.cached_bounds, offset); state.is_dragging = true; state.left_pressed_position = None; } diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 80bd8b54..0ff27127 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -1350,12 +1350,13 @@ where clipboard, false, id.map(|id| iced_core::clipboard::DndSource::Widget(id)), - Some(( + 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, From bd8347f7fcb8acad3bfd90cb0bc0891330db12e6 Mon Sep 17 00:00:00 2001 From: Dryadxon <81884588+Dryadxon@users.noreply.github.com> Date: Mon, 13 Jan 2025 19:31:26 +0000 Subject: [PATCH 106/556] fix: impl combo_box::Catalog --- src/theme/style/iced.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index b12d77f4..37920618 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -8,8 +8,8 @@ use cosmic_theme::composite::over; use iced::{ overlay::menu, widget::{ - button as iced_button, checkbox as iced_checkbox, container as iced_container, pane_grid, - pick_list, progress_bar, radio, rule, scrollable, + button as iced_button, checkbox as iced_checkbox, combo_box, container as iced_container, + pane_grid, pick_list, progress_bar, radio, rule, scrollable, slider::{self, Rail}, svg, toggler, }, @@ -1481,3 +1481,5 @@ impl iced_widget::qr_code::Catalog for Theme { class(self) } } + +impl combo_box::Catalog for Theme {} From 99b729faff21946ca4104578de01543e8df1faa2 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 14 Jan 2025 21:41:16 +0100 Subject: [PATCH 107/556] chore: update image-rs and set defaults to only png + jpeg --- Cargo.toml | 16 +++++++++++++--- iced | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b2afe43f..ee230e34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ a11y = ["iced/a11y", "iced_accessibility"] # Enable about widget about = ["desktop", "dep:license"] # Builds support for animated images -animated-image = ["image", "dep:async-fs", "tokio?/io-util", "tokio?/fs"] +animated-image = ["dep:async-fs", "tokio?/io-util", "tokio?/fs"] # XXX autosize should not be used on winit windows unless dialogs autosize = [] applet = [ @@ -95,7 +95,10 @@ cosmic-config = { path = "cosmic-config" } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } css-color = "0.2.5" derive_setters = "0.1.5" -image = { version = "0.25.1", optional = true } +image = { version = "0.25.5", default-features = false, features = [ + "jpeg", + "png", +] } lazy_static = "1.4.0" libc = { version = "0.2.155", optional = true } license = { version = "3.5.1", optional = true } @@ -127,7 +130,14 @@ path = "cosmic-theme" [dependencies.iced] path = "./iced" default-features = false -features = ["advanced", "image", "lazy", "svg", "web-colors", "tiny-skia"] +features = [ + "advanced", + "image-without-codecs", + "lazy", + "svg", + "web-colors", + "tiny-skia", +] [dependencies.iced_runtime] path = "./iced/runtime" diff --git a/iced b/iced index 9ad04dad..82b87f9c 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 9ad04dadde75ac4b445602fc6f31e05d7b7003c3 +Subproject commit 82b87f9caac6be9e1a6d75425e7a39d193a8590d From 4a97b3ddd2f972cb8fdc6b7c0482596aa31a8fb8 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 14 Jan 2025 21:45:10 +0100 Subject: [PATCH 108/556] chore: enable image/gif when using animated-image feature --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ee230e34..c5b9bcc1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ a11y = ["iced/a11y", "iced_accessibility"] # Enable about widget about = ["desktop", "dep:license"] # Builds support for animated images -animated-image = ["dep:async-fs", "tokio?/io-util", "tokio?/fs"] +animated-image = ["dep:async-fs", "image/gif", "tokio?/io-util", "tokio?/fs"] # XXX autosize should not be used on winit windows unless dialogs autosize = [] applet = [ From b244970a18e811f955a9e20f223a4a5645d2f822 Mon Sep 17 00:00:00 2001 From: netraptor Date: Wed, 15 Jan 2025 22:28:20 -0700 Subject: [PATCH 109/556] fix: disable async-std default dependency in rfd dependency when using tokio --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c5b9bcc1..6bf1fdb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ serde-keycode = ["iced_core/serde"] # Prevents multiple separate process instances. single-instance = ["dep:zbus", "ron"] # smol async runtime -smol = ["dep:smol", "iced/smol", "zbus?/async-io"] +smol = ["dep:smol", "iced/smol", "zbus?/async-io", "rfd?/async-std"] tokio = [ "dep:tokio", "ashpd?/tokio", @@ -104,7 +104,7 @@ libc = { version = "0.2.155", optional = true } license = { version = "3.5.1", optional = true } mime = { version = "0.3.17", optional = true } palette = "0.7.3" -rfd = { version = "0.14.0", optional = true } +rfd = { version = "0.14.0", default-features = false, features = ["xdg-portal"], optional = true } rustix = { version = "0.38.34", features = [ "pipe", "process", From 90c5c84cced6aa723ad9efdb78827d42785a4878 Mon Sep 17 00:00:00 2001 From: Soso <51865119+sgued@users.noreply.github.com> Date: Thu, 16 Jan 2025 06:29:03 +0100 Subject: [PATCH 110/556] improv(text_input): use Cow for label, helper, and error text --- src/widget/text_input/input.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 0ff27127..b6a50128 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -195,9 +195,9 @@ pub struct TextInput<'a, Message> { padding: Padding, size: Option, helper_size: f32, - label: Option<&'a str>, - helper_text: Option<&'a str>, - error: Option<&'a str>, + label: Option>, + helper_text: Option>, + error: Option>, on_input: Option Message + 'a>>, on_paste: Option Message + 'a>>, on_submit: Option, @@ -275,14 +275,14 @@ where } /// Sets the text of the [`TextInput`]. - pub fn label(mut self, label: &'a str) -> Self { - self.label = Some(label); + pub fn label(mut self, label: impl Into>) -> Self { + self.label = Some(label.into()); self } /// Sets the helper text of the [`TextInput`]. - pub fn helper_text(mut self, helper_text: &'a str) -> Self { - self.helper_text = Some(helper_text); + pub fn helper_text(mut self, helper_text: impl Into>) -> Self { + self.helper_text = Some(helper_text.into()); self } @@ -293,8 +293,8 @@ where } /// Sets the error message of the [`TextInput`]. - pub fn error(mut self, error: &'a str) -> Self { - self.error = Some(error); + pub fn error(mut self, error: impl Into>) -> Self { + self.error = Some(error.into()); self } From 8211fb68bd01534c5fee61de1b762431d156f6e7 Mon Sep 17 00:00:00 2001 From: Tony4dev <78384793+Tony4dev@users.noreply.github.com> Date: Thu, 16 Jan 2025 05:30:21 +0000 Subject: [PATCH 111/556] improv!(calendar): do not select date when navigating prev/next months --- examples/calendar/src/main.rs | 29 ++++++--- src/widget/calendar.rs | 109 +++++++++++++++++++++++++--------- 2 files changed, 101 insertions(+), 37 deletions(-) diff --git a/examples/calendar/src/main.rs b/examples/calendar/src/main.rs index bfad5513..c73c4da7 100644 --- a/examples/calendar/src/main.rs +++ b/examples/calendar/src/main.rs @@ -3,8 +3,9 @@ //! Calendar widget example -use chrono::{Local, NaiveDate}; +use chrono::NaiveDate; use cosmic::app::{Core, Settings, Task}; +use cosmic::widget::calendar::CalendarModel; use cosmic::{executor, iced, ApplicationExt, Element}; /// Runs application with these settings @@ -19,12 +20,14 @@ fn main() -> Result<(), Box> { #[derive(Clone, Debug)] pub enum Message { DateSelected(NaiveDate), + PrevMonth, + NextMonth, } /// The [`App`] stores application-specific state. pub struct App { core: Core, - date_selected: NaiveDate, + calendar_model: CalendarModel, } /// Implement [`cosmic::Application`] to integrate with COSMIC. @@ -51,11 +54,9 @@ impl cosmic::Application for App { /// Creates the application, and optionally emits task on initialize. fn init(core: Core, _input: Self::Flags) -> (Self, Task) { - let now = Local::now(); - let mut app = App { core, - date_selected: NaiveDate::from(now.naive_local()), + calendar_model: CalendarModel::now(), }; let command = app.update_title(); @@ -67,11 +68,17 @@ impl cosmic::Application for App { fn update(&mut self, message: Self::Message) -> Task { match message { Message::DateSelected(date) => { - self.date_selected = date; + self.calendar_model.selected = date; + } + Message::PrevMonth => { + self.calendar_model.show_prev_month(); + } + Message::NextMonth => { + self.calendar_model.show_next_month(); } } - println!("Date selected: {:?}", self.date_selected); + println!("Date selected: {:?}", &self.calendar_model.selected); Task::none() } @@ -80,8 +87,12 @@ impl cosmic::Application for App { fn view(&self) -> Element { let mut content = cosmic::widget::column().spacing(12); - let calendar = - cosmic::widget::calendar(&self.date_selected, |date| Message::DateSelected(date)); + let calendar = cosmic::widget::calendar( + &self.calendar_model, + |date| Message::DateSelected(date), + || Message::PrevMonth, + || Message::NextMonth, + ); content = content.push(calendar); diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index 2fbf9f4d..e7fad168 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -7,16 +7,20 @@ use std::cmp; use crate::iced_core::{Alignment, Length, Padding}; use crate::widget::{button, column, grid, icon, row, text, Grid}; -use chrono::{Datelike, Days, Months, NaiveDate, Weekday}; +use chrono::{Datelike, Days, Local, Months, NaiveDate, Weekday}; /// A widget that displays an interactive calendar. pub fn calendar( - selected: &NaiveDate, + model: &CalendarModel, on_select: impl Fn(NaiveDate) -> M + 'static, + on_prev: impl Fn() -> M + 'static, + on_next: impl Fn() -> M + 'static, ) -> Calendar { Calendar { - selected, + model, on_select: Box::new(on_select), + on_prev: Box::new(on_prev), + on_next: Box::new(on_next), } } @@ -38,21 +42,64 @@ pub fn set_day(date_selected: NaiveDate, day: u32) -> NaiveDate { } } -pub fn set_prev_month(date_selected: NaiveDate) -> NaiveDate { - date_selected - .checked_sub_months(Months::new(1)) - .expect("valid naivedate") +pub struct CalendarModel { + pub selected: NaiveDate, + visible: NaiveDate, } -pub fn set_next_month(date_selected: NaiveDate) -> NaiveDate { - date_selected - .checked_add_months(Months::new(1)) - .expect("valid naivedate") +impl CalendarModel { + pub fn now() -> Self { + let now = Local::now(); + let naive_now = NaiveDate::from(now.naive_local()); + CalendarModel { + selected: naive_now.clone(), + visible: naive_now, + } + } + + pub fn new(selected: NaiveDate) -> Self { + CalendarModel { + selected, + visible: selected.clone(), + } + } + + pub fn show_prev_month(&mut self) { + let prev_month_date = self + .visible + .clone() + .checked_sub_months(Months::new(1)) + .expect("valid naivedate"); + + self.visible = prev_month_date.clone(); + } + + pub fn show_next_month(&mut self) { + let next_month_date = self + .visible + .clone() + .checked_add_months(Months::new(1)) + .expect("valid naivedate"); + + self.visible = next_month_date.clone(); + } + + pub fn set_prev_month(&mut self) { + self.show_prev_month(); + self.selected = self.visible.clone(); + } + + pub fn set_next_month(&mut self) { + self.show_next_month(); + self.selected = self.visible.clone(); + } } pub struct Calendar<'a, M> { - selected: &'a NaiveDate, + model: &'a CalendarModel, on_select: Box M>, + on_prev: Box M>, + on_next: Box M>, } impl<'a, Message> From> for crate::Element<'a, Message> @@ -60,19 +107,18 @@ where Message: Clone + 'static, { fn from(this: Calendar<'a, Message>) -> Self { - let date = text(this.selected.format("%B %-d, %Y").to_string()).size(18); - let day_of_week = text::body(this.selected.format("%A").to_string()); + let date = text(this.model.visible.format("%B %Y").to_string()).size(18); let month_controls = row::with_capacity(2) .push( button::icon(icon::from_name("go-previous-symbolic")) .padding([0, 12]) - .on_press((this.on_select)(set_prev_month(this.selected.clone()))), + .on_press((this.on_prev)()), ) .push( button::icon(icon::from_name("go-next-symbolic")) .padding([0, 12]) - .on_press((this.on_select)(set_next_month(this.selected.clone()))), + .on_press((this.on_next)()), ); // Calender @@ -93,8 +139,8 @@ where calendar_grid = calendar_grid.insert_row(); let monday = get_calender_first( - this.selected.year(), - this.selected.month(), + this.model.visible.year(), + this.model.visible.month(), first_day_of_week, ); let mut day_iter = monday.iter_days(); @@ -104,17 +150,24 @@ where } let date = day_iter.next().unwrap(); - let is_month = - date.month() == this.selected.month() && date.year_ce() == this.selected.year_ce(); - let is_day = date.day() == this.selected.day() && is_month; + let is_currently_viewed_month = date.month() == this.model.visible.month() + && date.year_ce() == this.model.visible.year_ce(); + let is_currently_selected_month = date.month() == this.model.selected.month() + && date.year_ce() == this.model.selected.year_ce(); + let is_currently_selected_day = + date.day() == this.model.selected.day() && is_currently_selected_month; - calendar_grid = - calendar_grid.push(date_button(date, is_month, is_day, &this.on_select)); + calendar_grid = calendar_grid.push(date_button( + date, + is_currently_viewed_month, + is_currently_selected_day, + &this.on_select, + )); } let content_list = column::with_children(vec![ row::with_children(vec![ - column::with_children(vec![date.into(), day_of_week.into()]).into(), + date.into(), crate::widget::Space::with_width(Length::Fill).into(), month_controls.into(), ]) @@ -132,11 +185,11 @@ where fn date_button( date: NaiveDate, - is_month: bool, - is_day: bool, + is_currently_viewed_month: bool, + is_currently_selected_day: bool, on_select: &dyn Fn(NaiveDate) -> Message, ) -> crate::widget::Button<'static, Message> { - let style = if is_day { + let style = if is_currently_selected_day { button::ButtonClass::Suggested } else { button::ButtonClass::Text @@ -147,7 +200,7 @@ fn date_button( .height(Length::Fixed(36.0)) .width(Length::Fixed(36.0)); - if is_month { + if is_currently_viewed_month { button.on_press((on_select)(set_day(date, date.day()))) } else { button From 1914006cdd689491e3199d9887a715866c91d8aa Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 16 Jan 2025 06:38:08 +0100 Subject: [PATCH 112/556] fix(text_input): compiler errors after Cow change --- src/widget/text_input/input.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index b6a50128..8d46b839 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -450,9 +450,9 @@ where &self.style, self.dnd_icon, self.line_height, - self.error, - self.label, - self.helper_text, + self.error.as_deref(), + self.label.as_deref(), + self.helper_text.as_deref(), self.helper_size, self.helper_line_height, &layout.bounds(), @@ -552,7 +552,7 @@ where .iter() .map(|l| l.text()) .collect::() - != self.label.unwrap_or_default() + != self.label.as_deref().unwrap_or_default() || state .helper_text .raw() @@ -561,7 +561,7 @@ where .iter() .map(|l| l.text()) .collect::() - != self.helper_text.unwrap_or_default() + != self.helper_text.as_deref().unwrap_or_default() { state.is_secure = self.is_secure; state.dirty = true; @@ -669,8 +669,8 @@ where self.leading_icon.as_ref(), self.trailing_icon.as_ref(), self.line_height, - self.label, - self.helper_text, + self.label.as_deref(), + self.helper_text.as_deref(), self.helper_size, self.helper_line_height, font, @@ -863,9 +863,9 @@ where &self.style, self.dnd_icon, self.line_height, - self.error, - self.label, - self.helper_text, + self.error.as_deref(), + self.label.as_deref(), + self.helper_text.as_deref(), self.helper_size, self.helper_line_height, viewport, From 00a4042c40f8f65b4174b18b88bcb120569416ef Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Sun, 19 Jan 2025 11:58:57 +0000 Subject: [PATCH 113/556] chore(calendar): add derive attributes --- src/widget/calendar.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index e7fad168..cdc4c7cf 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -42,6 +42,7 @@ pub fn set_day(date_selected: NaiveDate, day: u32) -> NaiveDate { } } +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct CalendarModel { pub selected: NaiveDate, visible: NaiveDate, From def11c6c967853c6aba338e6bb91381fb0e07a5b Mon Sep 17 00:00:00 2001 From: Tony4dev <78384793+Tony4dev@users.noreply.github.com> Date: Sun, 19 Jan 2025 14:37:07 +0000 Subject: [PATCH 114/556] improv(calendar): make visible public --- src/widget/calendar.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index cdc4c7cf..b1ee927f 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -45,7 +45,7 @@ pub fn set_day(date_selected: NaiveDate, day: u32) -> NaiveDate { #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct CalendarModel { pub selected: NaiveDate, - visible: NaiveDate, + pub visible: NaiveDate, } impl CalendarModel { @@ -58,11 +58,8 @@ impl CalendarModel { } } - pub fn new(selected: NaiveDate) -> Self { - CalendarModel { - selected, - visible: selected.clone(), - } + pub fn new(selected: NaiveDate, visible: NaiveDate) -> Self { + CalendarModel { selected, visible } } pub fn show_prev_month(&mut self) { @@ -94,6 +91,11 @@ impl CalendarModel { self.show_next_month(); self.selected = self.visible.clone(); } + + pub fn set_selected_visible(&mut self, selected: NaiveDate) { + self.selected = selected; + self.visible = self.selected.clone(); + } } pub struct Calendar<'a, M> { From 900fc34444c068ed8f91c8bd0090539ee0f6031d Mon Sep 17 00:00:00 2001 From: ellieplayswow <164806095+ellieplayswow@users.noreply.github.com> Date: Mon, 20 Jan 2025 10:08:36 +0000 Subject: [PATCH 115/556] feat(icon): add rotation property --- src/widget/icon/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/widget/icon/mod.rs b/src/widget/icon/mod.rs index eb91fc00..318b9fb1 100644 --- a/src/widget/icon/mod.rs +++ b/src/widget/icon/mod.rs @@ -16,6 +16,7 @@ use crate::Element; use derive_setters::Setters; use iced::widget::{Image, Svg}; use iced::{ContentFit, Length, Rectangle}; +use iced_core::Rotation; /// Create an [`Icon`] from a pre-existing [`Handle`] pub fn icon(handle: Handle) -> Icon { @@ -25,6 +26,7 @@ pub fn icon(handle: Handle) -> Icon { height: None, size: 16, class: crate::theme::Svg::default(), + rotation: None, width: None, } } @@ -47,6 +49,8 @@ pub struct Icon { width: Option, #[setters(strip_option)] height: Option, + #[setters(strip_option)] + rotation: Option, } impl Icon { @@ -80,6 +84,7 @@ impl Icon { self.height .unwrap_or_else(|| Length::Fixed(f32::from(self.size))), ) + .rotation(self.rotation.unwrap_or_else(Rotation::default)) .content_fit(self.content_fit) .into() }; @@ -95,6 +100,7 @@ impl Icon { self.height .unwrap_or_else(|| Length::Fixed(f32::from(self.size))), ) + .rotation(self.rotation.unwrap_or_else(Rotation::default)) .content_fit(self.content_fit) .symbolic(self.handle.symbolic) .into() From fdfd80f8b133f3a1240a122489310146a224a7a7 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 22 Jan 2025 15:41:49 +0100 Subject: [PATCH 116/556] chore: Update ron to 0.9 --- cosmic-config/Cargo.toml | 2 +- cosmic-theme/Cargo.toml | 2 +- examples/config/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cosmic-config/Cargo.toml b/cosmic-config/Cargo.toml index 98e98dc9..4a9d4944 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -15,7 +15,7 @@ zbus = { version = "4.2.1", default-features = false, optional = true } atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" } calloop = { version = "0.14.0", optional = true } notify = "6.0.0" -ron = "0.8.0" +ron = "0.9.0-alpha.0" serde = "1.0.152" cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true } iced = { path = "../iced/", default-features = false, optional = true } diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index 0e6c5c20..2034197e 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -21,7 +21,7 @@ serde = { version = "1.0.129", features = ["derive"] } serde_json = { version = "1.0.64", optional = true, features = [ "preserve_order", ] } -ron = "0.8" +ron = "0.9.0-alpha.0" lazy_static = "1.4.0" csscolorparser = { version = "0.6.2", features = ["serde"] } cosmic-config = { path = "../cosmic-config/", default-features = false, features = [ diff --git a/examples/config/Cargo.toml b/examples/config/Cargo.toml index 98b49b0f..40e118ad 100644 --- a/examples/config/Cargo.toml +++ b/examples/config/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] cosmic-config = { path = "../../cosmic-config" } -ron = "0.8.0" +ron = "0.9.0-alpha.0" From 9426a985c62288e996827a3560770c19271b337a Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 4 Feb 2025 10:56:21 -0800 Subject: [PATCH 117/556] Update `iced` and `cctk` --- Cargo.toml | 2 +- iced | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6bf1fdb1..f8591022 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,7 +89,7 @@ markdown = ["iced/markdown"] apply = "0.3.0" ashpd = { version = "0.9.1", default-features = false, optional = true } async-fs = { version = "2.1", optional = true } -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d218c76", optional = true } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "29ab323", optional = true } chrono = "0.4.35" cosmic-config = { path = "cosmic-config" } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } diff --git a/iced b/iced index 82b87f9c..4ea727c0 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 82b87f9caac6be9e1a6d75425e7a39d193a8590d +Subproject commit 4ea727c08ea78053aac78d7b6c5a1faedcd43239 From f59eb77252d1730319d532fc0a0c50ce860edd9d Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 12 Feb 2025 18:30:32 +0100 Subject: [PATCH 118/556] perf: set static mmap threshold on gnu target env by default --- src/app/mod.rs | 6 +++++- src/app/settings.rs | 4 ++++ src/applet/mod.rs | 8 +++++--- src/lib.rs | 8 ++++++-- src/malloc.rs | 14 ++++++++++++++ 5 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 src/malloc.rs diff --git a/src/app/mod.rs b/src/app/mod.rs index 45b53fc6..58b1c66d 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -138,7 +138,11 @@ pub(crate) fn iced_settings( /// /// Returns error on application failure. pub fn run(settings: Settings, flags: App::Flags) -> iced::Result { - let default_font = settings.default_font; + #[cfg(target_env = "gnu")] + if let Some(threshold) = settings.default_mmap_threshold { + crate::malloc::limit_mmap_threshold(threshold); + } + let (settings, mut flags, window_settings) = iced_settings::(settings, flags); #[cfg(not(feature = "multi-window"))] { diff --git a/src/app/settings.rs b/src/app/settings.rs index bd8a5f3d..a5782c99 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -38,6 +38,9 @@ pub struct Settings { /// Default size of fonts. pub(crate) default_text_size: f32, + /// Set the default mmap threshold for malloc with mallopt. + pub(crate) default_mmap_threshold: Option, + /// Whether the window should be resizable or not. /// and the size of the window border which can be dragged for a resize pub(crate) resizable: Option, @@ -85,6 +88,7 @@ impl Default for Settings { default_font: font::default(), default_icon_theme: None, default_text_size: 14.0, + default_mmap_threshold: Some(128 * 1024), resizable: Some(8.0), scale_factor: std::env::var("COSMIC_SCALE") .ok() diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 4980c91d..0ca8a4f1 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -153,7 +153,6 @@ impl Context { self.size = Size::Hardcoded((width, height)); } - #[must_use] #[allow(clippy::cast_precision_loss)] pub fn window_settings(&self) -> crate::app::Settings { let (width, height) = self.suggested_size(true); @@ -183,7 +182,6 @@ impl Context { matches!(self.anchor, PanelAnchor::Top | PanelAnchor::Bottom) } - #[must_use] pub fn icon_button_from_handle<'a, Message: 'static>( &self, icon: widget::icon::Handle, @@ -213,7 +211,6 @@ impl Context { .class(Button::AppletIcon) } - #[must_use] pub fn icon_button<'a, Message: 'static>( &self, icon_name: &'a str, @@ -385,6 +382,11 @@ pub fn run(flags: App::Flags) -> iced::Result { let mut settings = helper.window_settings(); settings.resizable = None; + #[cfg(target_env = "gnu")] + if let Some(threshold) = settings.default_mmap_threshold { + crate::malloc::limit_mmap_threshold(threshold); + } + if let Some(icon_theme) = settings.default_icon_theme.clone() { crate::icon_theme::set_default(icon_theme); } diff --git a/src/lib.rs b/src/lib.rs index 3e583d13..bd4651ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,9 @@ pub use cosmic_config; #[doc(inline)] pub use cosmic_theme; +#[cfg(feature = "desktop")] +pub mod desktop; + #[cfg(any(feature = "xdg-portal", feature = "rfd"))] pub mod dialog; @@ -73,8 +76,9 @@ pub use iced_wgpu; pub mod icon_theme; pub mod keyboard_nav; -#[cfg(feature = "desktop")] -pub mod desktop; +#[cfg(target_env = "gnu")] +pub(crate) mod malloc; + #[cfg(all(feature = "process", not(windows)))] pub mod process; diff --git a/src/malloc.rs b/src/malloc.rs new file mode 100644 index 00000000..001f9a16 --- /dev/null +++ b/src/malloc.rs @@ -0,0 +1,14 @@ +use std::os::raw::c_int; + +const M_MMAP_THRESHOLD: c_int = -3; + +extern "C" { + fn mallopt(param: c_int, value: c_int) -> c_int; +} + +/// Prevents glibc from hoarding memory via memory fragmentation. +pub fn limit_mmap_threshold(threshold: i32) { + unsafe { + mallopt(M_MMAP_THRESHOLD, threshold as c_int); + } +} From cba28b13720a5e5c9ebfa668a534d062c35cfd78 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 10 Feb 2025 14:28:54 -0800 Subject: [PATCH 119/556] Update `cctk` and `iced` --- Cargo.toml | 2 +- iced | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f8591022..0c75ccc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,7 +89,7 @@ markdown = ["iced/markdown"] apply = "0.3.0" ashpd = { version = "0.9.1", default-features = false, optional = true } async-fs = { version = "2.1", optional = true } -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "29ab323", optional = true } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "178eb0b", optional = true } chrono = "0.4.35" cosmic-config = { path = "cosmic-config" } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } diff --git a/iced b/iced index 4ea727c0..7b5d3057 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 4ea727c08ea78053aac78d7b6c5a1faedcd43239 +Subproject commit 7b5d3057c2499ae691c1ececd6062d97c21d09da From 0b7e23444afb3f351cd947c52babb6b87f30381d Mon Sep 17 00:00:00 2001 From: Tony4dev <78384793+Tony4dev@users.noreply.github.com> Date: Thu, 13 Feb 2025 15:22:42 +0000 Subject: [PATCH 120/556] feat(segmented_button): add len method --- src/widget/segmented_button/model/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/widget/segmented_button/model/mod.rs b/src/widget/segmented_button/model/mod.rs index 449c829f..c24fe85f 100644 --- a/src/widget/segmented_button/model/mod.rs +++ b/src/widget/segmented_button/model/mod.rs @@ -310,6 +310,11 @@ where self.items.get(id).map_or(false, |e| e.enabled) } + /// Get number of items in the model. + pub fn len(&self) -> usize { + self.order.len() + } + /// Iterates across items in the model in the order that they are displayed. pub fn iter(&self) -> impl Iterator + '_ { self.order.iter().copied() From ccc1068d9fd894334feef7d50a7a2b6930c7a34f Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 14 Feb 2025 21:44:08 +0100 Subject: [PATCH 121/556] feat(cosmic_config): add ConfigGet::get_{local,system_default} Required by https://github.com/pop-os/cosmic-settings/pull/975 to a modify a config containing a HashMap which is used to partially-override the system default config in the compositor. --- cosmic-config/src/lib.rs | 60 ++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index f915da71..0a3e0c9e 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -33,6 +33,7 @@ pub enum Error { Io(std::io::Error), NoConfigDirectory, Notify(notify::Error), + NotFound, Ron(ron::Error), RonSpanned(ron::error::SpannedError), GetKey(String, std::io::Error), @@ -46,6 +47,7 @@ impl fmt::Display for Error { Self::Io(err) => err.fmt(f), Self::NoConfigDirectory => write!(f, "cosmic config directory not found"), Self::Notify(err) => err.fmt(f), + Self::NotFound => write!(f, "cosmic config key not configured"), Self::Ron(err) => err.fmt(f), Self::RonSpanned(err) => err.fmt(f), Self::GetKey(key, err) => write!(f, "failed to get key '{}': {}", key, err), @@ -55,6 +57,15 @@ impl fmt::Display for Error { impl std::error::Error for Error {} +impl Error { + /// Whether the reason for the missing config is caused by an error. + /// + /// Useful for determining if it is appropriate to log as an error. + pub fn is_err(&self) -> bool { + matches!(self, Self::NoConfigDirectory | Self::NotFound) + } +} + impl From> for Error { fn from(f: atomicwrites::Error) -> Self { Self::AtomicWrites(f) @@ -87,7 +98,15 @@ impl From for Error { pub trait ConfigGet { /// Get a configuration value + /// + /// Fallback to the system default if a local user override is not defined. fn get(&self, key: &str) -> Result; + + /// Get a locally-defined configuration value from the user's local config. + fn get_local(&self, key: &str) -> Result; + + /// Get the system-defined default configuration value. + fn get_system_default(&self, key: &str) -> Result; } pub trait ConfigSet { @@ -216,7 +235,7 @@ impl Config { } // Start a transaction (to set multiple configs at the same time) - pub fn transaction<'a>(&'a self) -> ConfigTransaction<'a> { + pub fn transaction(&self) -> ConfigTransaction { ConfigTransaction { config: self, updates: Mutex::new(Vec::new()), @@ -288,6 +307,7 @@ impl Config { Ok(system_path.join(sanitize_name(key)?)) } + /// Get the path of the key in the user's local config directory. fn key_path(&self, key: &str) -> Result { let Some(user_path) = self.user_path.as_ref() else { return Err(Error::NoConfigDirectory); @@ -300,22 +320,34 @@ impl Config { impl ConfigGet for Config { //TODO: check for transaction fn get(&self, key: &str) -> Result { + match self.get_local(key) { + Ok(value) => Ok(value), + Err(Error::NotFound) => self.get_system_default(key), + Err(why) => Err(why), + } + } + + fn get_local(&self, key: &str) -> Result { // If key path exists - let key_path = self.key_path(key); - let data = match key_path { - Ok(key_path) if key_path.is_file() => { + match self.key_path(key)? { + key_path if key_path.is_file() => { // Load user override - fs::read_to_string(key_path).map_err(|err| Error::GetKey(key.to_string(), err))? + let data = fs::read_to_string(key_path) + .map_err(|err| Error::GetKey(key.to_string(), err))?; + + Ok(ron::from_str(&data)?) } - _ => { - // Load system default - let default_path = self.default_path(key)?; - fs::read_to_string(default_path) - .map_err(|err| Error::GetKey(key.to_string(), err))? - } - }; - let t = ron::from_str(&data)?; - Ok(t) + + _ => Err(Error::NotFound), + } + } + + fn get_system_default(&self, key: &str) -> Result { + // Load system default + let default_path = self.default_path(key)?; + let data = + fs::read_to_string(default_path).map_err(|err| Error::GetKey(key.to_string(), err))?; + Ok(ron::from_str(&data)?) } } From cd8f4ee8590170f09ba3c90542be92daaba38e87 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 14 Feb 2025 22:39:09 +0100 Subject: [PATCH 122/556] fix(cosmic_config): treat errors getting key_path in get_local as NotFound --- cosmic-config/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 0a3e0c9e..357625e1 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -329,8 +329,8 @@ impl ConfigGet for Config { fn get_local(&self, key: &str) -> Result { // If key path exists - match self.key_path(key)? { - key_path if key_path.is_file() => { + match self.key_path(key) { + Ok(key_path) if key_path.is_file() => { // Load user override let data = fs::read_to_string(key_path) .map_err(|err| Error::GetKey(key.to_string(), err))?; From 580db26868a67aba768669fc3762e81a93e0fb85 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 14 Feb 2025 22:42:50 +0100 Subject: [PATCH 123/556] fix(cosmic_config): is_err method conditions reversed --- cosmic-config/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 357625e1..0c0f4db7 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -62,7 +62,7 @@ impl Error { /// /// Useful for determining if it is appropriate to log as an error. pub fn is_err(&self) -> bool { - matches!(self, Self::NoConfigDirectory | Self::NotFound) + !matches!(self, Self::NoConfigDirectory | Self::NotFound) } } From 25bf8f60cc695175dde945e8b09ac629af96c145 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 17 Feb 2025 01:17:52 -0500 Subject: [PATCH 124/556] feat: improve accent_text for low contrast accent colors. --- cosmic-theme/src/model/theme.rs | 164 +++++++++++++++++++++----------- iced | 2 +- 2 files changed, 108 insertions(+), 58 deletions(-) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 821450a1..0740b4e5 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -1,11 +1,11 @@ use crate::{ composite::over, - steps::{color_index, get_surface_color, get_text, steps}, + steps::{color_index, get_index, get_surface_color, get_text, steps}, Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, Spacing, ThemeMode, DARK_PALETTE, LIGHT_PALETTE, NAME, }; use cosmic_config::{Config, CosmicConfigEntry}; -use palette::{rgb::Rgb, IntoColor, Oklcha, Srgb, Srgba}; +use palette::{color_difference::Wcag21RelativeContrast, rgb::Rgb, IntoColor, Oklcha, Srgb, Srgba}; use serde::{Deserialize, Serialize}; use std::num::NonZeroUsize; @@ -97,6 +97,9 @@ pub struct Theme { pub is_frosted: bool, /// shade color for dialogs pub shade: Srgba, + /// accent text colors + /// If None, accent base color is the accent text color. + pub accent_text: Option, } impl Default for Theme { @@ -276,7 +279,7 @@ impl Theme { #[allow(clippy::doc_markdown)] /// get @accent_text_color pub fn accent_text_color(&self) -> Srgba { - self.accent.base + self.accent_text.unwrap_or(self.accent.base) } #[must_use] #[allow(clippy::doc_markdown)] @@ -847,6 +850,105 @@ impl ThemeBuilder { text_steps_array.as_ref(), ); + let primary = { + let container_bg = if let Some(primary_container_bg_color) = primary_container_bg { + primary_container_bg_color + } else { + get_surface_color(bg_index, 5, &step_array, is_dark, &p_ref.neutral_1) + }; + + let base_index: usize = color_index(container_bg, step_array.len()); + let component_base = + get_surface_color(base_index, 6, &step_array, is_dark, &p_ref.neutral_3); + + component_hovered_overlay = if base_index < 91 { + p_ref.neutral_10 + } else { + p_ref.neutral_0 + }; + component_hovered_overlay.alpha = 0.1; + + component_pressed_overlay = component_hovered_overlay; + component_pressed_overlay.alpha = 0.2; + + let container = Container::new( + Component::component( + component_base, + accent, + get_text( + color_index(component_base, step_array.len()), + &step_array, + &p_ref.neutral_8, + text_steps_array.as_ref(), + ), + component_hovered_overlay, + component_pressed_overlay, + is_high_contrast, + p_ref.neutral_8, + ), + container_bg, + get_text( + base_index, + &step_array, + &p_ref.neutral_8, + text_steps_array.as_ref(), + ), + get_surface_color( + base_index, + 5, + &neutral_steps, + base_index <= 65, + &p_ref.neutral_6, + ), + ); + + container + }; + + let accent_text = if is_dark { + (primary.base.relative_contrast(accent.color) < 4.).then(|| { + let step_array = steps(accent, NonZeroUsize::new(100).unwrap()); + let primary_color_index = color_index(primary.base, 100); + let steps = if is_high_contrast { 60 } else { 50 }; + let accent_text = get_surface_color( + primary_color_index, + steps, + &step_array, + is_dark, + &Srgba::new(1., 1., 1., 1.), + ); + if primary.base.relative_contrast(accent_text.color) < 4. { + Srgba::new(1., 1., 1., 1.) + } else { + accent_text + } + }) + } else { + let darkest = if bg.relative_luminance().luma < primary.base.relative_luminance().luma { + bg + } else { + primary.base + }; + + (darkest.relative_contrast(accent.color) < 4.).then(|| { + let step_array = steps(accent, NonZeroUsize::new(100).unwrap()); + let primary_color_index = color_index(darkest, 100); + let steps = if is_high_contrast { 60 } else { 50 }; + let accent_text = get_surface_color( + primary_color_index, + steps, + &step_array, + is_dark, + &Srgba::new(1., 1., 1., 1.), + ); + if darkest.relative_contrast(accent_text.color) < 4. { + Srgba::new(0., 0., 0., 1.) + } else { + accent_text + } + }) + }; + let mut theme: Theme = Theme { name: palette.name().to_string(), shade: if palette.is_dark() { @@ -879,60 +981,7 @@ impl ThemeBuilder { &p_ref.neutral_6, ), ), - primary: { - let container_bg = if let Some(primary_container_bg_color) = primary_container_bg { - primary_container_bg_color - } else { - get_surface_color(bg_index, 5, &step_array, is_dark, &p_ref.neutral_1) - }; - - let base_index: usize = color_index(container_bg, step_array.len()); - let component_base = - get_surface_color(base_index, 6, &step_array, is_dark, &p_ref.neutral_3); - - component_hovered_overlay = if base_index < 91 { - p_ref.neutral_10 - } else { - p_ref.neutral_0 - }; - component_hovered_overlay.alpha = 0.1; - - component_pressed_overlay = component_hovered_overlay; - component_pressed_overlay.alpha = 0.2; - - let container = Container::new( - Component::component( - component_base, - accent, - get_text( - color_index(component_base, step_array.len()), - &step_array, - &p_ref.neutral_8, - text_steps_array.as_ref(), - ), - component_hovered_overlay, - component_pressed_overlay, - is_high_contrast, - p_ref.neutral_8, - ), - container_bg, - get_text( - base_index, - &step_array, - &p_ref.neutral_8, - text_steps_array.as_ref(), - ), - get_surface_color( - base_index, - 5, - &neutral_steps, - base_index <= 65, - &p_ref.neutral_6, - ), - ); - - container - }, + primary, secondary: { let container_bg = if let Some(secondary_container_bg) = secondary_container_bg { secondary_container_bg @@ -1098,6 +1147,7 @@ impl ThemeBuilder { active_hint, window_hint, is_frosted, + accent_text, }; theme.spacing = spacing; theme.corner_radii = corner_radii; diff --git a/iced b/iced index 7b5d3057..654f3c33 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 7b5d3057c2499ae691c1ececd6062d97c21d09da +Subproject commit 654f3c33b54679c06c041be6eea2c03f6d7cfdce From 3f25af87a3a41c53bdbd69878f32766bbcda2194 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 17 Feb 2025 02:11:33 -0500 Subject: [PATCH 125/556] refactor: small widget container colors --- cosmic-theme/src/model/theme.rs | 38 +++++++++------------------------ cosmic-theme/src/steps.rs | 34 +++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 0740b4e5..3a1d0c83 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -1,6 +1,6 @@ use crate::{ composite::over, - steps::{color_index, get_index, get_surface_color, get_text, steps}, + steps::{color_index, get_index, get_small_widget_color, get_surface_color, get_text, steps}, Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, Spacing, ThemeMode, DARK_PALETTE, LIGHT_PALETTE, NAME, }; @@ -847,7 +847,7 @@ impl ThemeBuilder { color_index(bg_component, step_array.len()), &step_array, &p_ref.neutral_8, - text_steps_array.as_ref(), + text_steps_array.as_deref(), ); let primary = { @@ -879,7 +879,7 @@ impl ThemeBuilder { color_index(component_base, step_array.len()), &step_array, &p_ref.neutral_8, - text_steps_array.as_ref(), + text_steps_array.as_deref(), ), component_hovered_overlay, component_pressed_overlay, @@ -891,15 +891,9 @@ impl ThemeBuilder { base_index, &step_array, &p_ref.neutral_8, - text_steps_array.as_ref(), - ), - get_surface_color( - base_index, - 5, - &neutral_steps, - base_index <= 65, - &p_ref.neutral_6, + text_steps_array.as_deref(), ), + get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.neutral_6), ); container @@ -971,15 +965,9 @@ impl ThemeBuilder { bg_index, &step_array, &p_ref.neutral_8, - text_steps_array.as_ref(), - ), - get_surface_color( - bg_index, - 5, - &neutral_steps, - bg_index <= 65, - &p_ref.neutral_6, + text_steps_array.as_deref(), ), + get_small_widget_color(bg_index, 5, &neutral_steps, &p_ref.neutral_6), ), primary, secondary: { @@ -1011,7 +999,7 @@ impl ThemeBuilder { color_index(secondary_component, step_array.len()), &step_array, &p_ref.neutral_8, - text_steps_array.as_ref(), + text_steps_array.as_deref(), ), component_hovered_overlay, component_pressed_overlay, @@ -1023,15 +1011,9 @@ impl ThemeBuilder { base_index, &step_array, &p_ref.neutral_8, - text_steps_array.as_ref(), - ), - get_surface_color( - base_index, - 5, - &neutral_steps, - base_index <= 65, - &p_ref.neutral_6, + text_steps_array.as_deref(), ), + get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.neutral_6), ) }, accent: Component::colored_component( diff --git a/cosmic-theme/src/steps.rs b/cosmic-theme/src/steps.rs index 2c306e3c..506b6fa8 100644 --- a/cosmic-theme/src/steps.rs +++ b/cosmic-theme/src/steps.rs @@ -1,7 +1,7 @@ use std::num::NonZeroUsize; use almost::equal; -use palette::{convert::FromColorUnclamped, ClampAssign, FromColor, Oklcha, Srgb, Srgba}; +use palette::{convert::FromColorUnclamped, ClampAssign, FromColor, Lch, Oklcha, Srgb, Srgba}; /// Get an array of 100 colors with a specific hue and chroma /// over the full range of lightness. @@ -35,7 +35,7 @@ pub fn get_index(base_index: usize, steps: usize, step_len: usize, is_dark: bool pub fn get_surface_color( base_index: usize, steps: usize, - step_array: &Vec, + step_array: &[Srgba], mut is_dark: bool, fallback: &Srgba, ) -> Srgba { @@ -48,12 +48,38 @@ pub fn get_surface_color( .unwrap_or(fallback) } +/// get surface color given a base and some steps +#[must_use] +pub fn get_small_widget_color( + base_index: usize, + steps: usize, + step_array: &[Srgba], + fallback: &Srgba, +) -> Srgba { + assert!(step_array.len() == 100); + + let is_dark = base_index <= 40 || (base_index > 51 && base_index < 65); + + let res = *get_index(base_index, steps, step_array.len(), is_dark) + .and_then(|i| step_array.get(i)) + .unwrap_or(fallback); + + let mut lch = Lch::from_color(res); + if lch.chroma / Lch::::max_chroma() > 0.03 { + lch.chroma = 0.03 * Lch::::max_chroma(); + lch.clamp_assign(); + Srgba::from_color(lch) + } else { + res + } +} + /// get text color given a base background color pub fn get_text( base_index: usize, - step_array: &Vec, + step_array: &[Srgba], fallback: &Srgba, - tint_array: Option<&Vec>, + tint_array: Option<&[Srgba]>, ) -> Srgba { assert!(step_array.len() == 100); let step_array = if let Some(tint_array) = tint_array { From 7d84d21129e0b4590fedf20b377da80b6a177b47 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 18 Feb 2025 22:25:40 +0100 Subject: [PATCH 126/556] improv: switch to Open Sans and Noto Sans Mono, with tweaked text styles --- res/Fira/FiraMono-Regular.otf | Bin 125712 -> 0 bytes res/Fira/FiraSans-Bold.otf | Bin 305832 -> 0 bytes res/Fira/FiraSans-Light.otf | Bin 359048 -> 0 bytes res/Fira/FiraSans-Regular.otf | Bin 357996 -> 0 bytes res/Fira/FiraSans-SemiBold.otf | Bin 367408 -> 0 bytes res/Fira/SIL Open Font License.txt | 48 -------------- res/noto/LICENSE | 92 +++++++++++++++++++++++++++ res/noto/NotoSansMono-Bold.ttf | Bin 0 -> 523412 bytes res/noto/NotoSansMono-Regular.ttf | Bin 0 -> 510612 bytes res/open-sans/LICENSE | 88 +++++++++++++++++++++++++ res/open-sans/OpenSans-Bold.ttf | Bin 0 -> 224592 bytes res/open-sans/OpenSans-ExtraBold.ttf | Bin 0 -> 222584 bytes res/open-sans/OpenSans-Light.ttf | Bin 0 -> 222412 bytes res/open-sans/OpenSans-Regular.ttf | Bin 0 -> 217360 bytes res/open-sans/OpenSans-Semibold.ttf | Bin 0 -> 221328 bytes src/app/mod.rs | 12 ++-- src/config/mod.rs | 4 +- src/widget/text.rs | 34 +++++----- 18 files changed, 206 insertions(+), 72 deletions(-) delete mode 100644 res/Fira/FiraMono-Regular.otf delete mode 100644 res/Fira/FiraSans-Bold.otf delete mode 100644 res/Fira/FiraSans-Light.otf delete mode 100644 res/Fira/FiraSans-Regular.otf delete mode 100644 res/Fira/FiraSans-SemiBold.otf delete mode 100644 res/Fira/SIL Open Font License.txt create mode 100644 res/noto/LICENSE create mode 100644 res/noto/NotoSansMono-Bold.ttf create mode 100644 res/noto/NotoSansMono-Regular.ttf create mode 100644 res/open-sans/LICENSE create mode 100644 res/open-sans/OpenSans-Bold.ttf create mode 100644 res/open-sans/OpenSans-ExtraBold.ttf create mode 100644 res/open-sans/OpenSans-Light.ttf create mode 100644 res/open-sans/OpenSans-Regular.ttf create mode 100644 res/open-sans/OpenSans-Semibold.ttf diff --git a/res/Fira/FiraMono-Regular.otf b/res/Fira/FiraMono-Regular.otf deleted file mode 100644 index c30b25b974989c2705747b2b33dc37cbe964709e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 125712 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 diff --git a/res/Fira/FiraSans-Bold.otf b/res/Fira/FiraSans-Bold.otf deleted file mode 100644 index 3e586b4254a9f2c350e75d8f7cdee9c9319457af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 305832 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%0D$*u^qF=V0G^p$HTDY5diWqM_uTo^Q7+9UjP4F| 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�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%0AIPe87It)M3mu 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 diff --git a/res/Fira/FiraSans-Light.otf b/res/Fira/FiraSans-Light.otf deleted file mode 100644 index 1445a4afd444e420a637cb39e74d96310679e8bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 359048 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%0A~ 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 diff --git a/res/Fira/FiraSans-Regular.otf b/res/Fira/FiraSans-Regular.otf deleted file mode 100644 index 98ef98c8db44a9cb8632228989fa65fdd183a06b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 357996 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

yL05P_V|dJQhhv$Vb83Xg-B9|F*`At8okWj_8Kd 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 z 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%0D1GP3O=dnd&G<5X`2K4Vo8ZlT}7 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%0A-st=4 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 E1Ey2bjsO4v diff --git a/res/Fira/FiraSans-SemiBold.otf b/res/Fira/FiraSans-SemiBold.otf deleted file mode 100644 index 6f7204d84faab5f260aa15f90eb2aea0159b55fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 367408 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⋘2?=mW^r{-&L>oxBs)qYCd!oRli6Wufc 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�zm98aLBuDc~>-hyarbYQ&y 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�uIkbWRefykPo2A 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%0D!r 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, - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. - -The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the copyright statement(s). - -"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. - -"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. - -5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/res/noto/LICENSE b/res/noto/LICENSE new file mode 100644 index 00000000..d952d62c --- /dev/null +++ b/res/noto/LICENSE @@ -0,0 +1,92 @@ +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/res/noto/NotoSansMono-Bold.ttf b/res/noto/NotoSansMono-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..47179fae7696c95f71b9e15102cdfc44ca029daa GIT binary patch literal 523412 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

#CQ#CWM5c@aNPG>M-YZ6In0* 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⋙i~crYe!8&6yyQ3LaoZtI$p=?< zmi7}HhH&~T`UrSf6oATrR|8eid3hI5i9i5)wW~Z^ev~o}Hio|N z)@#7eTYV3qtC-7Wzzz5;_6+bUAg^vX^iU20C?6*XU<+Cxmr%iwQYq6kqLt3W;Yn^I zF4)QGn4QD?y_y^AkZS;GaEPwE3vb_2TR~%y=OKLr9b2^ zq<_mRPORl?FC0lffg#a*DE|C?r2%?C0Q~gX#izrN2~HPi6v4F?kzSQz#i@nztWb;b z2Z%x>SEV~j1o2|hke@&%;!ttI$|xRRW~xTAWe667#3ulyqIuV`zBlSm0*>iH@I1%a zD2LXDNQ5Y5nuk^dr(U~c+_bMCkK=HAH|CUh80E#t=tg6+cTS&m{_LJPL5us!iz4>m zLPx5-XL^5Xx^wt}E3SE9aYJ%otZQ*%+Leb6M$X%H@fDX{jCD*1S_N&urksbbD5`+J zKZ97c9yXft*1$0#2``KGrmzdHU6j^=P*sesvk}s%FPaRjfKFH4o6 zhxN2HV^ZGcPjh+AIq_g$ zf2-roHyh7uts1H>YwlSwbL)DWb&q0c?n(1MSFEgU7(&S{@N3}WPNfmH$b)j=MYZJt z4{3}@012}L0@v`N7>y$TTP}V;y%e30UCx9bH0t<)kTYMFOSy?P1*#kLrHp`c!iQMO z0yFf@Knqz0%xGCQbWI6@oGdnnR(p9Zw(M}QR|@Qdv%}?1x7T0du{D0N#s&ZjNJojK z|FdcaQZ`n<{OyW)jg50FZd^UrrjA;Rnp@J3{2jK2*^jll)1-1!T`ccqkvlr!F|*&PJ>PfimS@Hw*EQ13P;aDQ zxz6+{{DXTQQI7D()R(b#++y#CFW5<8G?ZML(sdH)W9N(%owPx9IV^6g8$c?>3=2QI zq{)E7mu>n6vTc@E#A0~v;`GzIjDjYFvzw5$@l|y*2Y`@5)fb@ zX8&)LW!2q`KpKV&AjQ_O`AXh=;edRQ1*oKA>0lZEIv(Hpve)N zAtVITFQW;zif95H{DpAE9A=3!iCZc^AU_(FcMx-&}?@Gb>S2uIeBrU}2=u4*Pk zj+fmAgJNA>c|~olQpne)A9|@Oo`^&e@hb7I6?oSt1Wl0VB@{YL)lqCz5$cnr3d|y? z;-lt_Vm*12{;qS&)|V;2rq|Hx(tGI5KnY~%FW5u;V&x@O)4qswM-=Ca*Z0o2D0pGbwFG~3e4PJ?&lB*{ z)|}`J2gCHsf0TZ(b7ki}{P>H3YpZZaAKrh-cr$-%92IT(;P}|zsJa{S%l%DMfD`>g z?mm8kVlQ!KM)R?sJ;Hy*zX!}{r2P`(=6H`eU;y}vS>h`2`|eFaC&o zhyrNV0V;8h`CM>0t6x`^7SQ&B=t4QnZnslEFKFuj>Qruxm!(Xry!B^ zYoZldG)Ew}YW3?PeNs7+Ck6bTBBW1>@+h))5R=v8d=XQn?>FFwOv0Hq{N8|#Z!c~{ zN@Y)|%GcPwp!?+BVoSxg8Rv|i|2@reg~jGaRP(9ffx|n}CzMwuUpjAs^-;pIp9n^Ekn(OAD%H{!~-Jj z2d}^S`a5Uz&FX3I8JvFSMHumpSu-|v&N$>ec`%?YTQIcp(V>Nhc9!$=G2*t}=lo{y z;Cxy$j`0tb#clK{yUbXmiB?etoidxr*zqirkl5Xxc z8u$|0RWC;JPJ$~D#vS5g7#@+H6`7zJa46=R3_tQ3Q9`wV5Y`glN+6)TtfV*;^!uMT?mt$)VuLZ}4>9H#R_6(s zJFEypE;5hV)PVV?Jj0YG41tQ^*WeeKp(ln+qprebm|QG5m&NFHA`t^OhFw^IfmiT~ zoPkrCPC0DK_<~`p&F+9pb!bH)w^Mz0V9Bv7t~j=M>9J#jE!IG+Q?ppMakX~)>FXYS zH2vrFJ5N2uL!Z6r%bTG<65kCA(y--J zs+D06_Wwb@JLC!BK5SooQHMr`Het=7`LI#)70OG4&)VlN>#X|BqfdU}ShBSy+|oQ) zue>relzupUGj|q<6y&>c;zxbyg^g_;blNiX>T=4ruQ5-d6V=;7aOO3%+6WQ>QOY_6 z8NFa8q>Qhm8CHutDoK%Woupra5(e=RP+h%XAp+Dgg5_3cK#Fwh7*@2rwxX6y%5fiM z2GfwuF0-I?L_-Az-NlhMz(}`FY1smy-W?8GL1%q;hhzTLmt1;$p<`ZK^)#1NQTi)l z)uk2D%6C;?e^qk**3;?t(rADAVkaXj? z8Yedkk@$eSy5`<}yV9C|g(uTL;$7*P51hK2?>zsq)bMiNTy@Ukefy8xd*s<8Fed(q zU-zwgB+h}9aDw+O#Jp5tuQ83=T}glWVT?uaa1_RF^R%9c=QmZd6OR;HW^ zhf5<7=f&G!`*dk4$!PSn?8<;s21Owf#vb|zkm zk3qDtMWt*7OTd1_@q2uT61UNU#q#noUnu0YYI4(r1Ysa;Evz9gV_e1l>4J^3mR5g$ z!v(3GbASHWzhEczrI!X5&zf~={o>{Ouu}J=@1Qj*&nXsxFP`naiI4r>8Mkg9p1H97 zz5~nqhZ4Vf;3wDse{ki3d9!DKa?#xRP3d3qV46&IcyG&ip=ATc+rdjEL4`dKKXowE z+OXW=dS+$ih)A=72+dbC(-l3VbR8%f;T1@0X}61$2PvCIG}y#h9T|%RnKX*bhLk$~ zW8RPyHxR9V%p2$psisMKqiMP@#CcP0k0_O42`0a>p+1?wX+klbC17vU!8@EnIGSW* z6d8HssR!|{&Jaw?MlK?X@PV`}Qm$&OaaR>KHG1qtMSi5-DQJxxId%JqSii4!THm7B z(v_hq=k?#Qhf50FEz6v)gBII@CDqIK+`Kvcv}*s%J$F9lvL6a`ZR%aO$EK#=Q6TnrWD??c*+_eu=Rr_NdM;N- zf=8%O8j07FGC|35;E*P8I1yGrA@YfoNKC2;USKd87xi_5BBdTl*(TmZ7|be^^5jJz z%D^8aFY-+jQpH)4Ia2Cnr%goGsf;p@?C_-%wz-gS5qN zEKX+&VjBd+W@6Z&he(vtSxVg7$A8>@WZ*d@Ao^1J#n+YPV_z0@(>>lV^wi0ooA@}- z?dI=vUp4<#aNdFR55B>lNe_zW(*9=?`Ws?xsTQnD2B$7q-f<1vbSkzhM*zi0Vp<*c z`eQf_ifLL!gI`jCJO>iyK_pf6w!e@Z?pZh5c4WcE+WxYWC;3;r!L93`!D!OEeTC_N zz}x7|ipE1{R$Zxfd1yuYkfLq}c^mp1Iw23Es>Lx!_9ROCcqdV9SIx;eiTW#z+&GEi z%nf~~8NCtOm~cyg-CyRN!r^HMVivitfC~~l3N(E>c>(j1Ho(#nlv=3dm1F~?&5GCL zpP>6DfDg9^ij;o|wgpD;bNjvcSv_=p_44bT7-L&>O>`!HAHB3|P1nq>)m`t|FWfr+ zvdvw>ZqUmfRVox8_Gk?-b!GQx3)pm=4_ygw7+6@q5Y$1G@mvJWm^@-s1zFP2L5?hG z_=ExpOfSXC6;2Cw=#7aa4#~xDx5&1K^T;q?)BM&mu}_^FJFoS(UyI%MiOT7R53N76 zzV2%LyBrTQ<#3rLQ-Q*X_ky0c1zU&z6M0((Wgo&u zViu9N1-oz1EA&>;3scIL?1lVnEkcg!{$gR0njbq`3+eJ?zn%r!q1as5KZz{FfsC>h zb&sKqZB&LS_|Y89tRbHsu};Kd;a?s*dDGZ`oy0nS26mgVF3eQzxJ_xnGm~s~YGyPD z+lj&aR1l$l;)l1;;T1``ZBPK=9u9+ab-W7RH32^aT#_gCgwwi?0P_KM(AXWQah&xK zw}WM^olGXvVx7ToQ_0{QoJn9qxaj7ksMZ1P`Peu6EepSLYC*C-J9nZ zrnm9Wx)*HOy|exvu<-Nvsj-zOZWlZxjCHgK=T-@Jo74fkRS}Py+@xUJB564N%i@ z3@>viflKMh*@cMbyK0AGRSPRSng(Vxombcz?`b=E-GSXly}O52?{)h8mxseM>U%oQ zJru88c20T`f9dL*_N=?Wvtz@^rDRWWj*n4}OzvjYw8wrCdELkgsk|brkcLg^2e5~% zO}Wc}63U2-owtV7E6YYuolJEE6Dtm_MCAh-R2fqG$e497VrW7{;$p1GJA{%BA)G?M zUSu^+A#gqwE>f{wHK~A|hM>0b_G?bIePaDZ^Hw&x=XE7p!_#UCYF(lBicNbuE_Ocl z@aB6isGJ^Muyk|Z1?M#nHxJCT+0J(qTy<#a@#UcRLd-GQ55g=;C)1S97WRG!3D|+u zqhxRtgvqwSgGVyRNM(7c--k&4Fh^BRBQ+9ikm6JgIu=YiC@&!QQ8-QeyWwRQj=cK0 z0}d}jh6)PQ;d{SYK4BI>)Qye!^aRUIuZ>0R2hb+f zPL;u1$ge4k`KzJ~FjzA%SSuEWLZ+4g5Md#KZ}=97rKQv3R&_X>s7I1Ia=y@A3tCZg zy%K4b#Sow~W3*~K<7<#?DR+6xW5q>vh2HMc8H*bI?r=qGgY-Wrus2UHN!G(P9-O6d zK_XJ^wc^d7uVZ*W>a)Y!BhKceW>Ibf)kY5XHYD!1Q3et&HgTXb+9*(XGm>!oprZPa z7_XwNv^eO;&7NpH>coK|PG6*1D&r8QPH6e!NL$~gPu;(EL;Z$D7j0_pTY2U6tA?wW z&*!H)n-fD@hvzpAHm1+(jMdNGx@>lGZk@p6WpK&+-^$C_7bj9NcxBK@HJQ03@)Yo5 zuN&zJZP25DG;s_iWDEqYAcOYyAiwY2qcg6&bobhUIqMgNxxN3~{@n+!=LQ2|3K9W`#p5d8D-XQ1Fo?ru<7;LtUHrn_jZ5?V)K-lYkIwWbp z<>jVE%C|TS06#f-&We(j0(+&mEc6qpi;o31uW(v-sai#ZZ_&?H_l&zO<(Q8+Tc1KO zIqg#{_5kcTevVR$Sop~8Y0-`{4QUY^R{#lii)tUGKIvp6ToK7yW8H@Jv(-vnjI+2e zQJp}(R&v>kKpG-T54`iDL7BlCoz)kS*ujEbBh0#m(wHkV{`KIY+1H$V*{0V0&HeK$ z*6r(iEPabIbmYjMk#Ae~Zl1Y)dh%$vxU4nO*sL^X&2#5^Ahn)E`TnuLs0k6mDU{$<|xR`V??@o4hV|OT4 zw-z?0R;&oV{F8U=+4Rs`b2bkpJ6boi?D^@e{TqhXPP?#mv-9GSjoUmsHXn8b{JVml zp6kx-ZtmzQ4qu)`cd-U)$3Nh|1kQc1M)#$9&|^{nbO}m%SfQEk0)rBYhN+JM1OmXH zR?wW@M}rq3(*Y+cP}|9YrJFoBcz>kPDWlJ1nKHFX;*3OR1(bRGK*xY60PICp2p@Z0wv zzV_O+k6$VVQCakLr^j-%q65Vv=-5Xe%w3lkU^wS5py8BH52qXB+Y$vsn@MIgV~=r6 zd5zVu>T2>hsD`gOotO=xzQHWz4X~6_Tzfc$B)1wgPOCK~2M6U5Fsw<&1BEK7TeYcnOFk_5Dj}aXgA}SM zuL%&}^ZKwucgoE8VMFB8(vNF?P0yCgi6aVMy+CO2%_m!;$F|*b^5i{xudRH)4fWc?HTN2gE67Q|u;(uXcPeDJc%4<5RV==E0M zC<@$>-^7lmE9sa>M@|{oHux6bMZ0ssvSIbrh!{wziR4|)aiJCZ4Nk>Mijls$P$XSZ z$=8A$vB+ve>A|7E3IQZEb{oe4A$_r~!=pz^`0GtKzx4bKg*DSk`e&ZJYV41VcQh)l zLaXL+uG?mPMtLpmZfbJRSa$r|K)E!-JJeY;ysB^H5-ATv13#ltnHmnXJ@;WeQ3VK=bM#y0RM`uQ7FlZ+8P{d9%LHyQ_tm{?_zE(&K7k~1p8VX7O zqCy3!1S;AdpdJ)nj&uq_I=Sb+U)uHbhRfSFpV<2JjyF?x>u%EN$=_(Vvr&WIZbXUE zd=G~Fbrw&5>jwB2$j9)U!{O@gaygS-=}$y91~r->)K<`R!OUqI04f=^wknt&@}L@+ zYFuHs(UAU$X->fg2y2&CnT3H*fcP*Mi6Mf(3{?)VWPmJDAT=-QvV_!N>WlYWBJqxt zFYsPB;YP{MgtehTuP=Vbb*M~j*Xzz0^-bQ=+$90upt~h3e^U;tW^m;pv+FZ?$PtdJ z(s?BGgBxJ-gmHDyPJ%N#PfeRi1>*`_C;+*GR}vJSyqDtoMr;8$%ES;t6kMS*}e$0J+pFT{O=~Z zM@IK6SX-Jmy}Oj+DySqSG%{lKdHH`~{;1i5QM~Q^myT$W4ogOrLyx5cV~CcPuwH}% zK~6=O;b%RN%dyKuOp6&W`efq`|B%_f( zmEuQCQdKf0mX%@`z|6t!034rM6wD~yHUA?7v+Tkm#UBDiF3-)eh*-(=ky99u3c3vG zT6FhN)d^I2BY&&`W7f-T7MbR?{9L3~!1skZO=R^1)AYD)00yFjj@RRLI;=&uB8omR zBF8a&Sr&$31%iL#_56My^ADbcswhBwsvx?+>&Yt5oH2NPd1p`FawgB`HJmxq(x&^J zuC1lDUHb=ZoAE>UAGrMZ6PFI${~>w%ma(xdX-_IOHO+M|;3goWejD*2^_YWBI>aa+ z#e#GecSIHDg7zKMUeA7y&v6srFNzf*UJH%}FCQ9p@+`~+2Jkdg*qWtEPzv0cGc;s~ zKsb=S#8D!cE(O-+0k{aDgc4Rk?r0Jkf=xDY*CFsj3H#W)_s(ze$dBbW%W59?(Q@K(c* zx}eKT%gF`px0DAU^c2TMDUarc%oH#K*nzpK;L7Inmv!Fd-RzS)4(vM+eV4)O(W!n> zQB@JDx=-)5Rj;UKYYy+e?$&hL@3Oe`{~~5YG*;uU^``DNGjlVUlLRo8YA-F(#VR3%6jV6FJwe=#QYE#Ru}P zI2Dkqkz!gDgi#($Vobm+S+>XpYgS4KJwYup1JYqicsCA>h;0uP4R{kSpkf&^=A3sG468YZCI0B z3Gk&R;4LAe^{C$`tB0`x)I8}S43Y%zPzH|;3ws7^!^AeiA##C-YQwcE z!j*)klbwKYDmL-(DqZGsc$>)>@2Lv{o%!DJ&qOOu@#Uc%h-LzqzfiW!n>H&#!6uHnZMTU)vbJdH2lPX%C{?>7C()>tV|y zTV{W<2NXpr6BUYNLc0JnAaxPPM7y23rZAoq+*4EsnhFOCnTtEj!0p}PD8I*>P?1VV z-MR>`O~qY`Ivw{o30@WbNPHkxUlCm%UmF=(-!eR#XxY*{@v%$s_@%iI$3{k?@tX2b zDp2KW9BWO^ZL3{lyyK3W-}NrqyNDTBcpl@W*p}%e!SYoI%!itoi6#V0i6Qm}#^WjM z-U-w>$J*nv!@Wlk8hk9V+ur8m#po6XlJQgcjSFZ%7i32mfU+{KZq@R;-`z2gI-)k^ zL%Pk&SI@oYAD=XpjUYeTFMq{~(osu$rz0cSRw3^<#%$&_g%U*LdWR9rCjyxe5f6|F z{Y(RZE)9_M#%&a>1kx^*2Bn0QfWlK&CTtxDVt~~XVM*Mm$U*C5f5UGLDiRD8trBA4 zVi3d0ynIquHAbVHiC|D6o2F5JvOa&;&P0Vh?rdr`4Dt{Lbv}hK?72MER~A~~FJIMf z<^c@e!7iJldF-y;^UX(ZD%@_=4~$R%Lv(%VA82im(ExwZ9Y=Vddy|FGWe`Ek9cp+r zG$mrb1kl&0nW!@hLxe0q@WfyMf(P$)ppzFtFR+1UK7@S1FChdXDc%0UW58+cE+>T~ z6OM=kzEGn>&jmguq81)2aZHG}X78QVgM)WH{P6l)Z>m2!v#e#Pzq7ZWeaW}S_wF@A zdxuo}rV`t`wtu6(xjz2+tL95KJmlNds|=7>^fsLV$o5Uu$g)x}mRz3VLNM@cZeH;Sm@wE&bWoz9!!gH-v#>+z%(_<} zNZgDvTEd+#Oq<}p#@A^l`H+7KJ>YxW`5p&Jg9Cnffw>ibh>XfnH$fJ-B6dxT4 z5!|w@I65dE7MwWXhg2D+tFXia&>e&~Saf{MN2U>SFbeB3f{sXl%#?-TcOg_Wg({F6 z99v5&3JUx-chr69Qsu;O=Z5M-Q}v0|8&totG%V~Q_LjzV$C^5JcGho5>`ASiIM~3x zSP}P?N2@4bBI!1@^4BH5)R3%2JwT*&f=7!7=wTO z>})a*)mCLhJ`nMQ(a4800rwzeTYQibniyxv;%8#XWjS4xJ4P^VY%lQaY=IPuy|J8! z9Oi%+b=s{f2IH6y^s(SB4MW@klmswNX-Ms$hG-YO~ z!Foq=KFS~`A~$VF$9+4v@fN9@V-xkkaO#cw;x?W`lw9B$9|+c1DG$uexb?4C|PP~z0@ z*BRFIu-$d7T}|^t*dWkzmHlUvHe6%>73J(Z$O@2Q`J{yJK5GHzpYUN*A?SQO7e+4Z zHZl=hW4B$s)0XZss`a7Z>M5=s-a|yMA#ogqd**C1PGd!+M%_NENfaPJ@VsKvCO--t zQ8KqvLJA0Y+=({5-vjRnYh?EUMec^jp6IQ_4}d~I z0ia|OvSNc#s^oKGx1f51Vk#6mtCNeTc$|&mEb&w*dn&>kTdF?)_Yi+h-#qg76gl|D z{Huf4m$IGzxASXq|Ikfk>4)(yvJ2Gm{W_5B74^v_6!Oxj-LRvnK}(dJQ&i{dT9Pdy z1=)X^`-o`!p;R9JBjMDNF$D;|LBZ)+*Cd(Knk`S?)3~alHq>3ZdA;$Y&l=XR4G!1E zw^m(zcODDgeEBJd{g|y_di~9tH(H%%Y}WTFGO4g%d{c@@tCFJyxIYlzfO4i9gx+I# zK8SfxI#lqrLd!==6=H^qrzTr!n*r4j@Rya?EcwP9R0GN1=xRA?A&H9zEGI6Q%N(o$H}2DiVu~h6*tk>xU%rMJ4eE!(?hs})j1X(fO=(C{ z0^}{MXs}t4_5tTZ8$An!ex2O$c|Eq4Gm8U)@CXt>Uzg1agaVc2#N9mewcU8_%5RlPp7(FnIqdk<{) zh~Q~t1{&3_^k?%~efppAbNZt!lAr!pxxagV!0>u{sBv3BkKrwj(GHumR~k(YmzBDl zWM!oB^kJ{!eWd+=Cj7_kWCB+U03_9KtufyYTgUEJ^J7icCN)c?0 zvBO~n^1-d?An1mxKB1#r9-o=L#j$cwoou8`8TXnJ6{@Yahu9N&V|ia29n2du_H1_Z zOc}qIS0(L5_SgFy)ynzVjPp}NepQqwlxjktt21AQN}^)rQ1Zwm4PO;z2-!?Q0>C*_ z5efK05wZzd2tgubnOxUCjZ}a|7tPRQorpZ>3orHOtABm8%-470n&!D`?(_GyRxs0; zvt)VW5+}awA2WM;Y6iB}IGo(oUs_%UYO#eUus4W*d8F+^fe2t?U^=L$g)#tCdKMj! zi%zjFbFH*E85Esxk-dE{k-CEFY2r`V;aAMi;ua6`S#vWLF)jRW(%>!AsjgS+gKPWm zd;0R_r{$I0{=`jEH3acQ1x4UFuOTCo-GXug_+Y7iP=r@m;cQXCr3h6>I6O0`VfE5R zn5-xo%STZww6Vynp@RKMrBZ}xGHXy47H*eQy6{XsS*s~uVWk@|!T~>%DkA=>Kvii; zk<)4xLbqZ7$@(K3$#qI5DFGXzECBFK!oraCs4-*RWL3>v)rzD_0=OYEI%iCFNhTMp=<=NSV<#G%@_89xplTXf=*-yusH%NnHI?;Ll!Nb(;?#_WtG#nbatMa-hQ&9qkGR2 zYYGZi^z@95_V?>Y&X13uTfY4E@$vKJA3Dnp%xvB~vt!d{3=MldfC1us0t;4swakgn zm0IukJaP5sm_x^c19`Iu`w=8r9Ow^LY{wG1VA$d8NlXxaRpP=sXcNdQ-9Gn) zAJT$>YQT7u-HO>kVk3nX>>sJn6#N^c@K<&2Jp0Pzzvo1OPX6e8hp*o3>@8a7b?2EKHXFr`su%L*b@EqWdqk{YHMXV=Fnty* zwp#$pR#A)wMq7MUD@uS9?vK7ughgj5+5p(bNG3E*(t&y>fz&BeL*^q=ex^hRNQn4Y zXh7u>#ArvTT$rU`*jcm`J6&X>0?JGg5Q!y6RJtDyTVut>fW4%uq^Uhn7`x-Xe;G4H zJ%wc^XSjIPh%0*QZ`kku&>q!j_vnq`;l%x{HSJ0NBwDIf@7EeOZ+LI|GoU!(sK};6 zxPTyO_%vPkVzC~A{X$a`_nZL(=VltpG$NCkp`@6kPalsqa~}s_iOOpU zGlvc(y|Wf>VlKHBj~`!ub6Z`a@1}hRHZ{eo6KkiNWAV1FFiWhP=vkkqF|Axaz5gxw ziAXFSelq=q#_H;t&(NME?A|i6Q5dTE9v#OVvbnq@o#{me;1T*&alTABzEFjTx3}?j zoVUNkDtY^Aw2SA!8;1TbF=%9X&iI0j^e*&AKi)3-SD`=72HN2p%4xp^ ztNmR$APuR1BX5=ztTUd|fwL5r0|*a61xx%MUC3k4@ZYxhwbGQo+nFQ%yUYv zgs-Ve`hd6{jWAtk%E2IbW*Oxd$}A&3j!NXp*Bqh{JVcGc_@=-vW_bDtAZ$5;h3=C^ z>WUO6r>@Z#+ZST10(QX$$bM}sSsIBjDN+@widO`D9yj86px4029%kXZB}R6nogp2N z?^>*Tq+wB>fO$vkZx%mw=MDRJ9ZtU@uWxVc)xgi^(rUJ9b?!u!Em}~L@Uq5=NQ=h! zo_C#k;M}ogot;JYqLMN*!g>PsV6p<$m-TFYQ&+q2%`F4H)L0QM`x@v6!z6w`B*&2# z`eTAT#Xhea$=j+UML3`JQDl6{T+bALmGi0g%1;&ghTzgu=^#>OP^=U&jhkvl!35#5 zhUKd`83xJ%u5cZY)U_W?|kcfkr zU_wD%B2fqN1ClGnNz;C+aYw+vr8UqNy1Ti)ZzKgPe6;4err`t5<;Fa#rND0Vn=3uh zx`I4IW4NKe>7dbSK4$iqO5MFP?m&yxGGljCR+rQ_gbn&3v$M5b<*zVmOf9WQIta32 z-`0b!VP1bZ#P_Kmzf=6HA6{f=NJfY}UYEJhpav%aG>U4&K@b&|BC@v0DX@9u?IPPf z;>|hDf&nH4qQNNH3QdIl@-x4Zt4M&H;WoSWyvY`W+u%E!rvAk0YWv&THf~Ot28>1o zK4zz?lgDSmWoOi0<1NRxbhQWe_-4jDi!+pOeQ@Wx&bn9=rh|Q>oKxu3}7rd4gw=-TT7eRL0DObXutvxXj+p9cv8?jVFZ5GY>&wx`LX!jbp|CaeYA?U}F; z1rtmn4Q}Djm9Gs4m%G~=Fz+LO%*(sUXlNT`PgrK>-#kLXAD#k6MsYDV3H51faZP%t zrVterg2*~cay=43QXCj8=fzilh|){Sha!AyN&O+p0hz&-Al?b;K9}TYW@aw$yG(z{ z%TlZGCpG_~ylnnOJ{}a!m5uT|e53tI3wEkhM1eAS9E*@~G9ed2`CCAXQD4d zOC>244P(!{LSYp|6I&#KHQ+l_DDMsjpe7i`+1S3CjcwIQXTTD*N6I$DZn+ydo|`^7 zxYAc(*sn2!0@qb+>PkPrfc-@OW_<8Neti z>Wj`8-yF|$xAaqy^sY!Mf-DR}9bq;ACcbjef_B^Ha>u-L=iTg6J0goCF4>XpW1qrs z`Q1f#RVD7ewHUsR5{1uG{!CeZah~VTM4t~YnAqPU(Z&!m_+MUlyyC&|P9>koj|?i9_dEWz-pOR~yE9nJ?xyX!aTkd@Bai)t1?b zY7!r?ggBR=X#iFdV2z+xNT@-fQCZ+hRE^4TG?B+e0zi{71eGoZP2#&qjY?2yn?;XE zGNhAjB8`K*OH1(5O%I*MIX!#!$kEKP{YvU4%k1w{KRL2bIlFLt$RNoVS#U= zJK13XOa~wf7!^4tR(&048sf}hb2AFWunWv$!ojTgGGm!6UCWKYYp zY)IcN-NU?@y~JK*-t1m_C%y)X;ZSqFhS*ysW%-&2o60E0e;r@Lc$*l3FNCDeqfzfNEnmAFk7A&zm%RZw(IU+y z0Vc;vn+pLZgEay!9wUZ`a3!oEJOwozm@T`|M z3-Y8Ega3%cy%E8*9WKY6*KgP`^V+&~bIjXp?Q3f{cAahOvo?dy&q{Z*5Ak&$l6EYv zyPREj^r3v1Bv*$?u7(``sv^(VV+~5=0Cdw)T8mdQaZCZeV`S-GIcxbiKX=BQc|sAKz^nzwFBFCbBC zba`PcJUN$6)vqPh5A$pSZg-dbG|+rIliLyP#Z(~^6ondyBDF9r!Rx?ryjt$;xQ*bu zOshYIJR!hGW5&2^flZvI(EaHw@10VEkyOPaDBwh473AMR9=du~ub}_QwT-AS2aG@5 zYY0hwQKp9mMDZXvwosFVF6erNNp>Ldk+)b6kD>@N*$7;XE){Og%y zy2fq0{;yIQ|NS!P* zyA5X?0vLi{RDKveGMR&m!D~d=2(0ITS&!>puE}jVEA<>*%YR{CV5x zhQQ#u>g#H&cb9kW8QOB9=uqmqJllSwQB!sQb-}h!^GK2FqzxW%+?QtdEAUtwtWc<; zc}08)Uo@ntr6k>A!}3@@9`oj~E1?=Np1n*C>u48)<@2#H%As5FjJ2{@f`jp(a{g z5TOVOt3BkxWWLKR58r)vaP8kbz^CIdJ*T&-_Y_7wt(eyVnpX_-O0PRwmY8Z+W@ZgL z=YJR67BYr6A-*2fpM8$-x^Ll6?3d6bLu4%ks$Pbn5P##U;iQsNSWQLS#{DBf(&NTZD@+gwkrkn%nadnj zhpXcjp*MrGFw=(RvGH&`+}056itO1^zcv~T^_7+f8`_i0cI*9zZ9pBD23+QFN9~Gr zPV;o3wb<$LdrAhomcbmR=DCH2xXxP%^j5F*X<^8BSWFN&<8Z;qK~xO$d1T2zrvIzk zHE36?{}Hg6d}cH-Bk-yuuafJ{o zr6vAg(DP^Uk8$JLP3u*1yxWnAmXy?t_og4O^_H}^v)!ezFvhzL{`5b!*Hl)A13u*+ zai%D*(@N0ZhZB{M{!?Je3avzW!UI!MCsG=yMVHe8Y7_X3TnPp(Gf<_AEUokPNav{q zJ3}HOPHK?|j=XHVFAim9S+D^!KVJ2%WGj6^kB7g_7dXOQykrF--xuEqjU!lHMcx~+ zQh-iX1}e!MqO6pU`3Un4=+x4ca=K`#u&22pNUK%QZrsnM$lA0#_w@~Dm#4bZ7VRC| z5E*HySmr1j`kAk|*jMWHe(~yMaTnD-aB}0Bp>S1$Z+3jkX8eEigg(0(zyx9~hOrh= zR0sNBp}`e`#c{HSeZXvEB?RD_nl-dOXx375a!HFGDjimcLea>(XHXLniPRKwHM1g5 z4`Vv?4Xsf*3*w;^nj*StG`ORwKNu<)1fbs8?_}0ANl6K=*_%I_cT>K}*GZ>*5liEZ zRlvPkRnZV@2nDI|2P!U!0~BS^{A(_Y(@`g6at@0i<&cgMUmmDGS1w8SP-&63rgZl@ zv)0@m>IoEg%2%&O%~Sc|ow>_z{s_#rcE;Y20` z0G%k?nWASe{e*&Gp=YB}O=~2&QzI=3xhVahoer^SVo?N#BKI72us(ZcX!SzZ=*StA z=&hgwDLydsK(%_w(h)OT(iikEF2}u95eZh6SAqD&3hjYhRM3Q190^J%$&=Reu(d^{ z^GZH7*^kOp+I+*#qn|xVyh?jD&F7}Kmo*1HRmH4z{>NZXS&EDNazyW9+*Y+rw}j$q zxrFHrMG!5oq8l`EC|Lxru~eS6ALNxyMSN1q37epgUBM=e5+gSq*l&@pB}B#S`|O?pwt| z{$8WnBdeR6igB89B!Ic;^~ynu%|G@W+pH`p^!wG|YgX=WcntyohzuhY)xYpayOIWX zk)r?!6*4tKN?@XZU#gM*3AMhmpxaNaNm|v;Y8w!z1d}LtM;^ z9}ZP2ZbAqM*o?Tbne3F9@uc{D>1Sgc&Ipi;)18fYTpV9cn9h=#9aV!}tB*X~+Sj=< zx}QB@qW9 zAqdiyfp_S*)i41Y@JDWY^c-_NdK=7bt|k^n$*Ipbrf-KM2!CK}!F&DKw-&7bRkk*I ztuwnjck#_jY=#^7q3y!Nw)m;?!4;-8p3@}3JtB(z7eh)scj3Zi7C3ecb+FjZR8QIX zOQ|%9VS#-JTw^81Lvfh{$zB~$+{n$MQt2fX56t8?fEgZ`sZN2%OsjcZCL%9#;xh4l z3qY6wgk&QAFN4A4wq9~ghTybZ49To4ynWBy?FE(b{IiGlpEEIab;9snePc~+lkNxl zmY?jJyYbkbUAs@+vgg}vjScPT=ek-F-5GzwvvTL6zX7RU|1A6s=|7V_4}9W&#AX)Z zK88i$ zzDaT?G7)H&>%`e$PsoT6AQSuCI?u+sp|RM3$*OfFh}D1Lg^Tk|b#={+ZEa7asq?k)= zcyXgHc@b_cS2%=xnXyn^xDGQwsh9F{E3gJ7ahHPOs|VIZA*Mo0I0D1sX3E5z5-pw1 z0+rU)TxGWIG-<7g_{NRbO_bE;-JG9iHf!ZSRffxBm7&6TZ$ZQn?Tx6|oo!{|v-jV9 zSApiwtVrc^*^2ca+%Udzq`&`p9TpIXihKkd7i!0n*Muj7aVOj(WhyNSGg&q2+ zoYO)8VhU9yS-Ff%s4N9W&lp!)#3|#>6{MIcP6j|0#Egkv*p}Dm#2urJ^;I;Em;+Ip zF$0CRArKcRB7x`22|0!NV+pwc8?ZPsA${p^PZ83tnN0<{GG|9~UZKHLxTQd2s)+Uv zG|aB>H~RvmzM{8&tt%}m+dA-+-J#pA(hZEfx4Oq-EKsvgHh6+Jon?RD9^ zY5k_<>p&;i>0o~s=kIpfHt5?BYN_!`| zK*2PXQiaqk-IjFNkd)d56)g<&nWU|NrYwKBVkJZ=4n&Zs~v9^aJ1{^0%B*7EvfEg_z0I=!g*T_W5 z;(Fm$g)FX6+wdxO`W5t;DK4w6Temh)abK1$|5f>IcOZu@>GS)&32!*wH!=_#t^QE@ zjR(H4ghmWn6$&EB0U$ZwHeNQ@!k8N`ODQ3tVG!Od6EEvxx8JmLHr^bL-g+!OeN#{D zo;PPA0Z(LZ)7rijmha$xE?hVYyHb_#2B8ECeDEq9W8kE&jFTle)fI8F0i^T5pGXMA zM7|4x^waCJh#jydp6FkHOO;P+56WroTdFD|Y ziboM6IMU*=@>*&<+!d-A@c>f9xN@xP9=rA7__Aet?1lnh=JSnI`N-e46$z-P@A!H8 z>8{dNkEbEPTIPQ={t4Q<5Uz2a*I`|1$vYe_DTJTWNDg~ar&OanM1(8!ikpZKKHTbF z84vhL>V?Wt%W4(Ssxa0<<3Q3DO70B1xTxEO0V)-|_)o~K$7-z}quya^cc^`Z7CpAQ zz97Howwc2L4T?6YjhgWJpayUj{8WB$uf4VJEv{+ovVU;ZRHxhBof>^Y4*VjpIu=_M z_&5IV>cB7Ne>t-!PV)j_)-cBLrACW$M~WOWp#NUM6m|&Bv|@@*2Y_Xp%{;1b@AM*ye2I6 zj7V>>L`<-Wt*rNh?7M$`^lS zjO{jL%$4PLjN7Pd;A$Nb#A>GLS0kp5raKoN8IxS4tWRjN5(jb~SY_g7Lblwuer zJUoPmfbHTCQQAgG{8!@!vqwo}%jAH;N>;h#C<(4d>~ARpBFbejf=t_Xw4!of!|Ij0 zCI^$lYkaNkRhz=Cjl26+AL-t)YI?T&^jl8*X}2fYI?!{#87r%7C@s3q7p&XVGCY>2 z&Y#)THw!E+?lA-A(hd9$<>WZZJ;$uqV5oONrV?xBrm1Kz$Gv73!uK-&P!%1 z7!i$N=LoGqxjIop7Ih~SMwV}Ya;TD~^x8bAj}uwTRqH!_i46GJuRxcCPir#w0mNIH z{?Y@qQzXSqh_9!pUIurFp-dbUv3)C%mZc=UE|5Yp*4N}iadx0$Kvf%ZKDTUQZ4HX)us-E-BV!l zI6Nh##`EvF_gui`tTKOW{=oa+zw41lR#wL*y(7Kefl=?e>#kcb_SbgkYh))loHUbV z0$5@a|FFw47myd*5Q>J3NM03kqVU@Y*5N6{94L8w!dLddwZlK;UXpCb920`Y=Ah_< zEZ8(V+30gHAOsRqZ7UCBM&o6&zHEH{l=h*g<{o*3#+z8!uD%QWEH0gU0ZH5p+>An^ zYfMrbREVI%UefB$<$Rj5JQJ%tw>BICT_(f zpRcXzXuMoo)!rlr1`(i#s=p6Cbm);sSa(P3x~cR6{X4ZzssCGsbJqrbP$nIJUIpYm zS&_(P5|MCuf*_|5N})YR0Fo5g8s^1?$0?&hr&8=Y@ufYfPbLyd5j7Wk3m_~E3X~g_ z0-Sj2(M5xTBX^r-ii?%4H)`^#LoLmX*qED7V1_qXJtYn~P+jC-wK4sZr)VwNp;%>e zclsGxQ^G*^%P+w)^h6TDglg1|gM0=B0dq&*9yfL+E@z+H zhsU`13R#?fQ+^qZlDlb9MR{>MMsmVo>YtjPP1KK;U=JoHu|kO(o!)pwX+@PBSi7!! z(0YXLz&GDyHKew@3q!`3g)e*_mXS*7KLm%TqN*xCj64B~nS*|v)1W0ctLRn*z%lCX z;BcbkDH-Mt4gXOcj2KG>yIHI)diV&SM;n)|UYYJ@ucrUGOQ+eP z&zCEE69bK)^B{4%|GJj=a8=D{ylIWEdV72Q#zyvISEbwIDRa7@(a5M9|6g(=a*R;y9mWi| z7plLKEWlKAgf#+M8te#*>xQ3FMBFW=H%GcGZU(@B3G+-kBOfd&|5@EXHhQeTbN68X zZ0%fWM@_|4psMN84IPcmZSBoX#8;Nj3&c0VcOvN)LvO-Nt-gG6d6ra~G#1!1=szux> z@^lE?smNVUn29oJ-?j5}0CStozmYS2k*9;tJeci~<(OR41O>9a#N=^NgpkT+X=)WR zEMrS%L{Gk;4$EpJ=G@eWux0** z+_keI>gVd*FxH&4Be(!pijlNw$%-i-2shSW$jWi$3N2lo`2{>0@wwbrNErHS=jFh5 zA+QJhgS7GgU*+Z4vhql>yZJo((wfex=Fzs$U@)@A*t&aUXtwmwp^8;)!-H<;ElwMT zfwR}Muu*nGhNC*yw9Sh51IUnljpCLi2K>pb1M@yfcLR$RJn)*)I2J+I! zSET<{*%fkeCO$d=EH>GCUD5<(Ax%~k^k5E{3RT0&>}VdWf-93F$i;_zb1a%okT{d> zRB`3w6jO*pFz3n_!W}MwE2O!JsrqxB8Q~40Tl$U9EedR~D`Jl{@bfpYn0Lel`KE}y zP2LeMiD*+Gx5zs}hYIFCCGQ9VE^cZZs3vPdsO#>qOj^xo9xEoL~h5PriSt524@E1A+oLr(O zM)h_bVaKYgjaAe(*ELtv#A+%+5gQU;kS~yeqeu(~2%7SCWTR2YI7*Nm4vND40xK!* z88WgXk}tY9m1G89Emd0zdU+Nf%Uw@$yifcp2b%X4b4oXhx%gm)#;ZIfIZ*z;x!@s?I*LTHk|vQY1=bcA)FJ9&=2@TsO3N3OUii@8{=;XU1y0~| z`D6L)`{^f&t2`=S`BR^ZjD^yFCaeHtEWs#OF~jSjbJ%EQ za6KdvLQlfUs=xf@1FcQ+munvc)mhCTbR(;A%=qUYMqXggjg3YO9Rz~RjNdD5O}eef zO@_Qggxo?}U*T;xk;z2N;L4kV5(OgV+0{1%J`gtrPdnx2xoNP+KyL&*Wqg229upn1 z=peGB;^HSh!NadscUl?|niX@k_8M7!`mMXi%ld<3!Fzf9l{W8ojlQd{aja>IJL@ri z@Cf2#l~m6Qh&ZV-NZ1DxP94;t08X1Kn_C6kVKKK#s45gyBheBze^%5}hJ;Jk6;cwb zkjqzzc>qlLtkc~()7!MCp{jaa+#hOdV(hfbbK<7D$=djyws2)j^GCD}pKxX6jQp}@Wu0pK*6bK6V5B606 zI!>O{@Ga$R5oU-~~)W&f{!RH=O2=X{CPL8QK3e9q&1wyb<-+ zlI#`wZ~jMg?*ZeWqUTdxUgWaCqk$Dc%}SVnfSbmSq-?G7BxQi6K+2^Rc`ZnHYM`Y5 z0Ep+v^T_jny9s$5*{@>z=28bg{Uvs$GO;5h2fnd-&8kVN{Wj|hb)2U6yD{H>tQ#R3 z?h)I|Scqr`Ggn{XYZSlW{TV$dn1`x4u*gFcp%l$zq2N1@5LE;VplpC}{pa@LpD%G* z!_}c6u+ST(2{#}+Zm%243#qDB6u5y+n);Vg3B(q{hK7yer{ z=ShbA1LZsS8roP@vDaHH%YpCwbbi_GX4_7u<=BnCV9S#&ElH3R=L&OfMVxya(Jfn& zo9%_L1Qoz|9{{7!R0Ju9mwFVo6lksn&1^2jTnptDpjrh+SXqf}USC;XTTMkei#-VE zFq`rqLBv^HAp4*R2u%hy7bPGYa}niZbEkvI4l$jFhnqZ=v6?DZrLQ*BURTp!6dPT! z;(MOB%hg_1((jLV7CDC)EAlu)vN1C5-I7|i%IBDJM8c!D9vtlGaog=XQQbIR5oT+< z`dWsS`Th}W90O&`rTw&`I?BGr*$G)EipldyXeqk9U{l5hpUz@blV0p}LT)HID`&3k z0c-PO0%RxQa`l)vw(_KMiK zQn$Ob)Z@upzna~VzA;ed4Y1n`mh?~4zpSg9T{oFpzb-Ykj-OY`Rk>WQWJBswI;lzN zm8DT@bcRJfONyC=jOzv~DuUlY9;Nuuhd486X;kvil|iocLR2JqE9V1s&IcJA3y?E<;6|lWST;S%qY@}lMUJxA z3U#(_uXY-OWrS>?b<`iFh}75U4+c{XT(nYIH}f5c{fp3almz#nHbWMeP8C8G+AcUcDpTp*YqZdkUiqr29)WyQpL zm8xjezoT|Cm444a^O}lGs)3FUcBrMcN@kI0IY3aLHPKYidKstnOAPr2`P!fzW>OKm z88Ka=oGVxho_hsEqwo2#r^3>Cu@VIcHHIohWs{MhhOApwpr;ob6v~&f0qVi6Jv02iGhbtlvx{ENHoCSngNU3N~kbcerc#%c!3|LCe=0xCE zn^Kp<^|kf3%lnG7HFa46I_x^*Y>LgKUrlsXu4(32{+88Q(s?_OrGe}sd9WdT6Sju6 z$yLaJt#aZ5!zRoo6w@FRAu>x-2`!$H4%>rotf;^u({y;CGr=}JMMyeGNi`6er_3ND ze2@iASkE#-3W3ldseih(*-{wHFAZ+nHai*VDE1Wu?NyQE$4CFjL&JiV?4{8_`L6Z( zdAqba__p??`^I))>2Q7w_-l33p4?RYEr(g?1{+?{RH`_kBq`f5DzI91E_M`Hg%}d%t+4k|=cKJ8i_UTMJ_AKU~ zX=kgYhjYd=ooO$a%Je6CsXni?N2q<_JQMSHO0rN+g@yCLIDSmco_$KeI*K`4v(HM= zvw&gvIsV!V+7}`!@&=KZDoU(bPhoi|OQY6nXAL+STpH`)Qp+Tla(YS1sg?}bO@>2P zegk^owY~tw54lvKLg96pnml=i(sSMLiC9IKZH z5AHp5_VCR&{|;i~!h#q#U&E)BH6+liN}9oZDli{2EII+HFPVh=2&+6|w~%IB21-#8 zEPEhW!fya*1>ac39ux{f!5~kdIDop6ZmSzlDD)c)NXVA4!#j9TZjiK22rEGc9JAtw zna`iNzByV_>piy1RTrwPX&Y*YRy6g?g99s?df~4sNz|+wXWRXyrN!6(A?huS5Hts8 zcE!S<A+CuGJpA&HxZc{B!oGb`Au z%xTQp>DZ3xo7;MC8K2x#8R!f2EgKjKjzpuY^&@9DYfdNM&MqP=8Lsyv;^4I_mt|oMj1eK?-*K!PoA7@^A0eZ(9FYhdg-sj{EN0 zH?i&S((l1L=tcC7d`N)Elz}A&w}Y$$*9=q(P`U^AB-r63;`mj)s+9#MsJk$<+bpJ{ zf+Bn^55l;S_zk63g>9aU@R|2=4DR4y&fE9y{r7{v$_(J-!4sS=e4d<+ALsPl#zamq zWo*dt^&Ea=5_+@$#8~qP!$!v{H$GN5e}v}7@k9TtzGdJ=w3R5?2FD3@@aNz8&d=q+ zJ;!!m*h}lkpUc;mKv~2q#C2p~T?gh~mB^tw%YcBrqY-8EqvCWaoif?;pBzXMHMERW=l98I6pts3|-6$LhWM z``@$W{D|FrU4f;3Q)2UW{eeB{ubFMMmVAt=7-;cXkU&>32{%n{^{vG|k!+u>*X$Fd zxr#o(4Ai2};&y04u!$Ur}TtZWIsE9A+(ueiq;0@7kj90?|%qQM6AkX{WO&+WFYB&!ICX+BYxhPiIcFk1c7ZGbh@|sU5Um zz0fP`F-MXCa1(*&Vl{bwFG!4_!A=#YJlPF#mdvOiV78fYjdKx^DhP;eo)r-(-)0CB zFDvzSg-ScE-HjLbrC+%4X=C2aMnh-6teOA6lV?6nf*D?)nFl}fVqDXi@#4&b_R4s1 z=9lI4SCK3u`lt9jz&jUyi~J5lm`5CTDg_%S(^U$_35jGHvl7QNGjcUX4uxmQ$QKp| zU8szhZAw3V@(dp<-XU+~`{Y}E?Y1(KAw{0o7SOOj!agzO8^E_A#NwrpUSV307GZv_zzFq)gTY1|IZq-{C#>p{i**5V2+^C;6hNw z^D*atqW9vgHuCpEl;CRz4U?x0%M&H*X-k^8+}IKsz`b~JBACG7xo_J#m9ho_3-)97=%oW zcBYe#OK&dx7V$d_Ig@_N^YIAVWEeCzk>qe?Zr!U0fe0*zgA<|&ZuKU37+io?JN~Al z<;bNq&A0#hdx6QG|9qr+YV_rUSkEZlUxQ~mX)jq!gkl2;NO%-$3SlxsVKRy%l@?7O zQ>NmQ;BjF2qUJn>=|p)!`7;J1(=NMfxmIPt^nb%F^Dn=z=g>xH`sb`T{NS#K_YzPCY2}sWb?k(E+VGq%IqRr1CmzYE(xN(z3gyM9(~lWIQ`lyxTs{jsIzACknN}Q zFS9pmwvO2U8SBp9!Pott#kwnZtY{}bBib`}eihaTW6HE+jUYRKMpX+d`B=GKsTglX zZef+0Qm;XNgjX+qqiEz17VFxGNu|l)o zUK3XjUOBBTn>4EHRGMJu%J~t05c5d!@yf`X{3o6_nQEvYlOa}95wRD+4Yu;L^Wt)tkOM_K@qW>s;r*hW)t zc)w^T-Y?oSykE2vj~DHn$7kQW_?!s_=wPOu^Ip-P^Ij}o!NT`JtLt$ad*P8rg~9?; zo=TR&p<)l%nF}^U$jYM#dRPwjDy1-&KAtj`Q?#G0^!t1fe60dVD!dNIIQvtCA zA}YJf2FtKO+(uN+JBI;aLeA+`ZSmoFIMi`+W?8ev{%UD-wEylEJq0hjL-jS0VQSJze zuHmL}Y$AZ!$h2Iwv@0&5KByXS{s^t(>Ms!juH~=zl5P?P>E6s+sRX8YX$0SI&U~K- zC-Qe4eV*JeVF3USp$T^9Z%1}EG|aBF1x(ET!1?nJq<@(oGMaowV~Mpo7Nfl5W4DiJ zG<%QkJ-=VAJ_!5hCcR;HSqMYKt{i|EW+Utp{4)?>Q~*d2kPy2QHY9N+n(`&d zYRkFmwwk-{p{m+B!Z2N_02(f@R`;TSNeP3V?&{6Fz{|r8|Rt7pYv9z z6r8vEa@vXaiS}(wX~(^W@n+g_uZ`k+Oxli|Qh$WF_D(sap!nf#TeQm{!G0#E6en*+ zq(az>jU&Z06rp>WlC+C{Lk=fbhcQNa^wfX92*I<2^Gs z51Z+%uW5?cRd;#U8Kb_&);mrfojqdN*fUVOU7LRd-q%2AY`fjl-GibuI}RM*usUU2 zIZ(TXPWdqQcmejfPVy(qFjtAExQC1{jykOaH%|)@pI2+L{Nhpe`N!CI_U%hUs?2#t zp#&Qb0lDBKQj#0<4a2X6v1i(`{zCQ`#yQAo$2y}O<4kZ{$RMopkVVP=0htUxL^FICYS++0zzW&yZUN(`wr*}sijg`NH z_OL<2$C}ZFMLXmnr9C4LiFTTs=%0~?L_6dmr9C4LiFU|CN_$2gy7D;_3N3g$$wNwi zArBEP%-r4)q0E7>Agaot8;8L4A?m`V0=v)-VV@wWavHn=G7ysjK0t;nL?$W1B8%pr z1Q&OgQcK6H!zn0B_U@M6%0NMxIYYlhb#_*g|=X)E@*TpiZ z-~Y!;`!$*Rr@P<%k5D^4V5+OPTI*{H**Mqpv7a)}kC9FlLfupBDR_J7at6UvQ#=w> z7>*E{ZYeDjI+ZAIionLj@}`88;pI((h*^&S6{gd5H{FaH>$q5QYnnm@(d40|>2r&9 zOb5sJ_e89NeXi1;gz3c>%Qr>CTY~mT_ujGjmtQaH_xlH2uY-1ctbAX>FOVari+0+7 zqCK>7_H4q=f83Koa!prYqsxi*FP44c43D;^7#AsrEWUOX>$1roIf5)Cr|B97v zV7KmFm%edtmIs>n*eQv~})xB*PD=@}VgWS*l33~%tD!)&qO=!-VSVd%(q#|@e*1`(JRRVD>epF@(M*KQE zjE>8=(eXud1AFSih4jS5i+IQgr#aRCBYQc<%Kab!8Bo^Rx32-%1ac#TKRb;appo<& z?6b^y;^h;(1|V`1;F)^VgvrCm71VB7PmW7chF%kQ0cglrdq97O;iYkTqFa^#tZY=l6=bPTAZ)Vu5n2 zKJdCu;}S#`tJ}a_-8<=-|53>B>tdr`Y0S{D0;X};^|2FvJ( z3KM)cF2%y-LBBujw^-rOv6^)7=QN?@X+wgzJptu)??~_c+unB_QT%x7gL~eN=C2%k z;il=Y9((B;RHyqvyYOG^C&1ARNzHOZl3tOL7#P+gA2>UzJ7}Z3%1nv70V_bs2fD8q z`5oR8Yd$Q7+tDjHD2FT%6a&fqm$@4fwocd@7LZ0&T7R)4qxID&Ok6AwRp;*m!tx89!UYHPps#a(w;*B=@lKD5Dh*VN08kFQ%X z`2=1q=`rWkct7R4ht%t{U$^cDF=ZP3#=eN~!TBbxP}!tE-T(kyR4r zz0`v&3GKpl3k4NnQm24_l35Bdk1WcAgq4KZeBD|Tbv3c5-&;~#vXgkmw)KO*@yZHOI^1}b%uQt?_q*zqgg`iJDtwba(yk83N1yo95t44Y9 zaDLd!Ribj9&+Q8ZLjfHs2iXJ+SFkYzLJRH|6krDey^Ony%r3n#Ub1FY-_~O-XYTy^ zmpTSUdun8FX;7m&q}H!Z?QKbQ%^f{f)6y1(J_=hh{=k>=2IM0%#@CbV!k7R)MQT`# zi92tm6HYyvb3l$E~~`^;}D8mxvVae*#vtczD+QUJRP~t$ajvh5e$R;Xdw#Q z3_6;dKjE0(wR77KPNyGRHtdww98dpx_ntjRP))db-~K({r#(%w3e~iAqPApjvKz+< z1}eC+V4jkU290r!^pPM>8=ys$E{wY6+LaXlP~vfw7M0?APC}j29}~_Q`M7`x;fG8a z5XF-w#DJow$4;L)lSgBE_t-C%4d=^mOiWDd8|H(O(h>~{JcsIG)ne_2*z5AIFMksc?U#FiWH|94mV0-;t=8t?h@lgYHmR|3XWoWHQOZVse}Hi4m=!u z2?EPV*BPkjw>2<$UQ{TfXkiVcSV6Bk{V1~^{|8q0#7)1u@yWA4W=}j^ZglCiMR|}8 z{*4vSH?YR^m-v4lnk;U0x!OFm+{kqcI-_PDexg-J4PV?x7(P}2?gRBM^x=GiO~U@c z$_d#=He)U7)krD}tbZKa$8^dtR1J}!@$)ZzA^jQm0k^(TuQN~nvh2h1m+|`V?%WyZ z4Fr1AX8~nVEu`_@ajdTcH1kRQ$sQp0wFqmLvEfFs>-1Wk{*-|zBqPL*m?$?&1_i_r z197|EI1deU0a=_5Bk@wW@;LuSNGrEW0>PjQvP6)o($S?~Y3+*NB&%+z>aoNxdiu`{ zdM>i+ZJ+#Ve!S6?sLcD}rg7W9_V@p*Wo-P1x8o1{;LGWAwRH{EM5oLelk8k1?R(ym zv&v<#GZ>M_8aq^3W*GhHkigQ0Gs4T-Lc-Z3X?ZX0HG3hrfa(k**oeYWaEPQlni>ug zL59St`*T*jQ1(YMi~fJ#&Ry}<%h=iBCCi>!@^8HG0`Xwg!b#<9T^@OsJQ4YJI6Ih3 zku{LY#T4P}VVfVsMT!kUS`#_x!Hglv(IZ^+xtj8dT=8+TIQJ6}M0^_? z3txsn0rtTUtF1glCDB}{qD)enVwX+WFlX%aKijlH!;0=ouimWl1hl;@cK*^zt^JJr zTKc+~6$SfM>S&$J=9}f7`A)7^N(=Q|4j5vek#o-~wG&?{#7lv)M;HNyy^dlh6e(2) zO*u%D=*f z4zjY(-k7dPzw{OS!v1OSsrREU^6$Yz=>5gg5haQRtPNIHL3xBA(J$EanJ5;}UGWJi zbXAl2mFS)<$^D4qooQJha&XF$-8JLjL8u3kIMW61BEr&rh_gad!2d|mDv;a4{t|ns z&!9Ci_Wi@RZ%2MFDpJkZYI81zSNL%gfKoOyW|>^*1NThM?Rvi$Ai;`!C#i-~AUZ+&|La zH#*waccG-L4RJt^ihqo+J@b*zzV^&BubsH*__3RAK6b-R&zAf8%YELE@(<1V1oENQ zV=iGs2V7=4LkIg4h#@1-UA;W4dVt^F$l#^{<0LqUjF=R$%@GEb5k;5oa`FY@Yi43N zcD~DzOcGm!ZcV<;{SL9s3lrrX*4y6fIg@^4<&z^CPY_#s`Zo`;hOf+jGS(HiSRUMCFi$ko2dM7ar z_#}O#qa0tkAEBIX=IoZk!IuZ}t8jlIij0y{Yc%2*qx@k$1g>&A$1tg7x{-E_4ob*b z6w;Q#pfF7ALUFDU3|?PXTT@jTtB8a{sQLjMv?wu}UXuIapHHUAHOR-E?sE=H2y?OP61N-`D^7nKv(A zA80>iblFZjOB$JGVL$YkR8q(T)n>zbBTK$ZRg8&CgD`Yv`|`aaecTi&sa$}^N< zgSLTJlo7JGvzIz}3_!pD`yeHSOL7$2M7Ti&sv(sSsxJYhNoD8gP+{bx zpymD>1VWR7wVrGpIpMIyPFS7s6DLgdB~AXif7?932GZXMmi4fz^f$Z#`~$Oy-R6$P z=*$=wDv|T>4PXuu(tDHED#YnnnbuIkG+J7CO4do)vuY^VM?VIqkyfKa*rUdTj3*lP z4kF9V@9-V4n(S8~07w!TMjggdTT>kfSVOS@PGbSI zYn(w65>N~$1;Tt;-m=&Ja|3Ok_BWSgly|zD9@^(_HoD4 zMVV91>-oN&tuM2$+!8V>enf{x`7Q0sc|Iqdx0CHXBz4*O%dhfmW zK07m>Np_mf-#Jv3x$O^#LOLO0}Hnk4Xg0<;yod8+ErdKrzDUSbuux+Efn} z?}ORH+9C1<&O+{l_%7tGZg32$AYrn?IQC^=qB37K94kaZqBjS#6yaYC4yH>x^2>bx zay%YR*`IrkH=1mb$(}D(^Z6OOa?a5l}Z#ZtmwlA z%A%knQMA@ci`XhEh!Ax}_%P)Xq9#y-xg`rk(bRH;a#mA9j%X@3Ws;LW%t%z zJUw&Q%<0)ry;jVpTe>{nOmYcS^$B4Y`}_9HgC7U0fUfc=NT-=?4QiigT99f^D8${H1J$5@H3H%ll|GDm|1 zHR%(IQW-Wev;&y@?G--z%p;FJ{mdhe9NZ_(Wk0tso0Cejx!p_3rnbeuch_;_H@;yy z{?hIfra$_l;48%EZhq;ef!Pqu7ecO568xY+RC1O{nKXweHIGg7dSn$C z99p?;P0bVcEb%z2-0aF}lrEzu_}pJS_uOBs{EJ3EODGV|tAM)NO&&>FOEMaE;K6%MPgJP&$|?Lm7j(pK3Lr zX)xeQQpgRWbRmm|agueN0Gc1|A5rg9S5iYpe)J;^CEz2QG(fU+YJbfKH;Mj^-`K1W zN;oO>Q&0)Z_)3xNi20EFr&3@T2tiY*xT|avlM;=7hStyjO{e9s)ld9ph1;zpo)4UOM7idUU+brNhnfg-6ZUj)F%#Odv*HlTLko#5^$Z+!x}To8y-0mc^`J`%+V_!hz4SIEW4 zEHF8@Bbx~Yq3;$l?b-Ht6b+Ws9v5pdic+2$d4qG+l_I7OF>f`9K-7~Y(23%t6xr<- zF%6LY%+Ejl{M@yJ^9%XeLUJ;p)(R*dm|VT%`0`}P7wdKSIwdXfbj~&4HutE1@3Z%P zW^J+~+wIrr!n0@%Zqgff&rTc?cD6a(*|=I&^G3YBm`+!y5M2nI2Ryo02qSjDO7V3{ z_<^L7H(}UN3>3#uxPa7-W%}f(@);)uKuo4OWgRdBOeII5f2Q&=N;ZX{s?(Zy@_~$7 z#`nxUSo_n#CwowF^e`Z`(q()gr4TllR5G_I(vjTQO8FKycHP2{ z*v_1}Ie+%9qxUGHh5oQE1u>!JZ}wt0-{FpgJ>+MqyoSRsD z(9L^mHD0KF-}TVa#JPd<(*pxjvwZ^;R)Uop05)yM9*T0$irLO}Y+BBe{I(#_>|hRhL?Jg55ltsBnD0UQp zGIe3!0w|NBN&ICapd613All`p{1?AmRi-^O4)%z!JkbR0NJkV@K=g zuXcO%cbmyMKlRQfnx3P5Z#lT_1U_~}*U@u#UA%Y~xXd`-#fZJ;Cs|l6#hga;$HZo1i~vqB z{&Di)ax6|01}K&J3#5Z-t3|6Je@EQ}r7Z@>SBacmaO9L=4A(O=#Jvyn2aK4H>|X20 zrOQ6eQ@ThfQ+Pf*mhNat?dZ3~d;t&8+12sl!H&Yz@Khnw-(0!z z9t{Sft>HksJ0Qlr)@w__1_DMKQbIQms({hjEs$dwFa(J3TQJ)6Ef{SoftrOr^rC|k zN2`Vq$?>^5MoY#<5i*1svvx|d1v(3xmdR??rWTqpIXC3;-&&qsTxv-K%(iea5H#v9 zYE8ayFl;ji5`~ZN*cC2XZDsX)-t^=%&V9Q1)x|qBU;LtG|LW?zZlAOEqiZjq5r+$O z(2xZ=c>Mz$dcqUbLA3^;&DW&?QOg?mM4EMV5PLbmo+C&RKFNSHfLm>m80u`0M7RZh zC2vfV7oQ3HjZURH>)pMyks;sPgkEzSxH7n{D90eDjF8W_!(C&y$6Zl9*J*a%9(QG) zlMT2lfx0CSr-}>{XatofEFIxtgku zW3x07SNQ=EYh{zfqe?U}Rx16z8H8=l*c$<1Ss5^TJ^%d|FTL}fp2a=8r`~&!*SJFg zk6*a54~0tZoLgGj#sAmZ$i7%ClDv+6?!n#y!UHV!@7MP~*76ImH#Oi3J|M`CRrQ2s zqAZ30?++NKsFO!HGzyc$Ook=^S3it8F2?$NNl`IF2J0TK4HO1jzlzQiKU{-ql%7FV z4f5JhV=&no?2;5K3k1!#>ixNw?Vr zJ|{uyiivy{(D4|KOX#u)W1D+de;<;{@*n6%C==!z?bR7`5F!Ot@-I=EZ;h9XArQ-q5%E<$s2l(W!Pwi@J zGyRLX^zoiC<11}#zib@s`S>#a@N;*4J(Me8o#=hP0NtDVfN1c>F784T&!v`WAdp+( zTFl;NP?u%Xqc{n$X*CbGp^{<0TP}lB*N%`WyUUI$gSVo(HiUs%LO|j~9KSy4U%1A% zEPV7&Z+vnEK~nGDI{B}@dgD9a``+dS!0N;1fDR0F3v$k;hHw#3Q;NJ5B-%)=W`rIE zo0duwtkMFNPqxGws4J9E;<)w9P5^WSyZ)Dp&&-~RHK`5Zs9_mi|c zz%OWi?A-lz%N#0C-EocTf#NS4a90oHGY8giwF$DuX4ausv}u5D$xAn$WxiY}6HWBB zkzWTV5bB}ToulFe*9QVaTQ5w(pj6uw=Ahdo<-c|0>BEZ544iZ4q_V`pPds{0ZGnI3 z!@mh5j^{3cW)HJ5Al{heI&TDAW19e1#J||#c8rxy5dmkAYeRS{chsOpV-ZwEkG&wOo{^Vx7b@cMm^3~CyD=Ujv z$L?8~ncBNDGri~Vt(fa|$Q3u_N+9jILi7bA1C8E;_Qd zV6J5K$6K^lf4gb)gQ$l;e}s2^sWx6G@#(!l!+CBW^2YuH@0cR~?*#1Vx)pCti@X4O zujs4vdF)mYXo^qW4sR8hrq-y40yVRNw^~zcndn+PUkJ4alEbO9$BV12slb#klX8|4 zx#7h5*Q~Bf7JojKsAO#3?j5~rr@g^Doz6_ymri>8y*vAl--lU*CkXp;0dl;|tLu2H z6AmLPwXuEAUvHQf$4I;ju7`$dR@V$g+0NA|NYr#|?n2_&v!c zD!b|0TM^lf0PYC_aPh!8fD2{eRsa`ixM(?_7xCL@B#hdAR4j_A$Ys7v2rkeb(I*GJ zf#J7xCzc4`g81RzCioWhhb3?cP>WG)i1VHvN46!1=w)v#=?mF|rl6xg+_zxX4;-00 zy6fN_yN`5ejfru!PII>}5+6L`414@q)d8KkTJ`5l-@b|ExLNy4Ah_TCp)u>tj6nrz z_*fv*Ul+H~Cr$85vkE**r~=3x|DfZ6(h*?NBt%oa-D&yCsQ9&+0?U|xpVQj`bY1(`_8IkKAb$!tGO7RzA!Nz}SCdmI z2!?0L02G`+h=S;WBb65`N~A0yy*I(ac%obs8bcgN8^zXJ7`#@8wLpOD`h(b>6i{@; zBm>swpUY3Kgr=*Gq6pyvdrLZ=<6k**W}n_Ux2N`R#htAR**#KOxb>Ta!hMf_g?NLA z-%_7Q;I}5&>WEq1h~Kh}wgJC2=<4{bmB$vTL>2v4Fh(e5IxsiBQV7=}wuHj%PK;1--tWD{uBMZ{DGZvC?@ zlPg3*U5sLGeCH2{lnxQyzzq*8g?i^e7j9s;R817FZk&f*L@ZsP@FUS}LAY(ilG=>h zDwtyr9W}sh8_;c|?NX%DvfP~=v&T(Ycfg0pN!NJS>Y?X~nXdNdgvrsFpk8%ari%Mq zs(3JwI<{7OH{U;0Ts%OHr3k|{Fxpf`G4cB(^pJspi zG7o-C)amQ}w}Igj#skzCGP@0W23eYRhT($lY=q%@91O#SE{`U_4G1@lNOq>>QO<$i z=W?3uX1hTTtwYD_2!R6p%_Ow5?cM4JUcGX+(G1m$;kT3v92R{l@JYTvE>O>sv- z)Dxv2z`OWF{1yQG>*KfZs%*t?LjZ8exj+DJ9ly0U@LOh3h)RJN(}i+s8_lE!a`cgF z-+0fffB(V1xb~5a>SV{WAK3BgtF<5Vs@hL$-{)<$AO8JA8&yl#%VgJy_GP`!OxU9X ziUsUej4fQpY(c#eGUX07Fxybj=LIjIrdhB_8X>e)MW1N`SmMoiE&Q7zUMt`2uvB~Q zu{a|te!TBW?ZfX5RJ^;T+R0bElPQJy%X}f0HtgB)sb7rdG#8$(P1XJo2H#&wY^Jlx z*>Bx%aY(Ikq65$?y?Y3>`o+5K2fx)iPD^k>9l0fF7S^D68UbkxrzOpCGfo>rvrw|A zn+UAvrVnibbL*rfURg(Bsqyz_EO!F|ZiZ?b)}w}6y}*9l0M*iBGb5PUj~5CdtJ@s( zI8^-M%EJRIy8N8la84D6@dyLq)i3ieec($=maWWPftjSH#wzdJlwXrIdrU!0!0XRt7xaIL6RiZmt85rnH zw8h~+xcT*)?-!my{ACNGTCZ{M@}8uhZvIB&Cv-1?iU=jni|en8teV32(NCe`5mjJf^;d>J1SPVv7iNgylv z3ZKOsk$>YN@{IpRIEH}k@6pex7vp!p2Ihp%^8wKY#x?K4HNVcT`98PzcGrL=9{^$g zC1M8@+~Cy9MNpU(2U&O+zNeLysVGwFK=Uv%xx`irt&L_G^#T@3@<-!8=voi_!teh+ z1^!>IeVypO5p${Gc8K5M0WpGq^RlaC{|Qvws1?jvCM!~rV0ZH@9eQm262$3O;Vo>Y zBq)>=#7*u53gM2JzPICo;JvU@n5q3c|77hFuepKRsh9^bhU#m;Vm^brVco+=)s8Xy zxFc1&*Q12u$(f;kB0K;nG{OP+De*l~_nsz!l_M*X1QbYCw^QpHCp##Hn73{`CB7oq zqd5_?n#5N`C@a#Z<6}L}AN4>kq&CeSh=ks@bc{*%?L)3hglHJ$U8ADT!PmtJ9vdwO08hs zASF5_x%Ctky4rL~RllowR7%gPA4R<=@r!ymn5Js2;wmVH=T<-@2GrC8r$iL25q@+T z{OEeUat%qR8@m>5Y1u&mg+vIfNRphfpWvH}nvC_klO^5S6NJ zhP@7A-Ea3cl&dn1*fNR$xVrIyAa{Bz(h@1jtTD3Je_nO2`6#tsr&sInNSy+qX;(q+ zkOpt$k-Sc~K3}X%S8ix9;CH(XhRN~4nW35fzMihOQZ^HVEgTqd``pj~3@(GqZjD$h zO5_3p6|Cz|q^yEdqiGI2H8vRqO^Bg=2-$!VTPcBiGb5a3x01aEmos21Y6Zl=xVL%( zZ9!KsJTUN@DG|~4L4Vs4QS0rh} zEhKHKleBRYNw05)NUM*Wr5mRMHrF27+scz+| zFh+$sPvsD^@dnx9R-Q^v5qT;Cw%_POL{`Xw6>^(cp#VA-Ss|eQpNADT9(5}TzQIRs zWrfu@$O&DRsF64!->@Umivy9Mj{h4@$ea6zA^~@xE#STDKfww=FAQY-NtdI*o9sgH zKfwxbON;BgP~Z()2(B*st)RNCr-&D_qqn2Qbzaz@MaV6Y6dTNEUA%8)JmQybL zhU4b3U*f0!>WwkOO%!Nl%n$mM;D+a~A3wFt^(X7c&un?lopc;aEWS@ech2*=CMU0J z&KZg$LLt3ChqB2Zhk|o_x zRD#((Ol8>i9#ng6Xsb=-K;6(%x4R4CJ0Pl%h{B5a^MU+kwNdv5)W&9_9n%#byS-xA zS=`ZbE$VmW?e^;Hs(-=RQT|fx$xukGIHe|I66dVtq=(fG3TD*028j99-H(_yHv^X1= zM%1RXZ$O*cye!vmuP?RUepznOingxG>+PAk_Om|4jWwz3Imp*q-%cB}pY@}SHOasi zC2BpO|GKEzj*{zNLVKnzS=c>Hv}}71_Dn+r;5Jd6!JZMN$aZ(V-ku=^gZ9i;_P$lY z*t|Hryve{fsSuks%_cUzMN!zgJlHemx7{;!jbMGc8|%~52sWJBpb@MeZLCkaxjs!z zU^`l_e+TWEt%85sTd-$tS)GPJ-tMN`?-`Q1v}d+1%T}qoWm$C1W!WTGiyJr1re)bI zJvXlkFw zhgy;0B1*0y5OqzCO9C;s&T-jIOdxK5Q?>0@o~vm{#BFZ_1%*VB*n+4O2*tKQ+h(Dt z|G9)BJSGWvS>SQm#%J=$eaF++THAViEXKE*OmBWY>G<-gPr`}V}(%E^KA1J(1R z?|A_`LR_!)b@`Y2x-{h@TNjd9nxr(%1Kma9i4~b6MIabuPZrw%6Y$BnWAr zY~{Gy3&Qn<+0LTgx=}W<-pvwmo29wU+N|%Dx?Egeo2ERh@0Ge`yuLQ;`bks%ZA-!R zrP-jLG%erl@420R(vX1L-nHFcAt6Y6W$Th`F9bI%$u=hDhqi2%O-rs2_Ic3$l4RAM|prl9Z0_!p%6SWWu*KXK-ME@L58^V z1Awm@)nU=>K5nwb#OknR#IuXRmMq`|RUqM2ZDi6pY?i0n2cEg_(lgbLXI|^fP7e%D zr90O`_l%yoa^>vk`S99t|FOZDx%rvFBSEU!#&ZtTgs-Aj-2^vQ9d^U}Gm=9Jk1!o} znRQwj;l4;e;t>D#IE1Ny%s=po6}&79r;<|Y=RmcygNn;=6a0jnrTZ}mxB~RqfmC6? z1bI7-I4BQSg#-9cX84kIyRxBi3Ha#@hC6P@|y{v*9Y)6q=^v30onTdH4i&G*r z9ANEbV#qvqnWC5R#nGY~1yYlSHv23K> z>+iE$?24DmThj8Dp_)Sh6?T7 z9=V`#+M)+$r_Zz(+8oF>fh43J0KLK=qNc$Fx4XJzG+-^dI+3V@>OGhba+^$tkO?`3 zWkQCGP$Bx|@xvcvDoWqcF2 z)+ez()fPgO(V&b%!^G3ruGqq~J!m_yx>RyhR;vh>K)U4fpbDzRoC>RSaCsyJ(aju7Zf$5_Nk{X+V{oYv7xOfi-Bs8{82h&PCt zxVT*|2PRcPQUF4X7!MWqiIv~2@Py5Pt&H}zR-+;0ksB>)Ng(b`SE~+p&g;j1OT^Sh zQ`{T#dO|8gt~;99HKg?n?`n^x%uy3{hKGZz=!j+qC?P@MR}{mRbR=&;QqcTLX+ z(=+@jom)EQ2O)Hf0lN1(K zuNR=99R{H>J3Ronx>(3(Q1f9q*Ut_>0)A_6thw#yFg3PdiV2-$QP^Ee7M2oxu>cg zbgf4pTDdHN`AQ-#67!907vR@&q#za`QqTbxOF+GKP|#eEOMtS;q-zkr`^5AN3Ku&_ zwc(=83jxxhml>;o&Tc@6%v$|2R@x&TrJnXE=t47|=krRmF_1ONXp$;{3`^ALZiWNY z!&?z!Te8*jiFw_XgT?bLLsNnHfu+um>^ySzGN|{fG=)^Deg0+j-3NwG_cPi{Al9cKTt}1)II|n`;lX^wF&}#H zkoD<+-q9QuYJePrz9{ghA;!YeNUUNO(Bv@@i->jZbQ+8fiNvanqa&JWj*Vyqsl`M^ z#49j3Ru)Lkt1hhMPi1xw?ASN|uJOHh78ctQo%XoJYjx+lO27BB&4wTKAONVS^FoJJZdgFuoh>aL>} zFKTZzca%zpCdss=-u%QP58reC=;4{kf&Pw4sWlNpDNQ3f)x4K~uYO%G6ZZ%SoO;P? zFjfK5N$jnJ;}XU?p`9_Wg&qP@lvXUZ89|t0*ClXIKdxk_Ng>5?rZO=3$5c|v%3dcU zwqvP-0T{Ag;QgILVYMe=b|eB0mD?(}W}?G)EM>ImYSz^o4UhV(4;=Iu%AJX*KQxJ= zp)zHlbFR?VmFns;_I+YsawTIlDJ6Zo(j$v0V@G$gbL9|f&8s!JR;@kj^V+Q)-43fw zlT7PEDx}P5^bVP`qZArwX&uOhEh?SdbNC6BC!IH^TP$9`JAg=YwL#|gT4$f$>)v&| z+UV67?%ue!>q1c?d^R{?raXoyUR z53_C0%O%J!LTIh9LiK97LnvE=Z`jFm&t1EC;mqBqPVCt|KHA&eR!k=yHYCnH%0H@O ze3wa5#zRH=U@ByLg%n0CF#gv|>eN7q%5<0sU9v<-l@WCxtTC=6&5>mru>}XEP)&8j z6^RdMuZV|v9BcA&9Qv!?;eet&;ED$A8n@X}Z0VZlOZCsY^wHJ9;9S3{y|2?|aoN3g zt$T1hst?V~MQu)_!C+RKR~{Nu2Xo<|HI@&?SC1UZMB;6CuUJ~#-dxaRvvzc~pifDu z7k;G9O!PG7#niQQ0VOqj9$92|Bx~1eT1WHFlsZ<*Dcf89K93iwyUL(V1iJ58b_{s+ znI6Z`vopqgF5op9lF5KV-7;6yWEU$gn=EG+M#fKQrz!8!ba)F=_&X)p{QE z;Da94&&^XEuMu#?EP|1p$dW*If*hGSa!7>+R|Ipw+aegF$rix!8CPg4#iFP~VKL~j z(EWVBa-9>@7n<2*Z0YKX6gIj|+^Eb%OTdz#jZP{m>;$YjV-a?K|M1R~t`K#_A`Z7} zX{c-GL^j{$>3sa$%v(lFQ++*Axk=How?`tF1&J}gdm?n|g?ERiR!$G6$Af)mmy7PW zI@DS;rYb(nHnP1<;nwWlQB8R`TsVC^;kFtiJtv-DF(eG?)I^8Yqu>=Dt-Y(ORo``d z@SnRMxq2=m(P`|nhfWkCaT5m4y#sSbb!2C`{oE<;Wc3*8DN3>d=rSXat83GJ8mU}O zIZUXf1(}MBU8H3re2SQ>TF$FapclEEDofEPHUHEIkh?n$9^kp7hYy@OcxvwoqMWLw z)=0?bv7l`TWX>$@oO)sc6DdUd0u|zbhR=#uK*ET;)c;wrQAJ z1O8a7vt#gLJKvq1%*8r=Kc@Wtbuwzk!agQ=XM2HFTld6d7L~OpOG~r};yvDZjIn^9G#JYG)zI1i%VE7Qz^{<=i##m=ATq2B z$LfHhz{)aNS^NRy1R%Z_n}Bs3rGk-S__B%>SQ6ui69_8i@wdqTkC$G&G&IDI4Gp2F zV7qYr#;LM!@W%UvgZNIKdkplD1wFjL-B-PMftRZ<-E(eCs+3!o3whnTAOPP&lY+TVTVGev<$7D8{D%qleDKoHppT} zDLry;7E4a>vk(q!hdQweQ>AM z>vXDh`a~w-5sd0o_qdwu^3f^^@53n4Dm5q>n;$N$2HewY7l-W;Q*!4}YNo5$;SPFz zD0`*Sn`5q#D@!vkP6W$3v`K>m&Q<5LDCxW|f!kWM^TQ>buc%RJl(y7q2>`s9Bb1?{1q6{12@$nW^tZz3=AGaTxFeT^dVabAt{vyvZS z?>~VX1TG8gGz;GA<-%0^iZau}K9|L;MWa(U_$nCitqd3LT*h#bj!vxB%X{mU(3p0F z<*h553M99H+fvsU32B}V{=swCC#K(VF1?&`Cg#JrfslVV6Pr)B%zt+wl~(Wh;OR3T z+pjmZn$!mKjXyA$RhkOg1FS9l;>>+1yzYj*MLWyFA-fzV1WIY3jvVx3=mos=7^;{^ zB=Tjd`6)S|hHp_$-NqWZBh83?4GPMMRVT%`{pL=4>BW~eY_-}~_%76(+*|wb`gX&- z^lprS&P6lxG` zBq&KT{unGKL9$F)0jTB$cN_-XsJMySwegUWO2(N&^*RA4CQQSn3VIZ&+Lq|y?Mp|p zmAxI0fyZ2W`spu4TTrRDxYFBqpuP4jjJ)zwGFlRC6b8e?O zUBP+<+MSuHypMxiM5?(kH%UPWOA)IuW52`Z^dI0p# zViFW;QBc$$9p7*>-UPjhKc@$@N=EC!Rog zR4hObuRC#esQ0BWzr{f$bnm?{MO)G-=j5X%zh3y-;k|?V9JNpI1GV2EV)|>;Mg1pJ zPfw*&cjJ@1mHYtMQ>e7X`r^@^LY6EtB*{ep@jsk~be5@9=3}Z!S3A{SIJKUy?k63-S@u^-nC{|`d0eH6Q z(N%(W`w{+6YX2YBM)+Ep=?+X@WWfL1g;yb2?qa`FeiHJLARqFW9|84Te~PZVe~Rxv z&t0gV4fvr$DtV3UY_Fi!ETPH()QbA{26wK<`g0n!OmjlVL%&dwexZW1l=`SCK)Hkl zaBHaCui$pvag^uoK7I839q0G$ot+*X?(S?W4U{ZEo>{IXuO@ zdT@w!Zy08m+#5{Fz$Ge{N@G?!BoNc>8)xAMS?4W!0c%7nLwARvr+%}0DL;I=&*14; z>uu=`>Fn8XC>l@8+(Ea+p9vP6iIKEK;x0$M278AsKilHZJCn2h@wmg=8RL1gM3Nam zV+u`jWY-myDW;L5s;oj{^eM_K?IS0LRQircVWG{RZ#BESQmKHy+o(1c!fsbbFXuDwaUZ_52HbME%W`Hr>{ z+(7r-ab*9##f6En?#|NvZTCmR=vQDv0RkmgV0(_JT|_ZY>l}hekV-vKwqI_aV!#^J8U<&@0s^jb`EM|afd14 z2&?7RkX^5eMA2|kGNX3crIux>`{av!xzZZjwJ?&R>R|@8J>!KAnx8w`p3bI67t(5{ zL*eOj6%X{*zLXhBq&oawXOK*8y)or4>>H}ei#~0nP1<$zK#MxM=V%$?QD$z5n3SfX z)zX?)*^Mo&W`%a{v3b(2 zT4Q69_I_~x_-KDG>-Bz-zewduvAM7p#5smvgDf!SyMXOWa|^bP*<=$;x;2WOH60&K zS6QF5B3>ziX|%P-bk<8@I={C%QA(l@e9~w$WfJAp<=rir)s<AOD<-L=-&PR4XloH9}eAbI?7-{!!RCq>Dm5 z+lv}`Xwr(Bwo>UTmDEd%*D9|5?2A{bzEmUVIdEma^uyVsR_xzs3tSRB{c<9=LcH`L78N5zDQWQA_{|t1U!P z4k#*cpDU|T`b`f1sZ;zrYybX*FYJ3wBRwZk`2GCygM8bApMS9Sk<~>ev#6ea9Ja6< zEAyflAEq}!iGWu{4jfquC9O&a0Nl|5R4r?CrwKv*QovgUjwj>Su&wwt6y>4_00~td zI7IM6s(FndU{G`+^prKqyIrkcOip@Si$E@~hv(8s!hJk>Z=S|%&{10mwK!JVeU&m> zEv%HI#2O&8^h1Dj;$k-XTiAbGJyhATFfnmzxw7b!XdgQ7Qn<$g&ph4Q(mAR$bWc~+ z#ns-iqe_W>&%ok=XrS{(dzUwP@Av0^t2Lj=T`0Dri;7V1HSovC`_B;ffY!+ZGJt9l zkOELf91Ipu5c_6f98OFc(1tK7SZ16Vh-ON?XJ7++^ZEdYjS!V>DnizLd)}~ks<>lu zY`W#>j$;q^_C2~bGdsN0k?)&U8mc>o)tv{rCimjq=Ld?r+V_6w?z=y-dgb(E7al4_ zlI-1SPl@aLH7?3MD6Xnn0SXI|u%ZeD;xB;a8ms9c`30xf3F^~_rJ{9|QyF=-j)5&_ zbW5*U->>e+aX`U7}eHj4>8cep4YrD4}K5;knHZd1Jl33w;f+mBEJX=Ak>t1nLe znJ3tSH5X~;#K%9@)x(zM`}`%gR-}(A5jVTU>SNn6&MdR4j2a30=&@Qc$fhJaS0Uv= zqu}duAk5%KQV9%)W`THAcL<6dWyrAv1KrGfX+_Ud`Y;Klo02c=nww&*W>L3ip@dHgh_vP|C`I&WPElT3xF?d}^RQ z#kb#39&PLD5}v zSOHy;6-cmZWLEGe!ntD0ba%ku3R-QJZ=C(~xx0o|hlKffEZs9!suauFwoE%EeSOM3LdC;TNr4qbABPBp3; zfyBsm6y5}Y7!@Oy@qw^2>>Dy5-7Z{gb=hPvmK>zL@FfevD4_U=hAo9nNFolu#PJ_G zhoN@!zg$+BfXAwZL!H0l2%=fIS^a_GyH#$FRd~(2xGyD`Yd_J)G!iGg_^KQK-up-& z+WEHm`I&#PRJvPr_*NzM!W`bw$A!2yZf|wB8x2vh3#1w)O6^jeRdNHU5qtm}N`+#* z)ME>6XN6<84tHb+NcZ;A)x4aMvaFQoR9a3I4F}atZr$fQVA)j zktFk$5-1g8yB|A;0bN)S@g)C&o*imw_uw5b-LWK{Xyo!Cbz?F>8ZUHgvQJC zezii{8c=l&^hlK@^+>n1Z0GegAOAgbq+I(~n@?jls2@})yZKjYBN9HA_BxR8Tl+_A z#u0~=kDVgM1^xtcfc~n`7HIr9{5Wb5&_cnhCE-^fS0!r*E0gTNJt$)lwm@0w_I6JjlfqAaH8o*K$9K#a zJI%Ep@gb{2;g9Xz{XN>#>+clf&VNqND`ce4QA3ghR6AZcqe4erh5Su;RWStiI44sm z*yd2IVYeuDcqoQ}7asqa7p_!+;lOPcqXE$$wH)nuEs+Wo07VH&;;%EfWAnxx`s;?z z8b2p&GswyP^Yi<)+YVIV_M%?L|3=*(SG#6<#tfRrTB{%i1Ka={Qk zZf%K0LjkjqOY%vIfNTguz@xOQMIC?{1dPnEcTise=SZE=3;&U7ck2{cgU6;+87hwr zqBE|e>y9}D0a-k=3n6=8eqXLT$>1sOA=`p9Qa%I?}RRl9Gjdy3o>QJZq ze2%!KtUC}NNOnbii+f^HM?NkjS`!kl*KD^)G-{hi(t39$YFW8E)$67$1&{0pY7Yn_ z{99oEk8q3J@#@jA9p1s13p`LJor3y{NkC)`9O9S+ImMV<#jHVLlB4eo9Lb<~^qXN$ z8tS~kly!J%#zx7yA6*<ibj2u4B-2QYdaMJM|M z)n>p3brKQF8+0gv4J3@oqHj_VWE3{^#R`Z9$?FHAIqGfQTH}0!FP{@ad2Rbvl zF;&2sXt$d)F-6>~b5)Y<1EFNK_6|o^S*Puv@V?N0dYFG}WicC`3??%p^RwMLowD{w zZm#u3i91>9u7=on3wE0Lbf>Thzq9Ak^ra>PebD*w3 zlZ_xcEg~9VL=))pZL0fo+6jNNu)>k#>HOP{J#BX% zxcBI}&_FR#P|DQv%hp0#ZqgVdXYEe6BU}2$A0hC=S;mrtMmx3yh+{E-7ITFp77JQ=lK z+O_s?7#5?6|0zVU-~Eu04IhFH141!lG(oNV5O4shJ{@>nAHtE>^&#vh8dzr?8O6F;StsaIJ>~-6%V4c8Z`S=D~U7Vk-#(I0xTCYz@NBh(HG8=sBSmkLp zC(T(<50oB%D4$?Mo*LzoY$}LWpgv%%v!InId@4omGj-pDl2@Z)D8@j(3H*X4P4r8h zR}@ianbYZ25(eWM(0hny{w4?xH2SOmXw9?&} ztv;8>6Tj1J9v&MSKReLs^5yRCZpr53;h^kvD?#Gin{WOj|4se|IvXUoK-HTFd)yY- z3KRyxPY@$`90YnX?Gd!jj4VMFGucQ=4iZ%gg&%Qa?G;CYVQ>lHKsLVJBaM#aRF03^ z4@#uk-{!+vgEbMfo>EGMVS`@k(CKusk)F3WgT`~$q$paUdchkt>Ad#OV(ApZ360V1 zx}WwX3}(>OJoaS>?>1YVYD8QM)1VTZRtd+*A%fO5ERsU0Y%pQkb6_#pa$#_{a3Y+D zNAZwQENm*7VH5T+T7B?J_0cP8I}%vuq9L38yw$R@_nY3_1YVN%1?A6? z&+ONxUN(Wy?GQJ79IYc!s$7u(M}-*TsMbH7kl%U&`-5&vX;jEqAiq2uZ3)a1aVp;wrl7QW767<9{&S~|iriKV!&(lO9$h&n7E|CJwuE~~N7Nc8w= zrOBo8SZzin+;@DBClK%mg{A)Cd8zPcH^!eZTJEr$9=IY%JXWVivL9K9?rKLz6{8vE zdYGQ#;MS^Y4MdR^z6GJK*ZR-_kCVTtp~&t-Ll}_m0l*pjescN4lf%x#oeXUl?&J+; z#ddgN-x$<@S8BZ(YV=&XRSPlw@fQ7~hrjF6M&+aSyeLmvZpwuu);tG0c9i?7SWn99 zM_3c8g36Ga&i6%;p_EC0#AVZ9j1!(Jou;Cpw>=FCpyc%woR4r&$OH-kRVsKO@_?o4 z9FlM;4oONJ$C4~^w_YoiGRZ3;?d=G|v6|PTjzCX$OP=QjtKFkLqwQ_^u9hy;I2Ide zAUA<1V7;U@na9KiB8+Q^`-p)bR(N#D#7W@2s0&m>cY)O?MhB9aXecg~7AIThd~gx* zQqOGR;GM;N14lZf+K1XT7WD+;{JhJS&h}D!7bJeD)nMw52mIOB+@1My)ni4fliC>4 zB_mHiFm>Et?N>994PimJeiN@avbG1ZPSRTA3F2#0(q zC(0oc?KCmOL8M6nX(Hi-$zkjHtVR`e+LT(G&Y1LNs(!WRIX-MLdTctCLhIC;65io1 zljXyE{Cq`7Si(MyRcA8!tHst%7ypEAQb`7?5Ef3HGF>zMr-&AdFtilXlZZjY6UYgz#wOLN`I8`1*i#aT45|>B z3wjhITh%qJ%_7|E$U=Rc+i$(IT3|QfaqxM`Bqv|z?)sNOUc1GqFBc0f`D`j~3R{Z^ z%r!*~%=!Qxgy8cCgVP-;e8a$5^dz1|yG)A_?L!te z&#&^JL;6*0GL2tXs)&uE8s%Pz7J*H$s~A~Th^9nCQ(<%1B(Nw^$uEK6&azzI^VyEU z!4AAVeSG*Hx83gk7y4(9f7!Y=IC#)H?BDZi)`NqCYu07|9{x?iqVac(rC_jx4@600 zmyq4C8@#W?^&)G`jL0`Bhmd}WO4@A~l!&hrwd*J%m6cMJ@+w7Ab5NH-GW%KoIfQ(o zVI9jSM;DsLWq9^}7@XvmpwS+oj&h*}4 z$+f3DvRSJq|)vdIGQ3uNEB^$gzt?&@(9>$wV{o8ikYDh)Kl)5ooqZHv*wB zetyd=Rf^>@o14mAPG#}@>Os9*ty7!SQ*+((i`7D|yZq@u*k=*Kqw~ks&VkL&fNAVr z>ggTmQ%v@B9$cv1oG%8wR-+liXXB?{Z)AhDaF14P@LnS<}P^z)y*C*5?4e7P*`$L*&S+CYzVBX=pmMXu39C%DfTa2MMyPqER*lEV8&h1tthJ z%mG9iHb3LQjs}GwSC_OP@C|um8dPD|sMg>Z&JXRLE(b$25hv|tOGxH+I9%dv)Gj4L zsZ!A8aQmyheF)he7U$!f+3Jhw5yVOJaU6R2YtYNx&>Y9AMj#eISQ7CV5Mh~uM;5;g z3W1LJF#os!I#?w=N3;I>Md&qN565{@-V`3XEj^>kLSjSK8yn*_1P+a9H9xqg9p5W*kbFN8^`yVxP37tf#%`c%csu zB{Oq7^PU62u2_E3?u**ZwLh`5^_5^eUqK%)B^N^9*fO+vUkI=sjj2>jQ_>HDNoZ(# zeiB2P5Qhh|k%ou)V53tvf5qT5*$mlRene8hgO;eki@IrK_0&HAF$zS}BrG=Q9|`)Q zK2nVS`B>KJY7A1Qk#)v~ZBZJhpU;E}hY{F}p|)Vm8pYXoqRTY|b7y_PYMXl5bv!%} zXJb}4wEPo4dIuVEV%W8JV+QOg^Gg;I_5zo_`R9nS>BbIAL&55IIqgO%$ET78wMs5S zPI{OEZQ!JJ@QD8c2VcXy9AV`|1k&xXtD})fI+{+$GBT};jh4Z&4n}$PHqDO4h)G0Y zJrNYHus=5yvV@fnNwms$+5P#6NMxcd(&_R{dPC7`4+J{h7rM*xUogi?t=5k4PD@bp z4;q)_iru2qCd;Yl9Rp9kpj97Q*pcvmS3y1^Gh!RyC*j9$D!b(=ap&1xZ&FfLau&XPR z?y7cG`+7Rs)2*4-R5TW`#I!I9VElpy5DS3m$PAvw#4}5NefFVe!Jfq0IA{gHA7Ubx z;3vD?a#?zMdiKxUX{V#x?;8qbdY$&cWXP-Uwg>!6D>3gtAShW}(~bVSV}sc3!^Ff$DrL8X+aj_Ks+4vSeL z-wPzKULaGO#PA_fI$F2AQV-!=zi@_YILL6%N;Q+sCKKTh&t;2QB-W=>iA*vRjfCRi zICYLQ8nkM}lm+>q5xbigV+(o=ab*5IUDJuBm4(c0raRFgzen)6 z3yKS>e`fP5WXB%fb>$s1Tc`Ng^HXEBcRu*wgKSE%j}4H~6PVKwEW7dQ2ryUP1|BKE zzl+^VAy+(%G1|N6A439KRu4Lhgvsw8&P8&5uiHr}SFo!?e5mdL7KIATCegpkL`vCM zDw>QmW;xr;^>hDaadB~X!XJ|x<$kX>ppRSBAN}x0CsX$B-*MZK+GuyEpMLALXS9c; zlI8vT7j41vI9k&GUF~lx@b)gOtt~7ZSVQ9!3C8j*jAatEQR2F}J=JADdRGX1S7%G! zrb6xz%E=+TLNe|1qM=GWCU6tv$8U~Lv1xqeHUO^OMy2k~ei5lpxoT8a&k5q2Qf3ZPOThzCXYSg2|U zqkvDY0WHD}!>mS}=6|xI?=i>aKV3OlPNoW8m&vI&w$^HyQX)0~+S=UmXMbM^?_B5` zH9HSZ9`Xj|(sL+{5=eyIiKj3Dn4cN^^B?GEX{M+zg%lbkXEMTG77!?kU?K{f!j!>S z7N-j%5N7eSnKRpMQuqzYz(xB8(!47+Fh`gjGtZv?8=Jy8+%__N4c2MVbZj+Mkv+9C z_$-;9cwi7~vcUM)dC~cSAUenkCoFei+N4IXfTwDtPlU$Tk2BsyeiAF|D+3P2j0y%` zl2eQ{tNl!@icIMUE2Fhat*-Sr736}^651>)v<>Z#G++meBl-|`r}(ejW(uCQE1ZiJ zjbG*8na95xMZ3e^=hG)+&i0rPUQPE1!l9N58aWFRy+dzubrowrAYFj^jE!Mj5iSco z-)7b*F`<0O=e8imOoAj}0B@q68)2$Du~|?BAxz?rq{i2QX;S$@xJsyd0CCX)s1EyC zBlG4psJ#@6QoV_2HkPG~KedXB@R2&Y!YrTq;;fHzgWjKLDEgl!#hzBpJ>P3;b0_p^ z$H=fRWw2QN#+k#U?5mk}|2eE>zC*5gLZ)09w8FzX3HxOC(w>I4FZx6YZ^?5NUMmh3 z6_zO3AsO>Ipt4C*@S;ej?I?aU2~R(v11|bSxb-Rmb-nB796&GWDbTBA^blFIh-a7r zgQW^NQmM%nz%HkmLeq3PcL>lX(-*|AiZ`xgb4izrI55G08?S~B0x@Z=7GY%NZYDe zZXM3q&F;Y%DyysHS+k|&(1iTftltsWS@i)`b=GFL>cmyzbD&Y_q1N(75{*h!SS>m1 zv?E#Y2|cN=*ozDJ=j&K6J)(`YsE~x=PHT~H3@apvrJ<{s&MI;*GQRY<3!X~2fEY7a z`J5%%DuL?C%Ms&A0S_eOkV#oln^mm`S`O`RSC|q2 z*cS+feeRg*&b9B-kaRltPt~gIM0WDXr#zvE&le4`d7T9>Pzg5?d%wGCFyI}Hcuo9| z*98xtfCGDDch_s^zzk%BiWXXRUp7X~f^EgY7jCUR`m0Vf;c zFFx6p(ub|N#IXbAr1*ncdtR;pt9btS;{W08O#tJns{iqK-`TwvCaB9^ayLH)9C3Sx0VmZGxA7g0bI1r!BQ zq-yi#|2g-~B$G77>et`Dkj&)0`|kGcJ@+i1bB^*&-n@Phz5i%$fh zeAeT|CtgX)BATb{0^)ms)8f1}&d?F%JJ-Ue3aH4R0H++?=3F?v2QwWiS(yeMQg_&y zUBf#+5#$)k1H8M{dh*xgG=v>1TfjRPjf>&kQIk=89pxIp|fW#o~y2`mHu0g!>?%ef%o#4dn|&ro#j3)hyH z^WoGl9e%$)s(mi>_L?`}%*|c%HU>0095}bBuC9sp1nJira26Y3Px=8L&?AVW1=`u; zr7t>sfrO;!NnjQO{&znm+GK;sUr9qH9hx|z*WuJ8!4lM!PRt?#HJwrxveC-a1K4o9 zJXCVYqG0Ry~ zn~ecz4l#eWgaLNBB3SCK=liZ2wyzPf7`l#f;rZ_S{1}i0Mk(gAW`o<61RLz0G(U7v zoP064Zo$!T{oY2Rk(}vK4qnx;4)9OaSpeZF^%hQ3khFV4{G8^9Jt|DrnPi0ou;8^~ z*RSQO(8*=7TpgqlEe;3DgXN?V4F+w6K?68Krxn^Gh$bna-w9MCx8W<+m{2$@k-iuL z0im|4zgcCda2j&W_R^f@7OQi`fv;C(`rM6k+B@pIvx+vbVvDB~`nQkRH@;oyQmHp- z^v%74@5t94)0sMBHN8zu|0RFW<#d)o;D`ANG}r(+q8IbZt;jU1j!dk>Vz%P2hIHx;%&v*AE|M#>+c5(v7KIKKHP#Jg4s5bAw*~NL2cT5gf7){K;CKTn>3dWC!fr8>pP}X3w;SSIk8~n9= z`P-#VQMFyIbv4ypcwtY=PcWUFxA=t1WlnJ7JHcDvB+E2uB@K8(McwYiWaWS;B2txR zBXm|+I<&ZiVT4*q9>N8aZ(|A+{kkH%aqa?#0%8{Cx&m&v+hiE^I*l6I7iUs@!zcq* zDLQE_O>)0t$Ll+s(b1c}a^$ALp_}e-TgvhqJMtRKtZo-vJzd78($$Xxq-4D39{H27 z)v=(ZsI6!@+){G$+@1oyZpd%s?>M`SLPI|!5v&FPO>J+N<$LKYevCnu~@i< zG1)0=AS|iB_xbBPyRJWY`1{GciZf{54$qUT(#}9n@^s4HudLkJTkBE$W=FdvGXib@$CAKeo}1B%rvpp z6SMtpu)$AspeSJkFBZgXcf{LG3*o*z00S&e~Bf%cbf9)CV0E>v!9BaSch(xuwf!+E=$j=i|gz|@p;xE zyq8{PQtsXD%*}o)p2PL-XHC?DG`|!1c@Lwl7sPkb)&gO4qRgbWu0&hUQyKjS!n%J} zMpbj#GMA!1Kc@Z|w8Gx$>cKkwrMQaimi_{nLGz&9z^ZvhMd-~*-vEpdHUrmK{mSF< zdOPQN++NSzUh%7%+$>jR)DxYj?Zj7SRkgRKTgUl~<%t!DjqschHqWK#vFFwpt6WzL=P&A`(cNJ zr2|eNq@0BF5x4pW84>PmAv;GxmTR){V|vNv6^19W)4t;hQOwFQwzs}??xA&Ahr;av zgIQC!bE`ZrYaQx`#U5iX2Mvq@nu>}Mv=h_<`ZQ`GIe1Ki1SFLt1^FT25?rBR7>LWGQfv3wXWkQS@(e7T*IB!-^PH{S^A<48*6seXLg%d3brogH7tC5wQH_|P zpbxVXy`obrleD1yLSi8K76%kt67ClzRwABE2={SHBv#UW?9P?r_j~ZXN%Z4BB(G_n zk0m|W&NBzB5AcV1VJ8B%F9FJ<*&u$zR3Ou^<9Ab!%F+|@c8{3 zG3m$Yd0|KDdG=V^^EW2`&c4eY#r>VQKazZYWc>NYy(4^Wgc1Gp{ZspYY2t4fANnWO;6Cr4^q4%+zuR#C z=X75?Gj$*JPu$-Nca5KmuPF1E*q5B|edFW1mgTV*#pjgyQ|^P>PTao{_m9)_G=J0H zcd+k@FDvsm&He3o{#Eg~GJn&we;czS2RhoP`J3kX8*%?ts-Na>n&E*u}rT%xR9YaL?1#hPuF_M&d+=2C5qpZ)dH%T-m~m?C)uwzZB2ENYB&yRPIaZR}&g;i}o~HiR#AT|@;-7`E zU}EJo!-6UCSGfbHxJewAI|R%94D_f2^Lf9R{DM-zPY7jVIe2vgG|)H+DJCket^uE` z*gGWfwFr~|#69`nliIT|1))?TXE9Cd+E{FDD+}Mz-(MElJUFx|A{#~+rfF*Z!MKm= zXCkBe6%!yGd{jR=p!cZ(ri)Dk;-kP^B`iE^O)R!%)@!Jp{WY>_XmE3c*Up4T5+m#p ztSzHJQNNUU7Vx2hD4|lXA=aFXj4Svt4*82~3`t&aB614Mdi>`RCR~Jnw@d3s<#$Kr z-$L+W!co+FG_79J$l?a-6;Q7vaUJW5lw3DaFKJQvkI#IRYGuOhqs%kP+@mLNrh0dS zC;p|-4<8I)JWq}O5w`&^0B$cC;{61a+H5Af9->e{Le%cQt?G+?Yu0~6!Qoze4TU$M zj^DHE;PkH8QzEkm-^5ws zT%>0+3N;TS+PK7k4I-n1k>-xb$Qr^RzyyImm(df$c!>2cdno85mjx>+y_2@PtdYmR zJy;&PXl~ajrLGA1udTG_xI>S+|-THg)%{O~5Ln!@MvUX)1KRzIr@pj%uJHJh}gWzw#5&w(r zSe15a(Mrr7w!ZB}D`<+nGI0Djn&Rys@?7FA_*482b4gl{3fTRRbl(@iWJVBy6WUWG zLeOe9z}}7h1$Ge83XGNvvxHc1h7CX=@@S9ZDl(-ae=8#5F8$|^fqPUB;#pDMw=5p1 zsw@n;U5LaY#A+*NR?RFe4ptOaDC!ZbIm3x052;OH<(q((D*nSZAk8p`=7P=irF~D= z9;aXb+eaU*`}pGzedXo8{PJ?PD)~iTH}#5-^=?I!!3Vd>4~QjD9~T0<8BTr5*3`Gu zCyv;}-$681VebJ&0Z#EOMcrN?kh40r^#;7t70_=GC_v#*EOizdk$}l`mLiHULw=U=NdOvu{JTcXTs-!1aZXWDPEI(? zx{_Zg&M-R^&dCjjb92H!OnpKBB%xI(0q^}9=Gy|fr!hVQIZ~JfNJ|rxv>8w-Kn6fy zQYfp}kwT9P1H(eE4IZpOc;wmg6a_w+Y0_VTR9jeBtG1_KVh(p8;Kt7%*=G4V#4t=G z{}zY%W!#cWd+)ld_l;`#yQwSaqlh>kZjp(v0!h{+^u&Q`f>Bqc-4BR8)Xjh!h_Ks= z@YW@tFU&NBdV=hOSPt5?n%*cvm(GY)H$|I5Jd1>hqEuC}DzdXuo*QNIxIXe!aD7No zP(n&gLL|q%U$Fe%t&Zoxu46Oi`mNzioj$uWUe%amwq@82898=ap~;}na?g0gk(cMN z<>xcECy;5fXXxy%pe^F|)_ZGP-2N<}^R;>t(hF6(o%QaVnMGehP;0L@HwU5yrW?Dn z1(=i>kc=wgWpIv!$<+q)mb$xDHk`MRg)DB+fqc9=WQV1Ve*% z5Gq%EXXcU~AZ=`ZV>zJz^5<%5-Ff~6efvOyl zJSkqP8<@?w&@J@A7Q9qgC(e&&moUAF^?+6hUF!x!C&)6Xm$yRQNj_r+jY)6VE|~Nh z)7CterPJ!NwEF?*HS4rn1*gM_03mMDTZyB&nMO$uuSZ>$UbkK_=QE4dY#9}-1q`8a zY@>qR#{j}`jCx?WN`_!V+6_eF!E0X)6J7UxIcRtLM7tV;Y5%khoA>>vTPE6>HsUjG zSpWtI328L{#^A}Iv2B{c>i12V=BcCoKef3Gdi)iO7R{gE(=)5Jt`1?392QF^91(^G z7cE`1bjjlR3+FFf(BIS7)7Lw%V@_-LtnRvaUA(b@JS`C72A;j1T5qiqEXiU=GE8eG zA}wZTffF?OQ#1sMAw@4l+ER6`Px&D_7xUHnCdi$Z`|4pQa<@Znad9fTmH<^S{v>MA zLIXpafoo>8;y{k?yat!+l1Jq4vZu!$5MPzQ;4ak2G4?FNYh*wgZNANdlVu0$o$g@K#}xrLS-lRm=|USCmhNkzp~6&1fNbZ2H% zWMq1RL66p?snD3T?!rs;=1eQbtKUe!^q?FId0{;Azf^(A9Hz>!?ZJywLI<%7vRYVB zC5ScD2>yQPo)L5CJ)GTdrJdael(Rd8gIF?YGLVP`;fqBC07+46%G0hxd{3QjM2A%q zm{L&Cp*P2e|)6b^#gE*RYPNg5>tPDih?<*?$R`OFW zN`84f`H5P10kJKN0IG$>;jsKt>f6*$`Q_9V%#|H;)jsW93F-{EdEtyUipWiaQ;ooV zbZ%(1h=i&$N!)7`b2VNNxA=9fX5FM`{!eq2K0oR6mi|4x4EtkId3jN|qC#Gr{Bqlr z&+!{#Tc{|Ee?_6x=fu>UI(^Dn12ISpA^et8m>I9f83U9d&R3{1!STXgCH5#RA(+xp z&d-q@1h6?gOm34l8$2Yyt@&!9bp*o}jybXMfQJU#h9qWo7jd3s8Q&*66(CIjI$VPa`vWitMB7eoku~J3uNg*qQpG>t zC%`uoKB59#L??=;msu7MC@d^t8m2O@%Ep)u(=Z+9I(ah$tE+>7>go@ZpKRxp3*tE` z4pjbr>Rak_;tHk!b702$pPRP+cPNx%`Z+-0%Sm%kFnQ>h8!Y|t83POW|7r@V3gO@o zEGzqjJ_}1r*}2ItA0%&l79E&aDeGY$c4NgdH8f3atPMnsg7T6_-6?Gh4F> z7|c(gKrJ@`1U_*U)K@h&R^6NHiAC$HtLllicPIV~ef5hhE8|sQO@4SRk0)5+A^>a%bWn^2wBc1yms?6&({gaW{0N_d@niV{C}LMnb7d>>>l8a0tCdm~(7V zHq12`{fHwjn2bnmV@7#3DMqYjY)o@HH+%Op;kyolPql$xMcp+A8aK&*K0l2aC3*fH z$n&KbujWBaLv^W|#TfgR{9qM>c7I-_{2=B79oWQr+3&^YC(5yja=4W&$b^T`&YgS= zuDG3#Kp+PY#6zZ&PA!i2^C9$YyiZCS6EZ=4ycQAx1TMCpgQ$^U3Sz+|fj|w#&}8nP z44$B}NnYYix&Jfc_|KHnr@3O6?#CqV0FDHO_v3FHhhZm&vN<-GvZFj&w4H|z{YiRXE^U#K99>y+OAu2{hrIwlL)o6R8Q7KeM0*7`i$lU! zu^(u!;7kPV7#OD=%N5GuA@i{!0&-)RIIKfZbQqTj>*)l+;D16?oG3?h!b`G#3jUzT z_X>$5_yao)Uoh$1D@`7AW64i;DEVcA7L0u*b@Hb^X?}$j2(UjUt`c6-T(1Omj&1`W zVa0L;i|B>-%N4DJ1C(mK*pShX$V~S-?2^T-VK(?D2{wI?fn~972p8mLcLtAIr+JhR zB=o5Mvs<0+Q3Cj+KWdo1!_z%V&O50`jTq~sM>EIAdg`O`x@2Fcd5UG5LX2h8BuzYM zP$mho0ioL{aQ0HfmDe3K8ZgNKB1ro)U`vF$0~^z%7ESx$iOmp+eqT01%c@z{J|WAj z&Vrm`wy4dPeO6{cAWw^JgIV`_6*UZyj1DgE|4$pD^|$Un-4KAU7?55+ zbyS30JUgv1I5HU2Dg$yok=`hlO*?Vo3_aewIO4lkRhovqLeGV;k^?h<_%L0PPI|BUr^C}??cRmQwJ}InQAd}AB-64 zOw+!9Wt&X%ris8@IJFaMWF%7?_X|dNz*B}@gI?*2UU?CEh10Vd)^&nes%HAhrGkrE?1J3$a`oU=*{}nHN@yAd9 z;CtVB@PWI&`d@e6cIz!SU;X(D_wD`Mj&sl6I5M<&;oLcm4Usa$hRU|XECB{A3}>iR z4oeD7%+v4@g2b68;t$FbO@9C+n)-vZk?>+pyOH{1(jEFE6=GY3-ahS4Y z(jTZD*OPxJz?Cd9d6`P2s!)nJssY5N4)MAG&Gd(_%J8eRT$wB@Q)7U`82C7y-X$4L zI06uqNYWbBW;p*K(FnABT8#y+q{x6K;i4&vVCrHfvm(zLg>=lF^x&8O&8@sRP9s**=#>??hVmhD^25T{6dJij)~L{8d$17ImcF zqRV#Xo4mO$TPCQFGZKb3L0(a|)n_!TR8~{A-(_uX$k13#`fP*2>#I=tiVLBkM9}2| zx8%(#D%5LDS-R|UomX2}nrSr_8{D28W4$-mVA8p&5V5b+YBOaT_11Y` zNJEE67CCmwr5E8W&I&Cmoe}B!a14U|F&IqHqN}w5JAw^o=`xUWUayDlOv)9rkttSh z(&4bwXzd6zrj-n4WWSI^;$MWsSzH#DNERlB5qejAKn@10kEquqe3;4VV}~Ft4&2Jc zU+{Cgky`}$fS^C}ixCuq{-{WiE&eLz69z!=e+#yKNQt*_{_wltlP+UL;17|4Mz}+f zl?b3myrFO-?($Ph9mh2zNcvNduy$E$F zz`Mq(x1zWfwn!bh0>KVRHqLZegy|k+|MTF5Db2Z!ZRzNcul=tHVT7f%3;QHsNkV$7 z$fikSqb7(gPBo!a3qgWcha%|zX4c2v=QbRz5RCh#+I+d?6qYy&&M%=dCnP7Ixe)Ow zH?s@mU9qJ|c zyDLs+|2bh?QP@qVuA5#T!NOcDL$CqFJ4$21Co$e>*TsQizrVOR5Ri{`GQ0eK7jrRp zxBM=%cgpX}ujWT0`2~@P7)^eWA59h)8zTh;<>koXHun84m?2!<^83uuDZfiqI2f<8 zI9V2-@++z0d|jq=K^=y(38iri<_CVgJaG}?RGt7fZnwBF?fQJ^O?6n`9a#72<-e0y z%WeU_uS3WaSES10{eKP1;hv-%y|nv2I$3kLxeJzwq){G*JJ`{OfuvnaDgaONHf;9@ zYJ(VTsdD7dZk;S@n*sWT@tGU((--*HnCjbCOf2t5u(}_bSlzVmC;EkGe4w0+VyUO2 z1Rx+Q)_0|Lsu4q>n)tf@Kk5gV8R|Q8IPLYb^>>Azd1<20rcI3p60 zGt;7IHC4tt5!^R3E!-A!7tPFZ$6T%|S1K}Bmfzh{3tON&VoL__$_#jjP9K0?pkF)z zf27@z!?nkf*j7=?C3X>6+`huz5uM!fJiq(CGOSx0#tIuPSwew`8OCy1P3sk|M&NxB zED*Or@s{C3BJ+@!4LoHO4A-2Z(d}DD0u8OXn$FBa=M;}_+cFZI2|Q1y>C)qw4tu5} z*Enxcrrn`@A!ecidLLn2?*mK&*)|Kt0Sgm~9g@r8iiP8yzc9oBPscRiwM`WLmvlhb zeF|p_4l~^j!oHSzKMy9qB$A&KI-m)|#)KXi`(YN#5^dsMoXa5rad)_KA5*=7oKz6z z0f`MIaGjWN1g3-10XmER4Ct#eLK&eTk|mMvoH-#AGu~kgSFxO+^kwvm*|dF6x1WUYF1u8dp{5^_W9st_IlNly4+Bnbw}-z z>fB&~HP%GD$t|B;1{||dd{+K;_cAre3 zLCnv3;dksKP?0tFCBH+2TBpr_`(U zy8^&fqBE}|_SZ$?Uz7agbqd2c&0gd&>L>Q1qDTIW4Y`WEumgb45HSL^n%KGE)s-Yc z*w;uB1Z)4lrMpe1v8*gJdwIDo=D#RPU6qt`5wpf;Q7RS3s@RDs2ajPgWo`NtLMy85=kEq3zS@=bTV6K~R`#;B?j3 zvbt}voe+89^LMTMcVSE7=V(*&poZ>C;TYImSj1qbo?X)3TG2AAq;*zBtNizpIq{0t zwvx8iidj^*2JbwdT_oL=+>?Zz>|k-lVu*tUubS^DEd?3?h7yi>3i@vtu}+hMFqGMZ z$H*o;#yr7jGz^;|^bZ+GyG@5}0*=5vF09I-u6l!SQs%XI4hM zSN+7ph{MNW5<+S~!s%-f5ej`-JlfPafgEXTZR}|3I331Qd9ju$C#p7WJN&EPS;|h7 zV%`4Pr<9SRJkS0sU$DlWmA}odk`@68r+Kh1OnzQ8Y?nmc?IBYhAe2IjEC2pI?Va@dv zlopiM`?AGWl|?C69#G0T-8OegkjlB8Y30C+1|U<V#(G9=bCgtg5tRWoQvJ zqit4Gqsw8p>U8=rvPFl$ZREIdx@uCPl1l^~xDlNeVShD9tD#YiQV0!B4_C}XPz3_f z69==3WRmbHg>j1t3DTO0$Mg`W^I|db#E&({nrAkY76^wTR)0sG*eV05NS98IHc;+_w4y=4ymn*5U|9DzZ;Uw(nZq;VYO^ zFwRp~%Ycl8rNKx)*P}Sdx4NId>K0oT}rV|;$yEa=)K2zqZ_1$Y7F59-{n=WA2$;TyDA^ul&OE$~0 z&QZ>|wK(H0gI)q>+_+wX@)7=&;!wayMS4GQE1+g;NTef~)oKQY)y5EqY+P1@N|to8 z7@8vre#y0NS2S8u;LFfiV|A?Tr?H`#4Xt_MEQ?>~3Rt!hEfa-n6Hl?Hp=Lx_1VQ&O zl6VLr)-fm{=%MrY%wI3~JAT+-nt!r<%#B}oh6(2gKY~;BxsW?U@c`NE1(=PwTnW4Z z@qLggQ4mMA-x?t0TVVuDaY{CEIN42-zrvMMnJX<+tzEKkqxwf4k6!D^>0Z7PX$C~p zkNk$B6&2(QUr-?aNbm{V33>-nh`ZqvG@W8L@zB5Hp$bkd#tXpPfJzfuy%663sb4y6 zed<9f&VWw`(~}LMI6FTZX-J$7uz|?EsRGCw!ni`L!cm^@fD2iSYXO{r}3gHCmX68-*Ar1j11=a8iIg^)UDvz|O2@Aa2aIYzik~RnRuI zJo1J;{g1yA`X&j&!3TSrKsePvh$zr{U2gXh=jFR*M z->nqx27C$behM1t75d@`W22;JwE)4YBy5DH7<+b=gOc!lC2n(d_H4&%)PR;nJ}xb64gneBL_g4>XFI zsMK~-S!+-e4-zvtXUb7Y5_-YrulCT1+^NkYoYQ4T{r- zbDVbvcLo8H^X`aVNVuD+-Vewm0U^gbjIZ#DpO?ojQCtv; zO6K_kA@WNo!8LL}K>2wop9I>YFT$_p5PJw1r9FJlzMQCIm#{UG*6=L+41cdkZezda zZ=cFf+<$J``#(#phAr$zSb3|t|JRJfYIY(2d+0tI-xKhy`yz9|vO|_3TsR1goQU%S z$H)-$2Ta5?5K=Aa2FKA?Pb|6OcJ`2b!(7}tr4G!|L2L}TwBRPMLzq$rcXWEN z^GSw*eV$);;d%(yN8#o5I_;sj06xShzLWe;{l6;lMfRK&QRaY;8Es<^D^^Gp;6_NL z1C)4|n>wyyjc=`$u3LQad(_X#bU=BLMft@Df*jHl7pqC*sua36X1_NQE=HoiFJZo-}IG5 zYSu4`Rxci{WqIh8qeZ znMzFRznUzXX14j4tZ}W>wD{!aQ|iDI>Fa>zK$tQId>vf%)~dCOr6#I@>X?{=qIe;l zi~xGV*Av61x4}_?JQFe|$yrrbMchHG)>q916EOfiT6G?#07^OSTEr0cvMc>YmT5tISRrcaKLet{EEJUf#;gE$!EkN?UR)M$9+ zIuyb6yAkb%Y^EKeS;WfYbc3$n!k$q%sJv+YhN0WI=nsh@b+$z@sAJYz=luKJpq*#3@huUU7jv**~z>L*;!G&6+3Ypq-l&d)P6tM+lHVxCaLc7AkhM!j};-Tn83h8ph^s zQ}yPX9cD+iMvv(8WSgW6CB&I=lOzHuBa$)@Je13Y#6KrH*0;ph7z)oUSTf&Q=&CA@ zRJsbSmwFp3Mnk?0zQVI34Y_L|_-?AIK+L#E#a2Ac=>4Vo)M0pi!gh|nK9ktZuEzXp zg*nRn0~-&>g(#6g${pAFYjD#9VhnH&0Sg5wO9>bwN_ZW_1}6a-dSot0dbB_D!@j=D z#5Y!ty|tR=ewub5l8{b-PF%{1m~7{Cr0mm!6jW3c_#%;K*oE>%FZx2o z1qH<+a0{5vpC>M0dl9)R%kZW+z`lEo>y{DG>AQS`!G3{$dr7a=@VJGa z4f?3U6z|8Rv0;q8jly_IU`i#aUFyxv3HWltxnXxA)j*Rej5-;q;dhhP z**5~A++Z*_rz2cbzGlYCkw|qQ=iC;5q{3GaDSxk`+HLcBJPx~SttYp)qoUpC%yC&? z^o5HG3W~zYc-AL&u@BjgFrHtEOW6;`$Mb_Ety+v2niA$UY! z7Xs|R*--&p{(}_?y z!M5($0mMuf!awwm)%5m?$N9XT@mx3&aLy_ne&(6;jvr5-ivPo0C{_A+#UDNM%$uTZg0+P8x>>xAy)UT+ZRS3PH_YD)<&qr`za|t2 zu-j1>-b_ehq66ow6S#678Fu0V5-p^n^&+|<09nHZUY87zRiGC@W5;=ZF#pg{KKysq3W(&3QSle0M&cbDGVCKAIbf(40y>4GDy6KoEP>PD!V& zToDqmFQ1Qp?_6caSG33k3q^WBm)O>gO+CA;0|ffq{NkdBGylE7wOlL~-oH6|2SP z7LLfbvZa+(*<+Ve-&C-F-Jog&POei}03Vrk!iji61)Mx9nW#Y&eWPL7k_9;`_>4hg zWK#FEf+`{fIDz}CGxb`mT(0#sz!erK3Mo4y3xHqAIIf<91=M5`fDRD^B}p%Z?yvmJ zCrv2KOwd0jHGIPqb~^?pPG=Hy`}w1~(=>_#%%mGldlEX_(5jV-01R9+xN_a9bqiN4 zTG88s!FJAVZ<|$9U0xb23|Wvg*;}fv4TZ7nxI94bd^|fGWj7_!3CW^33Y#k~xZR&? z*#XbyTvwo06YwLs1|^FlsfY3_g9(GHIpUK7Esaoo?49Q5vIWc9XU=R@XV&BwR3owF zoO4<_a*MiZYZg_PHE|B7H%bH&BR>rVaD!;|oiy-febEQDI&%*Pb_{ zyME(YnSEWXlS#qCqMRaAOIeXhBRaZjBcihlYe9s#D2OfCLlP(_Fucg52sODRj$#KX zj1j?jxy}{1B|&8bq#5BS;1NuiSRG+v38Fz#FL1nWF(e;-MhyK>?Af<(>>+m9zJ2m; zT5}R;yeV6RZ&gw+`zP zOO#v7ka}8K|2W{V54eovh$7$Wooy?3p53O+(wdycHe0a`|F;>edYh#!R1)8}YGhB* z;f*`L=-zS`UdiZIl}UHL&ZN5Q0lc64Z*dPa_FT}p2k(4-;&wz%tN?W`!drhOo=KUA z&CIAn1{Y+RQvAg#ae86UTj|hHtG2;U5v-67$+-1;#b~$!=5vEVJ&0@*d>tko!7AuE zcJRy}i$^A&P#yZ`?SPIHlob04ka#^(TvbvPE-WbW6=5*>W^=x|%52V}ysc&)(Z8C0 zD_V-wN1&EYG=6r_DjNSX|E#t;qp_{Kx1lQ5HZZ@fr)_s_X(YB_q^_j20TG4Pty_1F zX+dY}BBR>W+qG~**7}i=5q5vLq%?F?e!3FF%$%b!y^vZk!C$h=A0+f+F(FSqm2p^J}g{Xu`lEce{P)f?(d z*A5mAf3vM&Ed^U|6^5ZWN2l+rtgMVyMr}dmpUnv*+TcpS zb~`8F(_9DHYOUFE*mIj*RUJ7q%Yzk;oF>=wSK+U_ur|Y%ZS=c$*O{#Jxgh!X zdC9-;-M$^~#{qe$ReXhIAw~IM>ir-~Uh4fQbT=C^5qKt|wX6r&HRMsEc~TUtiaG=D z338K-M4D(VAoM=Fn_ab7euJGgk9}{k{P0#303B}t|C))ZZ|(FBIyV*+3?KCiJksX`T=%H|ggehVlX` ztnT}=!07oX>VU|$QnqzLvyHUdf`}Qz+8)9yMlv+eZ<5anczA+?pHVKJKT$+;XquWp zp|j8#fL*IC)zy|NohAor=m0H-C_wtYYG)mO)$u&dKEGBPP%V+g!Nr56RiZfT$z3f< zRi&#J56Tk6&%bpJuB-?*H-{@$4t8REv)H+sr!hVo?N6-H$)JR%iXaFQ(Iq;w|$7@o*uk5~$ z{JfJf4M?L5sRcOj5ej0UC|?Q^!kD5Yoph;Z3G1?jA^DK`&qxJLMN&KNT7J6AJKWN&!WcHM9xX)DPf`<`uSd z-)F!$LwGx2>bG4MU&nL4jZi{ z0FuZX0k4jH$#{|}2a>3BUK&&ghcRtIRyjN}GCZ{M5E3wWatd-?hgN2sx1D95v+vN3 z=GpDt|2uD1eAbSGoOf6P-r+o)5VXRU#22P`2a$LNb{cL0nK`iPAPE8yzZCMI7W}YQ zqwUMdak-S`=JL9{#4h*@{CFciICZ*B9B$-GNzubj!xkWRVF{RkSyPyRyS8n69!DI- zD>^7maqz;m(g-K}|c^CRlxbv&Bb8F8Fh(VtloQ;99h` zF$IUw`E=`it$dWN*7nAqefHw>FF1H`b#ZtFD=tP#%JWBtNy|$7Rx9T0e0F| z|Kn%4Z5IOK99WZ}rhIEM&N%tf#RwBT&2l(1kUorv8C&cwj+OM$LtX_Sd2!CctcucQ;!ZCpSK% z+j==Mo=kgvW*Al>_p(LuU)T?qvOf$A$W|Jsh*Nt&tOXN0313&J7ctpdRxW?aAgswJ z4jw!>F#ZiJITQYfUuT0aIQ{(4)c$d=cw$UP?X)s0-aGMASwdyJ9AY7@G_X<=b{Lor zpkd}-EoqciGlhksH4g}Sl3n^3L88Ywq$h}kqOClk8SCH@Fg!uZ^Mc+&iz|Y>W6vRw zA)q|SNu`Bc3UNPan#X|v)4TB6^~*M(5AZi5pC2gN3W!!j z?P0|Qk6+?xUT}?F_UME6fAh!@`P2QEAGy76Wl`bE1-IW$`Ot1}zU{JuxA(uac60y1 zvbiNib0hu8kX{5PMTFeZ487zfKnoReo*0V<02NROP&W-I1#)x22^#0qgc)5&CMQ5< z1i?~h3FeU|&Wh)=eMB_Kq)x<))B?oef$<6;ZD!gPx}*t2k6gb0(8WiNeDl5s9zAlT zxvsAH$dQFB3yW6HZ|>i`cH{c~FJ5}+$ie=i!ce$BGPkH?E>|4G@gzCF4j~q=q1nY6 zr^!Sl6%zJAHkRV>5x}nFk3Q@TC>*jfkq{Qi6PSbOFD8>_GdXukjKrwZ&d$H!mX5hM z@4n|AQq5$XB-|e$So|{tF=ng24BjB>ay!fz|u}g-=NbK^9{7q@UyTH+u(ih95gh zcCfko(!PHdr$X!YHdxx85bwfVXcfB=@xhaAtID3{DmkqxrH<18hC%8$+RC>vpZpf{ ziEG>2cXG87=9pv}59qk@OwXoNAZ9w7NVG}*XKn_;`wl{V-^+e>!DAO3J%6kY^g%){ zkA&(R@jC1m8_05Ve5Moo0Y30r9S(2=5QJFIk3q1Au&yn|hoBim*a*nCL|4rMY2u-o zHS0CGrBG3k{TA#k2rPc!unriRSv&jLXMe*gS^00CefC}Xr}9f}M~<+UkFa6(@*s$C z#-NO->qhyLZ(n&O+|%7x5>4F)U#i!|`4ALv<~WQ79LrrETJBJR0LzDc69aG`+d3Ss zC3qUCa-D%NoDX6@r2JwPB-WS`tXX|pVZVX($`7z#%Xtv;lLG&Y9CuSm zehdP?D=@?J+UE76n;&dH37Oxj$ovrgN#;kROpp^!wp?oZjD1bag1qv5Rp=kDqtxAr zcU4;0&WxBpGoh4;#DT~tYY6GqyC7_IVgmR|N~7HTyryNOELxJ6$CnT?ZqwqCw0A3u zr&E4ZSvWss<0~gP*2Dti>ju_nCVZOsJbMhhik@uEfHy#CM$#-r*As^6CG;cJn1qHn z+Z&~nmF%&h;j=@P=Pq3(%47N}BJ51KC&}|<3Y32hsvg37Fp*eYN#QV*IL=hIA1F#Q ze=#-TTE*a_b=b{;Pdhs|wzO>g+ETbIE`e~jY1or9i2N!_KV78x!z-tLqGqf#tOt7Q zY#4R2epub8pVpcvAR|=UQDz)(m=oDCV#0IQSkVUrO8-MD;6u2D(p z@XeV&``OI59!yDxZ^VD`i`l=dA<+=+oH!%i?!<-^Z+8Oo`x@qVI*jFMv4JNxoOiDD zz{zg_HIi!cHH@9t0TPoMyrM#*!PA;Z#}|T}ocQH=?@14=KKTu*<3G2T5ca&#ZW2$s zmBc(b7dz6H9PzY{s&p zbwEpCiXxG-*vUfz2ZUHcCj;?y-$(vaQe@@Oj>Gft#!;v6_eo6gGL9(*W*pSBpBOrq z!<=Kuf$#@($_hpkL678a;_usa=WP zg>7j;a$-xa1xXN7D18L9rKG}UW+n{-X^5WpVaZoG1S>T|}Lo|2NDH8WD5MKfj;71!5W(#ufUt?7lR@cq18anTICn{aXs zaoIH;_c7@l2BXCT27yMx0|AzGfK5chUil;Nlyk+oV`S8yQWqBo0cQRz-5iN(FxH#H zds+5gRP`9D0;5ZHwI+TAx$R}4T(~E0&v9EMmAU}950%)H&(vn9#9?wbLUl6(UQZfC z2}H7At+e07G6W%G8|WE#&4jd~2g!rE7MX@o>r#BcYD;O{gg!5vIj26Gap;U5T^4tt zljTBrS!qdeQP>i)2MdWe%Y=!=N%5WePs$uns~A_1!4#p!5WgL9!6@!Cetg=q$`|bO z-*G%&r%$u0WEyLR8oV(_RY#V|@wHd|C*)t0=k3_OY}8T&~Hp?eTaoZf{?6)8_Ez^7Rdy`_EJB#>c7G>}T&;ONl163jdZ~ zi;iNcR!cm!=p>EZ$lKSmSATZ@tb645Ki?tW48<11`bcn$PJ^&DzA4vZgO0Q~fB;>h z6(>!JR@AckYEcI`6JnwFU^1()BD&$*M-Gk3Plsr&?K=}x$0s<0Z-M|)4 z8W^os$76ibyfz38g`q+}kXVIPWT*sGgrW;!5dmQayX*KL0Fgwsq%x%pnWUA}j_WA- zpJDCV zN<9W0|6(%!JXEejG5w2yot5>5pB3+nzU7)^MHy17Af(gsq~Zdp_l-B@?W5Bi1%Z;?-SKN>P$ z>fTv(Mc=NoUifR@oC}Y!wN`~UpuJ7_bSI>`bK+)?8@rm2DPTsh{>d8U0QJmQmb?lv zytaX06VebLgf!&-#-Ndu=NFrTw!?GJTvGVn1gYU zubfwSP9gX;NhN)T(U`Ff=myvmph8{`$p`oskRyLKb3W|~CEJAJUJQS9C5 z__G;@&iK)IHJ(x*6hJu6_#v1yJb3MJUraGrbUg5XSyQYxv(j?Q+cx~NhU8B-ww z68eb&Kx570-AO7UTe(6m5iO1fh{ZmON*!LE>xK0n2Y|E4J8x)tz^mW_T&1}SxWX;e< z{Oq5wOhZC7<;uqSh_zE$5wa0s!dyTgl6o+5(zH$&5vV8(uCKbNx*#vhL^du`nnTzJ zYK#YAL>(`9P$!lA6Te=1@o9l9>+MST1&q{x%Q^_JUq*7NZ}zXAKu zbI=Ew<3Q?yU&KkX%WfeMl{K3}fI+o#;^@xid4Ubg!m2Kf?YeENtVun426nFr-AE^N zBV;EtoMI<42nJ5*2&P3H)@VE9^ZXkY%s+R=?px2H4&V4@8*j6d^f9OO*J|VaC2VWd zVMd2B)Llo>;cZ{sS$C=YV{{mMtqu85zfZAZ4Y8-;$7Lv&Na2FKu{G?c5}$EVg% zj_m1F{E2DEcL2PT}Ih-iY~Knn~39vK2boRA?9m5ytj zjown0E|;q|Z0OstfjzOgean{i%_xaiFkhI3n4cGc570b_e30-hff=wCCO|t(fOePz zMO|e3dD|;0@GFv0+d&wXj@pb_WxXxs#^uKHcwhN72ABvlR2iYNLL=%_fm)A)y-4#NfkGqp0jr5o*np(7H$jttL`pg%DJC;* z(n7(+fYp*2@I` z$;)=nwr#)QCP|#xKLreDKevB6$L*iWKp@soSU>L1FDaocCWYdf@O|@$7k=E*LD8f?S0+7-P<4Ey_@`cDVHG+jS{J>dO7-z~6j>Y)6XY~SP>JOB}V_UMxvhgNNR^2t5dw8XF8 z^CY^k9c}k*pL>4Sygjpd7eE8%U_;8FHytDv!s5!k3fhkz!-uG836W(o~!l#TG&-kmy(ne7Q2T z@tD1=O8j(8e2CXU*pIK{OyhPCu*hTwLDjH>h-3$utaP!MEr4~vKn1)buh5CifUsP| z;~TrWHa;@s@scWb&r;PgX{k(y0rgQi?~-;9A?d0Q))q^O3P5jdQS2b(L`$`WD~@dx zvjGEqXbhT7ymD(2E2r2&D4YQ7Ace&6@hEZTa4me9-+7D7AaDLAGPoo7>tD|P#V_J- z{Khi&`j_7Q_v|1SCFfSLgN)CuqM;;yOq^`PfMqmlzG&CRb5~1?PWGbP7}|m4*i&|p z)ZkAOr@+a5V^8dQ>pf}F>Qo(3!2?=$7t|QAQ}H*GrVpcv$Zn|wY?#oO#?TkYD(2{3 zwo^Xr5wDUz-X{MEeNqX^ne($GQZT@=g0#n=QHI(QIuf{hU{@JVIcR_z1aPYW(Y7c; zaY3LU;P<5?29;>Vq{+vTs!&ZO1XeN_r{cshe$bSa_mt!R2>p&A6L3C$o>fj6c2j9r zS$St^X=iy^SLtGY3%AFTCk#|&!ZG$T7CNB@DK=5E|G>RqwM>eqd`d87bZ;DwF-`Ya znbM(2-8wdHchKqt4J9ufENBJYO~7s-yg$p#tJob*Ym_=Yko&+XHYoHXDlEVn*@`t1 zQeuEjYuCo@ls-Mi_30ox?Bl&B7KnLczhd{u3;3FyR2Qf(XQ)3*pH6Fy|F`$VUQ{J7 zVE2suiq^k!{uPQpK*L7zr>6Q9Vo2v7VN<6PZyoHOI-?Gn;ACg{`Nx;Cf}lW$<-=Ih zne5~5i)@Sh#+L7I*)nZg|MaY#zAXd_0ieQ6hkTUw4%(x(wy?mKv4qkV#o5^=mWXFT z=Ab-jc-zo_FqP1W6(H=gaM1(0C?%Xv_|o{zPqemv`_|U4Z2!*IAN*kJF|Y=h)qJ4| z@9YwwG6>5)&Y^(gOOKzz5L%AFoWK!qG_w*v1?vWf2Djf4lxOSUMZJOcFYJE!;Vo;| zY<;*XGJj*^;)}bt4=z}=8pLlhKkm!`KMrU`td@9k@Z!Xi&j%Aezp!vVnDB~i3S+*l z;w58Y8E3(P)w`y=FV0!>mR?e4pw1MI%WE(UwfbN{3d+ynN^W2VGKG(dBwKQy-*|K%+#RJO@%psOoumJlw2Rw}uM{0I_7Ls*< zf-%r6?utd46qs2=4ZOPXP6)*v;85a00s@C6kY{KeU~?d`#$vYVw1i7w3@_bhg)@f~ zlL8W!fraJEe-K^r{cj*c!YzL)f4w^s$!0fv)WSyCuVOKIoBY66*|Syb@{{*-`8O|- z2^{u6AhUy3gEEhwrF4ucv>GReLaa#z0E`JD^wcuMRJP}_f$j1)*{vV9OLw0B@Hav zON&=y5zJsdxx4iwD4l|&yLa3Aja%-$_rCw0edFhvwy#>eU~u~vFI{vA_y*MI!+90s z=M~W+LhdPO3gyh>1ceUHbXuhNDgN;V(Q(`(=55^y@{oKQ#$qqERGHPyeU4?w} zBH+?pL?!lmSk^X{IfgwTA?DK4z*(IDFX4c8`o@Qm{!~h7q)9~!WvdRx0n+Z`Gz304 z(;$jQtR850LD02YVJSWY?Vt%3K|u>Gby7u(gkhwjJ)?(~#dB;>gOPsL=CC=?8Gt~N z|42s*Yj?Vi*35O-wG5^}E4mnvvRn^GcuW_(fBr;Y9{q;Tj z_FeE8W*6g~EH85@2rM&6VFXiy7={nXVaWu6{b%ablP-}>5{pwP)t6GzS##>kkIAQp zU(4f_v~l)WWkAp*F}+Kwduh5L833lnJVF zpOv(NJm<_Q<&qW$Ue&ZE(tK9nv0|6JZh}^$(sDx` zcO}&0*k1;`6nUC%fTyWpSTN`f{j=Lznr6h{A;ACnGO$goig7z>kt2hgB6HgR#p|bW zCz#&LaQdtN)blXqiJ0 zsrNzGjfpXF9?nNC&_mI9r4iiLcpw#gCyK06@dJs%FfekML&kT7Bx|q)ol?|^P!y7+ zc(la=uRMEI*-y$3KXbm;s?k`rr8h|83-{hT7G+Z25}$8r?pJAzFyV5<>-#a#QC>zSF@VcQs-V$jj4HO&=Y`nPR$pt&Yq4t_b zHa&UPO%*U@qwb0GDjW~uZwB9oG@B$pbS57%(8mzNYxljd_qb;(G0$7EKgN)`;z98V zkn{lT8bC%>S5;O-%FCdT3l(O-;@<<+6KI*ZAC2IBNQj5Q#T9&r0zwJlz4a4nr?e#3 zYlEAzg4j_2+{vJMRRb73Okq7+3%vFkKv&yKk_rZgAh+)MxwVS+h2` zw{4jNg@k=c z*uoORl7$8cTcIgyNTH=uptR7^4s?Oib|}z=VJL+{XUcS1y3lsO&%f`v?`g5)Y_#+F z|G|#8_wGIS+;h)8`&p1#P@dKZWl=r?R#k<=w9Kkl72;UJ(J;`H zzhF`zvsD6*v0#cIAPN1rE4`FmAn#!p9Djw(vv~16ix*GXKVJO_aACi&ZP+f-j;GNs zX&w2nlPBs#ut(8e>9}x5J6KWs@u8lNM>>vzS&a=N(h^X_0sZ^52ovNRkRKbSp#NyZ zQ*77?fj<$_FK0WC5Bf_$HvXggWNr37i3{setC2+}k-UZ6bA5=c`M^92Gd=DX3AxhJ zT)ae9Nge{Jr*gT-6Vd8Xi~^}6a9-icKpOgTs^z~LY3YXHc(Yx-G=e651=T`S?2-tD z$}?C}z4GaQ4+TJUXN-Z~X-$Nae@h`P>r zlpm5AbOkL6D^neSNrfzJMdTR@02r2JV$b*+Y=itCOWD42*x*tbU1^Ai<#G!*E4hUs z@ES0Bn|^@sPJ4jx@PUVb(UQhq*Lj6KO5|y z*?qxvSDYpP;XLve6CLE|70yY`%FqG%Kj0Iey7Ov5J_@zl2mwN;Uk=$^w;I__IY{WK2@wi$pr$csRAt5G`Y?h z7Upx=z6hoHv4dpBQ2rN7Zr9}w*UDB2b`9-L2?Le%%ZbN-ne#kG@ zn~RG)fVfDfT{7q+R#9sp!@S-LPJ_5eN@=YFa+q)~4yHk?H^80dcp#IvOlh=li&PX@TW&y1%(4mu4GyJnijJ)&+uyKZMwy)-2QplhL}4GJku8 zyFMtE7y65ppX|=DwF^s|g7xj5vKdiZxYSqdQocu(u^)?8o2PthMY*+r&CzQ3x7z>t zH*-N5Lf!%f(~!i!Ari>NTV1RWQjJ1L1~hHKO`y|mLZUjPSW?TOa-duaCndc;M0MAy zITfAEvm+^2Ha3b<%XZFv@Klb9el-P;BGeauU|0g z%Sz$CGJ=*e_WzvE`P(TItk%p5lv0;2idchSj6MSbWlqCJB!c9S-UHUpZ(5`lY(%{m z{c6x?80c4ukl`fLE1I1r^$v{XCw?5p7%D776B+c>1j1F~=E8J1gdT9*xm^w7MzIAd zRR;}_yYA6!gvx%wlbfjv3MIp>@_e>zj^5~U)JJOT%POsQM=0tjDRId?L8NM zc?-_9-mnShSVCbENeZFd9SSI|fr6nN>e|fn?79;_4*o}WBTC-Ok}mPTKnQ7ZaP{O9 zEsIC-i;!4a3OkW}?_Ai7Si?lm<5$j}D?i%QbY%9r_IB~5`g*!}So{ikn2bi#==hnt zI3trmk2iPl$XhUaMK`p?c%Sd?{cQMCjzE0Rc6$eQnNJGBDbTT@} z$E%3@<=0UjEb%fXBCAP zsR>S0?f@z+zKFcm9aLIXqiP7}ZEyZ63r<3_O6J8>9e3+WZ@+!NMoJ zrcKGWGbCCRqU&gD=}GjUzGOTS&)<}ju`}dJ(MKiD9e1sLHPphOM)_BDZXbW;quIyt z(on|b4O?Kw*EL|?ksrUq>V=uo)yTD}rbrpcM$BP@c!cmBO~8*%zM^Ib68BuxAS5?5 zg6W~9!1_dDCJ;q>plWhrLWE+)oJCflnpGSGU zG^9F9OeV3$t*MNLDm-Pb;w801UZ*A4jEf_?rWmcskEN8l;rOWn{9$w=gye)gEX~{q ztqI4I5`Q5RLS)9R!^6t>n$RYQf%BJ)^47Yb`DxtY27M<0)zOhQcmqxZ2t5uWk%<``jqAT4gHK`2CIZn?e?+ zcc^wrF(gZ0C|oTTnrcLosl?kIXsGeWo&KN~gLHs_|NUBv>r)>YNflw5a-kg30UFD=C|e&D zYmlytf@JzQ{}h?%K9XAp z5~r{fz78tC6Cl&B8znR8ixVayqhve@NE(e1gm19?2K1N7n zZ1UXPJbyBC+fiG@&gUkLEs%$bWI)fNG$Cupgzc$;fmA!t^4+0^%dZ&AtbImgEN}lD z0%BHFLOh9O!?J>6CbaDAx{;X+&+Q~qVmEbl$rt3)&)CQ6nlp66=QW%70XXrz4lJ2x zV?;1p{)#~oXdBbT_RxCyj$29#7;;tM?RGit<0a(}<@&9cIX$!)eNOr7&I;oi zw#Vaq}iG+6Z^e;}aH`g5T5n-N2N1;5jHKn0~^DTcsv4A6~r`n+vZEu~tN z6sM|5WC_&juxwZyXw?*9n74h?Kdsyl9s#aB{6|aoPF^~4w6Kray^Jl5C6)$e=$wV@ zZh4N6y)MVR>}h#@p-bDF+?Mq8Z>zDzeXVe*wN^CSu%ASaxpldm2$rCYRMw>f)U+-{ zg7A!Q6yyUr@}rDWi+EXybD{O{by2c6J&tuDKm2%4XV;~KgdkTR>rxLj&h*rC{W&a1 zUeBKP$}#r3SDwx8E_CYJ0!v$BOJcpY<_avQwbd85)okncV40jwt0#UX?Pg~pZmmeT zBvk|fQ8XiSPfKQ6foea~8Y5T?u5oBRxcq4E(9sTSB&MZQx}wS*{K(NwPLFXyo%Sid zCd%!`lt%0pHPL|M4*$oIn+>-m*~7)}%WK)^Vz=I^JLepE4Lek^=A|{y6hG5^%Potp z2G{^+3fzS_OGRLw8ZDWZb8xGyf}&dpZY_H6ZGxaE@=$*fVX_*L!;QZY+&sQ{`lmSD zWKmb(27?9MNCm_lg#V*_1$y?4TW^iA&&g{e?2x?XoC?4U$eR~kz33Lquu7=cEMf`7 zsIGhp5K%0cBJXkOLpTR+It5PriD*V#gN2F^k*G+>q@?$}zbefzkg02PIC*rIdKv4K zs$%ZKVv2#D)jG4UAA6XGiA=ls*8IKs3b9&yF^VR6MD?#aC)r11{re}Q}M z!mlsNJZ#H63{6QLU-;PxpZ#9uS*S@T7#Ec})N#dIPV}sFP3Bpt_C(KW%IR4N@gLF; z@$512!Ls;}8tzqbC=Bs;j>d-@5x+x2Lx0eGBa3h0;BlTkc<|t)XWu!_v+um~PX4p( zl4Crpg&&MvB45ugp~pGy*z?DG_IbIBJ)eD6EIiJ$V&V9QVj+K4v=S{H>sjDITrqd9 z0j#i9 zz&TpR^GI4joC_pJQgKm|iU``K6t%)z3Qgm^7~miOJZbdVwdw_LiZW2?6i7-E2gVTb zx~+&3H~6f%nPUVi&Hr)V^e`yXgCAk#85n2`f&qozUIi-79{+qEJ#m~X@B=W+4W5zr zF1?v~SNb7lMQko`3J{L9S2UY~(+r-DUN07mNjSPBs^BF}S})&0V2j`-j7vWpzmJXI zr_f#=yb9Jh(1b9KZv|e$_4r}7O`Mm4a&zQ0Px_R?=uWxHxzk7b~xl0=xm!8|(d(P5^hNb7sY+HZb zeEbd1?4CdWy7g_H4ftcvxyzcGmYv&!hZ_LnkT6dy5Usd>N+`P%DH{-~5&kK{@kCJp zlz=0yrTu}qq{OXcZ)musGV3~4j9xrzx-!aQog2Ek*LQYr=ep}J8=a&d8q-EB1ZtU*f(1n@0H_Wc;udeQ|n%7M; z_u#hx1g&E02C4xAF0jn{3;6n#JtEaX%_m2hLj*mqMd&`PMZC9oNfA0~D?q3=u#Eql zBO#gTCSJ>@?*u4a8`g-l;90fOJfH<=ATZWY^06TL=sV+inF+=#8z@_ z7*Mtgg1k>;1cQS3?v0%QL6l#RYG(l#U7hRk2LdGp{6za}K;vH(=Al=k6@pAYZ*%jg z5+xG`3G?i}o-=zZdzUuQhSn|WR`27d)OQX1UFDRqi(LF&hZ?jWdMb5hZ||8Y(W>l1 zZrwzCUIBFwDS82xhMgFI%qr6bzVSRAV7ka_^C}paR5>-5sRN@=LLrDr^w?E1P%C~R z+E*Q$J&7h$m3^@<5LwjDp>V%Rbm0_~%nRBR#V$%M5;;}CsE^DGqzEXmWOL%Fifaaz zIN;!0K{QC@@aBPNFDNpf6mDTPy&$ncoJ9*ii^W1f=t*^Y?OY(tuy``Vpt%nTUL*Jd z%0xIIDAVNx_wYd{4P+ph4l7bTq0_;L$zuZ1I>zN{Xhhmp(y&Dh>hlHzZl~RLoKu#FD`%ZE9h|rjtE02Mv6F){`i!o& zw+abi!S?kHea1`YDZCGV!$UKl1%x%T>m%uUy!m~C4R`}6k5z6fS3sVWm7F0(7_c*^0wpk_&PlAYvtya*8N%+|?;83WCUMNv z+}3CG^}Uh0*|Bfc_Zu&zw2VE%J(`FnfxE3t2qC_+Sx5?T6?F(QwFL zQC@}|1_&uUK#~W|3KyXW4lgq&P2=z)dx#Beocd)Tvq1g2nMmM3mgZ$MvWP6*f2v#>Y(#lUVE&wWeZ|>{)o``sL!H zO&gXGJ}cvZdX0)7?bOzYn@o)vEG90H zS#Qqw+gT}c#ahs1!%4C5bO)f7 zD`HyUUZFkHYwwre$4qQN{SYg~kNBnh z{&{sn4S)u0dWh9=`OhX)rb4)2ARb=_a3*Dk0~x6A98&U5Pz6(2TXwZWinP}YzFafA zj?VK8n+}pj8I6wF+et=|$ zX_U3F7?)wfsQ_8jk1nQ*=g7o_qp_Ay$h3OsuqXFGj+M z${HbS#emBb)-}!{FpD@9Wi$?pOnfZufm$V@_6n>!%`q)v5Le1SUPv6oKk&&(@0roJ2DN7KF8bD}@4Deoo%H^% zF1>Vn-Wc1rQ*8{Y+|Z>NV3z>SsW}}mF!7!fNk>51+$Y_g?qVJCcK~zVJ8CvO&nQB1 z1*kG~$um^@0(Br=1Mva4Suv27`e|UGNFW*DgMIRt6sq~;KlqrP&+|PL_RGIzFN!Z7 z2Nnlm+u2=pM(D%D66h2a9^H#&+e?gE!yBbb?q zq_{v3tz(b@hp29x0(_4IID8*x4_|f}TX)%Iy}k0isnk_$#Z_0yhp+1FW2623@&kRK zlgzwSFW)RsoDgnPm~3|;2ai0P(>{0&i>GU7u_}D~P$$0M!K&n+V^r;wxeFo7%ufx{d>~ZJ2b-@*ohCpf)M!G7!~rT;Y+#H> z9J1A@*lHjuA`Jk>YYTi}R5nqeD6Bv^pE;NauFNL^$6g(JZHPXnhYk7wn3o>S zHVHh6vFG&6zX3@D7*5j11%^6Oqj+-mQvqghaQD#A_4s$*ybO}EfU$Rh5Y%3tyibHH zrNd7Mwv8AhhB)NE4#eN|33sLtQ8XD&ezZ$P2fEVb{F_p?I*1<#y?yCkYB<{2(cU&A35g)Crc9|3a)|Q`_|cFmS1@|I z?FePI1D%51d7s+6@9Fm^`ZRFZ6bJ+DlenB7SW|bmgqKVXxH<71A~B`pvTS;zd(&preCgn zr)R6-Ytz&2tr|OR<%;F=hGq}Ywo`dj6)Oc9I!337WHh8t=Dy-e)+Y3HF+1@my*rtR zQe!xL6Q$o~{-(|T9<_n>dj5~{*LYMLkLnd#kb{+TFHd(FyY8wSy0W*^)OlxQj(mME z$X@fy*Y&2`K1io~KWv-P`y#&nDV6G#$3r3ZT2Q{JySJU)+0m9d2Y=Y(Om_EP;%rHG zFR83#uZ85T-4(2>$N$oda=uA_j9i)S>U33J+1J@s5?LJzq*E1l_x83~J9_b7n?0Gn zvlqkk_NIfO@XY?u!S3D;eOHIM4ZltG?n(BfTUvU%k-jizRw!A4|KK#syUXZKsY#(4 zPKKi&|NiGU2bd#~mi){2-zN}pVc_+D&Y0uxy!YNQ#?+CGiIjUf*or+oBdQ?4i}Q8$ zTA_9$>J`V2z@OMplreuY6;3`T7j%xdkJFg+B`L)+nWY8^0do*dD0;Lj+IuCFH z@eysRNFAe!Y*5GW9i5aZ*KS4xVBqDD0Pv|S2{XR?y8K6)lFfacU%Vepp3{i#8SN?Z zfU8B<1f8hSkpx6!LlO|5lyAl{CdKuNyPl@zH1y$Xn5R(wFuT$>~of(8+5K+UO8Pj!-j1>7EYnGk1e{H9dN zGjv?%m^rwELO1hv8q!#hd?5i;M&;xh?zP^z8POjJJ~jj$@0cLmOenQ z<5hYIFzgX-XD>7I<(?7@RQV_ZobF*M`I8r4yd7V@)$t#$N%*(6wqN z{w`f7E+r!V3cN?qfl$t^MjX|Pqw*{Gdl+#+d&MRE?}{!4g@5n(?c(NQ*1}o{mxw@w zKV2NfGg{I}i7Mv;oQ&(6#{=OsfQ+D_kzp+W9S=%y4QqfiOUv&1p*TAJKqtGK#>&E@ zg-)j>Zx4q_8;KBD<8QOi%4cxkUViyU{P_WqEmo z=KqxVMs?j9R?q6?xBm5Cm2t26^kdmFz~+VmT;;;O(yi<_d@r(l7qMw+@5;qX#-C+- z<@4`la~?e7jP*44E8RMNy?9ANzVO}$9+dz3BA%x6b%XE-`!V|;jPFd@ zvCnhyDO^M-f#M9ZN;(`j+<*TgY^MB-`uykd{QLN`O55m4c>|mEc{cNY_3wA$@4v*~ zO<%(ImSPfNMK|9Y(E zyL)J!+@_+J4`Q|QDqSk=CLME~zinArK! zt9XxmulR)$3ZeKK<-;u>CJlfaqs3$?)rle}g)55LHnXSD-or^dGuw`C<;_3v1O+ zH@=>+WefY?&70*fZQlF{>SC+t9{_G>jtm_U{{LxnsOTsBYk>b|;Qz}i{tIcB0WvlQ zWsh;FQDUD2L(MVDCuEm}q*`o6?lZF`0|Vl}{c8Nb_{Z;l1smFXnD6}mgZb2L+P81g zd@#&SnD|-L^0SzA1uVznsr&-YhZ?$rntBjsVaQu1e>lLN+Q*Ah@-xZL*$LlwP!r+M zdu;m$``Cej@#io$&hyF5o&PSM89W5MU>H0^nHPg0|2wO;QXoJEZY@+B$o<0u@*mmK zk3=7BtDwApKKR4QypOaDcK*H(*!GXa-hu49CpUNAyO^2`p`cmtu^oAouv~fJ#jXSrU}*oaBh)o4fOsX=iOO4InMiloSiT42B8cNh`s85v8qq; zzB)bOooEg4>q#phL~{12#BTA3V5RIYu^}GV$FWcJPWbg0Ak^l|-9j33zy?VfGc$sTkc0iB^4HyD| zbfm;yY%-1&-sk7);ANo92)TN)6cf#KlR#{mMvvJQlaWf5rr zUz}%>{B-i`q#KJ5^}#-6AmuEpa+pFFpKhI1L=~JlEaTlsZq)3K$Te}EB&aXRTB z@bzNcC-DZ2%j3Whu_nRgis7lK4`pIuT%=-69+z=)At#widu$o29{-itJ1e{=JML51 zU*bzs?sBMCbem&9r7$7o6>vB)CS+{7)FJp}&MMIgzt>M`EFx!&#ThrPHp9Esd;U}5 z5m}KmD&z(Xo5Nxbk57F%Jc6_dk2of{*cp*y@2m*FW7V~vA|7*V&!Lg_M7?v3LIc%j z(g@U-5V1|9R#1KdI{l1CGPj!Kqw=_Od1;_(AbDBYoRIh+L{#g#}yIhUocu?e)Qy9-dd<^5^&ctZS1qZ?ZAHk_IWJnnll2JZF zr;dB7_~g9O27K-&N_}!K$0prVSsM7%YgaBk>^jC$?@%-48`JdySwX9zP$VrF6FlCC z1|Y`7dkqPbdkx`&z=>COiJvJ5Xe8WlDUDzIdHf0Ygfa)=0psC5r1rR+w%}NWi$TNZ zK)V-Am+fanw;IM)h%3vdFpqM?IE8U}GP@2s7t`~6lpH5P{^W;Ou7D|cqVW|xO_izC z28mO~m*~zT!Xp%Yz~wbAhbjo_owNsyJT+65B{{e_AoLXnM3Dif46d9J;uRs~oM3{i z>_*zfe==||NB`;F;nBSbh4!_@Xq%?K>Pen%xQ$|wk0+50mXN&+3TG11^=3Y@#D_DO5~epe!0MXYYc){!@;}&091c z>BL;8C-_c~=QhcbNDS-aqzLIwBwds1<)<(PH$~;f$k?C`;1VHw@D#a~c=xb95Tb$- z5j0-otvb-wh#9&&H=n=Zk%3#b_ooNGFYm^Zl>LNVC^HsbK1z)X1eMf3w5R9IGEmzN zT;{>e7j0?p_=s)){y<;fc6BV+EJ!c3WZ*i=X5moaV#RtLAB);TvEh!+O&4umxMV<_ zyQ42Xuwflj#)Iugk%0h;*N~L6LvwIj z;UMsI^c>Kb`5aJh)pelQ=t=t~unrt2!r^;ZN6t6F`8YYKxNm~515}?|N9G)#>^f)? zZtq~{Q_0I?tpju9t%K{Xip)vAV7e3)yEPK!A)P?xnj*iGNIHpLQo=Y)+oZcIT$V8`bb`cqN=)1Qut_R-Pt|Do7EQShSw zzlG?SW<-YzLmE6mI7nxd{2J|>zsK>hv_rBZa7d~T^ucN&R$&g zgx{lGsWDd7RMo6oG^Sm<{A(jCu}4_nYH_x@zGh)_YQxE`&`iBXOrn7DX`4}@(#WHM zjcZU0&|qB9&_GLVXl_7|>U0aHB7dj0U}}Z-;kxs%=)vh0T{gc&S#h#^+`;bL``kTy*U>*) z*wzuYRla3JzJ-pXVd9I@Ve~jE6(T~TFe}w(HKVRZO?3phQb_V80)h?%;g-roK(|L3 zdL(>mL90k@1l_JB!E+&su{A~;qb0$TAg?M#6hT?ht-Ka-oY(zF)wxhq$!bLMjoyJQ zHzbi!&XoA=(w4HBJH}d;4Wu{KH#E-LG1{_dAidgDpXlAzZ}nP|cE9xpP08M~U3yc? z*p6At@2I`;3~S5ij+sjjHthR#aD7+*wq8ji`%xq0%l<8${b%<=s$m9ze^^=w+_@3^ z+#$?M&Bd9^_Lv3WnYx3h+k+C2I-O=MDiG>43+rl?20Dp&ZAV>4C{Plk%pt_myKx-M z%&fM>%XXKIXO0G{(@K2h)VA7(OlPY|dA>LlO2!dZ%YNqxg*>I9&@=cf#rN0H(i2z= zts9G)g~edqeP{Rfo-O_&SXvs){~WI+49rv>#VcbiW4&kh61uD9(-HLTImV>7qoagYSP zb{c;$-G@#dXQz9DJ33~RTz`GZjE)__Gw5E)o#7H8fVgPf={3QC$5miL3Ph$X8eLIk zWfcE~-pz@YZV_EioCcuUvV;;V;hhc|bP%@}EE8JRKG zwj$oVqTOg{KkeL}o^w{V8&<9~uiCey=bY2pji-sXnP#5R-m!kB$vks?d;1wPO$vPa ziPh30>OMrNZZ5bLjCefv5|spDGDQ9!6lJA_FIru|rt^=qwIK0J!T8aqDh<4ELwxCd z+61Bb=wpPFhh34!IsX-fM}XQp3*?zNl!3HTOJdA%G*NeOR(&LxhR?-z_E93{uA(2byS+qu+FZEy@R^$^RCkAP28llhv{)(u2 zTq=;?kQ)z^31p~ji?6IV!&Zb0N`9O1VomM@tYQ=lfu!^gQl+P3Y` z5%H$Pg%>8qFFeA&!cLPPVvri9g~4XkYY>xcKx89_0V&&*rY~IwqYaRc;9)?rngAkO zN>O2fgHguA9O zHA-coYL-(EqQM8scLJJ_BN=eJ+%6XpI_0h713$6*LW?Vt16^Whfh}xzL~OyDx*1zH zU7wa;ZHgy7)dP}VQ&r%%+r8$Xp@{9!RQonJn4j!Re;^hZjh=#yKAVlnFR=TcKcKT4 z3gq8gO08FjW}VI;tt&y~GGO~T_{-15Pw*YZ;Y1PXqeEN_mOMA_Y{DjrvuQ9;pp}RB zm{O7>35zI5vy7?)5LMY-vw#=?e7*0yV`; z&syo58H_AzGF_TF%@Hnk_^iXMM^ok7SZlfQ8mYi+F3^1aX>GW4V?|J&U`W-{>y6^W zzy)LRi8ay{*k>o*0}ixSMwl%XBPHn`Qk-;=v$(jJAVYaUrR6h>gGx7P9c}_Kw*AV{ z&z^So*cCfY(^CiaRTu5xqTt+|^jyP}zsgRTmY6MXFZgsk{GF`p8 zrZ*Dlt$9a&&KC~*R--ttcVyI*n2&zLt)~0#GqujEt)16u9vxw?S(3rv42!ij7)l@w zTNFGKBm5i^b)d61=RJUm*bgNT=B^&4dweco5%feYc6si$YL%xxRi5`o-y45JtQ-}$ zjUO0&7zDxu_r%9yFYv+pZd(gPFqAnU8%0$`oC*X_M4TytI)u(Z$^Lfn@hD{E53G&&5^%4cC7>G#u^5}OA4R^l?pyZ#}gS<%cE|6q~}q0CGO9o z>d8v~8dNw(IYwNqRN8qtx)$hHLvKRQ=dv;;l=;e#iAQaKkP&HtP>Xzbx+Jt2L6)i~ zhk|-24?JTXXPj2fPP^=;eV5fP35A1c?-d0fe4rWVtsM-e<=ff6nr^!B<~_E;(ZYh= zBZIRXMe72$^E*^RhE`lG6Tc4Zy8%6H*=iD?UYJ8VFdEZy?5u@uLGgGCdFE=Kv`sPM z$GW{(A`(exoiC6qjeF^-dKo?!Ifo+bK+RXyPOG zHAGGn3b0S35eX{*4(V=EDs^QFYr{^;3&@y|(#kg0!Ph6)nP~m%9TM?E&*Q2$Weq$(ah9T zFJ|i0q<6AKm_8D8*JJu$vWCeJDBFNL?F!)`_V41C_&!9T8JxU6R9&U$Lk)FR@#=V> zq9p2x=F1nP4;^cVq+IVJr2ti4wTOS^!bULob#?o$Z5(cB7;bDFZfsc47)c}|;nr5M zm1{{Pizre@NInM-G*Kyyrp5&gOB0dGI0{Kq{)Hr5KCzX(i9TG^OT6MFWlg08`q9nL zk5TU6a&tgDMYe^RiJ4I3X7783Lg>*E13;&szTAGuh6t1Dnr$FA+H0Oxn-_0yjx7H z#1=*uO%xYRzzRbeDG5px{ee#sR6^*VA}+E&_}HTl*~eJ1{NMC>w%zv8_U#Av??1R> z$Edt*bo=YAbO19l_M2Brig|%&crQ^pK%eoj zFwa?z#~yQ>HTrM=#@co~^w4%TL;n7V{5{mVn-HpCziJc%qK@6hapME8YZC*wS7Jn? zVVplISOhR|gCXV^B9smljB)v*X&hS0P|GmGYII}L8-}Z@n4t6`4EoDTUBzfBWrhe9 zWl=L|RI%uwggR~*-s&vQO;~i#phz^7ch!(ZXX=}?=a;y4xJqh@i!Qb~V$Rab+^z)! zv4mM;NxmqSnfBi>c+Jezu9bJEX6@QFyZi3>H_htbJ?p0ZEe$OVbmTB*LhwQ8Yat0GS!?bJ^IO#QSr}2-?=f7=T13(WWVG2s$l=fRfVX>5Xl314wnru z207M%;9$I|6i=YRr2x|gP)8*)Du0E|lW$=|@)LXxX~Fod*Na<756R7Afy@&TR-~3f zV$_O}3Q#{nLc*0t>IOP z#8{ZaFaB4h|FK8?KpP&(Jk|8V7yXqH|D-kPLFW|$x_Z(&K--9+#+d`2Ku{*86ZJY& z5X*-1B2^n<0M23~+S!+RiYuHI=$+>cyK97Pn?qgKMvaaeD}$J zd17S-apMn1*p2c-)EWCqQh89j7fCn#(zMXIPuVP=>XiNbUirZ?(Pslr%*269F; zPRb!gsSe?G+3iGZ<)y9)cZJ<)N0%t8#b7gnwW=g%&4-dmF5V0xBfiBOW$~YKP?!1I z+I-()b06g2WPq1{87%MWDi7`)9)2uX-rZdu1hA9%mX+jXUM~Y-J%m1T+t6{zK}>f9 zaT$0DGO&@wL&adomefbxwhU3-u<5pgn{LR=KmLT{@L>lrlYG8rfDGN2N?F0SVG|;G5?!+#zn2Z{O#=sd1Y0I3kI311zN{;G73i&+sRdBnlH_zYC zL68=_DgP%cePi6hE_(M@wDx?yr{r{~JVH5JWP(8#Rsnr#ct6}cG$=g6Vxex$;wNZU!;(v&)x1zZ^Lbli@J|Gst{astlltKWzx5TTVua z!uH9pkJbptC*lanYzcE}ZBoPuo~u7SlwPAbotUr;QIqo-B8$p>;Ta4+A&07 z`DBe-OeJdz5u$Q|X2=SBUX^YFDtOGg>O!)|*>hfeifjm)WK(RpFX;4g*YC2g2P2x(4ILa8t!9bxz^3P~81M@AO z)xoRosha7Xw~&HOXH^QYi1DAWq4rB*nxhsC&S0J{z+ zUT4@zt5j}r^j#npYoG&afH0EdQ$`Md7>?ZPXs9+^8w`}V?69s)M~?7YaXEziR8gv; zMywSwBbrENS#5SHSJnpYg~5vQW>;yduY0gG6!i2*i%aY4O0$ca5pOFk^xE9zHgi|7 zCmw9_l}EgmeeTlc=2AE9QNFBbrK*x-CN*O_pevFK4(n5Icisj%Z&^7y&8umYN2iGW zisA~(FktzXmxxkd%7XU9rh^B`W{|tPNUrQmwNcNCd=3L^2}zdtk`*oWu%-%v-l#Xi zXQmXd$;^x_SBH&6Oh~Xp0km2q5f0BN-)8 z(*a*l(;;`!Sj_6CWw;vZIiOiOdL-JE^LWFigSTzEA#)nVA-ZP9pMU_JzfPli&lFe; zxMy&?VQFxEN}8RRUSX_U0Zuf{+328s&)Cq|zH2i-IOgnC+8kwuCV42tIP8P`S5>{dU| zZ1V33?{`dWWZ#C4M|ugb5`q{8E(XCMjjB}$u|b4E}Xi&AvwC zj(_p=(`oD;)Gn`woyrdH1@5YH@LF&V31<}=?sZr?OgtJY+l*h1kpG0OCH-)LVF;tr@JmwwdS(cXJmBeP#~ zp#Sm}yKbpA&27Ai&0eu;^~y&NQtiY^IxX=56Leafu9MVhxspw8Z6$*eDlI=zwqL*U z%II0ek3H%*i?xlAKD+&)hjx%kOZ$8I#39%rYOuFXr7n~WNC0IsXz{Q;f-}MB4rOk&A6MX}6j^vbz}2O(z4ttaumb!u zDXai=uzg}bd;3%BzQFT#wo(4em%fytT?MD73y0WWRQpw>081MBA%}tiKQAd}(V&Z* zpds@~&Z3A?TUm=@g)iS?MV7Qb(87>aTxRwLtwE{#x7hiodp}e$6!Y zsV!5Tv`~pBMx`TCKTgG9YIZSb5OEd~LqTW>5r@<>7*0>(KnSlQQxIiTUk5VMNGN29 zLqimUtemy@g=V26f*PMVCdjhpM%n~jQPi+0W2)sUvn_-$j@I(QRrAmAHiyQ-U7qSC zwULpgiuSTGwzR-BZ&M04{!Q|T$r6oh2>A7SnalsM*D?p}wGVMSVXd$*H9rXDK2##= zwc=1M(~;4Z2T^dSw75d4>#?Hzqn!>KU2ApRhFeor8TR|iJo$J(o+nbv*MX2Caw(zr zcO~7wM_YDV=lFVp-BDj}d2)7Fx-(Xu@{08#5A9O!^Q#?2*0O;A^iCiC1xocelt++W zP$mS)Tj4E0u9L6aQ)D%1;7!F1L*gLV0#Xe!sqHW`i5IzSCAJ9J>LW0q1JUth+@ZL< z5Y3|aZ}g!^R8QU=_L;yO|KK|7#i`pui=wSvv(HSIcLhV$j+JMzvuDrng&h^o>q4cQ zdj2Z^KvPw*vB7lfK4v9v&I^CN=Mr6kR`r)FwI*JJ_F4f87<3^ZTtnqvDaisgRn)i@ zBV-`Nt>6pdR#4U$aVu2YHyZKB0x_j-u|4QO=oZ{ioShhAi? zfjdWk{G&03unOvXseAw|%Y2KM;=+iu%0FJO; zP|m9d0QX4eb2_RZD<`Bfjbh*w5tIe9H`anQ4(dasL0KvalUID$B^4!VrNDgJAq(Cy z&#W3D_pi!f4L_rP{qlQa<$L1CiqpC@nE5)c6}KUPjs#@L1b{Q=%R+1(`Lf)F28tX5 z%LaK+q4Z3$F}OE^tx9b+54q9MNdPuic(6jy=R!QB0)&E)3NkTfomk_F59?hq{Ep(r zTCF;hw9+PGTq0%ZApfLNsBe<54KvtPZeCA4PeX^hd@;~ z!63-k1{h)W!<9`|Kc4m>iORig)~F@EXbJFQDRBr$k0DTP9j<6#+Mn^yST^1kk zWdR?(;*TL8k$sU$Wop@6a*Nwz?%ifh!S>2hF_PGddTm{1W9A?wQ2)<~zRa_%1tf^)~U2S!HO}o!q=CN7?7jscheuj!;plGiChpqdJqfcvG}+|MmCXWvkg(T_As( z-Ez@|E0%-BYtS{@gu?gOn zaQO`4ILHtJ6R$J9mP9CNVNoIC1Uy9^yi*Va&6U9H0e@~*WeFw0LSa~?dYnf(n^Vz- zzK*hl_X>BDqbzAJDG@iyH};w$W4NQh=3oC**j zQC?9lqKHU<>J7nV;o$D{gdX9kNn13D@;TdHocZF5*IxUheXGRIt5%Oc^ zH4{%rkBcwBKITt(ZH4GZ$j3Ehy~cRzq7ny>RMJPJkff!{Sc|}ih+8i`ZjgW5T+}T8 z*6{0J8<-2{qsCJS$}e)C!D1{Rjz++ckca|s_!JiyjTC7}Le)c_-8eRS5S zbipTk@ciR={_qK(fBbKm=g-3P|9yhz1Gcl+PUQve>$nA)oEO#Bb_*@i=85+NAtii^ zFk}3(AU-|u-s1_?=Bt_bt2ijW3`+3A50VmYPVKegk}81Y;D)8TiJ47Z&7#HJ&2$zW z3=P-@jj+>-pNtlraVMw)m!499%b;h5jfG4P@1{u)mTMBs7L$3ifafeDg2iH7Tf~eO zMezYg@A`5Gs?xHE~m$qWyDFu`zl&8r#WW` zfhiK=rg5AS2~wGhvm&eFQRe;@I)u8j>f0C2jm>b>xxHoSaDR74fg=gQVy*nQt^R1N zrj%VXXLw{F*yt#AhV8Q=fwJ1-;yQLyQ$w-=kJXti?S;0U880+=%H<2im&RM!%avZ6 zB$X9~x;QIS7RV!$8HBKZ{GmS5%x13Ad+i1x~dYoOUswGbWlW zHE2W)oFaw<*^E2$aBDJ}fLqYhG^jNbYRv*LYXORNM$?F3GI7+Ja;QzGI@*BRq3WT3 z4r-$bF7lE8ieKm?2XecVapE5S$gkf1J5W~)+XGeTiRw@UOAG(i1g5*i9lj-e|{ zRVuUdCvn9d>%oy!2{(on8q|`~#0deIiL_T0FbYJZ%a#KuTiBnM$xpLgFKyfQlKkhF z@CBd26QLF~Fr7Baz#MV7VFuBNRB0Qjm;6XIYN~3sFEnJ(tkWzQY|{uzc-sq!s#kRq?#4h^N}I>CD^f+Uxx51`D*d&OrOD73@6i z($iKgwHjv|&70y)CeygQ@5Q+tt@p`~)RvZXq*wope7$=oKGLLd?^g=v0XSs$i*~+e zwb-~e*d)qfV}_(?(ZS>s@)j2$+uI2Ri5qJpcqkYK&y!G)Sjvry7Q1iI-9OhAd$y_D?VUW#6MB+U{yU08> zC6|wpcNL%F3US593(($eqlj*c)NgV8@KNKQAO{Z-1xoiedYuMPhNEFI6!vxjjj z1s35wcBgm-`^!nc$L{2|kBL7EKg4@ACw?!g%=bec$AtMnqi3ML^adc|!qj z)E6zQ)LPIF1M)1wbrl&;r>8ueP*Sm2M2^%f?Y&P$zjzAq8(d4f4r%dAq>T6ci*HodoiYUZ!Ue6T& zh6sRW;q25|<;-fKP-MiBfj<|ZU5L07KnhT&uP|YSX2|OnowQM~z#(X{ZZ4vjs)7-G zDG=5c;#qY~j!1PQOe)JS2@w_{5d*?IDrYMRRj@hXB=b+GSun>VJWyF@ z&RGQ6LON@3iOb|U??<=o*>l^EZhQ4r_@!JUb1%Mj*8_70`jGS2Yl@Y1io@ey7Kg{s zJ9B6ZXWr&-+I!8ez1tVfnKOSdZJ50j=k)Z6QQ-)R`!o< z>wH4~+a2e~e`7!0zyD_!ZSURnhv-gsNoPZC%a$XTZ@GN!b!%VLZe7)VI>plJ1S@E> zUwj=2<>w+^>{?;3aIf%4>Y+rLs|dbWm~+LBR?(v0d&9L?U4Gex%jOu(mb-2p(QB+` zEKsL0X>=y2YbIvWvYmp_T3|I6>=X=wNoz16$RD9~8V1{^AVCL1tOZ<@ddrp~hA45P z*}B=r3Q!$(1Yfe$vVY$N=P}{V+xFeN|K3Y4I`4`LuGqYB!Eid&o@}U#Rm0ZdD=jIu z3#-{`J17S38P4=or5slPgi|hZCf&+yrgp`Fjfu#IJ9hN=uGy{1Pm~j(({mK+LzE5y zfmTH+>^@3gN9FEtx%AD=$!L(K(Cb||fw*JnG$O~xtr)ALx*!Fw(DQ$c#a#wXX*^Kf zRc^Di*a{2Hg+_RJ%(`H;D_|}06rm2J*sL5Ye z7+lq^H@TW$(gLJdC=!AyS-tWbMw`K4Dk>-_x9I$rYzYUJ)ZO0{GZ`Z_r~4|jlEH2$ zsw`%bDctUNtw`vc;WB@9iS}lL(`ME?MAVM04wU)|+-O5kP$)r_y-{M`#)5(6(ca-` zdAzJ5;)(h6`ifYI#~EohCr8}gWSD)^QC(iz-5#m#C@XFDO7@4?5$J;tOM3SjN3ljz z;43xi%YA0~zgnWUWof7%5Tu;yEFO`4IZcoIj z8lNV3VagI`N=5Kz@a_(fy*UCL&+0vz_ZQFRE z_`(8qc2}yG!W_XI^b`LpE|ij(821Nhd$>Eua15Dtvbl;L1{iqIU{}Rvi46GY(eTdI zBlm9juQB;;9XtCp`QODmK6xGwO2UGPv&A{mHhw=<2y0WTusx`*%QRZ(ml~f9q4k;} zrGv^`J)~8HAmJKNat6TD1np+BM(Q*p_=4ONvQz4YyzQS($H5?~E_qwQb?dU{8^?w!EpT zR(#3a+A?#_mC4KItSys&@?H6n&#>#ouUfx%>DRu-@x<<&7-fGsE+5x|k3%w07&(_y zqTqq;;%ivdcQJ@4l(7wB4eVil#LUWrkDH*q*d1W+4Y6=B3SAQhkULQ}__+D#zd(r@ zDx2={kr8DiY@Qn5VnXkBc!!uy04}*n8s)QfldlAIlFIB$xhFH1$7+nDGiqtu%ZM~i zVf2xals6DyLLeT9*Vk1=D&Ro^nc3Q6_^~5^ie%(;q>!~hw8r}6( zjfnzFyEE)8DJZV0jwdv1%`bl`A3758{qBE)fiKX$YJ^!6f7NUj>x9|p`Edqxi6yCp zjgf%U20yfUW>1@>7sMe-0zpr7Gn{>!d97EyUKruu2>P{F-qb_TLr23_yWp;k>$q879NCV66=au>O@$NXWIwXeQD zJZoUi%z{X$epY{7V_@%W{{6trSVM5{>{+3>D;cs{lS}=9Wv%q@^48X6{=lNdqduQ0 z5Q$V6ee7bN*XR$2{YEdl1m9LfB7URK*k?~%KXBH$=WVV!ZKSH$p17&+t#h|1-?!e- z|JJ#i(>Ju6OnnHLaw?y8*ACy{g5c1+c|*YkgFfHT{J{Kq^YG7*Px<})`TqG3Xi0M2 zBKCo&Yhl&%2&+=32^u4^sZ0nJGc!;knlV9h1=ECxgWf6#*A+z`v@5PIv>^6RE9%8z zyIrS4&peMEZch?n^YNwiX*|g48BR92*yc}t(iSAv{=67!I9zN z5&0YOe~Gg#Y>oWkLbhg3*PPjNn03)2`H!F-*doNekjIxn{K(iM)Y@X_;{ zVuFYR1>8~|9a%}hjPY)U2P*~(oOBeJ z${9^i(+!OXF#s}dY|b*Ew>0K6q8sU+NjP$GPcEB(*MXUXKQCd<9k7R4 zB%@@uXg1&a%6&zR2iQEZQ2rC^>hBwq^~R#kZkMa8!_E$mx3+$t;1h*^hi+;YcSHYM zB`i%XS~16szz`U&5oUzu2S_A`roI#F&?1hUgg}IfiMS%L-8x(m*!H!0q^bxD7eV4( zxN6a={&ZWizP75e!YeFb3&`&cO@PaF^e=#hEXSoRe47#DtYX%t!Gm?U(ASMrSKj>< zmPox%!XFf6K@rK|u)Dc=L48eSRb6#=%2m|eVs_h{UA?8lyE8M?{WB#g@v9QNi zRW7x4bT5v3j7EQ7TY5uX)!NFEws>Iid|!2KXGujUFyq6m15=N@S9+t!;rXVr`)eNC;U90GYrnEL zEpAK8b?nt~m<4j<83oi3hq%B6mT3n>nLxYEuB8EOQJicrVT^d>;1>_1*$YR~2M%Ds zPdQ#92jSOt1HvD$`_uB1sCfzyKkb+{8dHJsi9H14I%Y}#{O5c~jCnN%{v7bAqw`Pa zooC}wW+I9(5iY}wu&5JsNZ>@vASvk}4356hb9bM3mrM4uxA}TO4(fhXq{qW zMPM9Tv8XTML!hKC_@UwLuKN$A)8d_OWf=B0W&%v8>(9s13z1ZiVIZX-nm zce@YY1F-IJ%Wn3Co^iO66}W2f(%Zn5Q7BJ&Fcx$J5^JXp6$g&MC&BgQ*SeqDiIMJd zj=#KvJu|-G4!%B!$b5Y#7>95TODzZKweI_GQ3jG(BS@B|fRURq4(%Ps6SQBBbNn-P z^MEKP-#j?KI3K>Xf^*oDR9XN{p$jcxX&SJygGm^RQ`O-|p0EAMwlw?h)6L(NUuPe3 z;FyqB$1@5;Pbt^k(BR`v$gX66V#$Tbn44eXcUc$pK8$O{aU#adD~W-a(qLW z7-|ky@h?v_9y*w2FMg%r(1Vzj!K0PgBhVmRMjW@;@2R-OaBhx~1g(N>E`*Y**UwC& z645tXzV|LWGHE=$5KL9jB675oLvL=QSd!!jc38fnp0%f$kFBbgUt`S*es==4Q`+}L z=G4B~d8qGVF;F63!md}wf?ZqPtIAY}1X2#yGz-n{7JOKsOTJDFhpiXnOGJNmNZ>AW zehmVAsOtXnoyx5=J6aTjsaX42_bZ#yh@dKyU7N+l1Rlo9z-JVqsW4BQ=I3`B0JA%x z0LZuX?q78u#b_k(_ zZ_bxr>iEVr=?B@LN)XNX;$(cD3cPxZNDPA=zW*1UPu`Sf;RnQ$5_!&btOWVVkSOThW8kRQ{V6Ve$qnw-GF+yn=923X^*;8T%PP5k|plj z+F4Ut+#jH1Wxg>?$iR2X_~ZKx2p_!_{Nn+(IU>J(%U)$q8nb(SvS&F*m5=?P{X5&! z*FMI^g7O!(vmbFVvF6_Fn5RfTwjBK&es0DWuS&C7Y#=2692GoiuXFctY03>bWaasG`E1~^Xv918Qy7X~m8-AEx@^t|rZ zFw#yybH^Q6&@(%5>;O~l{-*Pvy_diQj>Cyjm`}M+?mp7}?xFN~>`J-FE#4tt%x=i+ zUyjaB21dTv42=5$qx>N&1eCrH3C5f(ihAWx6}Y@Y6>bsrB!=f<9hvXB+ydSQLmS;d zxO@0UN}H~IHk~Gk6l1GXm05%$9Pf&&MXefp=sd()HY^qw?3cO3AR$p}7SFg0a(Us zi83fb*n3`aES`C!3H>e|GoJK!lsh*k2Bf zFAx`RR@RoegA641VK4IT?kTja9EweyZ_=SC5&gIs1%PtBGEVN!0UyHgq}VA`pg?V0 z0Rjvu+z?kF+k!gm8ht50r+itVO&za4lulnWx9ptw}IGg26k^h=|F^c6aufOqU+$+iQlw?bmEZeeWTi!FCaU4g^cJ>Zr z??4i=2njRLviA%lq3on-34ubPEt@jJ-cTs3r7ficN(cS5MEZZvbMKYpB!#M{kX2@?ucVB z-_ZO(teU^6>Kks>XUKR*#nWACv0@7Oo||u|J8)B!KfL48<_E4(J5GZAcc`;9*~_1w zQ-wM=^5?d;JP_N&O}X(q)agprx&P8?>Sq@3JPVcnf&10i!3vIkUM(^H^`2|`;OZY6 z!{4KsrSTr+`cXOrT7TT}`mi0KP~yb=SRd(Zl(HpR&$rgKI9~5Lur`Wb9sH@KYq9$D zZFDWK54)apE$CI}09{M6uCLX#(D)3Nf&m)J7WC*dOWBef-?!?Mp=<%bODtuJg1IH* zI2f{QC**6z5e6zCP+AFjrk2nS4IC&s-*eg%cU&J8GuB0E^PQCORDG5X^GFH}qdqc^ z4pd{_as9JfqWt&K*dC>YBfPNsn8s>*s}g$5K`I5;u~eBUs>eI8{KeVP9ei>WvAYuT z@^`J*P?0K9u zz<8_k+B>2`#7^aRgk!I9Q-1|d6H_Mw+Dl%xiA`9N_8vijamx!`&#VRDt}KdeU1}&W zIBPTON+TMK!@MBnWJ8@m2$iB=#Y`A#q!i`FO|N!d2LkfT*rWXVQfv@==cvW6a#x$O zi6K|0jowkn6<8~VT(S4buAiJ0-OayO6uVhT7O8a}Wj~WjiyL0=+6$#c^Im>SQS6@U zc%kYhaM9vnM>oHuoFqJ%ccA`;K3;R6=>}dwH6{7#QO_@G!bl<4J>788k4a(ix91u! zCAkGqM|~Ducd(8*@91FGnoy3G>te&%+g?07{Dd3C~(+Mb(OZ z=C;Nw_Ci7N)4H3LdIGS!3;;Cj1xF*7U?Wa5dRh1LeOP6;=bI{B)MLqxM=zTgb?w>w z;LvA&C)UL}t;#t0FPiK1Xmwf?y5Epb?zo`)c}&9JWA~yyLx-Vo#H#aK$tb8*>DTvM zczgGOTciA*vt#Q~?QOTwgjoH&8^Hc9x{O;cp`K3U`(u0a`4_QcxvDi;FYQ&1x?Wc# z0fDP;P!Op8d+Y)N!GE|-p%40tXjex+*VgKNy6*bE`&%xy=%2MdzCq3}F_tXJDF^CK zdn6itpm~$%gp~3=`jM1lF~7iK2-DwS5VA1X@pRn-XGQOC*<*+(sLSflH_8LlA+=?o z9-Z}b>d`j-(E@boW`g;*smBsf4%FRnGu3nH4*u}LDc203MMu>iEJ_1H7v4?vZ08+3 z3mx*KKNpyDb#!#Yh1n+>H1FE3SNr%YvA2r&&PkYd&gP;X!>)?;4HMVb@;;Xx^(e_^ z?VheXZ;6WOOJnyJ@fkY|e7AVtQPSVTpmMUf}Rp|NYUJlw|3okJw&G1V?}%^NntdXRzHuHh9ioCMLur3ntD2ipAh?y zx_T|Y_dC|97Np{Td#<`&jlTTx*rQ>7-7Cg!o%(6XU=FfToAT$8*?y|@scg!`Z;pTC z_8X%7!`RbNljjep&y+t0tOX(MPz$?B}eBc^>|6%og z8@-sRogfHad`az1yCIkSpz&@?F1Z=_XnwDpx1ncAn*Ob2l8bh? z-4A_9?2|H86XAx%ACBJ6hAA~=k~`07dlfp9;8Up1RGO$cwd(v*wLGRajxXj6KV zi~fOXO~pyFpWmWR(wmq)-FG(CxsmUGp1Nu%PQFu}q&G3^yzNre&nzB(mnthqynplt*=uItzBkb|SB=C2|T4EIfAjal`m z9I!_vmsmS6$7}76okI1+ex@W7&aAq=Q4XL^VX2ZF@LJmyKT^H=C*Qjf-MScf*BXzT zd&95ychOT6C$F{dxeE30<>j&8?&7--PT4bXd`HnIHFrjzc0$IXk^P4ko6-rbhi^}F zi5?j=_g6cc4{SAQhqVT5w0HYPy(q{U(Tmta&>Kg^+}LN$#sW>^=+Vk135Dj<>znTb zLwWgvx_4vqL_$_h?iZkU`Bt(CYBpq(T_9(_fNZkA?ny&GWR3Nx>28uM7|O@xgGQOKJL*0H z-F${*bkh@c*FbV;*zr*F6<1rjmH{&1QOlT0grk+V;icWzL9}@%YVqX(^&PE@sazHn z-tD>e(d}UL(SI8Xm!u5*UF1PEGh3d$>+&dn{M6`mFhv=3&g!F8=hxfmEV;EcDGA?s z<(*eXFTFo{J%8M)b9ekkemC0MrK2D5Js+nmXTw}^__nD?CQGHmyGHi(bqJ+ri$WU?2d=b`g187Ua|#f7jalK zHEb8oWg}q?vQ#jf@j+0;WRq3|5(>(lXi~+|PiZ!ex|2zqDtb z2!j&TM|U&+fJigqL#XFf=JPGqbCGzC8qN0sAIhJNEj<$m6GP_k*_f1+ymttdUJ@>$ zATQ(iTD8UC{dyB5H}3Y0SuW4FggTR5ll?QQ6_j(YuS1> ziW-T0aPGh*t;6&1C+BOUuX>A$ym`gNVn0l;!-K)Xf`YiyJ?gwo#DDQrv z9s{1=@paGH80~h7e()$cIuCsBwr_Y3?WVluPdv!@7(Ow6P&=;|^`c*# zKcJnbs2ZX|#Q%-J2oqT`tA_3Ry8it|bQTdxBCVl5;Bo7^D5L{8bQW>oO^KU0ib%!F zkcf*fhW59Q+`ZXiX%mc%oCx}j>=VWp2IudR0mUTF8Of+w2+R@E;iS2}zSx{eEPZ3-0gQ(P53Ut!Q+6?@8+bw*COC{%~}oR0ZEQ`90?@7dy5QJR?VQe(d3 zePRiFNc@x8*>SN~FqN3g_-?U?&5;gf=aXWuoA+jm1#FIpp^F9RD6tquW z0@?nK+%*}Y%$8X*wQ*WUO;wlHuOEAFFdPmB%gTOee#*JIT|TG3H8=B&WBRMBY9r(3 zU5q2VMMd~yeX6k@gR$N&UqE*UK$~lIf$S_NhFDS@C@(0_^JMw6{mx7T5lUCX%NRI3hzJzc%?N3jYfBxJ+Z@#E zyg&Bkp+i|<4Q1Q>`PW*Vw?1iJe}OrCP|e|%*slkf!&$x4Ny^3|890Z`m_ny9g&7Wn zbvtwlk34}mc8{1IDmfs6V%aIe^ij-SQ&w@t8c(Ocx8dujZ$)ma-|6eh{hHa+;)l-N z*xpfyGntq>99X2Oa~M;H&{l^}-Juib&h?~c_)|tja@}B))?kh0q$ADR@#YAQG-;T# ziI}r@Y0i-E=^dIg+#82`_p5v36KghkuRpP7=b+pk&{jK=pjd1BJls1;j>NsmiE>df zpFND|(RMyHac>nox_2O!n4N8iy{2%5#~Z|N*~=0q9$848)ypF^QSvtVups*!(s5(1 z=~$jb45C-~&9Q}YJfw1*^LtUuV9n&p#wygikQfs0!YcwVqZmn{5{p6AfHSYn%fyU* z`|v=-x8ePB)%$kVtZInR45E2_z=VU-f*7e0!a6lZkXai9aTQ7{ye#&Gs#H~rdNWN} zQtqyys@_Jg%c)x)=Z80tk%7bmz6_tojc91;UYpfGy)?;FN#bgitFEEUab-?pUhK2P zrC5*^sjbX(md+^5OkSxmPDWqumNQYWpN&#|L53n4m1NshU}f|Q=Sz`j5&>y70qTM% z5-Jj{51Gz#vNQade(VJiOW9$bj!YHh2a-);Qjg$(^(B67(Tme9atM5zBbEb?9K@Yt zujngG?u`AKzQWjZpv=wu4YmMhq!E8CfkQvQy+!JtE%pLNNVuCU=SY-$7v**u<>F~( zxijKJ*!KAAocPU`U%hEaDPO&rXzz@88=k+3=UZYoef75a{B7eo@NG}Lg3XD44E$|W zki;Gl6b;9cfn_P@x(4KE$BHJ*Q#tE^FWz)v@76;gd`#H;Ko`6fXT zadz5VS00MP>YDa@WPN}2neNzEXpF~i;qR~oG8>%9_LEE-Zw6hW00!-J&Kx9nK{U&? zAtQ)W&7w3gDz`De+14?%c*O8RdiRNV4(pD8fp@D_KZ?x}QOtZ~Pp1v_Vp^C}vGvc< zG6DvK4EB^~#GdJH>+5UFMaqQFhYe}%9+jV)n@^L-<2&MwY<2u)fMYt9FH zv1)0rPK#GAQh-BXynH-^oO1Z10jJTEU{!!!JvKV_C|I z26WMg3LxQAZKuNBcyi>R8ZyS6j-dTfG$ zPqwZm5Jxq19oe$`t0mvgTDdZ zx5Pa-;hzIOI3@NXCX43a#dtMhJH8EF%MTbkln!D3CxOn~qi}oxzCkn;f^PskzDvA? z1cp=0g>c?Id|C9vclPc@Kt=rUcr-p4V}Am%g-57)Z$p@Mn9Aw&B_tUNSB7c8hd}gT zB#~3e9f?0snkeJ|lPY*{5&>FUR7^zcuMLsOZBv`N+uHrbjUE2tww}heK;Z7Ku4t$f z|GT=vVZdcvyp!D@{}td1dE={h&G~sZ0pD@)0zCf(oLK&_Ly6~e;tgz1{8c;$ zPxJ_p=y1>B!CpFf*;9*uXihvTo|eaI|dePBNSAo2XT_%e1~{Au9FhS(cl z{mXp*FXK62=#4L7pT_S1WjjiFge1IA`Rg~197%YA^>`((jXwl_rzQAUl)cCvh7-Q( zQ*49LW^6;EPYvuh>}5U=O1HzLAeR`aYGTPq9nH$G=$#A&bYS zuq)VI{6@TQ`w29n%{37OMmVLF*9^I0?oY7krZNrjr`XGgcPCk~!kt{)WFWalu;Ww# z%#JX(ukaCzVvlsm=C5AFyNB}%L+`p8v{jPl50i^gu^hlF>_+6LRzu}F%$Hw~OZ?(T z#3n{{w+?|LANA|V>xgLa|Bo1|iFIT|*1ISAVe}oWqP>v&qVXW`=VNp?Ax~2O)maNb z1Cj#PFQphnQXnvND~*nBihG0&<%mf4Fp>s8$TMR9`1qM;S{Gm($+c-plZMR#*#qEj zB+kKT2vqn0tmu)Pie|mcTjDNpmzFv*f(Bcl&7Tjy-n=lyA2$C!td?S1a&6u{v@;yG zR;GgE^MF#?$f07BH`7&Kn;&jy_GV?3 z)dj+h#u^f9d9B=mm^oNO@*P?a;sNKT59v}@WNj0xv>(4*m+I=q`qOGx_o!t)$MIq}`>$}61 z1-vT+G9zFV0@jG}Sn5P#pkY>=0==Nr8+?gMH8-u)T~X=|+e|zJ@{q2|9&RICqtJaI^&H=#N$XgnbiPYtYNp)GLHyM}|t7j@?9>)Q@9n4oBKtmX_vN zoWUJwj;Y~N${|w~DXk0FWt3!;6eB3U+5=@7`=3mcySY;IC`e3>F{Jcgjf?~mOVeU* ziC)KWN`}TAo z8r@Vy1+ep=?OTRmX&O5Y!OCsUxdXL?3UQVZNi6c`WH1{;07lT7`k(Pek&CN%=EB@%s z^W-&*KxE;(rszefUkJyh#>?4V@%Ny+YEq-4csC%_6A-F_jYnWI;FsE&6G9q_fn1NJ z7=Za0QBHl3dz()?Z5jT*J8xc34}MYaZCEGs;(uT^R<7y|K#ByqLV!6NOG3a9h(|WJ zOEWh^sHEKoaeU~!c|$+nx^UrEmLDUZg5}stA}SZ;VjuLy5ojjc;ZHS&^&?Q=Y;3Gn zLTWyVox#p!m$Dsf7rT}1*2<_wV~4V#YL;jd3aRWNC_-1U0qWpB4ew^RF2jwY9ke^n;S)KeSPh0`mC*A z)Za3`X`EhPTR#O6@^YZdzEO3uw60?N|Es<9Q!fW}j-@4mxH6Nc8!$}&-+^HbU|7W? z0HCa>1^^rCM9o-UU0YNAwXh7Jm$zePrAT+A3&%icqQl&gjSQy$Pjt5T$0_~9^w(6> z96J5~*E-C;5w>&j(|h2y+itw^+G{Vr?ELf2+`et=hV`pgEnYNt&eSOrCX5|BqPMf7 zsj;fEB;?O`<$(Oz7`tuvZM*Nd{l;5wy!DowZ~DQtyRO}}^M>oMyZXw@c3i&W;tS8a z^!!WD{@(U;&ph|EQ?{P5?TiyQtv_kQNo$W^b;9ZsmM>kja`DRf$Ie+ecj3(GQ)W+{ zJz?^M$&)6I?H}7ee%z?O-Z3M_3>(@pymNSKOJjRedtFUsLsdh0I20*~6c*$c`-_pA z#pm)lvz;p7Jp#}pHH^V;Czu2WKCamQj$v@!^dht=t*Ap`f5(1AZ^ZZ|n>F;l*^dovyaw&%t}! z#KP8tkJ0xV=}C|EBz7uYsnYZwRX`Q(TiJ@hcq>l6QPl5jTd@L{E82EC@t`1w{?Jdo z@ljAP+PXM@7k*l-Z?mk6#Ph@(s*d$-D|hW$+1A!Zyk;LlUQCl-M3^;Y(oBTkCdCRJ zfdg+KZx55n_9x}*{OQ=!J}$XB*Sa<}JBu;H+yez$W~mzjzM*484@sa38=N{-2)SE7cDkjL?bKJoRuwEP zEfxF6%`-Hz(=5{z8U=iAj+e59@eh*nJ{T-{XeoKWp`od{x}{0|eZQfprMel{E%5&V zT)}In#oq>8P5r0~5w0*OTFn27`!E zM*68YfwR*T&Ym+6XYuSXJ1*w^8o=ea_&Y3%l`1U8{22MKX(Sm8k{jFN<<>4&;Zmf< zZlF_fN6Tn$Pv+>%9`ERuj^fX&8=cY8(W6VF&c^Cq$o0Lb`;qu>k&lw{y3JDc_ao^e zcf+SlOaL;Gs}y2nDjORt$a4*28;tJ=GL0-(B_ZU~VG)(!%}7M=%2qjfJRJ5mBjBTD z?=^PE*lI#sWkkI+!q37iY#G?S)0?`xn>q^oo$50XghnfrpC2lGw4jhai$Cw62Mv_?iiMNWd#dNt}Pd??#Rsxq@&aUMj}2_r<5c zNmIw;Q3qz)ed8|NSba5q#n=(UCQrsMilh80*aNN=pFox|=MO4?fpi|t!g}kr+QqgX zg3h|?OK!PDOqe|JSmGB6KK!luh3A|2g_v|ChB1(apORdCpy9qlXt?d*Oko za)bUx=$?N{o+Zi+i%D_qt@$v;u^64iewot7HipuvJEmt9xGv|s&(N+ zbX1W6g{463Oxl2!K;$8Vs*V82urUFpv0F#JB3&+qlMlSErWW*;;wcG4iKkGns+_<{ z2??%Lr0qbmgA!j!Uapl4*fL;4!;w(W=nC4zJuMzUs>YiN5kwzSQtTfNx3)dIG1xcF z)YOQdgi6B2h+Z73Dx4K^mQ0G|ST|^0$oLS9^ZCR$&oaiDjd3EKL}Hw!gOAgJ)T+in z!-oyj%6?2EMVbJ}f^&P}C`M{4W2}_=*p*|btJ0GCoWy7;qf^4)DRK8AbF_B^8?(1- zZpW*KjkPT@sl*wYRhXGNSRR`XT(}iJBY7;W#+mO*rwLP1w5k7xxmcRwDesYxS0v;W zU^+oOHott{g6YA1!D$PQDO=jKeqCe#-U&;^@mXb65tpm7vMehzJy7f}sCI!?bRksH zlc49Isl7Ee&>A=@z$OVD4u^%CRwWTuzL-{=Nm$EEDLS|*73UdJ@l&ASJ;yh9t{rL; z?ri6ZbQh>L(X=kHh&_ciGAKX9pmxv2G(<`?L+Kq^2G7voBoCZ2Y@Wm~@`kQ$=~y#N zF$u9qwE*To_;Ca}3%W>>b!6Pt)f+FwuNXUd@~{#3HOjAotZ=RP9JpZ8NQ2IT@QhcZ zHufqBnHs8Gk0n!6%`JJ(ORBeCB4$jSJmOfq5c_XD3$%(sn%bx4EXxnRm;n(=OhERX z@zy#-zD=oZq&*Rq8Wg)q&8WkrQNX`ZXH%guieSN(f|;VAj`THymh7!c6e4md;CGTJ z4E9UQ8#YC}&Zc5_@8L*sp{$CKJjemko)A2YsN^Kon7rBWNKh#h&X4r8)_Kb+vu2+* zt2y8=ZLX~F<@$?UC6TtCFY=4>>jQsYl(I}tt(@AZR=vb7qxbL@!86N!)f5qu;Md}MUyKjQ3B){9CG zmjh_Kr$QkNB7b<)5tVgYl^ihQ*op8bW$|6S0hJM7I{sjTDa$n&Jcpi(b-cmw8;c*0 z(NkYuiSME-dMDO>_&c#z;A_TAUo-OJpt-(91mV--hu`WH-YjY#wP0VXTH^5o@mkgn zxj16^B6wh`LyleYeV|28E>jGVZ^@QNaxzgH!KW6N)h8@hK4@2zH!iUl;3p)?2#J#9 zG(+L{%}-y`>;Qg(aB#`foFzFee!mYtx%sabr>EPIl{(Go%uGlArvfdlC?^L?K~KY$ z3WOiR1%7zE2X?B*#GgQW+tfO)Ntj<~0!;f0`UgLM&ggs%-d1{kTY@i;!39a5h#|Bz z_Xr%f`FYE&>kk(%*eM z@Te&xJZjAso;Tq6GdOGVpLkqE9<|^cp%R$8TfjFKfNw-#$FIg{V9$@!&IF=)>`5`7 z50#@JCm8;}Gu7xQ2D078?W@y~I0qRJVES-sX=GUybPL%Ku=hqb1oHdM1UpQ18%@oI zona0>)rSqy{u8XQB;~q1Wz#3fygX#=nAn@a%F1A{s*1l-SXo(!i`buY3kq`KsU+t2 zw6*p0w6^!uSrUQuneT_>N57B{vc*L9IsEZ_tQ7GwO*>T< zrj{F<+On`epR?-9aBW#_Q82&MU+T`uaDrg^xX;?uda#Mb44Z?4!#G)ffrjOjp2gVL z`phBPy_-fi__~VQ$Bym{b^3js;f_(Ej@W<7OLM}${KDXu$iNUN3x~wSQAK5Ay4(AU z%R}L@L&M$W6FUooC4O(IDE0YDIyz|%dHg~#lAS1Lz*b`JFEcaJpxB}vN~(e_!L+hj zwlBf76d#51krFT??Q~jrTz+l#NLQ_QTv&|kX>PUWJFmJb9mbw!WJi|pBU}qV!d)i5 zj8xdtnGgBcq803xQw#hfCiq@yNO6ABc1eEwX2by-=pKR4j@6b5j!rlTZBjL_W0es5u*xGo&254gR# zxn6gmK2lm=94Peqi(Pu`%Rt}oDt}p-KlQh2cweBny)5+>U6-{N)41dJA-_yN_OWT! zK9bV-rScDh=Xnxdz$Co`JCHl6eVbOR@;^-0OFIFhg}thUhX=MZE~Pzw@cZ$c*la|p{$}47cmp^s_cU->;GEcW@HUynediF!nF*Rk(Y2iCgw#x z^=QXlVkDAjAr9tS>2ilh<+|OuvEA6!dParaF0s_tRn*($>k1XqR<$_PcZ|* zi`^%d^6_RrDv%3oyjr2dRYoaH#B3wA6knKbcfu@>wAVK4F3UBnaNuW^^b6PbuDXUFsFdH;8vkMh((444nzV0HVT&l<_`DcwuyH1+dp zv5H+N$MZ2-Vf=eyP4ET(^+sRCngsvei85!%oqP-%^)+SIv$LT87{gCTnU_=QN%~7( zk9tl~ZIusN&n;pNJ5Sz-w#KEF!MWXJe{MvXQ>Z^_e;KsQTG50)kB1$8npFleeLlNT zT(0`&ruk5H-Yro1x@9jhT)A8Ps$FUb>j)V4aj5Z;)45~?vbt&54Do3OJ zIRn~1Gugg5COU$aZ2$Wh+hr2r_SrGkdxkufGJb;NW_bh3+>u&FON9~5>lv!d1A~^K zdbX-E52u!qsrBsOFSFHRJ!HGPKu&Km`*M?s#~f$77O*GaZiZ99Y9*Vx%~a(v-cy?t zu=TLJ)Y5Hw4F4WjETBzqYkxvNZu+cc+H_gi7|d@dl~R&1Dj2Y*1+akr@3nK6Siz=Z z&!5gJ)p!btGeDVu4@;W>(Ni6Ou&A)ha643zK}lm{Nohla_;+!2RVY+d4W`CppT;Y= zSNZRnb%N)p<3NCn(&YkFwsTo@G!KqePKA(_Z{yE!B->;W z;X3$VkQ#i(-omF%xb{(7d*cy4GyXiX$PJhes6-$?4AeEdn5)l|m=9>a$aM)K0Ym{3 z0ruMOyyxLao7X`y#^O0UAHZU zZ8N*ozQhQA{9&&f|*(iLvXjfDKA*`6v_ zNlW4`3Rrg|Wex;6@a0ShJCGyT=V~{<_v4ekE1GVsDbObVZ0dS3>4-u1_hbETqV>z4 zOD!{CO+Ah>t5uojQ_Gn2z^La^aTYw1_M)B_Q0B;V=xo$;sjBCtLF>5{Yy5bkx%}lp z%Md-iK-Kfwpk=O9w1UR<`k-YlM?K$H^}I1?nVl#Dx`=w-9JGu9XX^7?sbvPhIa$x! zsbvPhFhR@tuTbX5FeLb_Rq%Od(0Z;%pHESJes|C^7op5~s?7U?mf0za*yXCs2VYah zg3pJ8mPx|-qpvArwe_1p%Ov0|R;QLp;wtFwbin7w;A45#KE#c^DU@udMziw<` zXgvu!AQlf=rVaJ{g~l$H9=R>db&HtAUcg+Jv&}@!`}dRMS`oZVpkJq)~bD#rgJkG5d>Rf1JNS)j3PXPF6IWM>N z*|g&_kF9%K@)SE&DhjV@Za3WfXAOx40zPO@TB0M`TKeFVYg~(2T}_4I0^~y;HN3U4 zudAu3H0UwEU~K2`FW5HdkwUCfajN=kaH<^A-A)LBHIlb7DfGaVpvNQSs(?+Fi>YPh zW#!hE5T=7<2MyFs=u=3OVz-y(hTzic%}UGCb+4_dvu{ZIC~rrzXfK#hTvinHrH~#yNq7@wXsjVH@}r&bq@6_rO=rB;@Ztv7J36JTZ==qup$K;Ilb+Z;vcSopc6y;Is4a>?FAa&*&_&dX|7)RGh*dmTo+wGsy$rTO}^S z+2qfpEpu1u6E%h-oZ(_uK_~qL>6bHi@mjEOqFaZZc?KN!iE7A~IBUcr3J-&N0+yD) z4`zr@PI@zwqnUDp3QvRe@$9V1z;w91!`K*_95LGIfo}UTtXn&ufpHl3UWA{_cF2Kt z+aF^jCz5V#CiHMWk{7@)2ex1KV*h?}pG^lb=lLWZiV3jd(2}QH(z=!ShMcn#DB(7} z6apQ9Pz*U1Y0aUNf)rkr(Cp6wn=79B^V2pgrB`C2=(lt1$!$Y24dH5+fHSn z&Ze@E9;LSOkfN{#`4QzHyS$8i)&r4(NH$^|cyq&^bVq>fdaX&ttvz8rKO~gH^}*sH zA%Cc}Z}{h~NJ;uxj{LM6W=?TTOslD>t+n^tr-NvDUA)ofuPmw=A)HV<<~hq}1~PVz z9h&dY_ji%}`Zcf=JUDMr*3 zRr-C6+~pOa!C*NVc)34;mrt2^2`??4JrFN(3vN;ds`jRv-%{}s+bt6>A(|LynRW!U z3?jRVU8hKFGzWQC)Ep$~YdV0Z`~4g$lR1Rmxo7?xJJ$2bPS za3CV)Di~s64}c+$_u|aZBjOG0o%bZ=pF&_17x2TQLv|+TP}hLP|`_>0?ykT-C$7Lv$eRgj;1HU;8K!zP&_*sGops6q2uT4)Ol2_zi0 z*)A&Zw|=NM7QmYLp)3%M;eyWkqCha5=Lg*)JK=oT3CR!6jBsJhc0&9iD<=6d!H$XY z$_>r~%e8SPpRCKJ$P!K#X2T&aFK-q1`wY{dy=B_?a z09&!!(rv?E)6(mw*s{xWC{>=)=E4+d9-7J$O1)n>s=B&7T;cY*gN0(c2n{I-*9HTb zS@yKFj3Nidc32yvjWA72Dc&G?)m9rypi8y`39i2|i25U?6;;(EtHf!JqKvdOdsb#3 zSQ{=G5`y?^!0_MTw^sr>W?Ti#I0=mj#Rh~&&=p9J?r^EQOt%~7@Fmi&W1c8vA{koA zAt3y?zpBw&F>YFm-O<)Rq%zm*2Pep>YYo^7ixvWgKEz6h{aQ5gu-FZ}ygG@O`H0JL z2ud2+>P?jNspqMvlz2?oIQZ+mgS}}YVNP~dhC@FL32~my)PqGzY@ZSO@pprPKtZD( zyI2>Ego5EvAc|W@x8IPj4Qsy zx))m}vaL7h3VdQ{e6QGmd!tEqj#C&z(yJO5g6}8A``wa15POR*H1mdztzS0Xuvr&& zTo{L`p=%o*ck!R$?B{OQ!+o(D#*{Y*va^Z`W;>Lp`~rNCpOdm7l$2;6zIeqAyRBa^M-_v3$87MlPuS8=7AU^9<%L7d=76}Gyq+-yVMi5 z7?Er@Tp7qL3!iKlKy4mpX?|tZF}|We=G-CT_>r2urlBfSl-2m4!letL+c;7F9ystG z;ynG-vQJ@NerhaN;J^*g$6O%$0rO)Ah550N5pDx5cCxYk`-zYOs3eg>G62*m?h70f zLHdm|)_^;B`M?Jl5ZQ6T$DQ0szJiie9W6KtZnJFw^X9o0-yUc3?0Km2Ht{<4jHAsq zNq)r?Q^Vx&ClHIMSU{Van=n&b+)OrS#IS=!HF^vmRL$KCUa3pDa zOq6j9u+I%x-&E!js>d-ewVnZO-3lAxT4iVaA<87^4ay|xO@jWh(W&($X#n6%I$WiT zWMfjx3}`E%8)ah$Et9ZqAks|A*i-9?{R)0s5uiK##t{dE{In*c%-y3U_1t`z6JG=Z8b3msr`tPE#gdh3E+Fipk)l32HC79rItBD zTU2K9pk>}epVyH+n)eP`#-RCxH&X^JV~m~3OdYgLLLZ;BSs$iXzA*MFI|IISW7y32 zw%Df@jza$UG_gLfMxW1Dcsz5^wysihf%WNFJ7}5RfU~lVuvvqaA)2{Ko`(Jm!l!^6 zgnx?PC2ipbO-Zq7C+sI|?x1ZY%M3!F(bi2E*Jh#xh=!4ZKgoFqN#^mF#a#GMdsu)x zyS;gt8KB3Kc_@4vLQ;~Yl33NT6Cq%WB_O6t#H8{HPaY|PeF#_4N)d~ZFVkmtsl#FA zt&;ZhlO~QSxct|13MSNVKXX|RcRv>B9NN;@xx9T6e=F9{J5H-;kH$_r@%-99?Tq~; zJeCMI%Za}vW+I+SJ`1rKy?F(GMDaw-?0h#ecte7p0A2wnY|^>8sY?qcQdGVrAopUP zKpa`Gc@tmhiClsnFOiex-d|ouu_bVN$?9(|ohhasha+G#IMK#iRiKTwnWMV$FMr`U z-^d~B@*VkeJK8F_c9Sd6*z9QpSRLkQS!X-;m3bKPnr7!`0(TxEH51EBEHyl(tC8A2CnZt?8Fw!M4#L#<_{xkZac)Q*{3_t*o4-Ngk%(C^atzeKw{4njo) zQ5$}2fY({X)ATZ~IiR}hr7yNJi~?#p<8d|KBlLiQ05L=$u&+j_5PdO%uIbArB4CIE z$8pnzrY%7v*52mB3)^g%I(h@ZST>Q_?eomH(z$wRZg04%(t}NLb7N(DRXe2hP%z+* zcp`ArbzrB5JwF8;;RFn2g|MIk=>g({+FAOL!BvLa0iSkqsW6Ncw0Y*8#p&*b)?it8 zUiX;ci{6QhsV$4n?;0|~tpzu1UT4<|`)x%-${qHyP(edDSIkd>?cjBh*<<_8I=;8I zZ1lnR$CTBb)HtEKtUg>(u}j9g zdAEE1k_qnEQbGVX? z&xmX{s{h>!+XVzK2wiFsj!|O z`!x5#5j(uR_Sjtm0ekB)MZDRyGB&H|7<7OECuahNUf|^L-fpHLaAfFt%VJr1=uMOo0(?5~X|AENb(^@lyv|`i-wtuq>c!Isu8f&u-JOLz5_>G{>+W3C^_zq5FK_C> zTxx(vpB%<25fNr@Z%#2>o$YYxwuuQo;C;Ul0fvHwLa#_hQ#inYRyz1%+81G($&gO# zWF8`g8`P%^g<;J5hO)i2Fu0}^Qt9@+2Sb@+g$J4giwKb-P}xM`jURD&HK!--?1>vn zFs-?bAxEKimOCp@IAYw8NOSAt8IhsBB4=7vS)?R4CsI%t2;^oLj=ZeDXV+r!Y;1PM z&=Gl8eb_(h^!B!4&e>BY%+HX{1rz3O%;l$EahdFCZ)w=lTvOkcxklj}t(R%yG{Crr zyWD1>+93F#cSh|%X{wxzS;)59)!sU!xuGr?@Ocq9(ZMQtrLls+ z=+vP&Oa|Z%pVcGIhE2jeV@!sjlvO88Z2a+&k^F#`m7leF+nP?@F7t;(ba&>|tTcb& z@X6(&P<=tqdD)15?kgFR;q(`@7Uk>C(uPw;cbzv{l*SGOyYsi)(_TLDKLa>e4J~#l*I5Y! z0v%d_WXA(z_-Y0pZEJ0{@r5hT9x-vpx*?OR8q0?lj+z#pu^>FI zDLi4gcZkcCpPd)-c%8F?a z+$9)y8;!dRo^H@wBP=_{od%H%AM@;Ijyy05S5UWFgIF?G=ga?XweOS@}2lrG}hFn?R%+iMbXveCi zA<=NOzJ5aOnX9WechomcDQ>C@cGL`=UfXxF(>AQaL$Ok4Px^hZAY30RD}niQa>t?t zo`MZ|?#lAQvO;fR``C_A^Kn>OccJD(7Rqa=4ni>41JqD<|C~$JySL`EG5U!i!@6 z<-21a@bK8tR?yic{4b|0iRC>sYl<0rwimU*NeO+w40e%9*1?AIOALyV>vkgAerGvI zdI7LG$PuCuWJTSUxNUbNZztqLA?8nX^0YLAoI^NtfU&V9kPc0Jx52;Bwn*8?OR+Ax zfd$qZ0LwD{IqgHr} zI*ZYgCzAnwY0g`bfy|V9TfvVijPp!hpC^3P@ZL~&RdLv<$u)o6c1E7Znd`_Z%k3Tc zT$Y{{kuI+@%?s%+-M4Sc7Uy|atva>6*k4in@Ea3i7u;Op*TgE#Hf2_CLt$`n(fDZb zh;cg2nfzXMoiA2;l|_{6e}DaNaWf{gf$3Ud)Ni(_Pt3n+j@)V-k4J!2u})EHMJEN z)>W3YhgMCuCkWlvQH@o_Ek(LEqNvn4a}LiPU3$_cX?N!>EAi(AOY&TiuEv?O+AVU| zzM-)&>~)p3R=a{}F`T6VZNr&SQ6WmP2WahWcG$645a<%54NftT+90h#oLz-gSdP7o zu9xYPfE`d;yO7`6V~0VdrL|p|Qz9T#;CEYzIOw!J@ZU#1Ws7I#I^FB#V24Z zfVM7z4)!^*68c``PzLfq`X_2izFU@$5D*1ULTCdgfAgdrE?lS|XZc;eyoWN*C+B1e$vG6G1}7+Q|1k{=K`gk-s{9kV;IGu3OBg` zwM~k0z>5Z@#N7xb#>qurq1`~+DYun@0)Y)Eu()abrnochu%#NCn=DDukcHhcN-`9q4@Ggn7Hx2Lt*9N`9W#=)0D z<-Hjh={VIPtbS$yMlOT0p)b(6FjlLiLXsg5d7%1!$UPJWd4mEcE7Rz=2BSTsl|(Rf znlGe3%kj0sLnf!9bomqgf4AAk+w{`v|2lhOcaDw{EP4E7=={z>Y}XQoj90-N8z9LM z>4^LlVQ|1VGCRmcFx!($0$wR$B`&|;WwXI83g-yG{YijqPr0&(Nx%ffwA+`4yVC{z zXqvm-jpO^cX1>Sc%bGGJqrl}V$XNf*%uJWl>B^jG^zZlR z-=k=@p|_U$$0Z`qpf`Z8U>$%S=w6+S|){#34lK|9vYGVsnp~=bw;r~+-`66MOq|( z3Ke`3y1sgjstU2=sT8IdbmdtbRxa-?rLNI3x5OSGBdPgt12IWmDkf=*A?louc+hw9 zk7Hkmg~v}m2!jvFFm62GWdo?ImfXgK?QkH<^dib74eaZV%_xy?5d5KfI#vkB+J1GN#Y6eJY;4 zd*59Tiplpam>sKKw&Gdzp%VMD639_L=z_xRB}0yKLdnJ9u7w>ibZf>Jc#2zkfeMKI zm>N6L&-oMKu%?+}j8h%urodh1evD+D>cL8bq|5^^Gf#i3H;A#4SYy_oQuX15u~v(V zi|8D6NeGcs!v%r-LO&D?t{m(&%yZPBI*Gjo9V)~ER&s}@)Q+#R88%Vx-^UzNUAJ(2 zYkvWbvRtyR$mXA#u{7s>TVvO1$49nyXaAX#r=2mrva+}{blXA4n93^t@RrsQv7evP zI~4d(3;2z|zM_du=p7ftzM>E*gRIyi+5{-5DA0)ncS!CkK%MBkJB=7e-K(o2NF@Z7 zkPC4FSQBp||LPR7LmLDv{Z{I~aBJ(gB@c^^1LUw+&OfVUrWw%yOUWDe!pxLE?^Ft+g7gH9JENYpgTcz#*=Yg!)D)Zov7~VXl&}3wsk+i>SFA z(frq5&f8AoK9q*tjNdOx4|^Xn{~_-iUKsrTt6zTk>W@x%74Pc6kuctEU}yE_R#ldl zX*N5;QZN^yJ(Dm99!my;GALjHOlH%LV-AoOWLrB9dkM4vma!NT1d)J(-pWCXDV+uQ zH0@Ji0h!wiRVW&5XvuTCo6F1c-09F7VZDV*NuDw73JJDcsvMk1rfY#@Qj%xSYbI_j zoLVd59dG(5&mNfElhYcAWYu_kM}kMV-1=B;c#WoA%J*F&q}#3WH9d0{xK_@q@31@1 z)zT&o4Fvl8hZh}lOwmSv0r~=19LN3qFX&+jn}5H>dJHe-An`}WH!8Ns=%n;5A5}nA(WJN>o!+)AW-Gn%g3IP>(9yY=boX)^D4U+`5DduUVyhKDGVnB6WB%K z8knesT;(`MLt$C@M#@cd@7O5WlsNl6>NrQ$L3wLUF#-DpI;w;BkCa0QOG$~Pzeh#) zzN#wX>_P73lru?Z#l1ni%@J7%w1z~+V>xjpo#_Tw^0+`ZTn~PD0p7TGZ0ssL!%sH* zIeN=A@EGJgp`Ozlg(?1o%dPhwZ>#?PO6g*1S$mlMI)inKXUywNaSdYG8}($dA^P=*qHqFR&$h9R zF!HQ~RKJ;pSTlBbO{^7HWAO9@++V|%jOm#u@Shl2A|UsZ*5ZFma|PLxsh$b z-(@H{32$%4ZwPr47ekhyQWNog9e$(uh2I2Pt4Di{&@K%{dz0~R46dvJsl+ zC5}{gC{b#b>dz+hlUk**C0h%jZ=2Z)^z1~{%T*|~4p$*)Mz#O~^_Zo4oZGL$ojNt% z4t2E=e_PRGx;qAc30}sxt@zf23_|o|B>t{J-J4arb!aD~#=TC#vJOu|@TA#^Dl_G%I?s9JLar(>PDWHI0d|Vl!%@veN;-3HY3>pi6j{ta-wK8fx+E zP#h)9qhDCbzeUezEHo-w$x9SQ8~rozo>m`W*a-BEW_LQSX_e4-`bPbjjjI`mjX0Uj z#3y|pffYUte<$JlI5tYXI~jLEpaYX}ccgk_oVuoZCu5vM9VX%UbqM(Z#;Gz?_j2^g zfDXZFIok@zY{cg#{3BeYyQ?v`>#=55<33Rjg5z@h`z~+|VfKt#tvT7G=3*&oUxDAH zsFhadI^fcY_#}*?e$r~#h<*?(Evi91q>?n-)H`~*8Fx10lb}n_2~UaYZNzt?BD99K z;ogaAWGA8wjl)=z)T3|0w?o&%CiIwQc0F3%1lUmD*5H%IxDxnJkSF7=`ibuZO(O1i z$WPXDxCx0G96$p@vw`zEpjC0gnw^12ELq@UInWNc;VqI2k8dw_Hu>;(2*8^+h&@gb zE5;Z~fH`4S#>&CBBA}F2tQr=PTF|h1^n*A@Gic@zP{=lT9CX09t_$?Mn+=2YYB=i! zc0_?aBY{n$K|RL;N5%tFCV)~*!YHO7e8M!$`wZaVEMVXqVA3({Sl9>WgOe^~i(s?T z*sX{}b~by6T?1{#IqVX4A+k>VklhBo^#$C{vwySyf#Ry1SMUh0^uAIiJ= zFy6z5^IkrJN0Bw_x4e&!3y;0yU8zL+oJOZhUsoUh<3`6|AeAIFd9Yxr8ej-SBS z^9}45knuM0&HO~jYJY%V`yKowzLjs|C-YPIsr)p4I(wUa#NJ`=vR||JApQJ`eZbG) z+xeONEdD+IeSS9NyL0(@{Cs`^zmQ+VFXor+ z3;!$sjDODm#=qcy=l|gUmWQr`2Epmhl7ATL%6?wudd?H`?ML-mYAhHt`iDD6gZlY9#MVTmv)+Hh;MU|)) zHS8Yt6HzPb*iYI0?3bdR-OKJ{PeK}f9Qj**gczU=qLDp}vq4SBZQRVh6fI(iXccXu zU39REL?^T^Lq)e3CVJSlVmSMZ-Ht42yOH_jDt0ydndrriJ}UaeNHGe2GGoM8F;0vZ z{fJgRQA`q(#S}3Ww(RL*20TD!iP?y0Jy#rqOxp7VBC3joVv$&k{nt{lOe_~G#7ePB ztQN=i!}KNfe2ec~>$U)(M35kC?4iu=S*#r^Qr{F(T<_=R{- zJR}|#kBCRbW8!h~g!rX+QamM|7SD(S;#u*WcwW39UKB5hm&GgMRq>j5UA%#VT5pNB z#jnIW;$87;@t$~Jd>}p)ABo?H--?gL@5JxLAH*l(kK$ADC-GL&TjWV{tK23}mZ!*5nTjg!?c6o=~ zE%(SD%DwVO^2hQ{xli6D_shHGJ@O~=UU{GVsk~o4Ab%!*E`K2(ln=>=2^0)G1`8)Z0`3L!l{G{zd*(ekMPcf0JLxzsrBff69N!FXg}G z|H%Ky|H`lAK^c>Ajp3Yu(4?lpx4?$P%?{{voLah;p=D}WTDF#>xiq)t(Q>ss&8zve ze9f-~v;r-t6>3FVu@=%wv{Egsm1*T#g%;5&wJNPztI=w;I;~!7&>FQStyyc)hG?x? zo7S#%Xq{S@HdO1@hG{+8aIIGxp+&VmZKO6z8?BAe#v)n#c&%TXpiR^!X_K`n+Ei_t zHeH*c&D3UTv$Z+eT zr=6g!*EVPywN2V)?L=*hc9OPL+oqkYouZwpou-|xouO^l&eYD*zNdX(J6k(PJ6AhT zJ72p%yHLAGyI8wKyHvYOyIi|MyHdMKyIQ+O+o4^nU8h~I-JtE%c4;?iKhSQ{Zq{zm zZq;tnZrAS6c58dIA8LEGA89|=z{X_ew_Al*A?cdt}X#dgvt9_*%)M8p3S_+QTxDu&HblrwS>JB|kck1bS zhMuWs>DhXY?$X`5N6*#sbg%By^L4)-&-2iPL2uNX^k%(9AELMFZF;-jp?B(C`cSl91ErVCpCy1S~n z8dh7VwQC((r`Dx)Yx`(DTCdip^=pf?#o7{WskThpSKCk9UpqiMP&-IFSUW^JR69&N zTsuNLQaegJT02HNRy$7nhjzSnf_9>Il6JCoigv1Yns&POPwfotOzkY~Z0#KFTb=vjX4cd*`zqOmRo3&fC|7f>r zw`sR)cW8HNcWHNP_h|QO_i6WQ4`>f+4`~l;k7&!aN43Ya$F(Q4C$*=vr?qFaXSL_F z=d~BK7qyqPm$g^4SGCu)*R?maH?_C4x3zb)ceVGl_q7kS54Df9kF`&6O z`eyp(`WE_@`d0eZ`dED%eOvu+`Z#?%JxwkFY1vV>oxiWeHVSAzNa+BEeYQSFZ_szs zch~pO_tf{&_tqQrxq6e{thea%^!a+L-li|m7wYYLhu*1o>D~H1dXL_#_v!umB7L#G zL|>{e)A!Z))A!d8&=1rP(ht@T(GS%R(+}5=(2vxQ(vQ}U(T~-S)Bm9#ub-fwsGp>t zte>Kvs-LExuK!a%LqAhLOFvsbM?Y6TPd{J3K)+DGNWWOWM88zOOut;eLjRY3rGAxu zwSJ9$t$v+;y?%p!qyBIGCjDmp7X3f^t@>^H?fM=1o%&t+-TFQHz50Fn{rUs?gZe}I z!}=roa{W>LG5vA<3H?d^Dg9~v8U0!PIsJM41^q?+CH-an75!EHHT`w{4gF31E&Xl% z9sOPXJ^g+C1N}q&BmHCj6a7>DGyQY@3;j#|EB$Nz8~t1TJN@aPXhIi47{U~mu!SRB;fYaVv{+57F4hohiob}p=r>H) z5$lTe#QI{4_^a4JY$!Go8;ecEreZU(x!6K%DYg<@i?L!Gv90)<7$>$9Y2k~2ep;nM zgd!^{MU|))+l#-89mI}eC$X~_FLEL;3Zf_?5sMlzLF^(X(p?ji#AHz`riiIxny3@g z#SAf1%o6otwwNOt#BO4Dv4_}G>?QUVjbg5763wDT%oFoPt7sDo#6r<7Iz*@F65V1S z(Ia|ApXe8h#A2~TEEUVdzG6SIzc@e~C=L<_i$lbr;xKWzI6@pLjuJb>*v#16*uvP-*vi=27;9`}Y-{|@7-wu}qz&H)jEqrX zghtk=G^&hhV|(N8#tz1g#!kl0#&{!VW$gP9HYV5&Dh=8!`Rc%quFRN<{9&iR-?^WU@SD+ zjSi#J=rX#EeT*KX*XT3)jYY;{V~MfUSZ3^N>}Tw69AF%19Aq499AX@59A+GD9AO-3 z9AzAB9Ag}79B2H)INmtHIMF!CIN3PGIMq1KINkWCafWfGah7qmagK4Wah`F$ae;B6 zaglMcafxxMahY+safR_O<4WTy<7(p?<67f7<9g!;<3{7(#!be}#x2Hwj9ZP{jN6Sn zj603HjJu6{jC+myjQfoTj0cT}jE9X!jOE6o#$(3g#uLVq##6@A#xusV#&gE=#tX)a z#!JS_#w*6F#%spw#v8_)##_eQ#yiHl#(T#5#s|iS#z)4-#wW(7#%IRo#uvty##hGI z#y7^d#&^c|#tP#H<45Bs<7eZ)#xKVIj9-o4jFrakv`L0G^_p}Cjj7R3?g`T{P17=M z(=lCI_a0@AHdixOH`g%NH2-3*Wv*?mW3FqiXRdFKG5>0AU~Xt`WNvJ3Vs2_~W^Qh7 zVQy(|Wo~VbHMcRhHUDOgGq*F-rf&vj#;h=@2XjYrCv#_WyqPoe zX2C3)kr|sc<^*#WbE3JcImw)C)|ykyspd4Z&YW(}FlU;x%zAUSImc`;cQbc4_b~S~ z_cHf38_l_9li6&xnDfl}W~%jG(>%*O+dRiS*F4WW-@L%Q(7edJ*u2EN)V$2R+`Pj4mwBamm3g&!jd`tk zoq4@^gL$L*Z}TSeX7d*FKjy9GZRYLf9p;_pUFO~9J?6dUedhh<1LlL~L*~QgBj$4R zQS&kLaq|iDN%JZ5Y4aKLS@SvbdGiJHMe`-|W%CvDRr59Tb@L7LP4g}DZSx)TUGqKj zee(nJL-Ql^WAhX9Q}Z+PbMp)HOYwj->OR=K5Fx+N^bq6_sc+j1<|^61wXM_a2|t6Q`+(E5wDmbJFEj$ZfwiGUzwBadVr^<|W^Hb5VQpz`Wo>PZwYIUgwf<&}v$nI+mTv`C#;UMF zD{EC+RaUjNz4do%2Wv-bCu?VGyp^-^R>3M-kri7t)&y%8YofKQHOZQ6)ml@msn#^B z&YEt`ux47bta@vlXC(>lvK+d9WO*E-KS-@3rM(7MRF z*t*2J)Vj>N+`7X0mvyCem36gsjdiVcoprr+gLR|zZ|f%OX6qK~Kh~|*ZPxA99oC)J zUDn;!J=VR}eb)Wf1J;AqL)OFABi3^3QR^}5aq9`|N$V-=Y3mv5S?f9LdFuu1Me8N& zW$P8|RqHkDb?Xi5P3tY|ZR;KDUF$vTed`13L+c~!W9t*^Q|mM9bL$K1OY1A^YwH{9 zTkAXPduxUDgY~2Jll8OpU+Wj^f7Y+oZ`Mldce>f2%pPHnw99SH)@?yQ!)n@=ZQG9R z+MYei9&N8?uWqkluWA3qUdvwFUdLY7Ue8|N9%KL2-oW0_-pJnA-o)P2-ptv-|Bu_F{X9z0_W2?`!X8 z?{6PqA7~$BA8a3DA8H?FA8sFEA88+DA8j9FA8Q|H|HD4sKEXcGKFL1WKE*!OKFvPe z{-=G0eWrbueYSm$eXf0;eZGBxeW87keX)IseW`t!eYt&w{V)4U`zrft`x^UN`#Sr2 z`v&_)```9W_RaP!_J8bK?c40z?K|u{?Yr!|?R)Hd?fdNe?FZ}!?T74#?MLk8_M`S= z_T%;w_LKHg_S5z=_OteL_Ve}&_KWsQ_RIDw_N(@5_UrZ=_M7%w_S^P5_Ph3b_WSk+ z_J{UI_Q&=o_NVq|_UHB&_Lufo_Sg0|_P6$T_V@M*`v?0+`zQNn`@i-t_W$f(?ceN` z_V09)d6_f98R?Wenxi|yF&xve9NTdm*YW83i_y+%&g#w@&YJX#NozT4JL@>>I_o*> zJ7b)`IvY3}IvddsfN$b#>TKq0?rh;~>1^d}?TmG{akh2-=8SW;bJC9Q1Wv}Oa6%{R zR612owX?nRcV`D@M`tHzXJ@>VbMj8XDLRo8J2lP(XBTIpv#T@7ne5a$Q=F;JG^fs) z?#yszIfLPc6au0_H_1g_I4VbxlWVQ?6f%ZocT_x)8;I27CP-thtuhF zIo-}aPLI>;^f~>`B4@F)#98VrbM|%ibM|)*a1L}1at?M5aSnA3a}IZoaE^42a*lS6 zagKG4bN=BR@0{SA=$z!7?4079>YV1B?)=j^!#UGA%Q@RQ$2r$I&pF?@z`4-5$hp|L z#JSYD%(>jT!ugkTrE`^YwR4Sgt#h4oy>o+eqw{a)Cg*177Uw_Ct2cb+@nZFSq+1@1z(-R*EY-7dG=-N)^5d)+>_-(BP`c9*zI-DU2+?tbq6 z?g8$B?m_Os?ji1>?qTlX?h)>h?osa1?lJDM?s4uv+~eI7+!NiC+>_l?+*94t+|%8E zx@Wj&x@Wm(yXUy)y63s)yBD|@x)-?@yO+3^x|g|^yH~jXa<6o+a<6u;aj$i+bFX)A zaBp<~?cU_x?B3%3$Gz3P&Ar{d!@bkJ%e~vZ$Gz9R&%NJ$zOST^ z?mpo@=|1H??LOl^>ptf`@4n!^=)UB>?7rf@>b~Z_?!Mu^>AvN@?Y`r_>%Ql{?|$HZ z=zipW?0({Y>VD>a?tbBZ>3-#Y?SA8a>wf2c@2+rvaDQ}va({OJ>;B^Y&;8Z?&0XpK z?xpBjlM&uXublS$>z?oox}nqZY|rsr&+|rkqrKI<)x9;mHNC%hYk6za!Krn<^}O}H zG2UOj4ZID#jl7M$O}tIL&AiRMExawgt-P(hvEDY`w%*^oao%=b+Vj1@%Xk%D=w-c1 zuga_Tw)g(-?cnX`?d0w3jrVe1-Ya-TFY;oq#+%^n;!X5+^(J|fy;^UIH`SZw)p^ss z8Qx59mRIl1_U3pE-frIR-X7kb-d^6`UZXeHYx0`C7H^(6-)r^SyanDuuifkLI=wEh z+uO(M@p`>JuisnbE%ugpOTA^@zTSS`{@wxJf!;yh!QLU>q26KM;ocG6k={|>(cUrM zvEFgsKfL3;6TB0>lf09?Q@m5X)4bEYe|l$lXL@IOXM5*(=X&RP=X)1;7kU?Y7kig@ zmwK0ZmwQ)u|MIT%uJW$-uJNw*uJf+J?TB=J?%Z?J?lN^J@38Xz39E zy;@#7bJC;{g*wsS*_KY{(tt&?Yf(#2Z&O!K3&-RGzzkprSS@lLjZHmWouZMUo}1g# zvZzIGR6yjq=67|rEEJ6l?Lt#qPg8%#y!MtQcGKWb3k;Co`+FvAe6cucxcKwPj?~Ie%nJ=X@~*)$2m_rf~JT z6mX}s_IJ*2?CI}lZ|v`LyM|(V9aHWZLODd$!b-i4Dfcj#&S`rUu%{2Q#O@t@R&@>h z9AI9b!5{Z2P(DL8P@im|ncP7A+(0u?gMQRtCfA@}0c~baTjzYOUxx0?q2+h`hhkzT z8nU0ET|ZbZd-0IxIYXY84nFG*T*GAwSPj%P=*r6m9<}za&iTDoPL_p}H4Z$AT%@8z zV~c|Jl-~Bn-d29@8hi$CssO9CzOK%$UbnferKhF0tyf`IuD!dpQ9YWCon3t`?JaGM zPSoAoMg>sOM%0J*yHGh*lryEHO$q=;W)4YgP3dTv&xuxRqvXSAX^K{PzNN2Gt7+`$ zXjBEuR96VlD{r7zBZ;0iqm`b@B|&Z1#_sM$>hK+Nn;S<=?jKRxKVo(pbwyQz5mVbn z*0pwN)7$2EG>)9n*e_6;kyBgSMi%IAYHu5p$yGZtzobN?kTAim#sNk+EkkN#P(heI zvOel*&3=BLe5xWf^?mtVO0Lb9mD8GA+WQ&>@+x0OFGk9jeX43QGi{-&TDz(m7cyTM z(b+#@Nt@{6`i<;q?cz6uKjfxO-L(mODHw4`fbVJY$K`#WoYSg#d*X!r^G?MYMm_fag zqWWS5%=%;~nTwMreGVsFs(|bbLD~=9{?2AtPt9}N-F^B=l1iJXr?;h9?`Z3k%DT6u zsjIWuY+2GoBc4+6x0SKkIIpcOT~%G->)kEABqGYA{vI{z13#_m9r!#B2r9k@evma= zdi%)C=xb><$=YgZo8Q{k>h!gejlj>n_Pn-5$+Oc-W$DBVMK{36rIQ0FhvIaQu4Ibo zO2uKq6^Da#T5*h^YAJ%Mr3k8)B8cx%;xy+^@yI8gDx9gpei=$Wq$~J)g6OFsdP)d93p|S+n?;Y!qQ_>@W3%Y7S&Wn{dTbUwHj5sc zMUTy*$7a!Evk5))+$?%-CFoazekJJBRgJ3rm7ve4)k-Np9B3I z=;uH`2l_eC&x4P7(8qMEA`kj`(9eT@9`y5|p9lRs=;uK{5BeB46?xFlgMJ?L3!q;B z{Q~M=0Q~~!7eK!N`UTK0fPMk=3!q;B{Q~M=0Q~~!7eK!V`WV+07}pg=&@Y015%hVy zrz?t}Uj+Rk=odl12>M0PFM@s%^oyV$fqsPgN2q@U`Vr_ypdX?B5$H#tAAx=Z`Vr_y zpdW#L1o{!^L%vnSpdW*N4EizX$DkjBehm6C=*OTRgMJM9EKk!Fkf#+f=*LVygq#Z@ zCqtI!=@6D)2um+yc~0?6KZKD*g~u{2{3LLs0RDpei3h#UFx-KLl0z2rB*%ROKV6%12PO4?$Hv zf~tIe#N(47==1m_9Q1j75)S%2J_!eX9-oARK95hrL7&Gb;h@jslW@@I@##lAJ_(}! zJU$6W{ds&6j{5WXBpmhU@ku!9&*PJD)St&E;ix~4Pe0=ENf7nt@ku!9&*PJD)IZV3 zJU%HN^m%*|4*EPk2?u>1pM--xk5511@ktQ$d3+KM`aC`f2Ynu&goFP)J_!f^d3+KM z{`2@G9Q^0;NjUh=;oVA(}(`_p+Eg9 zRj;&P#pB+uQuQL7$DLorKWYTOaz?hracpZ++-n`bj1=-hJp>ANtmZzV)GBedt#o`qhVi^`T#V=vN>5 z)rWrdpO-H>)hJvZ=u;p1)Q3Lxp-+A2Qy==&hd%Y8Pkrc9ANtgX zKJ}qbedtpk`qYO$^`TFF=u;p1)Q3Lxp-+A2Qy==&hd%Y8Pkrc9ANtgXKJ}qbedtpk z`qYO$^`TFF=u;p1)Q3Lxp-+A2Qy==&hd%Y8Pkrc9ANtgX9`&I|$_x;TI?4xozz=;8pnIDjq=po;_O;sClhfG!T8iv#H306I8; z4i2D$1L)uYIyit14xobr=->c4H-N4Uplbu@+5oyXfUXUoYXj)o0J=7St_`4T1L)cS zx;B8W4WMfS=*|GTGl1?4pgRNT&H!`J0J<}P?hK$i1L)2Gx-)?83@`@`pgRN1K?CT{ z0CUhF@v%XN2GF4abY}qF89;Xi(47HvXOQSl%sm6lJp;@=1I#@G%sqoRc3Zo;7Be&nYm+aBgEQ92+bd|<|ji?SrPfiaDzYU*IM1AlJhLa9XHG?) z*%Qt)ry^T2g!9a)$d(M@Jaa0tB||vRoQiD8kRb~CY{?J~`fSM%4*G1#5DxmhmP0t` zbA1U1eXcLzpwE^L;h@i!4jI6p&z271pwE^L;h@i!4&k8BmJZ>d&z271pwE^L;h@i! z4&k8BmJS)-pwE^L;h@i!4&k8BYdM62K3hD5gFah4go8d?JcNTjJm5umz>940kiif7 zZ1E5d`fTwK4*G2I5Dxlm@emIBZ1E5d`fTwK4*G2I5RUq@#X}Pf&}WN>aL{Lqhj7qm zi-&N~XN!k$(C0NB!a<+cbO;B1wuA@=eYS*Xf&=<&2@wwZYzYw#`fLdi4*Kk&Cmi(I z4k8@%*$yHc^w|y~9Q4@^qKOsgvmHb@=(D^d9Q4^kPdMna4MaHTvpggm^jRJf4*Dz) z2?u?)foMVp`fLLc4*F~Z5f1un0}&4TYy%Mv`fLLc4*F~Z5f1un0}&4TYy;6m67<;y zA{_MD1|l5v+0#Hc=(F`hIOwzWLpbQO^+P!5v-Lwb=)-eZgy*mb&tVas!y-I~MR*R2 z@EjK5IV{3+ScK=W2+v^=p2H$Mhedb}i|`y4;W;eAb6AAuun5m#5uU>$JcmVi4vX*{ z7U4N8!gE-J=dcLRVG*9gB0Pshcn*v392OJLA>@4oc^^UEN09dsj?5X zg1n9(uOrCo2=Y3DypCay#;`|Y$fp?cDTaKCA)jK%rx@}nhJ1=4pJK?T81gBGe2O8T zV#uc$@+pRViXoq3$fp?cDTaKCA)jK%qZslihCGTPk7CH981g8F{D~odV#uEu@+XG; zi6MVt$e$SUCx-lqA%9}XpBVBdhWv>ke`3g=81g5E{D~odV#uEu@+XG;i6MVt$e$SU zCx-lqA%9}XpBVBdhWv>ke`461G3?D4@+gKpiXo3;$fFqYD26k ze`3g=81g2Dyon)iV#u2q@+OA7i6L)d$eS2)CWd^8Ay;C^lNj(__F)YBFowK|A#Y;Hn;7yYhP;U(Z(_)s z81g2Dyon)iV#u2q@+OA7i6L)d$eS4QCWgF;A#Y;Hn;7yYhP;Urc?0_}hJ6^rK8#@> z#;^}#*oQIf!x;8q4Er#KeHg<&jA0+fun%L{hcWEK81`Wd`!I%m7{fk{VIRh@4`bMe zG3>(__F)YBFosWe0e+)uI7sb!5qUQ3X_rxkp&v8ShNaeK?WKB1oayDbHKPP=8F{6EiP>*` zKtB`1^jRNMJhxNG`j~LFQc95d8?yaRIQOfN?SI0V&msC>$oiTV$WSlV*MxIBhODm% z2YuGpgo8foYr;XF^)=z#uS4|fQ1(j7tJl23Ptlm z1hibGw!>7tnh{))GOO%UhhHhuT*3^|Uq4@9$>V1Dtw+if^B%%R?jWEu0g} z*&r;$W?Sc?x&1_@Pd;%PtE;=ElOt8~EQVnL>?(j=1+c3Cb``*`0@zgmy9!`e0qiP( zT?MeK0Cp811_}@Z1vX=X0-G@exmy(2j3HdPeF$>16xfWRH4rtta+L#6H51{gn-EmZ zL{N1Tf~tEGRNaK25k=bXmTQ1X+9AbH5x zJ#?yqhMe3#EZLjJ=pm@8Lr_(RpsEHzReXYq96?n)f~t7wkc&r96_23m#RL_*2&!I8 z5cy+0KIB;gNvGT>Iz7_UwS;5?m&=jIcM`-)q(NV|Eb{<5w=byzZCT=uUq~jnWGKy0 ztC5Yeiu7J_m>>)G0t9-2C#2MZ38z)jiPNgc1Qja?q6Jw25snsPg_EOS&6IQq;ya$0 z5RUJ7B0@O6V}+Ef-|>hc9N+N>AspW^e{+?r012YUu|ZBa%Eg8`^+?o<4ROLzFE*%im8?Js zA{{HrTqP?=f+!a&NWxJr7D0rAJ{zipV+66nBpmI^3Nu&93X>q{vBD%Abn@9za!B}@ z{5X=D?UQp87_SHiaGVKMcx`UhC>+!rz;QHk0LKZt1V_JY87+@W_-Q|#p@VYmExo-1 z5g{Thb?T};*~LJeR8>ArVpEOVfVE26$7&x5WDn;tkpm}7oO3YBB1|eGo>Wk_IjgsS zZf{dhTX)|A`3Xf;dy);bQv^9NT3sB5=7XUqEgB9%X$@iMTXJbed-h5aj_=tEKsdflgd$t;AFyebwbA+S* ztkwue{aK9>j{37Yqd|(!&+3eDbbeN6gkzAhIwKr|j%VY9Ll`7NlV{&FxRtyoi2Cp> zn{WsPwvuVEBOTilG)NO1-|_5};=u>DVrh_~o-FKhc?hpO_>pIGfZ{p7JiHwduRfFT zk1@ClYA_BC?(6`LgWkzt9fi}^131Rl0FGm`32x78@1nD<<9d73nexVQy-jALI(?;{ z)WKYO@)`%v&l2hk9CB2t)LATgvT!0@J@VmPdNkD0S$c2>&(IQPl8~UY+w`P0%Fx1b zT6!#>D}yEq%%*{|G^wM-6i27nTbjwrlV$aWoMmV{!|P3i`SdQma|chptFH&RfFtu1X(eqz#O4uRK3}Gs zpV0A!9D|psl^72)^ytd-@PieRH4yT^J3(*ANq9ob+bA&G2H3=h-DSGA;YDv7Qgo+H zwf>B{UDd})$+flxgj+a1uO0_l(d0As6j?44c|*?1%eaLyHLIaYiMLZ=^7(l6lr$dF z6ev>2L-FdRIx|gAl)65 zt|447yON5J9(G)wKBE)4>W&4KYf?OK$l-4FBB8Ez$(~f+O@YxT&$3Bu9~sC7Gx^xP zdKzArP_e34zTxBc>M2P>=j{jk`H-{slt^)rrm6Jk&Tr}Ipj!#%w#$6VB}0Wgc~6k` zlgW!6=!vYC8r#P>FIMe!vGOd4rX~RzCtE>l@W+g&~IHC^p zD@qB!$`=t@zF5ZbNl$w6hMe3d)EPMOsZu30%a;&ZzEs97P0H;JIomJ4TspM0d`?t; zv`l`)Xa3bw!W(z+)IY^5TOyrk9rl^#D%p2L5L${&bHbtJ*hD9swN@4;auz0X7AA5Q zCUO=gauz0X7AA5QCUO=gauy~s{S>*fBC;@%voMjfFp;w`k+U$7v(PeGXqPNZ}$?tp)Ip8jk7R~v+PUCW!Z;JkhO9a zJ1Rq-qR=c$O%({Tl^9};r2t=f0lxAAeB}lB$_wz77vL)|z*k;?ue^|y7uHV})=w7J zPZris7S>M|)=w7JPZris7S>M|)(`#UHrj)=dluGD7S>M|)=w7JPZrvqel(1~gB3(S zUJ0D7hAgb0EUcg`tduOQlq{^2EUc6)Cev9=qO+KyWYKT4uwJsTUb5`dr*;5c_Tdw* z`Ze)im4je{tNu+mTOk!0U+InMbvzETm|A6FRb|;sr~LT43{P3p8MfXCDl09W!A{Oh zWxR&e8JRpyDbm?=#?KH;|4E!*5=S>8{2`r;8=8*rKjfFhm8L7rPo|S`NxEu!uA)CG z;{e^Eai!^o=9i=^&9Ah+GF=ikG(QX!*r z-YDh`cXP2CVPG=|H^y8@_O?7X4|;CP;&27WR>g~YFf#IIzDPxC0u!>S`tZ5~Pk zaV>umH;h+Ew_s?x)zq~wLu0(|VIQFa>`n7&djh);S^&EM)i!-9C?=Q?nUxTkl@OVg zEHWt_OaNZq%;hm*&tt-#hnFo6#^m8`%VPqchu1BSiF_X3w>&2Fd3fRSnAqpxjmu+# zpNCg2kBNRBrbiwsFAozW57n24DUyc@%)=zfLlx#>n&hDp^Dt5JP>p$*DtV~LJWQ56 zRAnBfOCBmS4-+O2)tQGWlZOh;!=%Z>k&uUJlZQ&pV|gOY?Lf;@N`(+qLzJL0DF`an zMUXpYn%jr)Wa6(nDw$|-4Dj+_E)UZw50#yV36;lk2ez5fc_h^z2!_V8mY%M0UA^-J z-Ta|o-(rQE>i!QIW6bO7@8Ji!1w^IkZCfItsjdQ1PwGApm8i3gGqP09YPw;n$L7wi z4ntB=50Z?0u$Yc~G!-F!l9W_BNlK+-TI!LBsf3D}dQvnx`#X9R4fP;t$Oq7nk4%G~ zBn_2L(opG`hI(WgDxsnwpLo!LnK(5|Yjw(gBFK&DV{OXE+LVvADIbmRW6jIQnwO6? zFH%t||1{f4L7MGDf=Z4P@cKbB1yD>?)-^S)b7bDBvA) z9=kJ%SKtlw3wFuUcc>eSKg!QvExFi-?v-hzd0}_!nn}z){qk0xuFj6e9#*Gx{{mRb z>KfP6P`2El*wjimH#9cw63$Wtn_8)O>2-T)ZKtQDSq?a=X=z2%Sbj~TOJkcX6&NMp zbu(&Ic+Z);?c1%jIfoJ;uTMF*~*OD-~ET~ z4^tw|lNlPMs+2J{kP$4dF_&B_3tJm$MVJopb#=(4{xwVD)MEc?1Cf1Q-74lUGHtUm zyQu%r{gtiqF21$YyFWxLR+AwNnDhx2A{h*i47w;bG}DF)2o0>$RYTZRL)c)0HEj^V zu!1Rx4ad|fpp%Fdwh(ATNYW!PgdkpM1G1y4B5Ot-3|AqlLWDVz19Yn(zfwr@%2Ui~ zp@kgvaum?yHEzn1IpWSNl2wG+$ta`eQOUEEW0Kmy?OKG4p5Ncr-b)uK(n`6au-ZUG zLPIgOl6U^lVN$MmCGQL(oV!&e?*JlPRXkUX1$I+S6ESPh+W_CS$UEw803ni+6<4+Ap*N@ARS_E?n+3`}*lUv znsA5~Qzez{eS)AZ(I2}3X@8o$* z>S-N=`7>W|)}973^NqHaDEjHjg59!&F2bY_=zcI2SIN83h-RfhvYkA6Nnx>%A%ks2 zm3+*Q&ZBcW%60HS1mNS7;^!=1nHK0wE9+0^#An6&fe)QJ#F(QF~4|@ zM16saRmoc&sW>PmufP+Iw&$&tgmVYYV2fLZSM=%51uzrlYCxMzdfPhM+8cYA9=6co zm^t0mfWC$;c0nQQ(3TNd=plTr%p1M^O>})=W8YtT`|0LVX~7Jg#;UwC*d~|B2VRl~ z*Pcla6cJ-HT|u)ETB@3d3#Cb3uoS|1Tap(^PsoqvL+}IKt0|3`R2nhIq{f%mD`MDY zu(^*Mc_T6BzoKtp(Ltn{uF`l9T&k=;~V%d-926)4qIm2gH@cMhy`agDLY> zgU=O}L!PS#pR>W>bA`&8sf6)he7a)rxgsy0J-Gxz(yYI|udTa%DH^!i;^^-7{$3ST z4Pj501g!g~ttazRDRD&)t=e$!!xcSri840?uIM40n-Ev@5T51ji?qbk-%V?%w6~ES zl?rtdOzPhOM5@~XMDA~7`n-{sZYbg2j;mkjrV_SiD*3=P-Q~b|h)Gq5NmYnRRfx${ zh{;q4`zFN3(U6_bbaMuGxDa++2uT}a(1)0Gg_zlgnAwMz*@x&WA!ha=X7(Y3ONc%d zf}bITO9-J8^2T7=al-r!c>^@zJc>i!08KdR3)K_yL3WDgJ{Be~%n%whghmb7+@hOKSpA1E$3mECAd^~Jun5N1`#8|0~e-2X$EQz6W(kT=d#d03W&yb+vm^fQ=SAzv>+ z`LF>LVuXe;!$RJ8Pw&y5*k~NWJPUb4KIMz|ypf%7l#4gAQ$5(i2w@(E*v}WThlw`) zvMj`veuSeuFc)y8AH}16G0(u2eiV=XgZTxn@}qdv7gK6n;YS;Y!FRYBur-q6(O9tTrjReSh^u^iK6#t2TZ3zUd#(J z;4dx&39xuaH$}1h$)LU&>|D-(uNh3oGT?Ux?yn4XE@xnpW-u|yfKM5?w=&q-oPqNX zi!)>ca{tPJe;M#O1CI+9Z73brHdsXr`ia6p|$K0Dg}y zf%Q~s0=Oj5Wi#+RX5fU%V9$OA9zopkLf?Uf=xP}ZfeeOF1~cRgx?~13Vyxd%mAR{A zFtg5JCYOO``!PO zxXho#RsKmFSHe#`dg%8)4tQjse|*SEzoJ01fEHeSO6HP&(wU+3dtT;0Nh- zdY5+0?P@2(zL&P!^|bZTq=$TxO)XqINa+l;UxqiplhYw%FK(gDYxJWcjlI1xZ8|7M zYbzLqdku%xtBB98BED)B@s(=xVla?@X};A7-IDK<_*JDZEk}~Rv>Zu%X*o*2&#s~z zNqlKJlK55SQ(BHBeQ7yL(^aja97%j>IgXv>c`1C;h0j97%j>IgxqntovNGvPQ=MLrH?FGb5bZ>zx3tTZ_W#If7vpk= zYL%(ZGz>|JebECcC9YB;Z&g8xQw$@hDoRi>h+xiCXD$YBdu3MA8&ws8iX{XUO9(2K z&;p%e2|>jYf}p@GAzU>HLF9w;*BP9@&fxqtrk|8fH3>nKnD-nJ4*EEsox%C+49;g~ za6UT|1P<-GncLFdwRo^HIHaAyA?*x%9@7~dE689wdj`h~GT7Fh!7+mjdmt%2D$gFs zbcQ{U1W|eRKoYL{13^@tJ&}Zi-Rwpo98J$20m4yv_COGhW@1kP;h@j%7`g!j%xCuw z;mDsoj)WtB_P`O2{MnO6IPz!D6yeC9-7~b21o^YuhHx}4yN?J*{n))jIOwxSi*V3q z_YL8o&z?2HL7&}4go8eN2Gj7M5d?qPBS^n-poR`XHLmG54%Bx9Ri~tQ)p_V<8P#bB zs&Y}h%7>t;7s2Gcsu!hG?MCsc+yqspr8CGXJ-t`-2qw5nM|hI2;s<@F>P1k|rT0mB z)O*6!dxFRxSMd~a)lZRE7U;dICqczG`b`ZkcaB$d2v_AM$mPzlFNbi>mrjey;ZHd8 zDaRfr!kK?LTuqe2)kHa5O_amcL^)hdRK(RgMO?j8#ML`RT)k7o)jLI8y;H>1J4IZ* zQ^eIfMP31-b^^a}6;BaY@f2|tPmx#9C>`1jSMn5bB~KAo@)U9EyNDYQidZo!Vg;>; z6|^E&W{OyuDPm=&h?ScnRyI*ja(AhAC8+pBQ1PFjYDa>q{Rpc5Ls0cUf~x<~z2T~z z2&#G#RP97iwG%2|`xk`mQ3b`6?#YuJbAkrAzXIOvDdD6>+^6#t@}Ld3o#+jxsq3F zbCoPx2`c&oxm_z+#uLtT@;LgK&sOri5ZUDR2D`1JtC?&Ix#F@OpM)89Y-ISWVMk8z zYBjl?hp(bQ48${6HHU?5+}s-A=2n8-6)RcYQBhS>5Ckij#dKtWyC^nhko4ztdEA|l z$0^QyHa~2?tK0^XPNm9HWxxNP8bxqK%1fz#BbU(gNvX|JJ5p>hwFiX@=zm$No&J}l z7SsQ-)PD58EOkig3<}Rm-Adu@WxJ%x%62W=mBPtolPR2DHj~2o5kzIg+9TGcaLkA? z6mB$PBMP@1u_cA+5$V*(5rq*2dX7fWw zSAG$Nca`6j8d-i{`2+O)Q2BBSAJbM#m1%2ebrjCfcB60)ZEp(aYE*KqU8B-!2WSUS zc(6u!YKQ6{r^@ut^v@{#QvZ>{U&P2%nGj+P3fB@FQ@Dc|PhnoH3j8-ha1uqhm6P)iuA8>>^ema!Iv>l;)OV^d>O3b!=2rf^$hTMEY+Qz)!6sJ_N* zV|NPoGUig)Y_w9i(CDDB%OFk~Jw^|Oi;P7SE-{u+c%X3*g@+i2PV{zo0ywZIMy6X;W%>~g@GARSY=jGxP!Sfg*h`v;jZQ!3U@b&+a~>hE`;bWZjf1vq#$0nl{}A zLE#bhQK>TLH0N{*A8;O^@KNVc3ZHhKq3}iLMGD_?sHL2DoOdYv$oVK$<{s}7Puvr{ zF{zQ>23|ddvpwRl*W$I(bDKwF!RzpP=(*SHrRPQ7q4a#1NA>a!_b#L7%e^b;`AY9f z3a|C9rSJytb_(zC?xeT(dXLbkDNB{ppj(sv?xnwvQmL}zX!MOy<09o=;a){$xYoTf zrMWk|52h^lG55*TdhQGEOQ{Xq*WA}r8@ung@256(KXN}#ZRvjMewy0a{lfhsHP-#w z{W`Uc`@Q>pYFl@uyE64R&-U!pIB!jF-PCs87^+uD_1Y$t_0nECRrD&n38~1N=uJ;G zc=c4@X0MfM+Cuf(KQ+%g$U8XIN%cB8)$N^1wL3u7g8EBpHTNO+VfPVtx%;TMnK#zk z$(!IU@lNzk_D=Io_s;ar^)8|st*^#x%00oojB50h`+RC$_ht9h)Q0Yx?%SzN+z;Fj zQk%PXp~D)2^nTB^dcJS$b{ zIi8cM@)?uXun-bUWW-X>I^&AlzXt-P(NUfWR3#(O!hK=qsG?MmM-rSFdM zj`vQW+MY!9Jyq5C465_l-Z@n7^StxD3%!fHi@i&{ONnP?)WTM(J9T2JyckcKks8~y zw5L6_9mkvdc3Qu)+~$-5ReO6BPkHHE2wz8RMixv5hpq{bC;Gd4<%+eJOo zT`%-JdCDXT>Cz?&>!wVmFqI;CP9m46jgTi@QeMZxj)kg+N%|@3Z&KEeB&emFxsT1i zzje>eQzKMw+=zPP>(pLvQoBr2?b1x` zvNAPawaWr(7bn$0Ei#6B%Ldd62dGv!nOdPfb)y*~-O|`<^sudnqt*~76tIvq` zQAz`)lm=Q)X`l+FfkLH$vPuJ0Dh*VnG|&{KfjX52TB$V9?@|NN5F-s#Hd1P!vTLLU zD!W!{pt9?v1}eK=>YlP2r0yxZQR<$u+obL(dtK_DvbUw~8TpaaJtIGsx@Y7kQtOQT zQfi%%-$<=9a)s17IjL{-ywo>(LFya5 zDD{m#N9r4Wcd2jmcByalKJkP2L0=^GjlP)lO_{#0G14g450F|%KS*jF{Sc{j^uwgq z(GQneM?XSp9sNkDb@bz<*3nOpT1P)wY90L)sde<5rPk4Jky=N;gS5^({T`#$Xw~nR zx<`LN>K^@JseANCr0&s|OWmVCDs_+knAAP`6H@o+PfFdRKP`2S{<73P`m0j+h!&}P zM4Qw-Vu92>VxiPMqMdZlj-tcd+1y!lnUl;pqFd@7ae&l4;!vr3#Nkr+h$GB>%zea> zQum0Xr0x+%lkVA1980?AP;s2pJ>n#(d&C`5_lUcs?h*G$-6QUkx<}kEb&q&J>K^f+ z)IH)Mse8o3Qul~QrS1{WNZlizmAXf~D0PqcKK^f()IH*Rsc*yzscFRTQm+_FqZs8D29+4 z#W18UA)DcgRH`wXD(l$c|L(8#j6eUr_5M^D{zi0==}5GO{*CAujt_nJfAc)DxtJQ+ zJQ06W3CF+7V zTfZPUj_?=oUZ%lc>udD9))1WRsN|i@M?SYweN`Uv`8&k@OmGb0D~G3-b!oE(`O-F; z@X>>KX&p8m!U@N6i5|Nnj-Xy4K@4#Cl$#(E7a8piKNN_hY- zXy{h>V-3$VywvbU{V(I6lX(4tdKEvjr|y6xeZxz2&cJso>KCbWw{m{ z@_oZgGXJ`38A^OGzOIMyIj;?rrYWmL&M*)AV;gg21=G;GL`JAWbyfFTi@o!DsDV3VJ z`t+4E$U>g;UBi3zU)8UuUogXx>Fa-)okG6!eZyDN52{!3(~p>boQ&UXGfuagzMCWQ z8KY~Pr;VFdLHqzchSOde|G{8BgXO8emFtm8jsJ|Ql^Xxe_#Y)qz90WMt~`n0oSXSbHkt^^;_Oq4%IC%c1a6NbhC5%vVC$?nydXpVIO({pmk*{Pgpt zlXRE;VxU}|+%8k6qMWV3CrW(ACp?Y_s(v=r$Za9t&s=@-PVznB+e&=u7P$a;mBgn_ z%BlCcVs4_u>H1&fpPMDycVaQOdv0#7mB+Je?*KV?q&%5<#pKr{F4IYz-YZ=47x+7nqY9rIH4AnK7HJ@h^ESMsfP z63bOdU$%38Ey!8YYjc+6$0Txhu-%#d)PZ&fKG5!rC-O3pAH_SFetr}AKED;0FBxZp zaxAG2^JB0dm&&swUDBWO@!9EjpMki606RJ0j5wTt6;{OqYB=Xg>_kFZr%CU4oa|5rgqV^O;8S zLE=d}F|6M&zevWrc3%i*)>XUC*R9-a$MFNH1QTr zH>pR${H+r`g;DBFF;R^0EBekk9|elugM!aDN1 z#D1ptNxQ4}g$*Xv{#m|-&4!n2C_R~eVsYZc78+@T@m#O^1%oT6{7Y^X} z1fd@X=2c2AB;OD6B`NQ~_=3GF?OUb?i28H6h(Bul$o&5NJ@cz@OyNZNK3=DAI?{~? zRQVOoDO@D+!WD&U6}-K06X3lPCixHPN69=<)u-@qgOWcJTWfw+-#t;I^h*8Jh36Fh ziX6Q(9$%>+N4ZhE@;ImQp!i*QtMGwJ_gURr5>8t%h0E~`j|Zyfk20R%uM8zW2jy>4 zKFLq{zUVPOWdB!q72~xxGEVgvFXN?spx%!_`hfA${D$DvF9-RN(3=+X_eF!huYIZJ z3z=@t?z>R$m*pw0so;9#yQhCc@?-j26qVgy3?``lBIS^jU(<(_N6IZZ&Xezl;+$T} z8SZaI)R&-2pUAc1PJ`bgpMm@suUGSITF6&&s5rGaSH>4-O&p=%?u^Uwvz$%Zk=v=* zTCenCv9s7G(iJsn5`I6m6L9LErI+PneKj!8 z95#MR-xseeUeEayZ((`M`IEhhc^{W=aNb$GQ>LH(bMbzbx5LX{T#o+vRPlvBD__zs z#n+1O$oCRY`WNIk+Ivu*?Lm@<+5_`UjGw7Rr5~pqRaE`I_+9a5nO|K`QQ03+d8G8< zobRI1GM?aC5=LX9O(dK?HQGwTcnSMKW=x7IBtA8WwwG|`*l4_j^s_dTB%F0J>z^63 zqdFBoJK9r18t1CKa=df-Bg|i-tAQWS{U=iWrC~)>ienxbsd+555BuF#)Gm_W*WSqT zy7tCI-$c#GZvpZz#qoWlz9)LhA4X8wtCWxGp9EEZ{*&?_h5S+e()rRK%Kv{@pJ|xa z4Dw6r>A~_$IY;VmiOc@X{FU-Nx{Uj`jkeK5zOXUP?dlXG#B$%x`#k2kWz{{*mb8q`iJU| z(|=C-BY#ih8~!RDS3C~qEQ@Z8ZsmD%&1=$cI_u=^lmE#fd3huU9UA}HY zU2XJW^ceS_`bkV*@h?g`Z{%^En@?Cs@%#Zk>^i2Ze!|J=4PLeLszqQRWe&R;)F?c_$-I8{k zQNiEEpT#FqJ${UTjZddizd`UEf?vfK#aAf4-5%dN2#K%P#y81)V--IL<+$PYh#zMD zCV1jU{o^>}^vO7=fj_VY{=ms6^ZUu@xBp*w>Gw6tpGfv#CzmIFLYABGsS;8BwuTc z8c*_-{FBPRK1fHl!<=O`>(y)|zbm!(2l-mF1=piy7V~AwIlz^DNBJthF2U0GJ8}A& zDxyPv%)$4-cVhWigL>4E-&gw6Wd5`+@>&hPA804!UsD8q79D$kufzUCBb?pOHonx_<8 z+3=p^bIr<{7bKjvpyoA{NBMDU)cZNhX5Ym1n0_AnZE8MZd%os7p5I8h!0j~%hvrXq z>w7ZaY0Y(N98UjvLb;shCUSE65fhX@WoE;`_XF}o<}3TV{C-Bo?Cb=xn24T|D}yju zXJI*)#8dvz13a&i@yUFh?b->$@|iGt;(Gs2bKe48Rdubs*4~F?pOfqmAUuqSh=3T5 zkOYXljfj}XNpkW&35W<3ks@+YDOIk+QuT7V)`C)s2ud*zq?Su5R}qm?YpJD3sSiX{ ziil__Qlyk3V$T1~Z|$7~1h1q1|35nRICIW5*IaYG=6cP&&)#{RBtIkX=Ucs{B9zy) zWj^!um%mXvKd*UMAj-{@IncJMZ1% zNFHWz(PQkXrMx27nxFTvgd^SZj<)1q5%cmpVeG{` zSn=|g$87HmoM|0d%r&dSA-_znx&p#vMQ_Ai2bFSz& z;o75R73Q2=;1r|?zp~bh|AMrFbjlqlJ|VcEdqHo(t=c13!qv|dgo*uq#Fof#$)_N@ zV6gF%nf0w|XBF+Dl|PQ@{ri!=Ucs%;&whLR`@(`~0oRG+|K)TAY{xJ2&#qU&BLzzfo-pMqsPpg|4`by)dps-oN1LKt535gC zoigPy{@>d9wI^i$_5HPee$D>feg&`8VrH!ES38jDfD3k-aKRfMK3=d_;tQaec)wCb z>CtGxArmf4EjS{4(NmNTx#OOkFW*!ckodw>U*2HJuPD7RRG1n&De3(D{qhucGIF&; zO}x)H`9#hXVtf>(1NSi4=!=~3@Segxw7^&?S*;801`I~e#4)Tea^hH+WHR^%I6;Af^E1V|!C^wIC2P20g$BNRSZzkks z7h;qPA7RA%^ML3v>594+!lGr~Fy$=jZuo@{3ocw-xJ>xbBZVsq8w#HRUSGHo{4LSr zg;)`yN214Ln2)Mc3%3{U65Jf#E$P93mwFBqrHCBtp!wV;=dQw!3y&6_fZRFHvG~ug zlpQQQ9f1O@A6&o6+DDQj?IT_M=TS;eN4iCNVm{1>WJ*51o%#2#A~})akx_`xtDP^N z*y@$d_W{l-2hK%&{w7ipsr{-T?A_sIRU3)&Ozlkz9b^C*}%M;kvuQGwWJQB>B4+ILNQgsZ$T(<99F1J9*#ABylqFCP10 z^SnpVakHMtK3~drcDQJ&DNoT1V>j@BbDu4KaN19*@snX(`F>x|PgiuOJTF$Yv*>QK ze{S}Z;v6C8n4-Ls#QJq)6HRidKqIKpuyrRvYOa!z7D(@$yY$9%nU_to*z|B@b<^2MIN9oSEQmR}C_JLH|yj+phh z=$v!gm%@Dh1^wmS&obqyPAv|Y{`c+EoD+*fo;`Z|tKyJ3=M|?GcM|#Hu3mhP=JI*{ z@1;loO8=uB{POoR_Ey}-%g+xFq`vjVLyf#iZ^jG!nz_Ye#NTH6PyC$5Z)C;`!ezxV z#(Vc^ipQJhSmeI4=mTc{E%su<(!R{ERr`A7^Zdfao?p0_eqofy^ZOdVU@Lp_>-WFa z|EzeicP~HB?`_&2_Yr(Qx92A>rl0xT{E9ulShKxsX&y%}8~>zv9tZlid+oi8?Yz5~ z>t6NT=s1}VTX4y86TTo`<;4?Y{%8C7xcETv$Hhm{-(M3~?KJtfV3Tjv&hz8q(`LV3 ze7Z!6p-K`;l1pHR7s9afaZ=wFZ2UjfsTahnjC=`k$-a_<(0Q3SE6sB#CHI!x zUxFRNx%ivvkFJY8E9LNU$-d|~EF^48--@|SSQv67P| zXQI|WizWYWQvc}Y=u49RpT+2BjxXS-R}S_g>bLr=@LYKwmh%cR?0v17$IbdE^I^#b z>0dKH`u%~D`Ss)aTb&xq@#ZHlY}OUD1O4}96(!iU8a;xI-=k!YKOgz+9Ce~85+6;A zrVB2c9_=m|_EjY~+B+I1&W;Wi4F1&UNa7h%?y~k}U4&oSH9A@_;@J+sYzJSDkI(HV z@V$13PW00I_xGvaALl-f-W|P9TAxi{jLe@_F-9bWIh@ z=Z|l{-=X&vk&Et(z7gH4RBU;4znt&Pc`$m&KNkv)o)UfLe;ocfFWOWZC=E$FNdMva zP5*x!xyEnc%OjqCsnXO^?uSadmi9RNd1JF*6g{%9E64K{(%z-hO1b|LzqDDWYjdT% zrDM$cEzhq|ZcOP=*{7D~n*VEn@9i^;yyP$GM8AjqFmY+5v`qN^efiQ@DNg6mH`&Y2 z+h6+WOJ_FUA7gs)Ly3HKYUymIn_D`Mcz!9*-+ns39Hom(moa{2X#?>yrR#||mTn<_ z**wSPKQ~so%ajAp<8gmqy1VpU${i^ESg^0Z^k{8v=?R7poW(y~rpgk?KW)aJIUk5U zmvt-aDeH7uPT6qZpXT33JQtT$l+}9uhJKR!IHJeLGM=jLmVEvH-}qSM{Co{I&rh~a zUp7bdc>m||?98_(zZ`!2m;cw}>#N*Xd9re?=q=K$x6->mGlvOO*FeD0s=%TJl- z#h{=4RCdtQcevRP4g1A@WMkQ}vXk`3o)JBM{wSZ(W0gCt`Q<6)X^=`Uho6%C?&aZH zSXz1Sa?WdV9+mT+#ET!Zx<@(ZFW=AU*SCCJ`9w+I9G+^P*OajMoBjVUor|ldmCq=@ zvs~=Ll;6X?zs3I_Q!{>~e5s$0%F$8ocx6R{@KG*r{4`^f zOU9E4L+>ghSMiJ|e{T8Dt;aD>?wtBmtQS7+*KPFht_sX(2ygN5%YrMmSNQzh686i{ z3b!iX*J{_Z%2RQma-Y|3=e9H3wcfp#bM>kqqO80}1)=V8>V za&0W3ve?5F9v^xu*H+e6PVnr-FAv+Ja*FBi%2}27v%a$`r+at~@x6v$z0ueM@B-lj zFJb(`X1)n8_wA;+Ugx#5>QpaZwlC|kuM#^u)c>G|H+cB4hhOvXo^$w{BrN*8{@v=u zZ}YG}o_y?&NA?5qJ9ixVaz6gL^5@DK`+K8ksxynUs) zFDLV#tULZbsA_4|6Cy8srmL%3L%go)S&z^4t7>!AOS}*1{oe%l`QD)1-m3kc9Md=J zIaGCo@yGr9O;z}R7yOvb|2HuC`SSieU)`y?tE8)jAC0(=+zmi@VD(Vq-0Crc{q?!J ztU5;i_-f3@ki-9t1w${}N3euj_7~p&jptS`CjY`1t18mXtv)}VS8dAIg1HZ@o>%Q- zqko>4&zG^$JI~X94o3aF_H4$~3%-x3AK?pO>PP$qu-CpTWq-o`lc`Tb^)u#q4F7*@ zXSZL=eQ4jm=KsI!?EY--*Ig#Q`G3#{sy~)?Z4S5kKeX`ubg_h3at!NkEEDT<7vOHO zp0NxsJe+d0OJ82@V@mqis8}Ac_dlgrZEQkpN^E*;R%}k}Uc}!YTM%0qTM}Cy+a%|p z*s9p-*jnHX2yX@67TZDop4h(F!Pw#0v6{i6Pwr15_GIi#jV0k4rzVBC`Tl)PZ@K?6 zcDN^h!u$VPZp~=QjjJiBiPqd*Q&lsrW+Kv0g$L_Sw0o|1j`GHzU!R%>YaXh3q-JT& z6E$@;7?CyWYM!mx4D9{S+}|I!{@;)6Z)=X!9GCn+o$_##!LHYO8ajIj?yC!$-OUq91skhvySN zT)SBIxwR{68yJ43c0KXN+AXy&gYUg}K+>E0Ip%pt|9|vjyNvB7@?(3Bg%!zjIAe2& zhmRd481H+_w(uS=^?DrdGt(8e1K7{P+Zc4%S}3%h37BoY2H2N$nDm%9-teF&NLrn3 zuK>)l@TN39iu_s|Z%@(_2*+4wNQ<`Xt&aikV7}uRdxM2HN$7Oa7g>0_gyjfS$28s# z2hHCFyxRIBV3z$f-~byh2m)WUb%ju1hLhGQwfU*!%Q`Z{Z&fM zS9wrcwKOg|%cVVaS^PS{{ubWBrhBlb#!|x=PY?8~Uo%IO?@xqQpAo)E_$p)Pl75Ay z8qR!k3Hw`O4;|xppPUX84rMMQ+2*&f&2O<2pz|c<3)mWA`(}~1o&&r}UnpnN!vcLAz5 zSi7n0{i)(({1=Wu&8|vsu%g)SON|>B3j1 z$+=WYqK;A07LIzKbe>46*U8yVP9f#KxA4VT5Cu`?nbz4m==rwBHjL zc$pCIi2;2N`L6>GxAB%iaORPNx7mRH72!qXFxDOc`rFhTvetmEU|v4A)$81D(rP|9CR&JCo8NSW<<_By~HNlEMn32&Cz zfEG!+R%n~OW)Bjb_AgnNY?i#7vuhc3j-hRo*mzeET3#gYVOrKjPm~hc50Ueb^pU-k z^ba`dOPRKoWv-<*oBB2RZAd2wRG*WR$2p;`(CTx>hNTv;?FE3H*`Ak5F6w(M&zSg9 z@W-&^S4zJ6O4B3EF^@1DkP#8K9}^h343KqKuZpDlJM9YlAn^McJxrBB{v>j8Dftx3 z-;t6XNe4X|G5o zzaal@!mG*2Vr+lXwbWoS_EvHdN!JiY$QewS(}aCYb(B-UqIXOi_t3A}vPH41%^h-C^H)bIDG09!MV0{D)`w27juR&+X>VTP6`045) zz!{k1wf=xG$hG5Yt{ra^4$v|W3?Tgu;YI3);EW^Pp7QNUzfTypEyRYE%(4B+8A_R+ z)?ws(7xN9MQg{z(;J18uT^ReKpJUh29EISpNp_pvUPi94iL+E;)Op zU%>gcK=ogwrwP=v0M`*_suIQ?1$Ed`A z>i&ROhDdEJ^^!@ej6C`dEzgygy^>4$%XmV`r#6Q$%a(o1(17$&*wC!C99FeD^j%Q=X-gblR+ooPqIRs;4GpgXWs zbL<0H*#`)150KUfnEl%jwv4>^L+e38+p{QRc01fH2S$+JPS1wScPRNCrur`F?~;C= z^z)>DL3)#cwOWw5U!e=H^C)UNfv5r(<12y;A!tqkzP z_A1n|fbvPyxk^TpZ3#!;N_dlja^})EQqPU#+$cE)E@rCrOl9^1e`X!0P_idaqd38*|7R9H$NQG+Ie*HI&cb zEYZjGS{!eIMDCe@;-$b`BxSa!A8-3b?z$)WJz19zc~&}1{?EDEenS3Q@_#}8v*f=( zeqU4LxU7$13o{IMFEY=BX$f;!k~t!8Eo7=W zv{mkN>~_Kld`qC+PWUbYhNo)6)7_W55a9vLmV`J=K;y;dshV^*AyH$1~rl zb{gm#D02;C3k0;`mqOId!Qv%L{2UbQEgDjb)c_LH{gA1EC48Z}=_ z4cAJJ>NQGkW$bP(JGz_5KSusO@=uT-ju#^~m)gRXtQTQh^37$*b15H=-vv&0<`N;l zJLMzHu{&jQ7(0+HkYmcm*c?xTlp$<#v{5_s^ z5=u*=XN8~59n?C)OubWN)Bu6{Es-IlKMLnTj7q$V9Z-GFcKAf-_y-(`;kbDJvIr-U z(_ef?ICn^@82Nn(zs=Z@+~?dvI>y$R#W|m6G+jYXKH-&=DQ1qFXsgwv*BZ;G%oVh^ z?u2qrNlw;cf#LWmzzmVJz9nZ0tBAAlb@b+4MVYJg&6FGgSmC9@o=vB*7t+Y-FEnZa z8MLry&JtSRL3#k`=_W1blOdcjhH$RvM!Fm69F{GIHCjo?U4i~j^27Ek&>6Pn6ce@= z5XxSwFJpU9zK17y1pI-d3ka{5+|~EkCh1Db_AqN5Gv%So)!e%tr_3|nIz!eJcp)+Xhr~kysWU_7QuPA%4mb@F zW^fhm%Q^OHavsrjh|MOa4>?a0W~+JN4z`lRiK=j+{&M z5a@Y~xpXnP^Slt|iSJ7G&Xs0X=M3N1`cTS4ejlDn`jhTY`f0*!OGBnFPb=Js>8se1 z&q*JtN7z$ALb9@#hBB?dWVs(WR_4$W&=RX6@Zu}05K~9 zVuk_4tfG|6GoTkBZJ4bQ=IQtVVOUC{K4dO6qTk|q94Aj1iBrj$$9$vYOk_I@COw#R z*jgf*DKmijv)Lo-1j-jl5L+o&1AgHmdd?g^h@raq>RVdc$%mt>^AUk}%#bRKMUH0Wb`jxN>}x>irnQ}lE_ zOWzCrG04r)_v-sWFTii1Nc)#wqL+iWO0U*y^#;8O*H*nv@37WbYxHY`XY?MuPalL= zEJecLAM@}@6K`22jCco>NTrdJ?(w^e=NNQ0!-K7n$bF4fh2Ko525V$l=uw*ClZzrb|cInFr-9Py|q@f8DN)k8O*Cd6t8 z`nb|x9F^IrX&~>KskqFXeXLz5&k8FGbXRL8=)V#^N{)*620vuY15B0GMD-#JiHu4j ztt^}zAtSJp7kew{OGu}R|4Nk@$vEe2DTBI*9NU@!+9jPvon1(W$Zt>Du|!)3((MS_ z3XC6ZIt+M<^eM_GGqw+5h%g1vpZBmLV&3Znn5`yY?$P)r)9=K>Rq_SD$_%_`)`r#h-dDB}v zu>0_SUzLaTHOpYFJ%2vu({16dhVZQoUq8+H^n4YMb=!nIw&J(V)YXOu!N$%JPo*fF z=9Pr4lfc+BS^G?wG9o8&2Z6h{@ZqT@-`7JK)P*`k{)`uoos8WZKj&!#&sH@1Gqkhe{QWl0bR;Uu#&Cz zRu`*_U1f)@Zg!Q`)5-wMv~mCsTbr!mxJFrdxK<-q%evn>X{R7=g0;;$h*VY96#R;< z4Wugw@3*E{wUC%%O^4Q5)*S0zkG}xk#U=J(Yq_<`T5YYxg>=>?JH^@xyv^ESy=LvP zM_T(t9@jW<5275$kZagFV_P;pNM)zl>2`O!w;i^#?U8l?wD_!PtpY954k33B_C(Sz3$(cE+W~TZMmUa8)_7&NCBzv6l69oNO~|#( z&LJOmhuC9cN8%p`{W0NR2%7}j*ArF}RuHZrybdraASKL^`z5L^VOM(%=&XRO!Xf){ zK*jammZ!LsYp(_DWMkih9A)>Dh;b>B0y~r6L_Nvmv?XjuN#&)I@@VlUplmk?wBQ#2 zJVlPw!eOdp=B}AGjWEf`+lL_2!N#4Z_(Ad$yjUsE#VmhEa@rDxPaSjM(z(GmE@HI=tObAyW52JOtzv4tnv5?B&cv_fIyW@Ulb8)W8?v+2 zT=-1pdnq0!UhIXILHm5Mn)pN9xh-Pr-ou`m&$ge>R-DS#9V>VD)Fg85Ws8=PGaoI7 zDHOkum4;aJ4aszVL$VvcA-R;_kle!BrF!3y%;t9_tFUX1v*sGRnnZl(vAya5xsKQY zc2-@~MQ9C?Xv-Xog!?sQ+MP#6&Q~qDwYflU{x>b7*U2|6@8mZvm-3sIoB2)4{qjvq z9guHYYTRjPE`7oU^qpJ!;CV{cB4bla=qqJRDK>*W#2JLJu_+2Z8Oz+!p($l5FRBwJ$c6Mx9Q|5vu>WG+fToJ(sbRGum@ou z!hVDU35OEq5{@B^%(&~eS-R|YARW7X*5ny_{H!~t-=-(uaoePsdfFX#WDd|X<%`#P z_8oW4yhG2u`&+Ya*YjW!*zeGKHBTBg!Zv0C`68;J5*hZPQc36KW4DBFm6{Ym(23_r zNgfx0Z^NQ5=D7y*9>SP;2-_37gdGWaAGg_eB$6^aU>+m9jF5LaRZr5F6Jpl|896^; z4kzrTu250?NJ2tF_k>dkrxI)3qwo*arskwh#kC;ynbgzm(%TJfSBq;du7&NI+Rthq zZ9g9HzV=&i?e37)A*Vxe$6Z}Uc3IqIH8hKMM_JZTogUif{=n)K+V4E${LJ}<^PKZ5 z=OyR2&UWWb=Pl=L=TFXl=eTpi`MdL3PzBorQ-ke-or0Z%T|@7N-a9J|WX_7;8+s@7 zr_lROowL$eK6L)#eB}JuIp!R8js`=) zgrE})2D>75buitj$Mq9jYjOP?*Lvr_as3ajXL0=!*Ymhu#I*_6%ea1xYn$^M zT))Hh3a&SBy@qQCu0J?`#I+mO9$b5Iy@TsLT>EgnkLv)ggU%^x!49F2&U?*v(mu>!-N>imSUve6x~U3P2}_q%us3`O z6Vz2|60Uygduplbj}>jBxf)pyjp>VW#bI;j4t7QkC~N-e=N zt)HtEs!6wjU)|9`)u@woH}$l>OlPajdXT~;wdAvxcxQpGzx}E!DcZF^bukrKnJIhXj-%Hl( z(KZv+G_=GVbwBno%W)##pthoI_MknEsgqd0PO3B7LMx=`G@Y)oqTzoK1NBgytH+@B zWjtvZpW+o&BLSq?qd*0`GXJBj9JDPRa_ZP-oDYP6OaF!YpSi=*J1O zo!4hE_4^cc7NHfjp z<^Iuq&3zqTW#8kzjW2uebN_^|vVZJ;g70-tNx21IIyc`rm#?}%p0WZyGVjagEG^e5 zv}_uz;TrTB=C||DWd1ycRB3GEPcgQS+5IqvvoLn=3A}~zdN0Q5!ZxovqcI{e8e7^l zVLbM%nQAC{s2F2zu6ht7Zkc*ky`uM8N9?9HsR=z2!U;JExd{aca&ilx&n)+OQhS@J zW29`Xt*JPfy^NE`^Z2h!F}OdoUt*kv-b%Kj!qEfB5naD+rrv3$J|*Sr`i-Zn1l*t5 zzh<1#RVo}kkesfl!6?kOWvbSjJMPBmVS#!CbylRPssUy5v*D^~wJrZb<$+aU(WBcA!4+ zl#87daGeVa1g>{&;szI|F@zhFPXjkV-x=b%ytkvZb<$sabxn|l!emc3-y8e9SkfjGL*Q^9Zp>D4kK=GhY&Zq*}!NKcOY?{n?qdh z4kB)F2M{;9eSpy-ZU%9k8z!!I`w};}R}we6m!bYoxmQbzxLML7?iJs#MQWj0T4Wq? zojabm-o25y!5vH7=uQEa7MV(1=T0N8cW)(baBm@QbpH)lTILSoI`>ZE26q;5qdOf~ zT4V-soja4b-o2f;!M%;R(VYM+Ei#d~&YetL?@l6aaBn7VbZgim-vX?2zs(l8=^M7l zNNAQ8$tA9HM-kV%*Ah3l*AO?l_{un2Btl&078BRIMZ^tmA#tNy0W2+2MO^2`i0j>I z;s&>pxX~>EmX;|cu5-(X>)j}EqdOW{T4W4yotsBo@7_S%;9gJM=#F5E7RKMR@K-G> zm&ySrg#CrA1}W|%SSOZYg?2V7z;j(y}~lA?Tdpy@Tf(>g`^t5`=LiI3O)jA1?3ib)S@8r0#D=~^{7SI zE2X1d`k_rmp+%~&XPKt%RQIa+!DWy!IX&i4i-Pb>fhQUs_ozj|C7^x??&9E&JZe#J zDXA6U{n(=x1)l(AO0>eG76n(55`9m4)K`_L0WzjU^&Yh-xSEt`X!NK>!BwD4iGJcy zi-L8eq(o17)S}=TP^Lsrd(@)fGo(b{Pd#dpSXRod=q>T}1yV3;dDOIKYPv@y>VMY{ z=tT+tfR7-M*aq^(jx2Z+9PEadIa$~NXJg+xz!9&r(|Xq_`^;7;QdB_a>3n^;?kV}o zPLzAl7M+m5-g&9h-+4?V{9P;eqBeTAyTUq#5?HYG;gBe!_O>2xJnsmPCl50L<)*>@ zG%P^!5Kp^S-)*r^j)xR-<*t4}i@)*w@%q~vrxy*I7PjXG&U3o6@`jJC821jG*lKU4^@OJlX`?wL+wKCLmfhC zp^l+Wq0ULt#+|?Nlu+1wHsHy?>eDK>-YOL*w(Uvd5{IaJFjDTavaIaHq1FIvVB)Z( zo>pg^NO0HuEA^MQ(x}yu5WUq%CG$;!B_{_t&HFXPQ}Usn&F2g3h`iWQv@M0RJ>)IS zy&gZq<99>rYMdf-ys)Pq&ld|HCt&c!M<+8E_E$(fnHt1mmFf;?fsa)TQ~`d*yXMDa zd)6dcEu6OkYC3l8@)Jum=PYAyUcMpB=ZNco`OM_Nl=$z|a`AqJ_JrPs|Le)Kyjf>^ zzz$v52NwFj;pY-BRReug;-M1nlz66IbhgqfWjs>ijS^4P4(E0Hp~MHZ*Lj!zC-FUf z;CY_J>m(j0@ivL4=?wi$;$ss3lK7UAf-e0^;#Go2iQXi5l2RtqZv>x_@fZbdc!w_J z0n+eC1mOFT6K@Z+c`vLKS720MsWNc4BMk3OKX`RA;S0)!FKD0|gmvm_%=AOBA0LJp zVFdg`x$p%|fDh<iQ4d>-bNcSpG{rq<)|t#?zY%@nq*C>QS{A|HWIP zehhEXGW9reU!k5*PvU8cm(`(77L_D^`Egq-vYn6x74@7^Vy(yL|ZISyrAFI{?4+#*0vY4zryaYsyEx6*wASB z7stSNn5XhFSA5yOB7R5lvBXpjPQ_!@ICW#Q-Tefo;-^%ds#guF5&pHOaYp{BdPeUZk*>JRFV>UI2I zZRfZ3?W#Gx&et3E?8(zH}Jy;LZBlI=;I(@yq0ju+AeMUFo4nHi; zzVUyzHjK`8R#D>4#QllyCmu-rAZbz3qe(wX`f<|Iq{ot$B|V<>RMPS!*%`FMlYdv@ ze>>sCNr{t_+9b74>XkGi>AIx6q{5_|lI~8rFX@418^)>;x(FeeU46Tjwem*c>1)0D GR{bA8G5>!6 literal 0 HcmV?d00001 diff --git a/res/noto/NotoSansMono-Regular.ttf b/res/noto/NotoSansMono-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..dd3e6d008f88c9745e4a75f0a351854db6561d59 GIT binary patch literal 510612 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

sP&P|wi8*Ei*a z{JikH2_TEAH=JOF?T+)Afwm7Nnuq4z_@9dh`~LB<_WzW|yI8mMKti`5v~_Ft*}kTW ze(}h>p{2L2AKLNt>{j+l1x(4bx1R|fwGxu*KqgyU1i&6E_aey&P}v>^u%Q+_U1_0K zM=VK;1UG0V+K*fb8tpoo(!_!&JxtUUi&7igax8~}<_b6tXENQQ7(5kx8&IGxCd4MiKkC{To3jENpzaA1lHauH(&Y@N(ET-vNm04pUf zpSL$XGu2nqK*D2oPkL5rX0j2VFRt1=gG9!7#UJUdGAzxl-sEgd&aCfi+LI;evN1WU zzAr6*7B31{lCU{rWA#P+eRGwR4Oh~b`-JuISDXtw4Ez<7>mrfA0tPNp=n?!#Rgp9r z-(Q+OU-JC1^`dp`59~hajLG)^HKzDHa1RtkBA*AK(txBh=UD7rX+ykwB-vf*96_*X`pU{Bo32xxi;U^kZR4fnzi@=HRu@jefqAY31BeS zQUoW?zi<3u*a)75jR1gv>1+h-7;FTu!&1g;k0HuqS1yv!r?!B$pM{HP_p;w)*C}BHU98o?3|EsVCGS z+R9V95lR62Cc${{26zBZ>IQ(oDJwT%-WMA0NncjM0h)r(g~z9&dsH|No1>Wa^G-As zIFN_=s7I7Ok?r;(*xSNF9R9VhWy}DhG3a*KDds+@eKP0O0YvvqwN7cE6V>3 z0oDv48~4B&jfPQ@T-N}#ZZKm1LoF`4NW1s1xN1cvSXmi~l$BOiS3+-$#3M+E5(<=t z%fkLx!paSXa)?lr0=G$TBnTq#J`^A%7gQmi1HG8bR&2GEyDJ>7m@8Q2D6v{<+`(W_ z`ds#;vv(XG3(EMoi=$?9AmH}dOjfIDQ2_t0&1Sy`QC}237IvZDnvB^}eVo8RO|2|7 zCH>Q+K`k7}oUb!CW;uUneuywMcs?X#@X*&Whs%VnOgkv4c;U$CrNQzbG^mVVqq#>2 zHf#dGu$B5fB}nrN9>Kh_8LZH+slrz|nYOIk(;o`yLNi^P1f@;rhbLSaHewvP z50m4|xffIMSQ;>H>1WoQ-`IHmnrEI_x3@nS>ff{O8TN~7v*`_Uv+L7YwOMEmc*_VD z`oDvhWwF;3#y9pMpC6buo(Jop4f_L0=7a|p2cWc*8-lFdP-8*Qv%rV>F0WLeDzX5$ z1G3S6+gYA%^z{VX^|88k7HS#tM=Zr9*472| z?gG<*u3dmjT$mZmw+1a?^QlSshS0F`^>xBwbve}b(XgJVeMHA6a}Db#&o#H>Cb-vk z$;~UV%FT5Z0YgG#JK5g>zpL!;pn~quh(ISGXyNYw)iLT|&J^^;J^i4r26b{AE;1{6 z;o%^IHeL)?dbr9SXFaT!`^ldFv+N}kZX9%@RA9Ii{ZYjnr|aG37Fu^#u}9^A(PA(wm{xHWtnBKbHb z+Gd;%r$_d2w6(}qST8DQk&goo4!AiU< z{dWC(A11Q5R)6qb%e%jBewQ$8m#{`SIsPWuN4QFnA!Hv%j>hDk5KkDNf7Py$OZqiC zIGhIEyGoHGR3C>*fRp42fO}2c^}~-fJNjiz4rIhq@V7&-f4~>TN2tJjMmHk=q@0W) zZyRmHphIJJq_gZHX{1#Avh<6r^m8%*pk>E-YHekv68|T9D=T{w zq1xIIUau+J&NaCo@FBy0!q2bETMp*L-NGB}+w6C|50qm_X+Q8T7#!#%Wb_7;fYF

2qoy5S6M%eZ&Mk>uK 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�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 literal 0 HcmV?d00001 diff --git a/res/open-sans/LICENSE b/res/open-sans/LICENSE new file mode 100644 index 00000000..c91bd228 --- /dev/null +++ b/res/open-sans/LICENSE @@ -0,0 +1,88 @@ +Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans) + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/res/open-sans/OpenSans-Bold.ttf b/res/open-sans/OpenSans-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..fd79d43bea0293ac1b20e8aca1142627983d2c07 GIT binary patch literal 224592 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 literal 0 HcmV?d00001 diff --git a/res/open-sans/OpenSans-ExtraBold.ttf b/res/open-sans/OpenSans-ExtraBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..21f6f84a0799946fc4ae02c52b27e61c3762c745 GIT binary patch literal 222584 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�LN9 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%0DpzNyo5m`c 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%0AG%b#%#Weyp=oz<}IGPqm z)8c4aT%)1Wt!Z(MiMFk2@iJ>#98HU(X>l|yj;6)Yv^bg;N7LeHS{zM_Yxd#ySkvN~ zeHg82+}v^bg;*NE&CYg!yli=%0AG%cHosL)|1w>IGPqm)8c4a98HU( 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 !}#gv~NV!5|5YF|kE)J0y^zWbk4>=%|6F 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 literal 0 HcmV?d00001 diff --git a/res/open-sans/OpenSans-Semibold.ttf b/res/open-sans/OpenSans-Semibold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1a7679e3949fb045f152f456bc4adad31e8b9f55 GIT binary patch literal 221328 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%0DJH?LFC28_mTUAZOar9j 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

    MAzfu-#(7W5VH7%hxxNU1% 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 -> Subscription>> = LazyLock::new(|| Mutex::default()); diff --git a/src/widget/text.rs b/src/widget/text.rs index 0e3febbd..b542f2e0 100644 --- a/src/widget/text.rs +++ b/src/widget/text.rs @@ -27,48 +27,48 @@ pub enum Typography { /// [`Text`] widget with the Title 1 typography preset. pub fn title1<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { Text::new(text.into()) - .size(32.0) - .line_height(LineHeight::Absolute(44.0.into())) - .font(crate::font::light()) + .size(35.0) + .line_height(LineHeight::Absolute(52.0.into())) + .font(crate::font::semibold()) } /// [`Text`] widget with the Title 2 typography preset. pub fn title2<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { Text::new(text.into()) - .size(28.0) - .line_height(LineHeight::Absolute(36.0.into())) - .font(crate::font::default()) + .size(29.0) + .line_height(LineHeight::Absolute(43.0.into())) + .font(crate::font::semibold()) } /// [`Text`] widget with the Title 3 typography preset. pub fn title3<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { Text::new(text.into()) .size(24.0) - .line_height(LineHeight::Absolute(32.0.into())) - .font(crate::font::default()) + .line_height(LineHeight::Absolute(36.0.into())) + .font(crate::font::bold()) } /// [`Text`] widget with the Title 4 typography preset. pub fn title4<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { Text::new(text.into()) .size(20.0) - .line_height(LineHeight::Absolute(28.0.into())) - .font(crate::font::default()) + .line_height(LineHeight::Absolute(30.0.into())) + .font(crate::font::bold()) } /// [`Text`] widget with the Heading typography preset. pub fn heading<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { Text::new(text.into()) .size(14.0) - .line_height(LineHeight::Absolute(iced::Pixels(20.0))) - .font(crate::font::semibold()) + .line_height(LineHeight::Absolute(iced::Pixels(21.0))) + .font(crate::font::bold()) } /// [`Text`] widget with the Caption Heading typography preset. pub fn caption_heading<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { Text::new(text.into()) - .size(10.0) - .line_height(LineHeight::Absolute(iced::Pixels(14.0))) + .size(12.0) + .line_height(LineHeight::Absolute(iced::Pixels(17.0))) .font(crate::font::semibold()) } @@ -76,15 +76,15 @@ pub fn caption_heading<'a>(text: impl Into> + 'a) -> Text<'a, crate pub fn body<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { Text::new(text.into()) .size(14.0) - .line_height(LineHeight::Absolute(20.0.into())) + .line_height(LineHeight::Absolute(21.0.into())) .font(crate::font::default()) } /// [`Text`] widget with the Caption typography preset. pub fn caption<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { Text::new(text.into()) - .size(10.0) - .line_height(LineHeight::Absolute(14.0.into())) + .size(12.0) + .line_height(LineHeight::Absolute(17.0.into())) .font(crate::font::default()) } From ab6de5304bd0b852550f41dd13bd5dc7b10a60bb Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 19 Feb 2025 15:37:59 +0100 Subject: [PATCH 127/556] chore(theme): avoid logging errors for unset config parameters --- src/theme/mod.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 30346ec6..b3cdaf81 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -91,8 +91,11 @@ pub fn subscription(is_dark: bool) -> Subscription { crate::cosmic_theme::Theme::VERSION, ) .map(|res| { - for err in res.errors { - tracing::error!("{:?}", err); + for error in res.errors.into_iter().filter(cosmic_config::Error::is_err) { + tracing::error!( + ?error, + "error while watching system theme preference changes" + ); } Theme::system(Arc::new(res.config)) @@ -105,8 +108,8 @@ pub fn system_dark() -> Theme { }; let t = crate::cosmic_theme::Theme::get_entry(&helper).unwrap_or_else(|(errors, theme)| { - for err in errors { - tracing::error!("{:?}", err); + for error in errors.into_iter().filter(cosmic_config::Error::is_err) { + tracing::error!(?error, "error loading system dark theme"); } theme }); @@ -120,8 +123,8 @@ pub fn system_light() -> Theme { }; let t = crate::cosmic_theme::Theme::get_entry(&helper).unwrap_or_else(|(errors, theme)| { - for err in errors { - tracing::error!("{:?}", err); + for error in errors.into_iter().filter(cosmic_config::Error::is_err) { + tracing::error!(?error, "error loading system light theme"); } theme }); From 76348bb98554301a8500bd3f7c8524c9e2ced4e6 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 19 Feb 2025 16:57:24 +0100 Subject: [PATCH 128/556] chore: handle more sources of excess cosmic-config logs --- src/app/core.rs | 2 +- src/app/cosmic.rs | 20 ++++++++++++++++---- src/config/mod.rs | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/app/core.rs b/src/app/core.rs index f090cdc4..42c4429f 100644 --- a/src/app/core.rs +++ b/src/app/core.rs @@ -117,7 +117,7 @@ impl Default for Core { system_theme_mode: ThemeMode::config() .map(|c| { ThemeMode::get_entry(&c).unwrap_or_else(|(errors, mode)| { - for why in errors { + for why in errors.into_iter().filter(cosmic_config::Error::is_err) { tracing::error!(?why, "ThemeMode config entry error"); } mode diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 30a116f6..fd876cfd 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -192,7 +192,11 @@ where .core() .watch_config::(crate::config::ID) .map(|update| { - for why in update.errors { + for why in update + .errors + .into_iter() + .filter(cosmic_config::Error::is_err) + { tracing::error!(?why, "cosmic toolkit config update error"); } @@ -216,7 +220,11 @@ where }, ) .map(|update| { - for why in update.errors { + for why in update + .errors + .into_iter() + .filter(cosmic_config::Error::is_err) + { tracing::error!(?why, "cosmic theme config update error"); } Message::SystemThemeChange( @@ -229,8 +237,12 @@ where .core() .watch_config::(cosmic_theme::THEME_MODE_ID) .map(|update| { - for e in update.errors { - tracing::error!("{e}"); + for error in update + .errors + .into_iter() + .filter(cosmic_config::Error::is_err) + { + tracing::error!(?error, "error reading system theme mode update"); } Message::SystemThemeModeChange(update.keys, update.config) }) diff --git a/src/config/mod.rs b/src/config/mod.rs index 035c2f68..4f1a5a16 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -24,7 +24,7 @@ pub static COSMIC_TK: LazyLock> = LazyLock::new(|| { CosmicTk::config() .map(|c| { CosmicTk::get_entry(&c).unwrap_or_else(|(errors, mode)| { - for why in errors { + for why in errors.into_iter().filter(cosmic_config::Error::is_err) { tracing::error!(?why, "CosmicTk config entry error"); } mode From 1f826e38b9572fcd37b10caf0f8b53f2d64e34d4 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 19 Feb 2025 18:13:24 +0100 Subject: [PATCH 129/556] improv: call malloc_trim after view and update calls --- src/app/cosmic.rs | 25 ++++++++++++++++++++----- src/malloc.rs | 8 ++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index fd876cfd..42a16445 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -117,13 +117,18 @@ where &mut self, message: super::Message, ) -> iced::Task> { - match message { + let message = match message { super::Message::App(message) => self.app.update(message), super::Message::Cosmic(message) => self.cosmic_update(message), super::Message::None => iced::Task::none(), #[cfg(feature = "single-instance")] super::Message::DbusActivation(message) => self.app.dbus_activation(message), - } + }; + + #[cfg(target_env = "gnu")] + crate::malloc::trim(0); + + message } #[cfg(not(feature = "multi-window"))] @@ -291,16 +296,26 @@ where return self.app.view_window(id).map(super::Message::App); } - if self.app.core().window.use_template { + let view = if self.app.core().window.use_template { self.app.view_main() } else { self.app.view().map(super::Message::App) - } + }; + + #[cfg(target_env = "gnu")] + crate::malloc::trim(0); + + view } #[cfg(not(feature = "multi-window"))] pub fn view(&self) -> Element> { - self.app.view_main() + let view = self.app.view_main(); + + #[cfg(target_env = "gnu")] + crate::malloc::trim(0); + + view } } diff --git a/src/malloc.rs b/src/malloc.rs index 001f9a16..e980ea6f 100644 --- a/src/malloc.rs +++ b/src/malloc.rs @@ -3,9 +3,17 @@ use std::os::raw::c_int; const M_MMAP_THRESHOLD: c_int = -3; extern "C" { + fn malloc_trim(pad: usize); + fn mallopt(param: c_int, value: c_int) -> c_int; } +pub fn trim(pad: usize) { + unsafe { + malloc_trim(pad); + } +} + /// Prevents glibc from hoarding memory via memory fragmentation. pub fn limit_mmap_threshold(threshold: i32) { unsafe { From 8a0e74b189f53dae9b8001c6fa5cf1820b3ececb Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 20 Feb 2025 13:36:04 +0100 Subject: [PATCH 130/556] perf(iced): improve window resize performance --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 654f3c33..a8c31a09 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 654f3c33b54679c06c041be6eea2c03f6d7cfdce +Subproject commit a8c31a0982b134278e7ecb5a91e0d3ba0fa6d2dd From 238603640ca57c8c47d8a323ba25e75ee0411cd0 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 4 Mar 2025 15:47:57 -0500 Subject: [PATCH 131/556] fix: filter out tab events with control modifier --- src/keyboard_nav.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keyboard_nav.rs b/src/keyboard_nav.rs index a45e667a..cbfbf4a5 100644 --- a/src/keyboard_nav.rs +++ b/src/keyboard_nav.rs @@ -28,7 +28,7 @@ pub fn subscription() -> Subscription { modifiers, .. }) => match key { - Named::Tab => { + Named::Tab if !modifiers.control() => { return Some(if modifiers.shift() { Message::FocusPrevious } else { From ae14aade11f2ffed2af4ad6173db5a5726892e5f Mon Sep 17 00:00:00 2001 From: Tyler Hardin Date: Thu, 23 Jan 2025 08:16:50 -0500 Subject: [PATCH 132/556] fixup README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 429add0c..595e0d3b 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Some examples are included in the [examples](./examples) directory to to kicksta COSMIC adventure. To run them, you need to clone the repository with the following commands: ```sh -git clone https://github.com/pop-os/libcosmic +git clone --recurse-submodules https://github.com/pop-os/libcosmic cd libcosmic ``` From 50d2104485649bdddf1247f63ec3cf2eb81c5817 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Sat, 8 Mar 2025 21:59:21 -0500 Subject: [PATCH 133/556] fix: include config id in subscription id --- cosmic-config/src/dbus.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cosmic-config/src/dbus.rs b/cosmic-config/src/dbus.rs index c1808e35..e689acc6 100644 --- a/cosmic-config/src/dbus.rs +++ b/cosmic-config/src/dbus.rs @@ -62,7 +62,10 @@ pub fn watcher_subscription iced_futures::Subscription> { let id = std::any::TypeId::of::(); - Subscription::run_with_id(id, watcher_stream(settings_daemon, config_id, is_state)) + Subscription::run_with_id( + (id, config_id), + watcher_stream(settings_daemon, config_id, is_state), + ) } fn watcher_stream( From 508753ae69ecfe15dbd14991806cc1ecc78dfc53 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 7 Mar 2025 16:53:22 -0500 Subject: [PATCH 134/556] feat: high contrast theme updates --- cosmic-theme/src/model/derivation.rs | 3 +- cosmic-theme/src/model/theme.rs | 3 + src/theme/style/button.rs | 8 ++- src/theme/style/iced.rs | 100 +++++++++++++++++++-------- src/theme/style/segmented_button.rs | 22 +++++- src/widget/spin_button.rs | 21 ++++-- 6 files changed, 119 insertions(+), 38 deletions(-) diff --git a/cosmic-theme/src/model/derivation.rs b/cosmic-theme/src/model/derivation.rs index 994ee35a..92761422 100644 --- a/cosmic-theme/src/model/derivation.rs +++ b/cosmic-theme/src/model/derivation.rs @@ -25,9 +25,10 @@ impl Container { base: Srgba, on: Srgba, mut small_widget: Srgba, + is_high_contrast: bool, ) -> Self { let mut divider_c = on; - divider_c.alpha = 0.2; + divider_c.alpha = if is_high_contrast { 0.5 } else { 0.2 }; small_widget.alpha = 0.25; diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 3a1d0c83..a36bbda7 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -894,6 +894,7 @@ impl ThemeBuilder { text_steps_array.as_deref(), ), get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.neutral_6), + is_high_contrast, ); container @@ -968,6 +969,7 @@ impl ThemeBuilder { text_steps_array.as_deref(), ), get_small_widget_color(bg_index, 5, &neutral_steps, &p_ref.neutral_6), + is_high_contrast, ), primary, secondary: { @@ -1014,6 +1016,7 @@ impl ThemeBuilder { text_steps_array.as_deref(), ), get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.neutral_6), + is_high_contrast, ) }, accent: Component::colored_component( diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index 9f32b72b..8459908d 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -50,7 +50,7 @@ pub fn appearance( let cosmic = theme.cosmic(); let mut corner_radii = &cosmic.corner_radii.radius_xl; let mut appearance = Style::new(); - + let hc = theme.theme_type.is_high_contrast(); match style { Button::Standard | Button::Text @@ -71,6 +71,9 @@ pub fn appearance( if !matches!(style, Button::Standard) { appearance.text_color = text; appearance.icon_color = icon; + } else if hc { + appearance.border_color = style_component.border.into(); + appearance.border_width = 1.; } } @@ -105,6 +108,9 @@ pub fn appearance( if focused || selected { appearance.border_width = 2.0; appearance.border_color = cosmic.accent.base.into(); + } else if hc { + appearance.border_color = theme.current_container().component.divider.into(); + appearance.border_width = 1.; } return appearance; diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 37920618..cba2e9a3 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -528,7 +528,15 @@ impl iced_container::Catalog for Theme { } } - Container::ContextDrawer => Container::primary(cosmic), + Container::ContextDrawer => { + let mut a = Container::primary(cosmic); + + if cosmic.is_high_contrast { + a.border.width = 1.; + a.border.color = cosmic.primary.divider.into(); + } + a + } Container::Background => Container::background(cosmic), @@ -644,19 +652,39 @@ impl slider::Catalog for Theme { fn style(&self, class: &Self::Class<'_>, status: slider::Status) -> slider::Style { let cosmic: &cosmic_theme::Theme = self.cosmic(); + let hc = self.theme_type.is_high_contrast(); + let is_dark = self.theme_type.is_dark(); + let mut appearance = match class { Slider::Standard => //TODO: no way to set rail thickness { + let (active_track, inactive_track) = if hc { + ( + cosmic.accent_text_color(), + if is_dark { + cosmic.palette.neutral_6 + } else { + cosmic.palette.neutral_4 + }, + ) + } else { + (cosmic.accent.base, cosmic.palette.neutral_6) + }; slider::Style { rail: Rail { backgrounds: ( - Background::Color(cosmic.accent.base.into()), - Background::Color(cosmic.palette.neutral_6.into()), + Background::Color(active_track.into()), + Background::Color(inactive_track.into()), ), border: Border { radius: cosmic.corner_radii.radius_xs.into(), - ..Border::default() + color: if hc && !is_dark { + self.current_container().component.border.into() + } else { + Color::TRANSPARENT + }, + width: if hc && !is_dark { 1. } else { 0. }, }, width: 4.0, }, @@ -745,9 +773,6 @@ impl menu::Catalog for Theme { } } -/* - * TODO: Pick List - */ impl pick_list::Catalog for Theme { type Class<'a> = (); @@ -761,18 +786,24 @@ impl pick_list::Catalog for Theme { status: pick_list::Status, ) -> pick_list::Style { let cosmic = &self.cosmic(); - + let hc = cosmic.is_high_contrast; let appearance = pick_list::Style { text_color: cosmic.on_bg_color().into(), background: Color::TRANSPARENT.into(), placeholder_color: cosmic.on_bg_color().into(), border: Border { radius: cosmic.corner_radii.radius_m.into(), - ..Default::default() + width: if hc { 1. } else { 0. }, + color: if hc { + self.current_container().component.border.into() + } else { + Color::TRANSPARENT + }, }, // icon_size: 0.7, // TODO: how to replace handle_color: cosmic.on_bg_color().into(), }; + match status { pick_list::Status::Active => appearance, pick_list::Status::Hovered => pick_list::Style { @@ -957,33 +988,46 @@ impl progress_bar::Catalog for Theme { fn style(&self, class: &Self::Class<'_>) -> progress_bar::Style { let theme = self.cosmic(); + let (active_track, inactive_track) = if theme.is_high_contrast { + ( + theme.accent_text_color(), + if theme.is_dark { + theme.palette.neutral_6 + } else { + theme.palette.neutral_4 + }, + ) + } else { + (theme.accent.base, theme.background.divider) + }; + let border = Border { + radius: theme.corner_radii.radius_xs.into(), + color: if theme.is_high_contrast && !theme.is_dark { + self.current_container().component.border.into() + } else { + Color::TRANSPARENT + }, + width: if theme.is_high_contrast && !theme.is_dark { + 1. + } else { + 0. + }, + }; match class { ProgressBar::Primary => progress_bar::Style { - background: Color::from(theme.background.divider).into(), - bar: Color::from(theme.accent.base).into(), - border: Border { - color: Color::default(), - width: 0.0, - radius: theme.corner_radii.radius_xs.into(), - }, + background: Color::from(inactive_track).into(), + bar: Color::from(active_track).into(), + border, }, ProgressBar::Success => progress_bar::Style { - background: Color::from(theme.background.divider).into(), + background: Color::from(inactive_track).into(), bar: Color::from(theme.success.base).into(), - border: Border { - color: Color::default(), - width: 0.0, - radius: theme.corner_radii.radius_xs.into(), - }, + border, }, ProgressBar::Danger => progress_bar::Style { - background: Color::from(theme.background.divider).into(), + background: Color::from(inactive_track).into(), bar: Color::from(theme.destructive.base).into(), - border: Border { - color: Color::default(), - width: 0.0, - radius: theme.corner_radii.radius_xs.into(), - }, + border, }, ProgressBar::Custom(f) => f(self), } diff --git a/src/theme/style/segmented_button.rs b/src/theme/style/segmented_button.rs index 3fc4ae1e..8c2dbdaa 100644 --- a/src/theme/style/segmented_button.rs +++ b/src/theme/style/segmented_button.rs @@ -30,6 +30,16 @@ impl StyleSheet for Theme { SegmentedButton::TabBar => { let cosmic = self.cosmic(); let active = horizontal::tab_bar_active(cosmic); + let hc = cosmic.is_high_contrast; + let (border_end, border_start, border_top) = if hc { + ( + Some((1., container.component.border.into())), + Some((1., container.component.border.into())), + Some((1., container.component.border.into())), + ) + } else { + (None, None, None) + }; Appearance { border_radius: cosmic.corner_radii.radius_0.into(), inactive: ItemStatusAppearance { @@ -37,17 +47,23 @@ impl StyleSheet for Theme { first: ItemAppearance { border_radius: cosmic.corner_radii.radius_0.into(), border_bottom: Some((1.0, cosmic.accent.base.into())), - ..Default::default() + border_end, + border_start, + border_top, }, middle: ItemAppearance { border_radius: cosmic.corner_radii.radius_0.into(), border_bottom: Some((1.0, cosmic.accent.base.into())), - ..Default::default() + border_end, + border_start, + border_top, }, last: ItemAppearance { border_radius: cosmic.corner_radii.radius_0.into(), border_bottom: Some((1.0, cosmic.accent.base.into())), - ..Default::default() + border_end, + border_start, + border_top, }, text_color: container.component.on.into(), }, diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index 6c0df95f..10c19125 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -238,15 +238,26 @@ fn container_style(theme: &crate::Theme) -> iced_widget::container::Style { neutral_10.alpha = 0.1; let accent = &cosmic_theme.accent; let corners = &cosmic_theme.corner_radii; + let border = if theme.theme_type.is_high_contrast() { + let current_container = theme.current_container(); + Border { + radius: corners.radius_s.into(), + width: 1., + color: current_container.component.border.into(), + } + } else { + Border { + radius: corners.radius_s.into(), + width: 0.0, + color: accent.base.into(), + } + }; + iced_widget::container::Style { icon_color: Some(accent.base.into()), text_color: Some(cosmic_theme.palette.neutral_10.into()), background: None, - border: Border { - radius: corners.radius_s.into(), - width: 0.0, - color: accent.base.into(), - }, + border, shadow: Shadow::default(), } } From a234a45ea6e9d79a44f6741dff8ccd48cb29db94 Mon Sep 17 00:00:00 2001 From: ellieplayswow <164806095+ellieplayswow@users.noreply.github.com> Date: Mon, 10 Mar 2025 15:21:34 +0000 Subject: [PATCH 135/556] fix(segmented_button): ignore button release / finger lifted events when dragged out of target --- src/widget/segmented_button/widget.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 7841b3ae..e40465d0 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -592,6 +592,7 @@ where wheel_timestamp: Default::default(), dnd_state: Default::default(), fingers_pressed: Default::default(), + pressed_item: None, }) } @@ -918,11 +919,25 @@ where } if let Some(on_activate) = self.on_activate.as_ref() { - if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) = event + { + state.pressed_item = Some(Item::Tab(key)); + } else if let Event::Mouse(mouse::Event::ButtonReleased( + mouse::Button::Left, + )) | Event::Touch(touch::Event::FingerLifted { .. }) = event { - shell.publish(on_activate(key)); - return event::Status::Captured; + let mut can_activate = true; + if state.pressed_item != Some(Item::Tab(key)) { + can_activate = false; + } + + if can_activate { + shell.publish(on_activate(key)); + state.pressed_item = None; + return event::Status::Captured; + } } } @@ -1666,6 +1681,8 @@ pub struct LocalState { pub dnd_state: crate::widget::dnd_destination::State>, /// Tracks multi-touch events fingers_pressed: HashSet, + /// The currently pressed item + pressed_item: Option, } #[derive(Debug, Default, PartialEq)] From 64ddcb3bf2aed84256fe8261a0b05e1831720463 Mon Sep 17 00:00:00 2001 From: Vadim Khitrin Date: Sat, 8 Mar 2025 00:43:13 +0200 Subject: [PATCH 136/556] fix: Support Spawning Processes on macOS Allow using pipe on macOS when attempting to spawn a process. --- src/process.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/process.rs b/src/process.rs index efbfdd10..d473ae66 100644 --- a/src/process.rs +++ b/src/process.rs @@ -23,15 +23,25 @@ async fn read_from_pipe(read: OwnedFd) -> Option { /// Performs a double fork with setsid to spawn and detach a command. pub async fn spawn(mut command: Command) -> Option { + // NOTE: Windows platform is not supported command .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()); + // Handle Linux + #[cfg(target_os = "linux")] let Ok((read, write)) = rustix::pipe::pipe_with(rustix::pipe::PipeFlags::CLOEXEC) else { return None; }; + // Handle macOS + #[cfg(target_os = "macos")] + let Ok((read, write)) = rustix::pipe::pipe() else { + return None; + }; + + match unsafe { libc::fork() } { // Parent process 1.. => { From 872e7dd65ec7b1c155dd6ea368dc4d3d44820050 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 12 Mar 2025 08:35:17 -0600 Subject: [PATCH 137/556] Remove unused cctk import from autosize widget --- src/widget/autosize.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/widget/autosize.rs b/src/widget/autosize.rs index a792c6ca..adef4490 100644 --- a/src/widget/autosize.rs +++ b/src/widget/autosize.rs @@ -1,6 +1,5 @@ //! Autosize Container, which will resize the window to its contents. -use cctk::sctk::shell::xdg::window; use iced_core::event::{self, Event}; use iced_core::layout; use iced_core::mouse; From 617de4dc95ff2940258f1524d089c39072fbdd0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Wed, 12 Mar 2025 22:27:25 +0100 Subject: [PATCH 138/556] improv(app): visually align window corners to content --- src/app/mod.rs | 39 ++++++++++++++++++++++++--------------- src/theme/style/iced.rs | 13 ++++++++----- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index bfb2a142..940cc456 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -838,6 +838,13 @@ impl ApplicationExt for App { content_col.into() }; + // 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 view_column = crate::widget::column::with_capacity(2) .push_maybe(if core.window.show_headerbar { Some({ @@ -891,21 +898,23 @@ impl ApplicationExt for App { // Needed to avoid header bar corner gaps for apps without a content container header .apply(container) - .class(crate::theme::Container::custom(|theme| container::Style { - background: Some(iced::Background::Color( - theme.cosmic().background.base.into(), - )), - border: iced::Border { - radius: [ - theme.cosmic().radius_s()[0] - 1.0, - theme.cosmic().radius_s()[1] - 1.0, - theme.cosmic().radius_0()[2], - theme.cosmic().radius_0()[3], - ] - .into(), + .class(crate::theme::Container::custom(move |theme| { + container::Style { + background: Some(iced::Background::Color( + theme.cosmic().background.base.into(), + )), + border: iced::Border { + radius: [ + window_corner_radius[0] - 1.0, + window_corner_radius[1] - 1.0, + theme.cosmic().radius_0()[2], + theme.cosmic().radius_0()[3], + ] + .into(), + ..Default::default() + }, ..Default::default() - }, - ..Default::default() + } })) .apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_header"))) } @@ -929,7 +938,7 @@ impl ApplicationExt for App { border: iced::Border { color: theme.cosmic().bg_divider().into(), width: if sharp_corners { 0.0 } else { 1.0 }, - radius: theme.cosmic().radius_s().into(), + radius: window_corner_radius.into(), }, ..Default::default() } diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index cba2e9a3..44af94b1 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -461,6 +461,9 @@ impl iced_container::Catalog for Theme { fn style(&self, class: &Self::Class<'_>) -> iced_container::Style { let cosmic = self.cosmic(); + // Ensures visually aligned radii for content and window corners + let window_corner_radius = cosmic.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); + match class { Container::Transparent => iced_container::Style::default(), @@ -474,8 +477,8 @@ impl iced_container::Catalog for Theme { radius: [ cosmic.corner_radii.radius_0[0], cosmic.corner_radii.radius_0[1], - cosmic.corner_radii.radius_s[2], - cosmic.corner_radii.radius_s[3], + window_corner_radius[2], + window_corner_radius[3], ] .into(), ..Default::default() @@ -516,8 +519,8 @@ impl iced_container::Catalog for Theme { background: Some(iced::Background::Color(cosmic.background.base.into())), border: Border { radius: [ - cosmic.corner_radii.radius_s[0], - cosmic.corner_radii.radius_s[1], + window_corner_radius[0], + window_corner_radius[1], cosmic.corner_radii.radius_0[2], cosmic.corner_radii.radius_0[3], ] @@ -1001,7 +1004,7 @@ impl progress_bar::Catalog for Theme { (theme.accent.base, theme.background.divider) }; let border = Border { - radius: theme.corner_radii.radius_xs.into(), + radius: theme.corner_radii.radius_xl.into(), color: if theme.is_high_contrast && !theme.is_dark { self.current_container().component.border.into() } else { From 26ea70a6bdbb51edfcd5270ff2368e16c0183875 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 12 Mar 2025 18:22:01 -0400 Subject: [PATCH 139/556] chore: update fde --- Cargo.toml | 2 +- src/desktop.rs | 41 +++++++++++++++++++++++------------------ 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0c75ccc9..c70b75bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -121,7 +121,7 @@ zbus = { version = "4.2.1", default-features = false, optional = true } [target.'cfg(unix)'.dependencies] freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } -freedesktop-desktop-entry = { version = "0.5.1", optional = true } +freedesktop-desktop-entry = { version = "0.7.8", optional = true } shlex = { version = "1.3.0", optional = true } [dependencies.cosmic-theme] diff --git a/src/desktop.rs b/src/desktop.rs index c8a7ab9e..1bee3189 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -107,7 +107,7 @@ pub fn load_applications_for_app_ids<'a, 'b>( true // Fallback: If the name matches... } else if let Some(i) = app_ids.iter().position(|id| { - de.name(None) + de.name::<&str>(&[]) .map(|n| n.to_lowercase() == id.to_lowercase()) .unwrap_or_default() }) { @@ -134,11 +134,12 @@ pub fn load_applications_filtered<'a, F: FnMut(&DesktopEntry) -> bool>( mut filter: F, ) -> Vec { let locale = locale.into(); - + let locale_arr: Option> = locale.map(|l| vec![l]); freedesktop_desktop_entry::Iter::new(freedesktop_desktop_entry::default_paths()) .filter_map(|path| { - std::fs::read_to_string(&path).ok().and_then(|input| { - DesktopEntry::decode(&path, &input).ok().and_then(|de| { + DesktopEntry::from_path(&path, locale_arr.as_deref()) + .ok() + .and_then(|de| { if !filter(&de) { return None; } @@ -149,7 +150,6 @@ pub fn load_applications_filtered<'a, F: FnMut(&DesktopEntry) -> bool>( de, )) }) - }) }) .collect() } @@ -160,11 +160,12 @@ pub fn load_desktop_file<'a>( path: impl AsRef, ) -> Option { let path = path.as_ref(); - std::fs::read_to_string(path).ok().and_then(|input| { - DesktopEntry::decode(path, &input) - .ok() - .map(|de| DesktopEntryData::from_desktop_entry(locale, PathBuf::from(path), de)) - }) + let locale = locale.into(); + let locale_arr: Option> = locale.clone().map(|l| vec![l]); + + DesktopEntry::from_path(&path, locale_arr.as_deref()) + .ok() + .map(|de| DesktopEntryData::from_desktop_entry(locale, PathBuf::from(path), de)) } #[cfg(not(windows))] @@ -175,14 +176,14 @@ impl DesktopEntryData { de: DesktopEntry, ) -> DesktopEntryData { let locale = locale.into(); - + let locale_arr: Option> = locale.map(|l| vec![l]); let name = de - .name(locale) - .unwrap_or(Cow::Borrowed(de.appid)) + .name(locale_arr.as_deref().unwrap_or_default()) + .unwrap_or(Cow::Borrowed(&de.appid)) .to_string(); // check if absolute path exists and otherwise treat it as a name - let icon = de.icon().unwrap_or(de.appid); + let icon = de.icon().unwrap_or(&de.appid); let icon_path = Path::new(icon); let icon = if icon_path.is_absolute() && icon_path.exists() { IconSource::Path(icon_path.into()) @@ -200,16 +201,20 @@ impl DesktopEntryData { categories: de .categories() .unwrap_or_default() - .split_terminator(';') + .into_iter() .map(std::string::ToString::to_string) .collect(), desktop_actions: de .actions() .map(|actions| { actions - .split(';') + .into_iter() .filter_map(|action| { - let name = de.action_entry_localized(action, "Name", locale); + let name = de.action_entry_localized( + action, + "Name", + locale_arr.as_deref().unwrap_or_default(), + ); let exec = de.action_entry(action, "Exec"); if let (Some(name), Some(exec)) = (name, exec) { Some(DesktopAction { @@ -227,7 +232,7 @@ impl DesktopEntryData { .mime_type() .map(|mime_types| { mime_types - .split_terminator(';') + .into_iter() .filter_map(|mime_type| mime_type.parse::().ok()) .collect::>() }) From c7edd37b03cac28650cb96023a9cb965d3e062ac Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 12 Mar 2025 18:32:00 -0400 Subject: [PATCH 140/556] cargo fmt --- src/process.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/process.rs b/src/process.rs index d473ae66..037bed1e 100644 --- a/src/process.rs +++ b/src/process.rs @@ -41,7 +41,6 @@ pub async fn spawn(mut command: Command) -> Option { return None; }; - match unsafe { libc::fork() } { // Parent process 1.. => { From 337b80d4ca02a63631668212bccbace22b8bb49f Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Fri, 14 Mar 2025 11:56:21 -0400 Subject: [PATCH 141/556] feat: Tooltips and Better Surface Management --- Cargo.toml | 15 +- examples/applet/src/window.rs | 142 ++-- examples/application/Cargo.toml | 5 +- examples/application/src/main.rs | 160 ++++- examples/multi-window/src/window.rs | 7 +- examples/nav-context/src/main.rs | 6 +- examples/open-dialog/src/main.rs | 11 +- iced | 2 +- src/action.rs | 40 ++ src/app/action.rs | 75 +++ src/app/command.rs | 94 --- src/app/context_drawer.rs | 9 +- src/app/cosmic.rs | 398 ++++++++---- src/app/mod.rs | 338 ++-------- src/app/multi_window.rs | 3 + src/applet/mod.rs | 105 ++- src/command.rs | 45 ++ src/config/mod.rs | 4 +- src/{app => }/core.rs | 77 ++- src/dbus_activation.rs | 214 ++++++ src/desktop.rs | 5 +- src/ext.rs | 2 +- src/keyboard_nav.rs | 14 +- src/lib.rs | 25 +- src/malloc.rs | 3 + src/process.rs | 4 +- src/surface/action.rs | 152 +++++ src/surface/mod.rs | 85 +++ src/task.rs | 25 + src/task/mod.rs | 58 -- src/theme/portal.rs | 4 +- src/theme/style/iced.rs | 20 +- src/theme/style/menu_bar.rs | 8 +- src/theme/style/mod.rs | 5 + src/theme/style/tooltip.rs | 31 + src/widget/about.rs | 2 +- src/widget/aspect_ratio.rs | 7 +- src/widget/autosize.rs | 4 +- src/widget/button/icon.rs | 2 +- src/widget/button/image.rs | 2 +- src/widget/button/mod.rs | 2 +- src/widget/button/text.rs | 10 +- src/widget/button/widget.rs | 19 +- src/widget/calendar.rs | 14 +- src/widget/color_picker/mod.rs | 6 +- src/widget/context_drawer/overlay.rs | 3 +- src/widget/context_drawer/widget.rs | 2 +- src/widget/context_menu.rs | 4 +- src/widget/dialog.rs | 6 + src/widget/dnd_destination.rs | 4 +- src/widget/dnd_source.rs | 5 +- src/widget/dropdown/menu/mod.rs | 261 ++++++-- src/widget/dropdown/mod.rs | 38 +- src/widget/dropdown/multi/menu.rs | 8 +- src/widget/dropdown/multi/widget.rs | 6 +- src/widget/dropdown/widget.rs | 365 +++++++++-- src/widget/flex_row/widget.rs | 4 +- src/widget/grid/widget.rs | 14 +- src/widget/header_bar.rs | 17 +- src/widget/icon/mod.rs | 4 +- src/widget/icon/named.rs | 2 +- src/widget/id_container.rs | 4 +- src/widget/layer_container.rs | 3 +- src/widget/list/column.rs | 2 +- src/widget/menu.rs | 1 + src/widget/menu/key_bind.rs | 2 +- src/widget/menu/menu_bar.rs | 16 +- src/widget/menu/menu_inner.rs | 6 +- src/widget/menu/menu_tree.rs | 5 +- src/widget/mod.rs | 17 +- src/widget/nav_bar_toggle.rs | 2 +- src/widget/popover.rs | 10 +- src/widget/radio.rs | 2 +- src/widget/rectangle_tracker/mod.rs | 6 +- src/widget/responsive_container.rs | 299 +++++++++ src/widget/responsive_menu_bar.rs | 78 +++ src/widget/segmented_button/horizontal.rs | 4 +- src/widget/segmented_button/model/entity.rs | 2 +- src/widget/segmented_button/vertical.rs | 4 +- src/widget/segmented_button/widget.rs | 8 +- src/widget/settings/section.rs | 4 +- src/widget/spin_button.rs | 12 +- src/widget/text_input/input.rs | 146 +++-- src/widget/text_input/value.rs | 2 +- src/widget/toaster/mod.rs | 2 +- src/widget/toaster/widget.rs | 8 +- src/widget/wayland/mod.rs | 1 + src/widget/wayland/tooltip/mod.rs | 76 +++ src/widget/wayland/tooltip/widget.rs | 684 ++++++++++++++++++++ src/widget/wrapper.rs | 220 +++++++ 90 files changed, 3651 insertions(+), 977 deletions(-) create mode 100644 src/action.rs create mode 100644 src/app/action.rs delete mode 100644 src/app/command.rs create mode 100644 src/command.rs rename src/{app => }/core.rs (84%) create mode 100644 src/dbus_activation.rs create mode 100644 src/surface/action.rs create mode 100644 src/surface/mod.rs create mode 100644 src/task.rs delete mode 100644 src/task/mod.rs create mode 100644 src/theme/style/tooltip.rs create mode 100644 src/widget/responsive_container.rs create mode 100644 src/widget/responsive_menu_bar.rs create mode 100644 src/widget/wayland/mod.rs create mode 100644 src/widget/wayland/tooltip/mod.rs create mode 100644 src/widget/wayland/tooltip/widget.rs create mode 100644 src/widget/wrapper.rs diff --git a/Cargo.toml b/Cargo.toml index c70b75bc..7649e5d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,11 +66,14 @@ tokio = [ # Wayland window support wayland = [ "ashpd?/wayland", + "autosize", "iced_runtime/wayland", "iced/wayland", "iced_winit/wayland", "cctk", + "surface-message", ] +surface-message = [] # multi-window support multi-window = ["iced/multi-window"] # Render with wgpu @@ -84,11 +87,19 @@ winit_wgpu = ["winit", "wgpu"] xdg-portal = ["ashpd"] qr_code = ["iced/qr_code"] markdown = ["iced/markdown"] +async-std = [ + "dep:async-std", + "ashpd/async-std", + "rfd?/async-std", + "zbus?/async-io", + "iced/async-std", +] [dependencies] apply = "0.3.0" ashpd = { version = "0.9.1", default-features = false, optional = true } async-fs = { version = "2.1", optional = true } +async-std = { version = "1.10", optional = true } cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "178eb0b", optional = true } chrono = "0.4.35" cosmic-config = { path = "cosmic-config" } @@ -104,7 +115,9 @@ libc = { version = "0.2.155", optional = true } license = { version = "3.5.1", optional = true } mime = { version = "0.3.17", optional = true } palette = "0.7.3" -rfd = { version = "0.14.0", default-features = false, features = ["xdg-portal"], optional = true } +rfd = { version = "0.14.0", default-features = false, features = [ + "xdg-portal", +], optional = true } rustix = { version = "0.38.34", features = [ "pipe", "process", diff --git a/examples/applet/src/window.rs b/examples/applet/src/window.rs index 4f34ddfb..15085d85 100644 --- a/examples/applet/src/window.rs +++ b/examples/applet/src/window.rs @@ -1,27 +1,38 @@ -use cosmic::app::Core; -use cosmic::iced::application; -use cosmic::iced::platform_specific::shell::commands::popup::{destroy_popup, get_popup}; +use cosmic::app::{Core, Task}; + use cosmic::iced::window::Id; -use cosmic::iced::{Length, Limits, Task}; +use cosmic::iced::Length; use cosmic::iced_runtime::core::window; -use cosmic::theme::iced; -use cosmic::widget::{list_column, settings, toggler}; -use cosmic::{Element, Theme}; +use cosmic::surface::action::{app_popup, destroy_popup}; +use cosmic::widget::{dropdown::popup_dropdown, list_column, settings, toggler}; +use cosmic::Element; const ID: &str = "com.system76.CosmicAppletExample"; -#[derive(Default)] pub struct Window { core: Core, popup: Option, example_row: bool, + selected: Option, +} + +impl Default for Window { + fn default() -> Self { + Self { + core: Core::default(), + popup: None, + example_row: false, + selected: None, + } + } } #[derive(Clone, Debug)] pub enum Message { - TogglePopup, PopupClosed(Id), ToggleExampleRow(bool), + Selected(usize), + Surface(cosmic::surface::Action), } impl cosmic::Application for Window { @@ -38,7 +49,7 @@ impl cosmic::Application for Window { &mut self.core } - fn init(core: Core, _flags: Self::Flags) -> (Self, Task>) { + fn init(core: Core, _flags: Self::Flags) -> (Self, Task) { let window = Window { core, ..Default::default() @@ -50,60 +61,85 @@ impl cosmic::Application for Window { Some(Message::PopupClosed(id)) } - fn update(&mut self, message: Self::Message) -> Task> { + fn update(&mut self, message: Message) -> Task { match message { - Message::TogglePopup => { - return if let Some(p) = self.popup.take() { - destroy_popup(p) - } else { - let new_id = Id::unique(); - self.popup.replace(new_id); - let mut popup_settings = self.core.applet.get_popup_settings( - self.core.main_window_id().unwrap(), - new_id, - None, - None, - None, - ); - popup_settings.positioner.size_limits = Limits::NONE - .max_width(372.0) - .min_width(300.0) - .min_height(200.0) - .max_height(1080.0) - .height(500) - .width(500); - popup_settings.positioner.size = Some((500, 500)); - get_popup(popup_settings) - }; - } Message::PopupClosed(id) => { if self.popup.as_ref() == Some(&id) { self.popup = None; } } - Message::ToggleExampleRow(toggled) => self.example_row = toggled, - } + Message::ToggleExampleRow(toggled) => { + self.example_row = toggled; + } + + Message::Surface(a) => { + return cosmic::task::message(cosmic::Action::Cosmic( + cosmic::app::Action::Surface(a), + )); + } + Message::Selected(i) => { + self.selected = Some(i); + } + }; Task::none() } - fn view(&self) -> Element { - self.core - .applet - .icon_button("display-symbolic") - .on_press(Message::TogglePopup) - .into() + fn view(&self) -> Element { + let btn = self.core.applet.icon_button("display-symbolic").on_press( + if let Some(id) = self.popup { + Message::Surface(destroy_popup(id)) + } else { + Message::Surface(app_popup::( + |state: &mut Window| { + let new_id = Id::unique(); + state.popup = Some(new_id); + let popup_settings = state.core.applet.get_popup_settings( + state.core.main_window_id().unwrap(), + new_id, + None, + None, + None, + ); + + popup_settings + }, + Some(Box::new(move |state: &Window| { + let content_list = list_column() + .padding(5) + .spacing(0) + .add(settings::item( + "Example row", + cosmic::widget::container( + toggler(state.example_row) + .on_toggle(|value| Message::ToggleExampleRow(value)), + ) + .height(Length::Fixed(50.)), + )) + .add(popup_dropdown( + &["1", "asdf", "hello", "test"], + state.selected, + Message::Selected, + state.popup.unwrap_or(Id::NONE), + Message::Surface, + |m| m, + )); + Element::from(state.core.applet.popup_container(content_list)) + .map(cosmic::Action::App) + })), + )) + }, + ); + + Element::from(self.core.applet.applet_tooltip::( + btn, + "test", + self.popup.is_some(), + |a| Message::Surface(a), + )) } - fn view_window(&self, _id: Id) -> Element { - let content_list = list_column().padding(5).spacing(0).add(settings::item( - "Example row", - cosmic::widget::container( - toggler(self.example_row).on_toggle(|value| Message::ToggleExampleRow(value)), - ) - .height(Length::Fixed(50.)), - )); - - self.core.applet.popup_container(content_list).into() + fn view_window(&self, _id: Id) -> Element { + "oops".into() } fn style(&self) -> Option { diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index 695f9897..23413086 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -3,6 +3,10 @@ name = "application" version = "0.1.0" edition = "2021" +[features] +default = ["wayland"] +wayland = ["libcosmic/wayland"] + [dependencies] tracing = "0.1.37" tracing-subscriber = "0.3.17" @@ -18,7 +22,6 @@ features = [ "xdg-portal", "dbus-config", "a11y", - "wayland", "wgpu", "single-instance", "multi-window", diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index a77b9d74..bcffc316 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -3,12 +3,27 @@ //! Application API example +use std::collections::HashMap; +use std::sync::LazyLock; + use cosmic::app::{Core, Settings, Task}; +use cosmic::iced::alignment::{Horizontal, Vertical}; use cosmic::iced::widget::column; +use cosmic::iced::Length; use cosmic::iced_core::Size; -use cosmic::widget::nav_bar; +use cosmic::widget::icon::{from_name, Handle}; +use cosmic::widget::menu::KeyBind; +use cosmic::widget::{button, text}; +use cosmic::widget::{ + container, + menu::menu_button, + menu::{self, action::MenuAction}, + nav_bar, responsive, +}; use cosmic::{executor, iced, ApplicationExt, Element}; +static MENU_ID: LazyLock = LazyLock::new(|| iced::id::Id::new("menu_id")); + #[derive(Clone, Copy)] pub enum Page { Page1, @@ -28,11 +43,24 @@ impl Page { } } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Action { + Hi, +} + +impl MenuAction for Action { + type Message = Message; + + fn message(&self) -> Message { + Message::Hi + } +} + /// Runs application with these settings #[rustfmt::skip] fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - let _ = tracing_log::LogTracer::init(); + // tracing_subscriber::fmt::init(); + // let _ = tracing_log::LogTracer::init(); let input = vec![ (Page::Page1, "🖖 Hello from libcosmic.".into()), @@ -56,6 +84,8 @@ pub enum Message { Input2(String), Ignore, ToggleHide, + Surface(cosmic::surface::Action), + Hi, } /// The [`App`] stores application-specific state. @@ -65,6 +95,7 @@ pub struct App { input_1: String, input_2: String, hidden: bool, + keybinds: HashMap, } /// Implement [`cosmic::Application`] to integrate with COSMIC. @@ -105,6 +136,7 @@ impl cosmic::Application for App { input_1: String::new(), input_2: String::new(), hidden: true, + keybinds: HashMap::new(), }; let command = app.update_title(); @@ -136,6 +168,12 @@ impl cosmic::Application for App { Message::ToggleHide => { self.hidden = !self.hidden; } + Message::Surface(_) => { + // unimplemented!() + } + Message::Hi => { + dbg!("hi"); + } } Task::none() } @@ -178,6 +216,122 @@ impl cosmic::Application for App { Element::from(centered) } + + fn header_start(&self) -> Vec> { + use cosmic::widget::menu::Tree; + #[cfg(not(feature = "wayland"))] + { + vec![cosmic::widget::menu::bar(vec![ + Tree::with_children( + menu::root("hiiiiiiiiiiiiiiiiiii 1"), + menu::items( + &self.keybinds, + vec![menu::Item::Button("hi", None, Action::Hi)], + ), + ), + Tree::with_children( + menu::root("hiiiiiiiiiiiiiiiiii 2"), + menu::items( + &self.keybinds, + vec![menu::Item::Button("hi 2", None, Action::Hi)], + ), + ), + Tree::with_children( + menu::root("hiiiiiiiiiiiiiiiiiiiii 3"), + menu::items( + &self.keybinds, + vec![ + menu::Item::Button("hi 3", None, Action::Hi), + menu::Item::Button("hi 3 #2", None, Action::Hi), + ], + ), + ), + Tree::with_children( + menu::root("hi 3"), + menu::items( + &self.keybinds, + vec![ + menu::Item::Button("hi 3", None, Action::Hi), + menu::Item::Button("hi 3 #2", None, Action::Hi), + menu::Item::Button("hi 3 #3", None, Action::Hi), + ], + ), + ), + Tree::with_children( + menu::root("hi 4"), + menu::items( + &self.keybinds, + vec![ + menu::Item::Folder( + "hi 41 extra root", + vec![menu::Item::Button("hi 3", None, Action::Hi)], + ), + menu::Item::Button("hi 42", None, Action::Hi), + menu::Item::Button("hi 43", None, Action::Hi), + menu::Item::Button("hi 44", None, Action::Hi), + menu::Item::Button("hi 45", None, Action::Hi), + menu::Item::Button("hi 46", None, Action::Hi), + ], + ), + ), + ]) + .into()] + } + #[cfg(feature = "wayland")] + { + vec![cosmic::widget::responsive_menu_bar( + self.core(), + &self.keybinds, + MENU_ID.clone(), + Message::Surface, + vec![ + ( + "hiiiiiiiiiiiiiiiiiii 1".into(), + vec![menu::Item::Button("hi 1".into(), None, Action::Hi)], + ), + ( + "hiiiiiiiiiiiiiiiiiii 2".into(), + vec![ + menu::Item::Button("hi 2".into(), None, Action::Hi), + menu::Item::Button("hi 22".into(), None, Action::Hi), + ], + ), + ( + "hiiiiiiiiiiiiiiiiiii 3".into(), + vec![ + menu::Item::Button("hi 3".into(), None, Action::Hi), + menu::Item::Button("hi 33".into(), None, Action::Hi), + menu::Item::Button("hi 333".into(), None, Action::Hi), + ], + ), + ( + "hiiiiiiiiiiiiiiiiiii 4".into(), + vec![ + menu::Item::Button("hi 4".into(), None, Action::Hi), + menu::Item::Button("hi 44".into(), None, Action::Hi), + menu::Item::Button("hi 444".into(), None, Action::Hi), + menu::Item::Folder( + "nest 4".into(), + vec![ + menu::Item::Button("hi 4".into(), None, Action::Hi), + menu::Item::Button("hi 44".into(), None, Action::Hi), + menu::Item::Button("hi 444".into(), None, Action::Hi), + menu::Item::Folder( + "nest 2 4".into(), + vec![ + menu::Item::Button("hi 4".into(), None, Action::Hi), + menu::Item::Button("hi 44".into(), None, Action::Hi), + menu::Item::Button("hi 444".into(), None, Action::Hi), + ], + ), + ], + ), + ], + ), + ], + )] + } + } } impl App diff --git a/examples/multi-window/src/window.rs b/examples/multi-window/src/window.rs index 15663ea3..96d166d4 100644 --- a/examples/multi-window/src/window.rs +++ b/examples/multi-window/src/window.rs @@ -74,10 +74,7 @@ impl cosmic::Application for MultiWindow { }) } - fn update( - &mut self, - message: Self::Message, - ) -> iced::Task> { + fn update(&mut self, message: Self::Message) -> iced::Task> { match message { Message::CloseWindow(id) => window::close(id), Message::WindowClosed(id) => { @@ -110,7 +107,7 @@ impl cosmic::Application for MultiWindow { ); _ = self.set_window_title(format!("window_{}", count), id); - spawn_window.map(|id| cosmic::app::Message::App(Message::WindowOpened(id, None))) + spawn_window.map(|id| cosmic::Action::App(Message::WindowOpened(id, None))) } Message::Input(id, value) => { if let Some((_, w)) = self.windows.iter_mut().find(|e| e.1.input_id == id) { diff --git a/examples/nav-context/src/main.rs b/examples/nav-context/src/main.rs index 58e20849..be458171 100644 --- a/examples/nav-context/src/main.rs +++ b/examples/nav-context/src/main.rs @@ -70,10 +70,10 @@ pub enum NavMenuAction { } impl menu::Action for NavMenuAction { - type Message = cosmic::app::Message; + type Message = cosmic::Action; fn message(&self) -> Self::Message { - cosmic::app::Message::App(Message::NavMenuAction(*self)) + cosmic::Action::App(Message::NavMenuAction(*self)) } } @@ -131,7 +131,7 @@ impl cosmic::Application for App { fn nav_context_menu( &self, id: nav_bar::Id, - ) -> Option>>> { + ) -> Option>>> { Some(menu::items( &HashMap::new(), vec![ diff --git a/examples/open-dialog/src/main.rs b/examples/open-dialog/src/main.rs index 5ae2df47..0edac466 100644 --- a/examples/open-dialog/src/main.rs +++ b/examples/open-dialog/src/main.rs @@ -34,6 +34,7 @@ pub enum Message { OpenError(Arc), OpenFile, Selected(Url), + Surface(cosmic::surface::Action), } /// The [`App`] stores application-specific state. @@ -91,13 +92,11 @@ impl cosmic::Application for App { Message::Cancelled => { eprintln!("open file dialog cancelled"); } - Message::FileRead(url, contents) => { eprintln!("read file"); self.selected_file = Some(url); self.file_contents = contents; } - Message::Selected(url) => { eprintln!("selected file"); @@ -142,8 +141,6 @@ impl cosmic::Application for App { Message::FileRead(url, contents) }); } - - // Creates a new open dialog. Message::OpenFile => { return cosmic::task::future(async move { eprintln!("opening new dialog"); @@ -169,13 +166,9 @@ impl cosmic::Application for App { } }); } - - // Displays an error in the application's warning bar. Message::Error(why) => { self.error_status = Some(why); } - - // Displays an error in the application's warning bar. Message::OpenError(why) => { if let Some(why) = Arc::into_inner(why) { let mut source: &dyn std::error::Error = &why; @@ -190,10 +183,10 @@ impl cosmic::Application for App { self.error_status = Some(string); } } - Message::CloseError => { self.error_status = None; } + Message::Surface(surface) => {} } Task::none() diff --git a/iced b/iced index a8c31a09..07880754 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit a8c31a0982b134278e7ecb5a91e0d3ba0fa6d2dd +Subproject commit 0788075435aa7a6532327b9435fcfc7a12e1b6a6 diff --git a/src/action.rs b/src/action.rs new file mode 100644 index 00000000..b7162896 --- /dev/null +++ b/src/action.rs @@ -0,0 +1,40 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +#[cfg(feature = "winit")] +use crate::app; +#[cfg(feature = "single-instance")] +use crate::dbus_activation; + +pub const fn app(message: M) -> Action { + Action::App(message) +} +#[cfg(feature = "winit")] +pub const fn cosmic(message: app::Action) -> Action { + Action::Cosmic(message) +} + +pub const fn none() -> Action { + Action::None +} + +#[derive(Clone, Debug)] +#[must_use] +pub enum Action { + /// Messages from the application, for the application. + App(M), + #[cfg(feature = "winit")] + /// Internal messages to be handled by libcosmic. + Cosmic(app::Action), + #[cfg(feature = "single-instance")] + /// Dbus activation messages + DbusActivation(dbus_activation::Message), + /// Do nothing + None, +} + +impl From for Action { + fn from(value: M) -> Self { + Self::App(value) + } +} diff --git a/src/app/action.rs b/src/app/action.rs new file mode 100644 index 00000000..44655ffa --- /dev/null +++ b/src/app/action.rs @@ -0,0 +1,75 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use crate::surface; +use crate::theme::Theme; +use crate::widget::nav_bar; +use crate::{config::CosmicTk, keyboard_nav}; +#[cfg(feature = "wayland")] +use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; +use cosmic_theme::ThemeMode; +#[cfg(not(any(feature = "multi-window", feature = "wayland")))] +use iced::Application as IcedApplication; + +/// A message managed internally by COSMIC. +#[derive(Clone, Debug)] +pub enum Action { + /// Activate the application + Activate(String), + /// Application requests theme change. + AppThemeChange(Theme), + /// Requests to close the window. + Close, + /// Closes or shows the context drawer. + ContextDrawer(bool), + /// Requests to drag the window. + Drag, + /// Window focus changed + Focus(iced::window::Id), + /// Keyboard shortcuts managed by libcosmic. + KeyboardNav(keyboard_nav::Action), + /// Requests to maximize the window. + Maximize, + /// Requests to minimize the window. + Minimize, + /// Activates a navigation element from the nav bar. + NavBar(nav_bar::Id), + /// Activates a context menu for an item from the nav bar. + NavBarContext(nav_bar::Id), + /// Set scaling factor + ScaleFactor(f32), + /// Show the window menu + ShowWindowMenu, + /// Tracks updates to window suggested size. + #[cfg(feature = "applet")] + SuggestedBounds(Option), + /// Internal surface message + Surface(surface::Action), + /// Notifies that a surface was closed. + /// Any data relating to the surface should be cleaned up. + SurfaceClosed(iced::window::Id), + /// Notification of system theme changes. + SystemThemeChange(Vec<&'static str>, Theme), + /// Notification of system theme mode changes. + SystemThemeModeChange(Vec<&'static str>, ThemeMode), + /// Toggles visibility of the nav bar. + ToggleNavBar, + /// Toggles the condensed status of the nav bar. + ToggleNavBarCondensed, + /// Toolkit configuration update + ToolkitConfig(CosmicTk), + /// Window focus lost + Unfocus(iced::window::Id), + /// Updates the window maximized state + WindowMaximized(iced::window::Id, bool), + /// Updates the tracked window geometry. + WindowResize(iced::window::Id, f32, f32), + /// Tracks updates to window state. + #[cfg(feature = "wayland")] + WindowState(iced::window::Id, WindowState), + /// Capabilities the window manager supports + #[cfg(feature = "wayland")] + WmCapabilities(iced::window::Id, WindowManagerCapabilities), + #[cfg(feature = "xdg-portal")] + DesktopSettings(crate::theme::portal::Desktop), +} diff --git a/src/app/command.rs b/src/app/command.rs deleted file mode 100644 index b39118f5..00000000 --- a/src/app/command.rs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2023 System76 -// SPDX-License-Identifier: MPL-2.0 - -use iced::window; - -/// Asynchronous actions for COSMIC applications. -use super::Message; - -/// Commands for COSMIC applications. -pub type Task = iced::Task>; - -/// Creates a task which yields a [`crate::app::Message`]. -pub fn message(message: Message) -> Task { - crate::task::message(message) -} - -/// Convenience methods for building message-based commands. -pub mod message { - /// Creates a task which yields an application message. - pub fn app(message: M) -> crate::app::Task { - super::message(super::Message::App(message)) - } - - /// Creates a task which yields a cosmic message. - pub fn cosmic(message: crate::app::cosmic::Message) -> crate::app::Task { - super::message(super::Message::Cosmic(message)) - } -} - -impl crate::app::Core { - pub fn drag(&self, id: Option) -> iced::Task> { - let Some(id) = id.or(self.main_window) else { - return iced::Task::none(); - }; - crate::task::drag(id).map(Message::Cosmic) - } - - pub fn maximize( - &self, - id: Option, - maximized: bool, - ) -> iced::Task> { - let Some(id) = id.or(self.main_window) else { - return iced::Task::none(); - }; - crate::task::maximize(id, maximized).map(Message::Cosmic) - } - - pub fn minimize(&self, id: Option) -> iced::Task> { - let Some(id) = id.or(self.main_window) else { - return iced::Task::none(); - }; - crate::task::minimize(id).map(Message::Cosmic) - } - - pub fn set_scaling_factor(&self, factor: f32) -> iced::Task> { - message::cosmic(super::cosmic::Message::ScaleFactor(factor)) - } - - pub fn set_title( - &self, - id: Option, - title: String, - ) -> iced::Task> { - let Some(id) = id.or(self.main_window) else { - return iced::Task::none(); - }; - crate::task::set_title(id, title).map(Message::Cosmic) - } - - pub fn set_windowed( - &self, - id: Option, - ) -> iced::Task> { - let Some(id) = id.or(self.main_window) else { - return iced::Task::none(); - }; - crate::task::set_windowed(id).map(Message::Cosmic) - } - - pub fn toggle_maximize( - &self, - id: Option, - ) -> iced::Task> { - let Some(id) = id.or(self.main_window) else { - return iced::Task::none(); - }; - crate::task::toggle_maximize(id).map(Message::Cosmic) - } -} - -pub fn set_theme(theme: crate::Theme) -> iced::Task> { - message::cosmic(super::cosmic::Message::AppThemeChange(theme)) -} diff --git a/src/app/context_drawer.rs b/src/app/context_drawer.rs index af937168..bb681242 100644 --- a/src/app/context_drawer.rs +++ b/src/app/context_drawer.rs @@ -1,3 +1,6 @@ +// Copyright 2024 System76 +// SPDX-License-Identifier: MPL-2.0 +// use std::borrow::Cow; use crate::Element; @@ -12,11 +15,11 @@ pub struct ContextDrawer<'a, Message: Clone + 'static> { } #[cfg(feature = "about")] -pub fn about<'a, Message: Clone + 'static>( - about: &'a crate::widget::about::About, +pub fn about( + about: &crate::widget::about::About, on_url_press: impl Fn(String) -> Message, on_close: Message, -) -> ContextDrawer<'a, Message> { +) -> ContextDrawer<'_, Message> { context_drawer(crate::widget::about(about, on_url_press), on_close) } diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 42a16445..919e6042 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -2,13 +2,12 @@ // SPDX-License-Identifier: MPL-2.0 use std::borrow::Borrow; +use std::collections::HashMap; use std::sync::Arc; -use super::{Application, ApplicationExt, Core, Subscription}; -use crate::config::CosmicTk; +use super::{Action, Application, ApplicationExt, Subscription}; use crate::theme::{Theme, ThemeType, THEME}; -use crate::widget::nav_bar; -use crate::{keyboard_nav, Element}; +use crate::{keyboard_nav, Core, Element}; #[cfg(feature = "wayland")] use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use cosmic_theme::ThemeMode; @@ -20,69 +19,14 @@ use iced::{window, Task}; use iced_futures::event::listen_with; use palette::color_difference::EuclideanDistance; -/// A message managed internally by COSMIC. -#[derive(Clone, Debug)] -pub enum Message { - /// Application requests theme change. - AppThemeChange(Theme), - /// Requests to close the window. - Close, - /// Closes or shows the context drawer. - ContextDrawer(bool), - /// Requests to drag the window. - Drag, - /// Keyboard shortcuts managed by libcosmic. - KeyboardNav(keyboard_nav::Message), - /// Requests to maximize the window. - Maximize, - /// Requests to minimize the window. - Minimize, - /// Activates a navigation element from the nav bar. - NavBar(nav_bar::Id), - /// Activates a context menu for an item from the nav bar. - NavBarContext(nav_bar::Id), - /// Set scaling factor - ScaleFactor(f32), - /// Notification of system theme changes. - SystemThemeChange(Vec<&'static str>, Theme), - /// Notification of system theme mode changes. - SystemThemeModeChange(Vec<&'static str>, ThemeMode), - /// Toggles visibility of the nav bar. - ToggleNavBar, - /// Toggles the condensed status of the nav bar. - ToggleNavBarCondensed, - /// Toolkit configuration update - ToolkitConfig(CosmicTk), - /// Updates the window maximized state - WindowMaximized(window::Id, bool), - /// Updates the tracked window geometry. - WindowResize(window::Id, f32, f32), - /// Tracks updates to window state. - #[cfg(feature = "wayland")] - WindowState(window::Id, WindowState), - /// Capabilities the window manager supports - #[cfg(feature = "wayland")] - WmCapabilities(window::Id, WindowManagerCapabilities), - /// Notifies that a surface was closed. - /// Any data relating to the surface should be cleaned up. - SurfaceClosed(window::Id), - /// Activate the application - Activate(String), - ShowWindowMenu, - #[cfg(feature = "xdg-portal")] - DesktopSettings(crate::theme::portal::Desktop), - /// Window focus changed - Focus(window::Id), - /// Window focus lost - Unfocus(window::Id), - /// Tracks updates to window suggested size. - #[cfg(feature = "applet")] - SuggestedBounds(Option), -} - #[derive(Default)] -pub struct Cosmic { +pub struct Cosmic { pub app: App, + #[cfg(feature = "wayland")] + pub surface_views: HashMap< + window::Id, + Box Fn(&'a App) -> Element<'a, crate::Action>>, + >, } impl Cosmic @@ -91,7 +35,7 @@ where { pub fn init( (mut core, flags): (Core, T::Flags), - ) -> (Self, iced::Task>) { + ) -> (Self, iced::Task>) { #[cfg(feature = "dbus-config")] { use iced_futures::futures::executor::block_on; @@ -113,16 +57,157 @@ where self.app.title(id).to_string() } + pub fn surface_update( + &mut self, + _surface_message: crate::surface::Action, + ) -> iced::Task> { + #[cfg(feature = "wayland")] + match _surface_message { + crate::surface::Action::AppSubsurface(settings, view) => { + let Some(settings) = std::sync::Arc::try_unwrap(settings) + .ok() + .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else { + tracing::error!("Invalid settings for subsurface"); + return Task::none(); + }; + + if let Some(view) = view.and_then(|view| { + match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Fn(&'a T) -> Element<'a, crate::Action> + + Send + + Sync, + >>() { + Ok(v) => Some(v), + Err(err) => { + tracing::error!("Invalid view for subsurface view: {err:?}"); + + None + } + } + }) { + let settings = settings(&mut self.app); + self.get_subsurface(settings, *view) + } else { + iced_winit::commands::subsurface::get_subsurface(settings(&mut self.app)) + } + } + crate::surface::Action::Subsurface(settings, view) => { + let Some(settings) = std::sync::Arc::try_unwrap(settings) + .ok() + .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else { + tracing::error!("Invalid settings for subsurface"); + return Task::none(); + }; + + if let Some(view) = view.and_then(|view| { + match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Element<'static, crate::Action> + Send + Sync, + >>() { + Ok(v) => Some(v), + Err(err) => { + tracing::error!("Invalid view for subsurface view: {err:?}"); + + None + } + } + }) { + let settings = settings(); + self.get_subsurface(settings, Box::new(move |_| view())) + } else { + iced_winit::commands::subsurface::get_subsurface(settings()) + } + } + crate::surface::Action::AppPopup(settings, view) => { + let Some(settings) = std::sync::Arc::try_unwrap(settings) + .ok() + .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync>>().ok()) else { + tracing::error!("Invalid settings for popup"); + return Task::none(); + }; + + if let Some(view) = view.and_then(|view| { + match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Fn(&'a T) -> Element<'a, crate::Action> + + Send + + Sync, + >>() { + Ok(v) => Some(v), + Err(err) => { + tracing::error!("Invalid view for subsurface view: {err:?}"); + None + } + } + }) { + let settings = settings(&mut self.app); + + self.get_popup(settings, *view) + } else { + iced_winit::commands::popup::get_popup(settings(&mut self.app)) + } + } + #[cfg(feature = "wayland")] + crate::surface::Action::DestroyPopup(id) => { + iced_winit::commands::popup::destroy_popup(id) + } + #[cfg(feature = "wayland")] + crate::surface::Action::DestroySubsurface(id) => { + iced_winit::commands::subsurface::destroy_subsurface(id) + } + crate::surface::Action::ResponsiveMenuBar { + menu_bar, + limits, + size, + } => { + let core = self.app.core_mut(); + core.menu_bars.insert(menu_bar, (limits, size)); + iced::Task::none() + } + crate::surface::Action::Popup(settings, view) => { + let Some(settings) = std::sync::Arc::try_unwrap(settings) + .ok() + .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync>>().ok()) else { + tracing::error!("Invalid settings for popup"); + return Task::none(); + }; + + if let Some(view) = view.and_then(|view| { + match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Element<'static, crate::Action> + Send + Sync, + >>() { + Ok(v) => Some(v), + Err(err) => { + tracing::error!("Invalid view for subsurface view: {err:?}"); + None + } + } + }) { + let settings = settings(); + + self.get_popup(settings, Box::new(move |_| view())) + } else { + iced_winit::commands::popup::get_popup(settings()) + } + } + crate::surface::Action::Ignore => iced::Task::none(), + crate::surface::Action::Task(f) => { + f().map(|sm| crate::Action::Cosmic(Action::Surface(sm))) + } + } + + #[cfg(not(feature = "wayland"))] + iced::Task::none() + } + pub fn update( &mut self, - message: super::Message, - ) -> iced::Task> { + message: crate::Action, + ) -> iced::Task> { let message = match message { - super::Message::App(message) => self.app.update(message), - super::Message::Cosmic(message) => self.cosmic_update(message), - super::Message::None => iced::Task::none(), + crate::Action::App(message) => self.app.update(message), + crate::Action::Cosmic(message) => self.cosmic_update(message), + crate::Action::None => iced::Task::none(), #[cfg(feature = "single-instance")] - super::Message::DbusActivation(message) => self.app.dbus_activation(message), + crate::Action::DbusActivation(message) => self.app.dbus_activation(message), }; #[cfg(target_env = "gnu")] @@ -158,29 +243,29 @@ where } #[allow(clippy::too_many_lines)] - pub fn subscription(&self) -> Subscription> { + pub fn subscription(&self) -> Subscription> { let window_events = listen_with(|event, _, id| { match event { iced::Event::Window(window::Event::Resized(iced::Size { width, height })) => { - return Some(Message::WindowResize(id, width, height)); + return Some(Action::WindowResize(id, width, height)); } iced::Event::Window(window::Event::Closed) => { - return Some(Message::SurfaceClosed(id)); + return Some(Action::SurfaceClosed(id)); } - iced::Event::Window(window::Event::Focused) => return Some(Message::Focus(id)), - iced::Event::Window(window::Event::Unfocused) => return Some(Message::Unfocus(id)), + iced::Event::Window(window::Event::Focused) => return Some(Action::Focus(id)), + iced::Event::Window(window::Event::Unfocused) => return Some(Action::Unfocus(id)), #[cfg(feature = "wayland")] iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => { match event { wayland::Event::Popup(wayland::PopupEvent::Done, _, id) | wayland::Event::Layer(wayland::LayerEvent::Done, _, id) => { - return Some(Message::SurfaceClosed(id)); + return Some(Action::SurfaceClosed(id)); } #[cfg(feature = "applet")] wayland::Event::Window( iced::event::wayland::WindowEvent::SuggestedBounds(b), ) => { - return Some(Message::SuggestedBounds(b)); + return Some(Action::SuggestedBounds(b)); } _ => (), } @@ -192,7 +277,7 @@ where }); let mut subscriptions = vec![ - self.app.subscription().map(super::Message::App), + self.app.subscription().map(crate::Action::App), self.app .core() .watch_config::(crate::config::ID) @@ -205,7 +290,7 @@ where tracing::error!(?why, "cosmic toolkit config update error"); } - super::Message::Cosmic(Message::ToolkitConfig(update.config)) + crate::Action::Cosmic(Action::ToolkitConfig(update.config)) }), self.app .core() @@ -232,12 +317,12 @@ where { tracing::error!(?why, "cosmic theme config update error"); } - Message::SystemThemeChange( + Action::SystemThemeChange( update.keys, crate::theme::Theme::system(Arc::new(update.config)), ) }) - .map(super::Message::Cosmic), + .map(crate::Action::Cosmic), self.app .core() .watch_config::(cosmic_theme::THEME_MODE_ID) @@ -249,27 +334,27 @@ where { tracing::error!(?error, "error reading system theme mode update"); } - Message::SystemThemeModeChange(update.keys, update.config) + Action::SystemThemeModeChange(update.keys, update.config) }) - .map(super::Message::Cosmic), - window_events.map(super::Message::Cosmic), + .map(crate::Action::Cosmic), + window_events.map(crate::Action::Cosmic), #[cfg(feature = "xdg-portal")] crate::theme::portal::desktop_settings() - .map(Message::DesktopSettings) - .map(super::Message::Cosmic), + .map(Action::DesktopSettings) + .map(crate::Action::Cosmic), ]; if self.app.core().keyboard_nav { subscriptions.push( keyboard_nav::subscription() - .map(Message::KeyboardNav) - .map(super::Message::Cosmic), + .map(Action::KeyboardNav) + .map(crate::Action::Cosmic), ); } #[cfg(feature = "single-instance")] if self.app.core().single_instance { - subscriptions.push(super::single_instance_subscription::()); + subscriptions.push(crate::dbus_activation::subscription::()); } Subscription::batch(subscriptions) @@ -286,20 +371,24 @@ where } #[cfg(feature = "multi-window")] - pub fn view(&self, id: window::Id) -> Element> { + pub fn view(&self, id: window::Id) -> Element> { + #[cfg(feature = "wayland")] + if let Some(v) = self.surface_views.get(&id) { + return v(&self.app); + } if !self .app .core() .main_window_id() .is_some_and(|main_id| main_id == id) { - return self.app.view_window(id).map(super::Message::App); + return self.app.view_window(id).map(crate::Action::App); } let view = if self.app.core().window.use_template { self.app.view_main() } else { - self.app.view().map(super::Message::App) + self.app.view().map(crate::Action::App) }; #[cfg(target_env = "gnu")] @@ -309,7 +398,7 @@ where } #[cfg(not(feature = "multi-window"))] - pub fn view(&self) -> Element> { + pub fn view(&self) -> Element> { let view = self.app.view_main(); #[cfg(target_env = "gnu")] @@ -321,7 +410,7 @@ where impl Cosmic { #[allow(clippy::unused_self)] - pub fn close(&mut self) -> iced::Task> { + pub fn close(&mut self) -> iced::Task> { if let Some(id) = self.app.core().main_window_id() { iced::window::close(id) } else { @@ -330,9 +419,9 @@ impl Cosmic { } #[allow(clippy::too_many_lines)] - fn cosmic_update(&mut self, message: Message) -> iced::Task> { + fn cosmic_update(&mut self, message: Action) -> iced::Task> { match message { - Message::WindowMaximized(id, maximized) => { + Action::WindowMaximized(id, maximized) => { if self .app .core() @@ -343,7 +432,7 @@ impl Cosmic { } } - Message::WindowResize(id, width, height) => { + Action::WindowResize(id, width, height) => { if self .app .core() @@ -358,12 +447,12 @@ impl Cosmic { //TODO: more efficient test of maximized (winit has no event for maximize if set by the OS) return iced::window::get_maximized(id).map(move |maximized| { - super::Message::Cosmic(Message::WindowMaximized(id, maximized)) + crate::Action::Cosmic(Action::WindowMaximized(id, maximized)) }); } #[cfg(feature = "wayland")] - Message::WindowState(id, state) => { + Action::WindowState(id, state) => { if self .app .core() @@ -383,7 +472,7 @@ impl Cosmic { } #[cfg(feature = "wayland")] - Message::WmCapabilities(id, capabilities) => { + Action::WmCapabilities(id, capabilities) => { if self .app .core() @@ -399,49 +488,49 @@ impl Cosmic { } } - Message::KeyboardNav(message) => match message { - keyboard_nav::Message::FocusNext => { - return iced::widget::focus_next().map(super::Message::Cosmic) + Action::KeyboardNav(message) => match message { + keyboard_nav::Action::FocusNext => { + return iced::widget::focus_next().map(crate::Action::Cosmic) } - keyboard_nav::Message::FocusPrevious => { - return iced::widget::focus_previous().map(super::Message::Cosmic) + keyboard_nav::Action::FocusPrevious => { + return iced::widget::focus_previous().map(crate::Action::Cosmic) } - keyboard_nav::Message::Escape => return self.app.on_escape(), - keyboard_nav::Message::Search => return self.app.on_search(), + keyboard_nav::Action::Escape => return self.app.on_escape(), + keyboard_nav::Action::Search => return self.app.on_search(), - keyboard_nav::Message::Fullscreen => return self.app.core().toggle_maximize(None), + keyboard_nav::Action::Fullscreen => return self.app.core().toggle_maximize(None), }, - Message::ContextDrawer(show) => { + Action::ContextDrawer(show) => { self.app.core_mut().set_show_context(show); return self.app.on_context_drawer(); } - Message::Drag => return self.app.core().drag(None), + Action::Drag => return self.app.core().drag(None), - Message::Minimize => return self.app.core().minimize(None), + Action::Minimize => return self.app.core().minimize(None), - Message::Maximize => return self.app.core().toggle_maximize(None), + Action::Maximize => return self.app.core().toggle_maximize(None), - Message::NavBar(key) => { + Action::NavBar(key) => { self.app.core_mut().nav_bar_set_toggled_condensed(false); return self.app.on_nav_select(key); } - Message::NavBarContext(key) => { + Action::NavBarContext(key) => { self.app.core_mut().nav_bar_set_context(key); return self.app.on_nav_context(key); } - Message::ToggleNavBar => { + Action::ToggleNavBar => { self.app.core_mut().nav_bar_toggle(); } - Message::ToggleNavBarCondensed => { + Action::ToggleNavBarCondensed => { self.app.core_mut().nav_bar_toggle_condensed(); } - Message::AppThemeChange(mut theme) => { + Action::AppThemeChange(mut theme) => { if let ThemeType::System { theme: _, .. } = theme.theme_type { self.app.core_mut().theme_sub_counter += 1; @@ -457,7 +546,7 @@ impl Cosmic { THEME.lock().unwrap().set_theme(theme.theme_type); } - Message::SystemThemeChange(keys, theme) => { + Action::SystemThemeChange(keys, theme) => { let cur_is_dark = THEME.lock().unwrap().theme_type.is_dark(); // Ignore updates if the current theme mode does not match. if cur_is_dark != theme.cosmic().is_dark { @@ -495,17 +584,17 @@ impl Cosmic { return cmd; } - Message::ScaleFactor(factor) => { + Action::ScaleFactor(factor) => { self.app.core_mut().set_scale_factor(factor); } - Message::Close => { + Action::Close => { return match self.app.on_app_exit() { Some(message) => self.app.update(message), None => self.close(), }; } - Message::SystemThemeModeChange(keys, mode) => { + Action::SystemThemeModeChange(keys, mode) => { if !keys.contains(&"is_dark") { return iced::Task::none(); } @@ -557,7 +646,7 @@ impl Cosmic { } return Task::batch(cmds); } - Message::Activate(_token) => + Action::Activate(_token) => { #[cfg(feature = "wayland")] if let Some(id) = self.app.core().main_window_id() { @@ -568,7 +657,10 @@ impl Cosmic { ); } } - Message::SurfaceClosed(id) => { + + Action::Surface(action) => return self.surface_update(action), + + Action::SurfaceClosed(id) => { let mut ret = if let Some(msg) = self.app.on_close_requested(id) { self.app.update(msg) } else { @@ -578,17 +670,19 @@ 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(vec![iced::exit::>()]); } return ret; } - Message::ShowWindowMenu => { + + Action::ShowWindowMenu => { if let Some(id) = self.app.core().main_window_id() { return iced::window::show_system_menu(id); } } + #[cfg(feature = "xdg-portal")] - Message::DesktopSettings(crate::theme::portal::Desktop::ColorScheme(s)) => { + Action::DesktopSettings(crate::theme::portal::Desktop::ColorScheme(s)) => { use ashpd::desktop::settings::ColorScheme; if match THEME.lock().unwrap().theme_type { ThemeType::System { @@ -628,7 +722,7 @@ impl Cosmic { } } #[cfg(feature = "xdg-portal")] - Message::DesktopSettings(crate::theme::portal::Desktop::Accent(c)) => { + Action::DesktopSettings(crate::theme::portal::Desktop::Accent(c)) => { use palette::Srgba; let c = Srgba::new(c.red() as f32, c.green() as f32, c.blue() as f32, 1.0); let core = self.app.core_mut(); @@ -657,11 +751,11 @@ impl Cosmic { } } #[cfg(feature = "xdg-portal")] - Message::DesktopSettings(crate::theme::portal::Desktop::Contrast(_)) => { + Action::DesktopSettings(crate::theme::portal::Desktop::Contrast(_)) => { // TODO when high contrast is integrated in settings and all custom themes } - Message::ToolkitConfig(config) => { + Action::ToolkitConfig(config) => { // Change the icon theme if not defined by the application. if !self.app.core().icon_theme_override && crate::icon_theme::default() != config.icon_theme @@ -672,18 +766,18 @@ impl Cosmic { *crate::config::COSMIC_TK.write().unwrap() = config; } - Message::Focus(f) => { + Action::Focus(f) => { self.app.core_mut().focused_window = Some(f); } - Message::Unfocus(id) => { + Action::Unfocus(id) => { let core = self.app.core_mut(); if core.focused_window.as_ref().is_some_and(|cur| *cur == id) { core.focused_window = None; } } #[cfg(feature = "applet")] - Message::SuggestedBounds(b) => { + Action::SuggestedBounds(b) => { tracing::info!("Suggested bounds: {b:?}"); let core = self.app.core_mut(); core.applet.suggested_bounds = b; @@ -697,6 +791,40 @@ impl Cosmic { impl Cosmic { pub fn new(app: App) -> Self { - Self { app } + Self { + app, + #[cfg(feature = "wayland")] + surface_views: HashMap::new(), + } + } + + #[cfg(feature = "wayland")] + /// Create a subsurface + pub fn get_subsurface( + &mut self, + settings: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings, + view: Box< + dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync, + >, + ) -> Task> { + use iced_winit::commands::subsurface::get_subsurface; + + self.surface_views.insert(settings.id, view); + get_subsurface(settings) + } + + #[cfg(feature = "wayland")] + /// Create a subsurface + pub fn get_popup( + &mut self, + settings: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings, + view: Box< + dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync, + >, + ) -> Task> { + use iced_winit::commands::popup::get_popup; + + self.surface_views.insert(settings.id, view); + get_popup(settings) } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 940cc456..4af3f348 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -6,70 +6,27 @@ //! Check out our [application](https://github.com/pop-os/libcosmic/tree/master/examples/application) //! example in our repository. -pub mod command; +mod action; +pub use action::Action; +use cosmic_config::CosmicConfigEntry; pub mod context_drawer; -mod core; pub mod cosmic; #[cfg(all(feature = "winit", feature = "multi-window"))] pub(crate) mod multi_window; pub mod settings; -pub mod message { - #[derive(Clone, Debug)] - #[must_use] - pub enum Message { - /// Messages from the application, for the application. - App(M), - /// Internal messages to be handled by libcosmic. - Cosmic(super::cosmic::Message), - #[cfg(feature = "single-instance")] - /// Dbus activation messages - DbusActivation(super::DbusActivationMessage), - /// Do nothing - None, - } +pub type Task = iced::Task>; - pub const fn app(message: M) -> Message { - Message::App(message) - } - - pub const fn cosmic(message: super::cosmic::Message) -> Message { - Message::Cosmic(message) - } - - pub const fn none() -> Message { - Message::None - } - - impl From for Message { - fn from(value: M) -> Self { - Self::App(value) - } - } -} - -use std::borrow::Cow; - -pub use self::command::Task; -pub use self::core::Core; -pub use self::settings::Settings; use crate::prelude::*; use crate::theme::THEME; use crate::widget::{container, horizontal_space, id_container, menu, nav_bar, popover}; +pub use crate::Core; use apply::Apply; use context_drawer::ContextDrawer; use iced::window; use iced::{Length, Subscription}; -pub use message::Message; -use url::Url; -#[cfg(feature = "single-instance")] -use { - iced_futures::futures::channel::mpsc::{Receiver, Sender}, - iced_futures::futures::SinkExt, - std::any::TypeId, - std::collections::HashMap, - zbus::{interface, proxy, zvariant::Value}, -}; +pub use settings::Settings; +use std::borrow::Cow; pub(crate) fn iced_settings( settings: Settings, @@ -143,6 +100,7 @@ pub fn run(settings: Settings, flags: App::Flags) -> iced::Res crate::malloc::limit_mmap_threshold(threshold); } + let default_font = settings.default_font; let (settings, mut flags, window_settings) = iced_settings::(settings, flags); #[cfg(not(feature = "multi-window"))] { @@ -179,142 +137,6 @@ pub fn run(settings: Settings, flags: App::Flags) -> iced::Res } } -#[cfg(feature = "single-instance")] -#[derive(Debug, Clone)] -pub struct DbusActivationMessage> { - pub activation_token: Option, - pub desktop_startup_id: Option, - pub msg: DbusActivationDetails, -} - -#[derive(Debug, Clone)] -pub enum DbusActivationDetails> { - Activate, - Open { - url: Vec, - }, - /// action can be deserialized as Flags - ActivateAction { - action: Action, - args: Args, - }, -} -#[cfg(feature = "single-instance")] -#[derive(Debug, Default)] -pub struct DbusActivation(Option>); -#[cfg(feature = "single-instance")] -impl DbusActivation { - #[must_use] - pub fn new() -> Self { - Self(None) - } - - pub fn rx(&mut self) -> Receiver { - let (tx, rx) = iced_futures::futures::channel::mpsc::channel(10); - self.0 = Some(tx); - rx - } -} - -#[cfg(feature = "single-instance")] -#[proxy(interface = "org.freedesktop.DbusActivation", assume_defaults = true)] -pub trait DbusActivationInterface { - /// Activate the application. - fn activate(&mut self, platform_data: HashMap<&str, Value<'_>>) -> zbus::Result<()>; - - /// Open the given URIs. - fn open( - &mut self, - uris: Vec<&str>, - platform_data: HashMap<&str, Value<'_>>, - ) -> zbus::Result<()>; - - /// Activate the given action. - fn activate_action( - &mut self, - action_name: &str, - parameter: Vec<&str>, - platform_data: HashMap<&str, Value<'_>>, - ) -> zbus::Result<()>; -} - -#[cfg(feature = "single-instance")] -#[interface(name = "org.freedesktop.DbusActivation")] -impl DbusActivation { - async fn activate(&mut self, platform_data: HashMap<&str, Value<'_>>) { - if let Some(tx) = &mut self.0 { - let _ = tx - .send(DbusActivationMessage { - activation_token: platform_data.get("activation-token").and_then(|t| match t { - Value::Str(t) => Some(t.to_string()), - _ => None, - }), - desktop_startup_id: platform_data.get("desktop-startup-id").and_then( - |t| match t { - Value::Str(t) => Some(t.to_string()), - _ => None, - }, - ), - msg: DbusActivationDetails::Activate, - }) - .await; - } - } - - async fn open(&mut self, uris: Vec<&str>, platform_data: HashMap<&str, Value<'_>>) { - if let Some(tx) = &mut self.0 { - let _ = tx - .send(DbusActivationMessage { - activation_token: platform_data.get("activation-token").and_then(|t| match t { - Value::Str(t) => Some(t.to_string()), - _ => None, - }), - desktop_startup_id: platform_data.get("desktop-startup-id").and_then( - |t| match t { - Value::Str(t) => Some(t.to_string()), - _ => None, - }, - ), - msg: DbusActivationDetails::Open { - url: uris.iter().filter_map(|u| Url::parse(u).ok()).collect(), - }, - }) - .await; - } - } - - async fn activate_action( - &mut self, - action_name: &str, - parameter: Vec<&str>, - platform_data: HashMap<&str, Value<'_>>, - ) { - if let Some(tx) = &mut self.0 { - let _ = tx - .send(DbusActivationMessage { - activation_token: platform_data.get("activation-token").and_then(|t| match t { - Value::Str(t) => Some(t.to_string()), - _ => None, - }), - desktop_startup_id: platform_data.get("desktop-startup-id").and_then( - |t| match t { - Value::Str(t) => Some(t.to_string()), - _ => None, - }, - ), - msg: DbusActivationDetails::ActivateAction { - action: action_name.to_string(), - args: parameter - .iter() - .map(std::string::ToString::to_string) - .collect(), - }, - }) - .await; - } - } -} - #[cfg(feature = "single-instance")] /// Launch a COSMIC application with the given [`Settings`]. /// If the application is already running, the arguments will be passed to the @@ -326,6 +148,8 @@ where App::Flags: CosmicFlags, App::Message: Clone + std::fmt::Debug + Send + 'static, { + use std::collections::HashMap; + let activation_token = std::env::var("XDG_ACTIVATION_TOKEN").ok(); let override_single = std::env::var("COSMIC_SINGLE_INSTANCE") @@ -342,14 +166,14 @@ where return run::(settings, flags); }; - if DbusActivationInterfaceProxyBlocking::builder(&conn) + if crate::dbus_activation::DbusActivationInterfaceProxyBlocking::builder(&conn) .destination(App::APP_ID) .ok() .and_then(|b| b.path(path).ok()) .and_then(|b| b.destination(App::APP_ID).ok()) .and_then(|b| b.build().ok()) .is_some_and(|mut p| { - match { + let res = { let mut platform_data = HashMap::new(); if let Some(activation_token) = activation_token { platform_data.insert("activation-token", activation_token.into()); @@ -363,7 +187,8 @@ where } else { p.activate(platform_data) } - } { + }; + match res { Ok(()) => { tracing::info!("Successfully activated another instance"); true @@ -491,7 +316,7 @@ where } /// 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; } @@ -499,8 +324,8 @@ where let nav_model = self.nav_model()?; let mut nav = - crate::widget::nav_bar(nav_model, |id| Message::Cosmic(cosmic::Message::NavBar(id))) - .on_context(|id| Message::Cosmic(cosmic::Message::NavBarContext(id))) + crate::widget::nav_bar(nav_model, |id| crate::Action::Cosmic(Action::NavBar(id))) + .on_context(|id| crate::Action::Cosmic(Action::NavBarContext(id))) .context_menu(self.nav_context_menu(self.core().nav_bar_context())) .into_container() // XXX both must be shrink to avoid flex layout from ignoring it @@ -515,7 +340,10 @@ where } /// Shows a context menu for the active nav bar item. - fn nav_context_menu(&self, id: nav_bar::Id) -> Option>>> { + fn nav_context_menu( + &self, + id: nav_bar::Id, + ) -> Option>>> { None } @@ -605,7 +433,7 @@ where /// Handles dbus activation messages #[cfg(feature = "single-instance")] - fn dbus_activation(&mut self, msg: DbusActivationMessage) -> Task { + fn dbus_activation(&mut self, msg: crate::dbus_activation::Message) -> Task { Task::none() } } @@ -648,7 +476,21 @@ 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>; + + fn watch_config( + &self, + id: &'static str, + ) -> iced::Subscription> { + self.core().watch_config(id) + } + + fn watch_state( + &self, + id: &'static str, + ) -> iced::Subscription> { + self.core().watch_state(id) + } } impl ApplicationExt for App { @@ -695,7 +537,7 @@ 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> { let core = self.core(); let is_condensed = core.is_condensed(); // TODO: More granularity might be needed for different resize border @@ -762,13 +604,13 @@ impl ApplicationExt for App { [0, 0, 0, 0] }) .apply(Element::from) - .map(Message::App), + .map(crate::Action::App), ); } else { //TODO: container and padding are temporary, until //the `resize_border` is moved to not cover window content widgets.push( - container(main_content.map(Message::App)) + container(main_content.map(crate::Action::App)) .padding(main_content_padding) .into(), ); @@ -778,7 +620,7 @@ impl ApplicationExt for App { //TODO: container and padding are temporary, until //the `resize_border` is moved to not cover window content widgets.push( - container(main_content.map(Message::App)) + container(main_content.map(crate::Action::App)) .padding(main_content_padding) .into(), ); @@ -794,7 +636,7 @@ impl ApplicationExt for App { context_width, ) .apply(Element::from) - .map(Message::App) + .map(crate::Action::App) .apply(container) .width(context_width) .apply(|drawer| { @@ -824,7 +666,7 @@ impl ApplicationExt for App { .push(content_row) .push_maybe( self.footer() - .map(|footer| container(footer.map(Message::App)).padding([0, 8, 8, 8])), + .map(|footer| container(footer.map(crate::Action::App)).padding([0, 8, 8, 8])), ); let content: Element<_> = if core.window.content_container { content_col @@ -851,45 +693,45 @@ impl ApplicationExt for App { let mut header = crate::widget::header_bar() .focused(focused) .title(&core.window.header_title) - .on_drag(Message::Cosmic(cosmic::Message::Drag)) - .on_right_click(Message::Cosmic(cosmic::Message::ShowWindowMenu)) - .on_double_click(Message::Cosmic(cosmic::Message::Maximize)); + .on_drag(crate::Action::Cosmic(Action::Drag)) + .on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu)) + .on_double_click(crate::Action::Cosmic(Action::Maximize)); if self.nav_model().is_some() { let toggle = crate::widget::nav_bar_toggle() .active(core.nav_bar_active()) .selected(focused) .on_toggle(if is_condensed { - Message::Cosmic(cosmic::Message::ToggleNavBarCondensed) + crate::Action::Cosmic(Action::ToggleNavBarCondensed) } else { - Message::Cosmic(cosmic::Message::ToggleNavBar) + crate::Action::Cosmic(Action::ToggleNavBar) }); header = header.start(toggle); } if core.window.show_close { - header = header.on_close(Message::Cosmic(cosmic::Message::Close)); + header = header.on_close(crate::Action::Cosmic(Action::Close)); } if core.window.show_maximize && crate::config::show_maximize() { - header = header.on_maximize(Message::Cosmic(cosmic::Message::Maximize)); + header = header.on_maximize(crate::Action::Cosmic(Action::Maximize)); } if core.window.show_minimize && crate::config::show_minimize() { - header = header.on_minimize(Message::Cosmic(cosmic::Message::Minimize)); + header = header.on_minimize(crate::Action::Cosmic(Action::Minimize)); } for element in self.header_start() { - header = header.start(element.map(Message::App)); + header = header.start(element.map(crate::Action::App)); } for element in self.header_center() { - header = header.center(element.map(Message::App)); + header = header.center(element.map(crate::Action::App)); } for element in self.header_end() { - header = header.end(element.map(Message::App)); + header = header.end(element.map(crate::Action::App)); } if content_container { @@ -951,7 +793,7 @@ impl ApplicationExt for App { .dialog() .map(|w| Element::from(id_container(w, iced_core::id::Id::new("COSMIC_dialog")))) { - popover = popover.popup(dialog.map(Message::App)); + popover = popover.popup(dialog.map(crate::Action::App)); } let view_element: Element<_> = popover.into(); @@ -959,74 +801,6 @@ impl ApplicationExt for App { } } -#[cfg(feature = "single-instance")] -fn single_instance_subscription() -> Subscription> { - use iced_futures::futures::StreamExt; - iced_futures::Subscription::run_with_id( - TypeId::of::(), - iced::stream::channel(10, move |mut output| async move { - let mut single_instance: DbusActivation = DbusActivation::new(); - let mut rx = single_instance.rx(); - if let Ok(builder) = zbus::ConnectionBuilder::session() { - let path: String = format!("/{}", App::APP_ID.replace('.', "/")); - if let Ok(conn) = builder.build().await { - // XXX Setup done this way seems to be more reliable. - // - // the docs for serve_at seem to imply it will replace the - // existing interface at the requested path, but it doesn't - // seem to work that way all the time. The docs for - // object_server().at() imply it won't replace the existing - // interface. - // - // request_name is used either way, with the builder or - // with the connection, but it must be done after the - // object server is setup. - if conn.object_server().at(path, single_instance).await != Ok(true) { - tracing::error!("Failed to serve dbus"); - std::process::exit(1); - } - if conn.request_name(App::APP_ID).await.is_err() { - tracing::error!("Failed to serve dbus"); - std::process::exit(1); - } - - #[cfg(feature = "smol")] - let handle = { - std::thread::spawn(move || { - let conn_clone = _conn.clone(); - - zbus::block_on(async move { - loop { - conn_clone.executor().tick().await; - } - }) - }) - }; - while let Some(mut msg) = rx.next().await { - if let Some(token) = msg.activation_token.take() { - if let Err(err) = output - .send(Message::Cosmic(cosmic::Message::Activate(token))) - .await - { - tracing::error!(?err, "Failed to send message"); - } - } - if let Err(err) = output.send(Message::DbusActivation(msg)).await { - tracing::error!(?err, "Failed to send message"); - } - } - } - } else { - tracing::warn!("Failed to connect to dbus for single instance"); - } - - loop { - iced::futures::pending!(); - } - }), - ) -} - const EMBEDDED_FONTS: &[&[u8]] = &[ include_bytes!("../../res/open-sans/OpenSans-Light.ttf"), include_bytes!("../../res/open-sans/OpenSans-Regular.ttf"), @@ -1043,6 +817,6 @@ fn preload_fonts() { .unwrap(); EMBEDDED_FONTS - .into_iter() + .iter() .for_each(move |font| font_system.load_font(Cow::Borrowed(font))); } diff --git a/src/app/multi_window.rs b/src/app/multi_window.rs index d20739b1..368f2f03 100644 --- a/src/app/multi_window.rs +++ b/src/app/multi_window.rs @@ -1,3 +1,6 @@ +// Copyright 2024 System76 +// SPDX-License-Identifier: MPL-2.0 + //! Create and run daemons that run in the background. //! Copied from iced 0.13, but adds optional initial window diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 0ca8a4f1..25af302a 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -2,7 +2,7 @@ pub mod token; use crate::{ - app::{self, iced_settings, Core}, + app::iced_settings, cctk::sctk, iced::{ self, @@ -14,20 +14,17 @@ use crate::{ theme::{self, system_dark, system_light, Button, THEME}, widget::{ self, - autosize::{autosize, Autosize}, + autosize::{self, autosize, Autosize}, layer_container, }, Application, Element, Renderer, }; -use cctk::sctk::shell::xdg::window::WindowConfigure; pub use cosmic_panel_config; use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize}; -use cosmic_theme::Theme; -use iced::Pixels; -use iced_core::{Padding, Shadow}; +use iced_core::{Layout, Padding, Shadow}; use iced_widget::runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner}; use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity}; -use std::{borrow::Cow, num::NonZeroU32, rc::Rc, sync::LazyLock}; +use std::{borrow::Cow, num::NonZeroU32, rc::Rc, sync::LazyLock, time::Duration}; use tracing::info; use crate::app::cosmic; @@ -35,6 +32,8 @@ static AUTOSIZE_ID: LazyLock = LazyLock::new(|| iced::id::Id::new("cosmic-applet-autosize")); static AUTOSIZE_MAIN_ID: LazyLock = LazyLock::new(|| iced::id::Id::new("cosmic-applet-autosize-main")); +static TOOLTIP_ID: LazyLock = LazyLock::new(|| iced::id::Id::new("subsurface")); +static TOOLTIP_WINDOW_ID: LazyLock = LazyLock::new(window::Id::unique); #[derive(Debug, Clone)] pub struct Context { @@ -161,11 +160,7 @@ impl Context { let height = f32::from(height) + applet_padding as f32 * 2.; let mut settings = crate::app::Settings::default() .size(iced_core::Size::new(width, height)) - .size_limits( - Limits::NONE - .min_height(height as f32) - .min_width(width as f32), - ) + .size_limits(Limits::NONE.min_height(height).min_width(width)) .resizable(None) .default_text_size(14.0) .default_font(crate::font::default()) @@ -224,6 +219,70 @@ impl Context { ) } + pub fn applet_tooltip<'a, Message: 'static>( + &self, + content: impl Into>, + tooltip: impl Into>, + has_popup: bool, + on_surface_action: impl Fn(crate::surface::Action) -> Message + 'static, + ) -> crate::widget::wayland::tooltip::widget::Tooltip<'a, Message, Message> { + let window_id = *TOOLTIP_WINDOW_ID; + let subsurface_id = TOOLTIP_ID.clone(); + let anchor = self.anchor; + let tooltip = tooltip.into(); + + crate::widget::wayland::tooltip::widget::Tooltip::<'a, Message, Message>::new( + content, + (!has_popup).then_some(move |bounds: Rectangle| { + let window_id = window_id; + let (popup_anchor, gravity) = match anchor { + PanelAnchor::Left => (Anchor::Right, Gravity::Right), + PanelAnchor::Right => (Anchor::Left, Gravity::Left), + PanelAnchor::Top => (Anchor::Bottom, Gravity::Bottom), + PanelAnchor::Bottom => (Anchor::Top, Gravity::Top), + }; + + SctkPopupSettings { + parent: window::Id::RESERVED, + id: window_id, + grab: false, + input_zone: Some(Rectangle::new( + iced::Point::new(-1000., -1000.), + iced::Size::default(), + )), + positioner: SctkPositioner { + size: None, + size_limits: Limits::NONE.min_width(1.).min_height(1.), + anchor_rect: Rectangle { + x: bounds.x.round() as i32, + y: bounds.y.round() as i32, + width: bounds.width.round() as i32, + height: bounds.height.round() as i32, + }, + anchor: popup_anchor, + gravity, + constraint_adjustment: 15, + offset: (0, 0), + reactive: true, + }, + parent_size: None, + close_with_children: true, + } + }), + move || { + Element::from(autosize::autosize( + layer_container(crate::widget::text(tooltip.clone())) + .layer(crate::cosmic_theme::Layer::Background) + .padding(4.), + subsurface_id.clone(), + )) + }, + on_surface_action(crate::surface::Action::DestroyPopup(window_id)), + on_surface_action, + ) + .delay(Duration::from_millis(100)) + } + // TODO popup container which tracks the size of itself and requests the popup to resize to match pub fn popup_container<'a, Message: 'static>( &self, @@ -240,7 +299,7 @@ impl Context { Container::::new( Container::::new(content).style(|theme| { let cosmic = theme.cosmic(); - let corners = cosmic.corner_radii.clone(); + let corners = cosmic.corner_radii; iced_widget::container::Style { text_color: Some(cosmic.background.on.into()), background: Some(Color::from(cosmic.background.base).into()), @@ -262,10 +321,10 @@ impl Context { ) .limits( Limits::NONE - .min_width(1.) .min_height(1.) - .max_width(500.) - .max_height(1000.), + .min_width(360.0) + .max_width(360.0) + .max_height(1000.0), ) } @@ -304,10 +363,16 @@ impl Context { }, reactive: true, constraint_adjustment: 15, // slide_y, slide_x, flip_x, flip_y - ..Default::default() + size_limits: Limits::NONE + .min_height(1.0) + .min_width(360.0) + .max_width(360.0) + .max_height(1080.0), }, parent_size: None, grab: true, + close_with_children: false, + input_zone: None, } } @@ -315,7 +380,7 @@ impl Context { &self, content: impl Into>, ) -> Autosize<'a, Message, crate::Theme, crate::Renderer> { - let force_configured = matches!(&self.panel_type, &PanelType::Other(ref n) if n.is_empty()); + let force_configured = matches!(&self.panel_type, PanelType::Other(n) if n.is_empty()); let w = autosize(content, AUTOSIZE_MAIN_ID.clone()); let mut limits = Limits::NONE; let suggested_window_size = self.suggested_window_size(); @@ -326,7 +391,7 @@ impl Context { .filter(|c| c.width as i32 > 0) .map(|c| c.width) { - limits = limits.width(width as f32); + limits = limits.width(width); } if let Some(height) = self .suggested_bounds @@ -334,7 +399,7 @@ impl Context { .filter(|c| c.height as i32 > 0) .map(|c| c.height) { - limits = limits.height(height as f32); + limits = limits.height(height); } w.limits(limits) diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 00000000..73c900c1 --- /dev/null +++ b/src/command.rs @@ -0,0 +1,45 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use iced::window; + +/// Initiates a window drag. +pub fn drag(id: window::Id) -> iced::Task> { + iced_runtime::window::drag(id) +} + +/// Maximizes the window. +pub fn maximize(id: window::Id, maximized: bool) -> iced::Task> { + iced_runtime::window::maximize(id, maximized) +} + +/// Minimizes the window. +pub fn minimize(id: window::Id) -> iced::Task> { + iced_runtime::window::minimize(id, true) +} + +/// Sets the title of a window. +#[allow(unused_variables, clippy::needless_pass_by_value)] +pub fn set_title(id: window::Id, title: String) -> iced::Task> { + iced::Task::none() +} + +#[cfg(feature = "winit")] +pub fn set_scaling_factor(factor: f32) -> iced::Task> { + iced::Task::done(crate::app::Action::ScaleFactor(factor)).map(crate::Action::Cosmic) +} + +#[cfg(feature = "winit")] +pub fn set_theme(theme: crate::Theme) -> iced::Task> { + iced::Task::done(crate::app::Action::AppThemeChange(theme)).map(crate::Action::Cosmic) +} + +/// Sets the window mode to windowed. +pub fn set_windowed(id: window::Id) -> iced::Task> { + iced_runtime::window::change_mode(id, window::Mode::Windowed) +} + +/// Toggles the windows' maximize state. +pub fn toggle_maximize(id: window::Id) -> iced::Task> { + iced_runtime::window::toggle_maximize(id) +} diff --git a/src/config/mod.rs b/src/config/mod.rs index 4f1a5a16..1e82becd 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -17,7 +17,7 @@ const MONO_FAMILY_DEFAULT: &str = "Noto Sans Mono"; const SANS_FAMILY_DEFAULT: &str = "Open Sans"; /// Stores static strings of the family names for `iced::Font` compatibility. -pub static FAMILY_MAP: LazyLock>> = LazyLock::new(|| Mutex::default()); +pub static FAMILY_MAP: LazyLock>> = LazyLock::new(Mutex::default); pub static COSMIC_TK: LazyLock> = LazyLock::new(|| { RwLock::new( @@ -153,7 +153,7 @@ impl From for iced::Font { let name: &'static str = family_map .get(font.family.as_str()) - .map(|&x| x) + .copied() .unwrap_or_else(|| { let value = font.family.clone().leak(); family_map.insert(value); diff --git a/src/app/core.rs b/src/core.rs similarity index 84% rename from src/app/core.rs rename to src/core.rs index 42c4429f..fdeeba5b 100644 --- a/src/app/core.rs +++ b/src/core.rs @@ -1,12 +1,12 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -use std::{cell::OnceCell, collections::HashMap}; +use std::collections::HashMap; use crate::widget::nav_bar; use cosmic_config::CosmicConfigEntry; use cosmic_theme::ThemeMode; -use iced::window; +use iced::{window, Limits, Size}; use iced_core::window::Id; use palette::Srgba; use slotmap::Key; @@ -95,6 +95,8 @@ pub struct Core { pub(crate) main_window: Option, pub(crate) exit_on_main_window_closed: bool, + + pub(crate) menu_bars: HashMap, } impl Default for Core { @@ -151,6 +153,7 @@ impl Default for Core { portal_is_high_contrast: None, main_window: None, exit_on_main_window_closed: true, + menu_bars: HashMap::new(), } } } @@ -205,7 +208,7 @@ impl Core { // Context drawer min width (344px) + padding (8px) breakpoint += 344.0 + 8.0; }; - self.is_condensed = (breakpoint * self.scale_factor) > self.window.width as f32; + self.is_condensed = (breakpoint * self.scale_factor) > self.window.width; self.nav_bar_update(); } @@ -218,7 +221,7 @@ impl Core { } pub(crate) fn context_width(&self, has_nav: bool) -> f32 { - let window_width = (self.window.width as f32) / self.scale_factor; + let window_width = self.window.width / self.scale_factor; // Content width (360px) + padding (8px) let mut reserved_width = 360.0 + 8.0; @@ -243,6 +246,10 @@ impl Core { } } + pub fn main_window_is(&self, id: iced::window::Id) -> bool { + self.main_window_id().is_some_and(|main_id| main_id == id) + } + /// Whether the nav panel is visible or not #[must_use] pub fn nav_bar_active(&self) -> bool { @@ -353,7 +360,7 @@ impl Core { /// Get the current focused window if it exists #[must_use] pub fn focused_window(&self) -> Option { - self.focused_window.clone() + self.focused_window } /// Whether the application should use a dark theme, according to the system @@ -374,4 +381,64 @@ impl Core { std::mem::swap(&mut self.main_window, &mut id); id } + + #[cfg(feature = "winit")] + pub fn drag(&self, id: Option) -> crate::app::Task { + let Some(id) = id.or(self.main_window) else { + return iced::Task::none(); + }; + crate::command::drag(id) + } + + #[cfg(feature = "winit")] + pub fn maximize( + &self, + id: Option, + maximized: bool, + ) -> crate::app::Task { + let Some(id) = id.or(self.main_window) else { + return iced::Task::none(); + }; + crate::command::maximize(id, maximized) + } + + #[cfg(feature = "winit")] + pub fn minimize(&self, id: Option) -> crate::app::Task { + let Some(id) = id.or(self.main_window) else { + return iced::Task::none(); + }; + crate::command::minimize(id) + } + + #[cfg(feature = "winit")] + pub fn set_title( + &self, + id: Option, + title: String, + ) -> crate::app::Task { + let Some(id) = id.or(self.main_window) else { + return iced::Task::none(); + }; + crate::command::set_title(id, title) + } + + #[cfg(feature = "winit")] + pub fn set_windowed(&self, id: Option) -> crate::app::Task { + let Some(id) = id.or(self.main_window) else { + return iced::Task::none(); + }; + crate::command::set_windowed(id) + } + + #[cfg(feature = "winit")] + pub fn toggle_maximize( + &self, + id: Option, + ) -> crate::app::Task { + let Some(id) = id.or(self.main_window) else { + return iced::Task::none(); + }; + + crate::command::toggle_maximize(id) + } } diff --git a/src/dbus_activation.rs b/src/dbus_activation.rs new file mode 100644 index 00000000..84b5d000 --- /dev/null +++ b/src/dbus_activation.rs @@ -0,0 +1,214 @@ +// Copyright 2024 System76 +// SPDX-License-Identifier: MPL-2.0 + +use { + crate::ApplicationExt, + iced::Subscription, + iced_futures::futures::{ + channel::mpsc::{Receiver, Sender}, + SinkExt, + }, + std::{any::TypeId, collections::HashMap}, + url::Url, + zbus::{interface, proxy, zvariant::Value}, +}; + +pub fn subscription() -> Subscription> { + use iced_futures::futures::StreamExt; + iced_futures::Subscription::run_with_id( + TypeId::of::(), + iced::stream::channel(10, move |mut output| async move { + let mut single_instance: DbusActivation = DbusActivation::new(); + let mut rx = single_instance.rx(); + if let Ok(builder) = zbus::ConnectionBuilder::session() { + let path: String = format!("/{}", App::APP_ID.replace('.', "/")); + if let Ok(conn) = builder.build().await { + // XXX Setup done this way seems to be more reliable. + // + // the docs for serve_at seem to imply it will replace the + // existing interface at the requested path, but it doesn't + // seem to work that way all the time. The docs for + // object_server().at() imply it won't replace the existing + // interface. + // + // request_name is used either way, with the builder or + // with the connection, but it must be done after the + // object server is setup. + if conn.object_server().at(path, single_instance).await != Ok(true) { + tracing::error!("Failed to serve dbus"); + std::process::exit(1); + } + if conn.request_name(App::APP_ID).await.is_err() { + tracing::error!("Failed to serve dbus"); + std::process::exit(1); + } + + #[cfg(feature = "smol")] + let handle = { + std::thread::spawn(move || { + let conn_clone = _conn.clone(); + + zbus::block_on(async move { + loop { + conn_clone.executor().tick().await; + } + }) + }) + }; + while let Some(mut msg) = rx.next().await { + if let Some(token) = msg.activation_token.take() { + if let Err(err) = output + .send(crate::Action::Cosmic(crate::app::Action::Activate(token))) + .await + { + tracing::error!(?err, "Failed to send message"); + } + } + if let Err(err) = output.send(crate::Action::DbusActivation(msg)).await { + tracing::error!(?err, "Failed to send message"); + } + } + } + } else { + tracing::warn!("Failed to connect to dbus for single instance"); + } + + loop { + iced::futures::pending!(); + } + }), + ) +} + +#[derive(Debug, Clone)] +pub struct Message> { + pub activation_token: Option, + pub desktop_startup_id: Option, + pub msg: Details, +} + +#[derive(Debug, Clone)] +pub enum Details> { + Activate, + Open { + url: Vec, + }, + /// action can be deserialized as Flags + ActivateAction { + action: Action, + args: Args, + }, +} + +#[derive(Debug, Default)] +pub struct DbusActivation(Option>); + +impl DbusActivation { + #[must_use] + pub fn new() -> Self { + Self(None) + } + + pub fn rx(&mut self) -> Receiver { + let (tx, rx) = iced_futures::futures::channel::mpsc::channel(10); + self.0 = Some(tx); + rx + } +} + +#[proxy(interface = "org.freedesktop.DbusActivation", assume_defaults = true)] +pub trait DbusActivationInterface { + /// Activate the application. + fn activate(&mut self, platform_data: HashMap<&str, Value<'_>>) -> zbus::Result<()>; + + /// Open the given URIs. + fn open( + &mut self, + uris: Vec<&str>, + platform_data: HashMap<&str, Value<'_>>, + ) -> zbus::Result<()>; + + /// Activate the given action. + fn activate_action( + &mut self, + action_name: &str, + parameter: Vec<&str>, + platform_data: HashMap<&str, Value<'_>>, + ) -> zbus::Result<()>; +} + +#[interface(name = "org.freedesktop.DbusActivation")] +impl DbusActivation { + async fn activate(&mut self, platform_data: HashMap<&str, Value<'_>>) { + if let Some(tx) = &mut self.0 { + let _ = tx + .send(Message { + activation_token: platform_data.get("activation-token").and_then(|t| match t { + Value::Str(t) => Some(t.to_string()), + _ => None, + }), + desktop_startup_id: platform_data.get("desktop-startup-id").and_then( + |t| match t { + Value::Str(t) => Some(t.to_string()), + _ => None, + }, + ), + msg: Details::Activate, + }) + .await; + } + } + + async fn open(&mut self, uris: Vec<&str>, platform_data: HashMap<&str, Value<'_>>) { + if let Some(tx) = &mut self.0 { + let _ = tx + .send(Message { + activation_token: platform_data.get("activation-token").and_then(|t| match t { + Value::Str(t) => Some(t.to_string()), + _ => None, + }), + desktop_startup_id: platform_data.get("desktop-startup-id").and_then( + |t| match t { + Value::Str(t) => Some(t.to_string()), + _ => None, + }, + ), + msg: Details::Open { + url: uris.iter().filter_map(|u| Url::parse(u).ok()).collect(), + }, + }) + .await; + } + } + + async fn activate_action( + &mut self, + action_name: &str, + parameter: Vec<&str>, + platform_data: HashMap<&str, Value<'_>>, + ) { + if let Some(tx) = &mut self.0 { + let _ = tx + .send(Message { + activation_token: platform_data.get("activation-token").and_then(|t| match t { + Value::Str(t) => Some(t.to_string()), + _ => None, + }), + desktop_startup_id: platform_data.get("desktop-startup-id").and_then( + |t| match t { + Value::Str(t) => Some(t.to_string()), + _ => None, + }, + ), + msg: Details::ActivateAction { + action: action_name.to_string(), + args: parameter + .iter() + .map(std::string::ToString::to_string) + .collect(), + }, + }) + .await; + } + } +} diff --git a/src/desktop.rs b/src/desktop.rs index 1bee3189..3f5f5755 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -74,10 +74,7 @@ pub fn load_applications<'a>( #[cfg(not(windows))] pub fn app_id_or_fallback_matches(app_id: &str, entry: &DesktopEntryData) -> bool { - let lowercase_wm_class = match entry.wm_class.as_ref() { - Some(s) => Some(s.to_lowercase()), - None => None, - }; + let lowercase_wm_class = entry.wm_class.as_ref().map(|s| s.to_lowercase()); app_id == entry.id || Some(app_id.to_lowercase()) == lowercase_wm_class diff --git a/src/ext.rs b/src/ext.rs index 215f7b7b..c85e6e86 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -9,7 +9,7 @@ pub trait ElementExt { fn debug(self, debug: bool) -> Self; } -impl<'a, Message: 'static> ElementExt for crate::Element<'a, Message> { +impl ElementExt for crate::Element<'_, Message> { fn debug(self, debug: bool) -> Self { if debug { self.explain(Color::WHITE) diff --git a/src/keyboard_nav.rs b/src/keyboard_nav.rs index cbfbf4a5..65211462 100644 --- a/src/keyboard_nav.rs +++ b/src/keyboard_nav.rs @@ -8,7 +8,7 @@ use iced_core::keyboard::key::Named; use iced_futures::event::listen_raw; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum Message { +pub enum Action { Escape, FocusNext, FocusPrevious, @@ -16,7 +16,7 @@ pub enum Message { Search, } -pub fn subscription() -> Subscription { +pub fn subscription() -> Subscription { listen_raw(|event, status, _| { if event::Status::Ignored != status { return None; @@ -30,18 +30,18 @@ pub fn subscription() -> Subscription { }) => match key { Named::Tab if !modifiers.control() => { return Some(if modifiers.shift() { - Message::FocusPrevious + Action::FocusPrevious } else { - Message::FocusNext + Action::FocusNext }); } Named::Escape => { - return Some(Message::Escape); + return Some(Action::Escape); } Named::F11 => { - return Some(Message::Fullscreen); + return Some(Action::Fullscreen); } _ => (), @@ -51,7 +51,7 @@ pub fn subscription() -> Subscription { modifiers, .. }) if c == "f" && modifiers.control() => { - return Some(Message::Search); + return Some(Action::Search); } _ => (), diff --git a/src/lib.rs b/src/lib.rs index bd4651ac..0515911d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,21 +9,30 @@ pub mod prelude { pub use crate::ext::*; #[cfg(feature = "winit")] pub use crate::ApplicationExt; - pub use crate::{Also, Apply, Element, Renderer, Theme}; + pub use crate::{Also, Apply, Element, Renderer, Task, Theme}; } pub use apply::{Also, Apply}; +/// Actions are managed internally by the cosmic runtime. +pub mod action; +pub use action::Action; + #[cfg(feature = "winit")] pub mod app; #[cfg(feature = "winit")] +#[doc(inline)] pub use app::{Application, ApplicationExt}; #[cfg(feature = "applet")] pub mod applet; -pub use iced::Task; -pub mod task; +pub mod command; + +/// State which is managed by the cosmic runtime. +pub mod core; +#[doc(inline)] +pub use core::Core; pub mod config; @@ -33,6 +42,11 @@ pub use cosmic_config; #[doc(inline)] pub use cosmic_theme; +#[cfg(feature = "single-instance")] +pub mod dbus_activation; +#[cfg(feature = "single-instance")] +pub use dbus_activation::DbusActivation; + #[cfg(feature = "desktop")] pub mod desktop; @@ -85,6 +99,11 @@ pub mod process; #[cfg(feature = "wayland")] pub use cctk; +pub mod surface; + +pub use iced::Task; +pub mod task; + pub mod theme; #[doc(inline)] diff --git a/src/malloc.rs b/src/malloc.rs index e980ea6f..0d271447 100644 --- a/src/malloc.rs +++ b/src/malloc.rs @@ -1,3 +1,6 @@ +// Copyright 2025 System76 +// SPDX-License-Identifier: MPL-2.0 + use std::os::raw::c_int; const M_MMAP_THRESHOLD: c_int = -3; diff --git a/src/process.rs b/src/process.rs index 037bed1e..f76dad7e 100644 --- a/src/process.rs +++ b/src/process.rs @@ -1,6 +1,8 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + #[cfg(all(feature = "smol", not(feature = "tokio")))] use smol::io::AsyncReadExt; -use std::fs::File; use std::io; use std::os::fd::OwnedFd; use std::process::{exit, Command, Stdio}; diff --git a/src/surface/action.rs b/src/surface/action.rs new file mode 100644 index 00000000..af7cc2de --- /dev/null +++ b/src/surface/action.rs @@ -0,0 +1,152 @@ +// Copyright 2025 System76 +// SPDX-License-Identifier: MPL-2.0 + +use super::Action; +#[cfg(feature = "winit")] +use crate::Application; + +use std::{any::Any, sync::Arc}; + +/// Used to produce a destroy popup message from within a widget. +#[cfg(feature = "wayland")] +#[must_use] +pub fn destroy_popup(id: iced_core::window::Id) -> Action { + Action::DestroyPopup(id) +} + +#[cfg(feature = "wayland")] +#[must_use] +pub fn destroy_subsurface(id: iced_core::window::Id) -> Action { + Action::DestroySubsurface(id) +} + +#[cfg(all(feature = "wayland", feature = "winit"))] +#[must_use] +pub fn app_popup( + settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + + Send + + Sync + + 'static, + view: Option< + Box< + dyn for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action> + + Send + + Sync + + 'static, + >, + >, +) -> Action { + let boxed: Box< + dyn Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + + Send + + Sync + + 'static, + > = Box::new(settings); + let boxed: Box = Box::new(boxed); + + Action::AppPopup( + Arc::new(boxed), + view.map(|view| { + let boxed: Box = Box::new(view); + Arc::new(boxed) + }), + ) +} + +/// Used to create a subsurface message from within a widget. +#[cfg(all(feature = "wayland", feature = "winit"))] +#[must_use] +pub fn simple_subsurface( + settings: impl Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + + Send + + Sync + + 'static, + view: Option< + Box crate::Element<'static, crate::Action> + Send + Sync + 'static>, + >, +) -> Action { + let boxed: Box< + dyn Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + + Send + + Sync + + 'static, + > = Box::new(settings); + let boxed: Box = Box::new(boxed); + + Action::Subsurface( + Arc::new(boxed), + view.map(|view| { + let boxed: Box = Box::new(view); + Arc::new(boxed) + }), + ) +} + +/// Used to create a popup message from within a widget. +#[cfg(all(feature = "wayland", feature = "winit"))] +#[must_use] +pub fn simple_popup( + settings: impl Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + + Send + + Sync + + 'static, + view: Option< + impl Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, + >, +) -> Action { + let boxed: Box< + dyn Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + + Send + + Sync + + 'static, + > = Box::new(settings); + let boxed: Box = Box::new(boxed); + + Action::Popup( + Arc::new(boxed), + view.map(|view| { + let boxed: Box< + dyn Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, + > = Box::new(view); + let boxed: Box = Box::new(boxed); + Arc::new(boxed) + }), + ) +} + +#[cfg(all(feature = "wayland", feature = "winit"))] +#[must_use] +pub fn subsurface( + settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + + Send + + Sync + + 'static, + // XXX Boxed trait object is required for less cumbersome type inference, but we box it anyways. + view: Option< + Box< + dyn for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action> + + Send + + Sync + + 'static, + >, + >, +) -> Action { + let boxed: Box< + dyn Fn( + &mut App, + ) + -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + + Send + + Sync + + 'static, + > = Box::new(settings); + let boxed: Box = Box::new(boxed); + + Action::AppSubsurface( + Arc::new(boxed), + view.map(|view| { + let boxed: Box = Box::new(view); + Arc::new(boxed) + }), + ) +} diff --git a/src/surface/mod.rs b/src/surface/mod.rs new file mode 100644 index 00000000..c08108ee --- /dev/null +++ b/src/surface/mod.rs @@ -0,0 +1,85 @@ +// Copyright 2025 System76 +// SPDX-License-Identifier: MPL-2.0 + +pub mod action; + +use iced::Limits; +use iced::Size; +use iced::Task; +use std::future::Future; +use std::sync::Arc; + +/// Ignore this message in your application. It will be intercepted. +#[derive(Clone)] +pub enum Action { + /// Create a subsurface with a view function accepting the App as a parameter + AppSubsurface( + std::sync::Arc>, + Option>>, + ), + /// Create a subsurface with a view function + Subsurface( + std::sync::Arc>, + Option>>, + ), + /// Destroy a subsurface with a view function + DestroySubsurface(iced::window::Id), + /// Create a popup with a view function accepting the App as a parameter + AppPopup( + std::sync::Arc>, + Option>>, + ), + /// Create a popup + Popup( + std::sync::Arc>, + Option>>, + ), + /// Destroy a subsurface with a view function + DestroyPopup(iced::window::Id), + /// Responsive menu bar update + ResponsiveMenuBar { + /// Id of the menu bar + menu_bar: crate::widget::Id, + /// Limits of the menu bar + limits: Limits, + /// Requested Full Size for expanded menu bar + size: Size, + }, + Ignore, + Task(Arc Task + Send + Sync>), +} + +impl std::fmt::Debug for Action { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::AppSubsurface(arg0, arg1) => f + .debug_tuple("AppSubsurface") + .field(arg0) + .field(arg1) + .finish(), + Self::Subsurface(arg0, arg1) => { + f.debug_tuple("Subsurface").field(arg0).field(arg1).finish() + } + Self::DestroySubsurface(arg0) => { + f.debug_tuple("DestroySubsurface").field(arg0).finish() + } + Self::AppPopup(arg0, arg1) => { + f.debug_tuple("AppPopup").field(arg0).field(arg1).finish() + } + Self::Popup(arg0, arg1) => f.debug_tuple("Popup").field(arg0).field(arg1).finish(), + Self::DestroyPopup(arg0) => f.debug_tuple("DestroyPopup").field(arg0).finish(), + Self::ResponsiveMenuBar { + menu_bar, + limits, + size, + } => f + .debug_struct("ResponsiveMenuBar") + .field("menu_bar", menu_bar) + .field("limits", limits) + .field("size", size) + .finish(), + Self::Ignore => write!(f, "Ignore"), + Self::Task(_) => f.debug_tuple("Future").finish(), + } + } +} diff --git a/src/task.rs b/src/task.rs new file mode 100644 index 00000000..a730b37e --- /dev/null +++ b/src/task.rs @@ -0,0 +1,25 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +//! Create asynchronous actions to be performed in the background. + +use std::future::Future; + +/// Yields a task which contains a batch of tasks. +pub fn batch, Y: Send + 'static>( + tasks: impl IntoIterator>, +) -> iced::Task { + iced::Task::batch(tasks).map(Into::into) +} + +/// Yields a task which will run the future on the runtime executor. +pub fn future, Y: 'static>( + future: impl Future + Send + 'static, +) -> iced::Task { + iced::Task::future(async move { future.await.into() }) +} + +/// Yields a task which will return a message. +pub fn message, Y: 'static>(message: X) -> iced::Task { + future(async move { message.into() }) +} diff --git a/src/task/mod.rs b/src/task/mod.rs deleted file mode 100644 index 379ea19b..00000000 --- a/src/task/mod.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2023 System76 -// SPDX-License-Identifier: MPL-2.0 - -//! Create asynchronous actions to be performed in the background. - -use iced::window; -use iced::Task; -use iced_core::window::Mode; -use iced_runtime::{task, Action}; -use std::future::Future; - -/// Yields a task which contains a batch of tasks. -pub fn batch, Y: Send + 'static>( - tasks: impl IntoIterator>, -) -> Task { - Task::batch(tasks).map(Into::into) -} - -/// Yields a task which will run the future on the runtime executor. -pub fn future, Y: 'static>(future: impl Future + Send + 'static) -> Task { - Task::future(async move { future.await.into() }) -} - -/// Yields a task which will return a message. -pub fn message, Y: 'static>(message: X) -> Task { - future(async move { message.into() }) -} - -/// Initiates a window drag. -pub fn drag(id: window::Id) -> Task { - iced_runtime::window::drag(id) -} - -/// Maximizes the window. -pub fn maximize(id: window::Id, maximized: bool) -> Task { - iced_runtime::window::maximize(id, maximized) -} - -/// Minimizes the window. -pub fn minimize(id: window::Id) -> Task { - iced_runtime::window::minimize(id, true) -} - -/// Sets the title of a window. -#[allow(unused_variables, clippy::needless_pass_by_value)] -pub fn set_title(id: window::Id, title: String) -> Task { - Task::none() -} - -/// Sets the window mode to windowed. -pub fn set_windowed(id: window::Id) -> Task { - iced_runtime::window::change_mode(id, Mode::Windowed) -} - -/// Toggles the windows' maximize state. -pub fn toggle_maximize(id: window::Id) -> Task { - iced_runtime::window::toggle_maximize(id) -} diff --git a/src/theme/portal.rs b/src/theme/portal.rs index 17225ccc..e3dc7511 100644 --- a/src/theme/portal.rs +++ b/src/theme/portal.rs @@ -1,7 +1,7 @@ use ashpd::desktop::settings::{ColorScheme, Contrast}; use ashpd::desktop::Color; use iced::futures::{self, select, FutureExt, SinkExt, StreamExt}; -use iced_futures::{stream, subscription}; +use iced_futures::stream; use tracing::error; #[derive(Debug, Clone)] @@ -27,7 +27,7 @@ pub fn desktop_settings() -> iced_futures::Subscription { .await; #[cfg(not(feature = "tokio"))] { - pending::<()>().await; + futures::future::pending::<()>().await; unreachable!(); } attempts += 1; diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 44af94b1..2e5d8c13 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -756,9 +756,7 @@ impl slider::Catalog for Theme { impl menu::Catalog for Theme { type Class<'a> = (); - fn default<'a>() -> ::Class<'a> { - () - } + fn default<'a>() -> ::Class<'a> {} fn style(&self, class: &::Class<'_>) -> menu::Style { let cosmic = self.cosmic(); @@ -779,9 +777,7 @@ impl menu::Catalog for Theme { impl pick_list::Catalog for Theme { type Class<'a> = (); - fn default<'a>() -> ::Class<'a> { - () - } + fn default<'a>() -> ::Class<'a> {} fn style( &self, @@ -824,9 +820,7 @@ impl pick_list::Catalog for Theme { impl radio::Catalog for Theme { type Class<'a> = (); - fn default<'a>() -> Self::Class<'a> { - () - } + fn default<'a>() -> Self::Class<'a> {} fn style(&self, class: &Self::Class<'_>, status: radio::Status) -> radio::Style { let theme = self.cosmic(); @@ -878,9 +872,7 @@ impl radio::Catalog for Theme { impl toggler::Catalog for Theme { type Class<'a> = (); - fn default<'a>() -> Self::Class<'a> { - () - } + fn default<'a>() -> Self::Class<'a> {} fn style(&self, class: &Self::Class<'_>, status: toggler::Status) -> toggler::Style { let cosmic = self.cosmic(); @@ -935,9 +927,7 @@ impl toggler::Catalog for Theme { impl pane_grid::Catalog for Theme { type Class<'a> = (); - fn default<'a>() -> ::Class<'a> { - () - } + fn default<'a>() -> ::Class<'a> {} fn style(&self, class: &::Class<'_>) -> pane_grid::Style { let theme = self.cosmic(); diff --git a/src/theme/style/menu_bar.rs b/src/theme/style/menu_bar.rs index 5acb0d09..7f99a1a5 100644 --- a/src/theme/style/menu_bar.rs +++ b/src/theme/style/menu_bar.rs @@ -1,6 +1,8 @@ // From iced_aw, license MIT //! Change the appearance of menu bars and their menus. +use std::sync::Arc; + use crate::Theme; use iced_widget::core::Color; @@ -33,19 +35,19 @@ pub trait StyleSheet { } /// The style of a menu bar and its menus -#[derive(Default)] +#[derive(Default, Clone)] #[allow(missing_debug_implementations)] pub enum MenuBarStyle { /// The default style. #[default] Default, /// A [`Theme`] that uses a `Custom` palette. - Custom(Box>), + Custom(Arc>), } impl From Appearance> for MenuBarStyle { fn from(f: fn(&Theme) -> Appearance) -> Self { - Self::Custom(Box::new(f)) + Self::Custom(Arc::new(f)) } } diff --git a/src/theme/style/mod.rs b/src/theme/style/mod.rs index 1cbd4ef5..a187374c 100644 --- a/src/theme/style/mod.rs +++ b/src/theme/style/mod.rs @@ -31,3 +31,8 @@ pub use self::segmented_button::SegmentedButton; mod text_input; #[doc(inline)] pub use self::text_input::TextInput; + +#[cfg(all(feature = "wayland", feature = "winit"))] +pub mod tooltip; +#[cfg(all(feature = "wayland", feature = "winit"))] +pub use tooltip::Tooltip; diff --git a/src/theme/style/tooltip.rs b/src/theme/style/tooltip.rs new file mode 100644 index 00000000..a0564e63 --- /dev/null +++ b/src/theme/style/tooltip.rs @@ -0,0 +1,31 @@ +use iced::Color; + +use crate::widget::wayland::tooltip::Catalog; + +#[derive(Default)] +pub enum Tooltip { + #[default] + Default, +} + +impl Catalog for crate::Theme { + type Class = Tooltip; + + fn style(&self, style: &Self::Class) -> crate::widget::wayland::tooltip::Style { + let cosmic = self.cosmic(); + + match style { + Tooltip::Default => crate::widget::wayland::tooltip::Style { + text_color: cosmic.on_bg_color().into(), + background: None, + border_width: 0.0, + border_radius: cosmic.corner_radii.radius_0.into(), + border_color: Color::TRANSPARENT, + shadow_offset: iced::Vector::default(), + outline_width: Default::default(), + outline_color: Color::TRANSPARENT, + icon_color: None, + }, + } + } +} diff --git a/src/widget/about.rs b/src/widget/about.rs index 5026d410..acd13ac9 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -183,7 +183,7 @@ pub fn about<'a, Message: Clone + 'static>( .align_y(Alignment::Center), ) .class(crate::theme::Button::Text) - .on_press(on_url_press(url.unwrap_or(String::new()))) + .on_press(on_url_press(url.unwrap_or_default())) .width(Length::Fill), ) }); diff --git a/src/widget/aspect_ratio.rs b/src/widget/aspect_ratio.rs index ec8e2bed..39ac9c57 100644 --- a/src/widget/aspect_ratio.rs +++ b/src/widget/aspect_ratio.rs @@ -12,7 +12,6 @@ use iced_core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, }; -use iced_widget::container; pub use iced_widget::container::{Catalog, Style}; pub fn aspect_ratio_container<'a, Message: 'static, T>( @@ -35,7 +34,7 @@ where container: Container<'a, Message, crate::Theme, Renderer>, } -impl<'a, Message, Renderer> AspectRatio<'a, Message, Renderer> +impl AspectRatio<'_, Message, Renderer> where Renderer: iced_core::Renderer, { @@ -146,8 +145,8 @@ where } } -impl<'a, Message, Renderer> Widget - for AspectRatio<'a, Message, Renderer> +impl Widget + for AspectRatio<'_, Message, Renderer> where Renderer: iced_core::Renderer, { diff --git a/src/widget/autosize.rs b/src/widget/autosize.rs index adef4490..6c15750d 100644 --- a/src/widget/autosize.rs +++ b/src/widget/autosize.rs @@ -90,8 +90,8 @@ where } } -impl<'a, Message, Theme, Renderer> Widget - for Autosize<'a, Message, Theme, Renderer> +impl Widget + for Autosize<'_, Message, Theme, Renderer> where Renderer: iced_core::Renderer, { diff --git a/src/widget/button/icon.rs b/src/widget/button/icon.rs index 0322d58c..3b46d9d1 100644 --- a/src/widget/button/icon.rs +++ b/src/widget/button/icon.rs @@ -29,7 +29,7 @@ pub fn icon<'a, Message>(handle: impl Into) -> Button<'a, Message> { }) } -impl<'a, Message> Button<'a, Message> { +impl Button<'_, Message> { pub fn new(icon: Icon) -> Self { let guard = crate::theme::THEME.lock().unwrap(); let theme = guard.cosmic(); diff --git a/src/widget/button/image.rs b/src/widget/button/image.rs index a0508aaf..74e3b378 100644 --- a/src/widget/button/image.rs +++ b/src/widget/button/image.rs @@ -1,7 +1,7 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -use super::{Builder, Style}; +use super::Builder; use crate::{ widget::{self, image::Handle}, Element, diff --git a/src/widget/button/mod.rs b/src/widget/button/mod.rs index 6bf6338a..9928628b 100644 --- a/src/widget/button/mod.rs +++ b/src/widget/button/mod.rs @@ -111,7 +111,7 @@ pub struct Builder<'a, Message, Variant> { variant: Variant, } -impl<'a, Message, Variant> Builder<'a, Message, Variant> { +impl Builder<'_, Message, Variant> { /// Set the value of [`on_press`] as either `Some` or `None`. pub fn on_press_maybe(mut self, on_press: Option) -> Self { self.on_press = on_press; diff --git a/src/widget/button/text.rs b/src/widget/button/text.rs index 2070fb16..1b682393 100644 --- a/src/widget/button/text.rs +++ b/src/widget/button/text.rs @@ -1,7 +1,7 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -use super::{Builder, ButtonClass, Style}; +use super::{Builder, ButtonClass}; use crate::widget::{icon, row, tooltip}; use crate::{ext::CollectionWidget, Element}; use apply::Apply; @@ -42,6 +42,12 @@ pub struct Text { pub(super) trailing_icon: Option, } +impl Default for Text { + fn default() -> Self { + Self::new() + } +} + impl Text { pub const fn new() -> Self { Self { @@ -51,7 +57,7 @@ impl Text { } } -impl<'a, Message> Button<'a, Message> { +impl Button<'_, Message> { pub fn new(text: Text) -> Self { let guard = crate::theme::THEME.lock().unwrap(); let theme = guard.cosmic(); diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index a51adbb5..c3e51495 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -55,6 +55,7 @@ pub struct Button<'a, Message> { selected: bool, style: crate::theme::Button, variant: Variant, + force_enabled: bool, } impl<'a, Message> Button<'a, Message> { @@ -77,6 +78,7 @@ impl<'a, Message> Button<'a, Message> { selected: false, style: crate::theme::Button::default(), variant: Variant::Normal, + force_enabled: false, } } @@ -90,6 +92,7 @@ impl<'a, Message> Button<'a, Message> { name: None, #[cfg(feature = "a11y")] description: None, + force_enabled: false, #[cfg(feature = "a11y")] label: None, content: content.into(), @@ -163,6 +166,12 @@ impl<'a, Message> Button<'a, Message> { self } + /// Sets the the [`Button`] to enabled whether or not it has handlers for on press. + pub fn force_enabled(mut self, enabled: bool) -> Self { + self.force_enabled = enabled; + self + } + /// Sets the widget to a selected state. /// /// Displays a selection indicator on image buttons. @@ -348,7 +357,8 @@ impl<'a, Message: 'a + Clone> Widget let mut headerbar_alpha = None; - let is_enabled = self.on_press.is_some() || self.on_press_down.is_some(); + let is_enabled = + self.on_press.is_some() || self.on_press_down.is_some() || self.force_enabled; let is_mouse_over = cursor.position().is_some_and(|p| bounds.contains(p)); let state = tree.state.downcast_ref::(); @@ -583,12 +593,7 @@ impl<'a, Message: 'a + Clone> Widget } match self.description.as_ref() { Some(iced_accessibility::Description::Id(id)) => { - node.set_described_by( - id.iter() - .cloned() - .map(|id| NodeId::from(id)) - .collect::>(), - ); + node.set_described_by(id.iter().cloned().map(NodeId::from).collect::>()); } Some(iced_accessibility::Description::Text(text)) => { node.set_description(text.clone()); diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index b1ee927f..ea96360e 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -53,7 +53,7 @@ impl CalendarModel { let now = Local::now(); let naive_now = NaiveDate::from(now.naive_local()); CalendarModel { - selected: naive_now.clone(), + selected: naive_now, visible: naive_now, } } @@ -65,36 +65,34 @@ impl CalendarModel { pub fn show_prev_month(&mut self) { let prev_month_date = self .visible - .clone() .checked_sub_months(Months::new(1)) .expect("valid naivedate"); - self.visible = prev_month_date.clone(); + self.visible = prev_month_date; } pub fn show_next_month(&mut self) { let next_month_date = self .visible - .clone() .checked_add_months(Months::new(1)) .expect("valid naivedate"); - self.visible = next_month_date.clone(); + self.visible = next_month_date; } pub fn set_prev_month(&mut self) { self.show_prev_month(); - self.selected = self.visible.clone(); + self.selected = self.visible; } pub fn set_next_month(&mut self) { self.show_next_month(); - self.selected = self.visible.clone(); + self.selected = self.visible; } pub fn set_selected_visible(&mut self, selected: NaiveDate) { self.selected = selected; - self.visible = self.selected.clone(); + self.visible = self.selected; } } diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 2930aadb..65961114 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -469,7 +469,7 @@ where text_input("", self.input_color) .on_input(move |s| on_update(ColorPickerUpdate::Input(s))) .on_paste(move |s| on_update(ColorPickerUpdate::Input(s))) - .on_submit(on_update(ColorPickerUpdate::AppliedColor)) + .on_submit(move |_| on_update(ColorPickerUpdate::AppliedColor)) .leading_icon( color_button( None, @@ -611,7 +611,7 @@ pub struct ColorPicker<'a, Message> { must_clear_cache: Rc, } -impl<'a, Message> Widget for ColorPicker<'a, Message> +impl Widget for ColorPicker<'_, Message> where Message: Clone + 'static, { @@ -874,7 +874,7 @@ impl State { } } -impl<'a, Message> ColorPicker<'a, Message> where Message: Clone + 'static {} +impl ColorPicker<'_, Message> where Message: Clone + 'static {} // TODO convert active color to hex or rgba fn color_to_string(c: palette::Hsv, is_hex: bool) -> String { let srgb = palette::Srgb::from_color(c); diff --git a/src/widget/context_drawer/overlay.rs b/src/widget/context_drawer/overlay.rs index cc62b2db..c4b779ac 100644 --- a/src/widget/context_drawer/overlay.rs +++ b/src/widget/context_drawer/overlay.rs @@ -17,8 +17,7 @@ pub(super) struct Overlay<'a, 'b, Message> { pub(super) width: f32, } -impl<'a, 'b, Message> overlay::Overlay - for Overlay<'a, 'b, Message> +impl overlay::Overlay for Overlay<'_, '_, Message> where Message: Clone, { diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index fafa8a19..c59ae407 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -155,7 +155,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { } } -impl<'a, Message: Clone> Widget for ContextDrawer<'a, Message> { +impl Widget for ContextDrawer<'_, Message> { fn children(&self) -> Vec { vec![Tree::new(&self.content), Tree::new(&self.drawer)] } diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 338775b9..10f3ef3e 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -46,9 +46,7 @@ pub struct ContextMenu<'a, Message> { context_menu: Option>>, } -impl<'a, Message: Clone> Widget - for ContextMenu<'a, Message> -{ +impl Widget for ContextMenu<'_, Message> { fn tag(&self) -> tree::Tag { tree::Tag::of::() } diff --git a/src/widget/dialog.rs b/src/widget/dialog.rs index 0c7907e4..3c8f181e 100644 --- a/src/widget/dialog.rs +++ b/src/widget/dialog.rs @@ -15,6 +15,12 @@ pub struct Dialog<'a, Message> { tertiary_action: Option>, } +impl Default for Dialog<'_, Message> { + fn default() -> Self { + Self::new() + } +} + impl<'a, Message> Dialog<'a, Message> { pub fn new() -> Self { Self { diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index 4fcca830..31fa0305 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -243,8 +243,8 @@ impl<'a, Message: 'static> DndDestination<'a, Message> { } } -impl<'a, Message: 'static> Widget - for DndDestination<'a, Message> +impl Widget + for DndDestination<'_, Message> { fn children(&self) -> Vec { vec![Tree::new(&self.container)] diff --git a/src/widget/dnd_source.rs b/src/widget/dnd_source.rs index d57e099b..4936ca10 100644 --- a/src/widget/dnd_source.rs +++ b/src/widget/dnd_source.rs @@ -111,7 +111,7 @@ impl< clipboard, false, if let Some(window) = self.window.as_ref() { - Some(iced_core::clipboard::DndSource::Surface(window.clone())) + Some(iced_core::clipboard::DndSource::Surface(*window)) } else { Some(iced_core::clipboard::DndSource::Widget(self.id.clone())) }, @@ -153,10 +153,9 @@ impl< } impl< - 'a, Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, - > Widget for DndSource<'a, Message, D> + > Widget for DndSource<'_, Message, D> { fn children(&self) -> Vec { vec![Tree::new(&self.container)] diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index 045a5ef0..681a4c37 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -3,9 +3,13 @@ // SPDX-License-Identifier: MPL-2.0 AND MIT mod appearance; +use std::borrow::Cow; +use std::sync::{Arc, Mutex}; + pub use appearance::{Appearance, StyleSheet}; -use crate::widget::{icon, Container}; +use crate::surface; +use crate::widget::{icon, Container, RcWrapper}; use iced_core::event::{self, Event}; use iced_core::layout::{self, Layout}; use iced_core::text::{self, Text}; @@ -21,13 +25,15 @@ use iced_widget::scrollable::Scrollable; pub struct Menu<'a, S, Message> where S: AsRef, + [S]: std::borrow::ToOwned, { - state: &'a mut State, - options: &'a [S], - icons: &'a [icon::Handle], - hovered_option: &'a mut Option, + state: State, + options: Cow<'a, [S]>, + icons: Cow<'a, [icon::Handle]>, + hovered_option: Arc>>, selected_option: Option, on_selected: Box Message + 'a>, + close_on_selected: Option, on_option_hovered: Option<&'a dyn Fn(usize) -> Message>, width: f32, padding: Padding, @@ -36,17 +42,21 @@ where style: (), } -impl<'a, S: AsRef, Message: 'a> Menu<'a, S, Message> { +impl<'a, S: AsRef, Message: 'a + std::clone::Clone> Menu<'a, S, Message> +where + [S]: std::borrow::ToOwned, +{ /// Creates a new [`Menu`] with the given [`State`], a list of options, and /// the message to produced when an option is selected. pub fn new( - state: &'a mut State, - options: &'a [S], - icons: &'a [icon::Handle], - hovered_option: &'a mut Option, + state: State, + options: Cow<'a, [S]>, + icons: Cow<'a, [icon::Handle]>, + hovered_option: Arc>>, selected_option: Option, on_selected: impl FnMut(usize) -> Message + 'a, on_option_hovered: Option<&'a dyn Fn(usize) -> Message>, + close_on_selected: Option, ) -> Self { Menu { state, @@ -61,6 +71,7 @@ impl<'a, S: AsRef, Message: 'a> Menu<'a, S, Message> { text_size: None, text_line_height: text::LineHeight::default(), style: Default::default(), + close_on_selected, } } @@ -102,20 +113,31 @@ impl<'a, S: AsRef, Message: 'a> Menu<'a, S, Message> { ) -> overlay::Element<'a, Message, crate::Theme, crate::Renderer> { overlay::Element::new(Box::new(Overlay::new(self, target_height, position))) } + + /// Turns the [`Menu`] into a popup [`Element`] at the given target + /// position. + /// + /// The `target_height` will be used to display the menu either on top + /// of the target or under it, depending on the screen position and the + /// dimensions of the [`Menu`]. + #[must_use] + pub fn popup(self, position: Point, target_height: f32) -> crate::Element<'a, Message> { + Overlay::new(self, target_height, position).into() + } } /// The local state of a [`Menu`]. #[must_use] -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct State { - tree: Tree, + pub(crate) tree: RcWrapper, } impl State { /// Creates a new [`State`] for a [`Menu`]. pub fn new() -> Self { Self { - tree: Tree::empty(), + tree: RcWrapper::new(Tree::empty()), } } } @@ -127,7 +149,7 @@ impl Default for State { } struct Overlay<'a, Message> { - state: &'a mut Tree, + state: RcWrapper, container: Container<'a, Message, crate::Theme, crate::Renderer>, width: f32, target_height: f32, @@ -135,12 +157,15 @@ struct Overlay<'a, Message> { position: Point, } -impl<'a, Message: 'a> Overlay<'a, Message> { +impl<'a, Message: Clone + 'a> Overlay<'a, Message> { pub fn new>( menu: Menu<'a, S, Message>, target_height: f32, position: Point, - ) -> Self { + ) -> Self + where + [S]: ToOwned, + { let Menu { state, options, @@ -154,6 +179,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> { text_size, text_line_height, style, + close_on_selected, } = menu; let mut container = Container::new(Scrollable::new( @@ -163,6 +189,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> { hovered_option, selected_option, on_selected, + close_on_selected, on_option_hovered, text_size, text_line_height, @@ -172,10 +199,12 @@ impl<'a, Message: 'a> Overlay<'a, Message> { )) .class(crate::style::Container::Dropdown); - state.tree.diff(&mut container as &mut dyn Widget<_, _, _>); + state + .tree + .with_data_mut(|tree| tree.diff(&mut container as &mut dyn Widget<_, _, _>)); Self { - state: &mut state.tree, + state: state.tree.clone(), container, width, target_height, @@ -183,20 +212,15 @@ impl<'a, Message: 'a> Overlay<'a, Message> { position, } } -} -impl<'a, Message> iced_core::Overlay - for Overlay<'a, Message> -{ - fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node { - let position = self.position; - let space_below = bounds.height - (position.y + self.target_height); - let space_above = position.y; + fn _layout(&self, renderer: &crate::Renderer, bounds: Size) -> layout::Node { + let space_below = bounds.height - (self.position.y + self.target_height); + let space_above = self.position.y; let limits = layout::Limits::new( Size::ZERO, Size::new( - bounds.width - position.x, + bounds.width - self.position.x, if space_below > space_above { space_below } else { @@ -206,16 +230,18 @@ impl<'a, Message> iced_core::Overlay ) .width(self.width); - let node = self.container.layout(self.state, renderer, &limits); + let node = self + .state + .with_data_mut(|tree| self.container.layout(tree, renderer, &limits)); node.clone().move_to(if space_below > space_above { - position + Vector::new(0.0, self.target_height) + self.position + Vector::new(0.0, self.target_height) } else { - position - Vector::new(0.0, node.size().height) + self.position - Vector::new(0.0, node.size().height) }) } - fn on_event( + fn _on_event( &mut self, event: Event, layout: Layout<'_>, @@ -226,23 +252,27 @@ impl<'a, Message> iced_core::Overlay ) -> event::Status { let bounds = layout.bounds(); - self.container.on_event( - self.state, event, layout, cursor, renderer, clipboard, shell, &bounds, - ) + self.state.with_data_mut(|tree| { + self.container.on_event( + tree, event, layout, cursor, renderer, clipboard, shell, &bounds, + ) + }) } - fn mouse_interaction( + fn _mouse_interaction( &self, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, renderer: &crate::Renderer, ) -> mouse::Interaction { - self.container - .mouse_interaction(self.state, layout, cursor, viewport, renderer) + self.state.with_data(|tree| { + self.container + .mouse_interaction(tree, layout, cursor, viewport, renderer) + }) } - fn draw( + fn _draw( &self, renderer: &mut crate::Renderer, theme: &crate::Theme, @@ -266,25 +296,138 @@ impl<'a, Message> iced_core::Overlay appearance.background, ); - self.container - .draw(self.state, renderer, theme, style, layout, cursor, &bounds); + self.state.with_data(|tree| { + self.container + .draw(tree, renderer, theme, style, layout, cursor, &bounds) + }) } } -struct List<'a, S: AsRef, Message> { - options: &'a [S], - icons: &'a [icon::Handle], - hovered_option: &'a mut Option, +impl<'a, Message: Clone + 'a> iced_core::Overlay + for Overlay<'a, Message> +{ + fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node { + self._layout(renderer, bounds) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &crate::Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self._on_event(event, layout, cursor, renderer, clipboard, shell) + } + + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &crate::Renderer, + ) -> mouse::Interaction { + self._mouse_interaction(layout, cursor, viewport, renderer) + } + + fn draw( + &self, + renderer: &mut crate::Renderer, + theme: &crate::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + ) { + self._draw(renderer, theme, style, layout, cursor); + } +} + +impl<'a, Message: Clone + 'a> crate::widget::Widget + for Overlay<'a, Message> +{ + fn size(&self) -> Size { + Size::new(Length::Fixed(self.width), Length::Shrink) + } + + fn layout( + &self, + _tree: &mut iced_core::widget::Tree, + renderer: &crate::Renderer, + limits: &iced::Limits, + ) -> layout::Node { + let limits = limits.width(self.width); + + self.state + .with_data_mut(|tree| self.container.layout(tree, renderer, &limits)) + } + + fn mouse_interaction( + &self, + _tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &crate::Renderer, + ) -> mouse::Interaction { + self._mouse_interaction(layout, cursor, viewport, renderer) + } + + fn on_event( + &mut self, + _tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &crate::Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, + ) -> event::Status { + self._on_event(event, layout, cursor, renderer, clipboard, shell) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut crate::Renderer, + theme: &crate::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + ) { + self._draw(renderer, theme, style, layout, cursor); + } +} + +impl<'a, Message: Clone + 'a> From> for crate::Element<'a, Message> { + fn from(widget: Overlay<'a, Message>) -> Self { + Element::new(widget) + } +} + +struct List<'a, S: AsRef, Message> +where + [S]: std::borrow::ToOwned, +{ + options: Cow<'a, [S]>, + icons: Cow<'a, [icon::Handle]>, + hovered_option: Arc>>, selected_option: Option, on_selected: Box Message + 'a>, + close_on_selected: Option, on_option_hovered: Option<&'a dyn Fn(usize) -> Message>, padding: Padding, text_size: Option, text_line_height: text::LineHeight, } -impl<'a, S: AsRef, Message> Widget - for List<'a, S, Message> +impl, Message> Widget for List<'_, S, Message> +where + [S]: std::borrow::ToOwned, + Message: Clone, { fn size(&self) -> Size { Size::new(Length::Fill, Length::Shrink) @@ -330,9 +473,13 @@ impl<'a, S: AsRef, Message> Widget ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { + let hovered_guard = self.hovered_option.lock().unwrap(); if cursor.is_over(layout.bounds()) { - if let Some(index) = *self.hovered_option { + 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); + } return event::Status::Captured; } } @@ -348,14 +495,15 @@ impl<'a, S: AsRef, Message> Widget + self.padding.vertical(); let new_hovered_option = (cursor_position.y / option_height) as usize; + let mut hovered_guard = self.hovered_option.lock().unwrap(); if let Some(on_option_hovered) = self.on_option_hovered { - if *self.hovered_option != Some(new_hovered_option) { + if *hovered_guard != Some(new_hovered_option) { shell.publish(on_option_hovered(new_hovered_option)); } } - *self.hovered_option = Some(new_hovered_option); + *hovered_guard = Some(new_hovered_option); } } Event::Touch(touch::Event::FingerPressed { .. }) => { @@ -367,11 +515,15 @@ impl<'a, S: AsRef, Message> Widget let option_height = f32::from(self.text_line_height.to_absolute(Pixels(text_size))) + self.padding.vertical(); + let mut hovered_guard = self.hovered_option.lock().unwrap(); - *self.hovered_option = Some((cursor_position.y / option_height) as usize); + *hovered_guard = Some((cursor_position.y / option_height) as usize); - if let Some(index) = *self.hovered_option { + 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); + } return event::Status::Captured; } } @@ -434,6 +586,8 @@ impl<'a, S: AsRef, Message> Widget height: option_height, }; + let hovered_guard = self.hovered_option.lock().unwrap(); + let (color, font) = if self.selected_option == Some(i) { let item_x = bounds.x + appearance.border_width; let item_width = appearance.border_width.mul_add(-2.0, bounds.width); @@ -471,7 +625,7 @@ impl<'a, S: AsRef, Message> Widget ); (appearance.selected_text_color, crate::font::semibold()) - } else if *self.hovered_option == Some(i) { + } else if *hovered_guard == Some(i) { let item_x = bounds.x + appearance.border_width; let item_width = appearance.border_width.mul_add(-2.0, bounds.width); @@ -538,6 +692,9 @@ impl<'a, S: AsRef, Message> Widget impl<'a, S: AsRef, Message: 'a> From> for Element<'a, Message, crate::Theme, crate::Renderer> +where + [S]: std::borrow::ToOwned, + Message: Clone, { fn from(list: List<'a, S, Message>) -> Self { Element::new(list) diff --git a/src/widget/dropdown/mod.rs b/src/widget/dropdown/mod.rs index 6e3db648..3ebaffaf 100644 --- a/src/widget/dropdown/mod.rs +++ b/src/widget/dropdown/mod.rs @@ -5,6 +5,7 @@ //! Displays a list of options in a popover menu on select. pub mod menu; +use iced_core::window; pub use menu::Menu; pub mod multi; @@ -12,11 +13,40 @@ pub mod multi; mod widget; pub use widget::*; +use crate::surface; + /// Displays a list of options in a popover menu on select. -pub fn dropdown<'a, S: AsRef, Message: 'a>( - selections: &'a [S], +pub fn dropdown< + S: AsRef + std::clone::Clone + Send + Sync + 'static, + Message: 'static + Clone, +>( + selections: &[S], selected: Option, - on_selected: impl Fn(usize) -> Message + 'a, -) -> Dropdown<'a, S, Message> { + on_selected: impl Fn(usize) -> Message + Send + Sync + 'static, +) -> Dropdown<'_, S, Message, Message> { Dropdown::new(selections, selected, on_selected) } + +/// Displays a list of options in a popover menu on select. +/// AppMessage must be the App's toplevel message. +pub fn popup_dropdown< + 'a, + S: AsRef + std::clone::Clone + Send + Sync + 'static, + Message: 'static + Clone, + AppMessage: 'static + Clone, +>( + selections: &'a [S], + selected: Option, + on_selected: impl Fn(usize) -> Message + Send + Sync + 'static, + _parent_id: window::Id, + _on_surface_action: impl Fn(surface::Action) -> Message + Send + Sync + 'static, + _map_action: impl Fn(Message) -> AppMessage + Send + Sync + 'static, +) -> Dropdown<'a, S, Message, AppMessage> { + let dropdown: Dropdown<'_, S, Message, AppMessage> = + Dropdown::new(selections, selected, on_selected); + + #[cfg(all(feature = "winit", feature = "wayland"))] + let dropdown = dropdown.with_popup(_parent_id, _on_surface_action, _map_action); + + dropdown +} diff --git a/src/widget/dropdown/multi/menu.rs b/src/widget/dropdown/multi/menu.rs index f5ee5f5b..3d37d928 100644 --- a/src/widget/dropdown/multi/menu.rs +++ b/src/widget/dropdown/multi/menu.rs @@ -180,9 +180,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> { } } -impl<'a, Message> iced_core::Overlay - for Overlay<'a, Message> -{ +impl iced_core::Overlay for Overlay<'_, Message> { fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node { let position = self.position; let space_below = bounds.height - (position.y + self.target_height); @@ -279,8 +277,8 @@ struct InnerList<'a, S, Item, Message> { text_line_height: text::LineHeight, } -impl<'a, S, Item, Message> Widget - for InnerList<'a, S, Item, Message> +impl Widget + for InnerList<'_, S, Item, Message> where S: AsRef, Item: Clone + PartialEq, diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index b3bb09e2..14f89d72 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -159,7 +159,7 @@ impl<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static> cursor: mouse::Cursor, viewport: &Rectangle, ) { - let font = self.font.unwrap_or_else(|| crate::font::default()); + let font = self.font.unwrap_or_else(crate::font::default); draw( renderer, @@ -278,7 +278,7 @@ pub fn layout( bounds: Size::new(f32::MAX, f32::MAX), size: iced::Pixels(text_size), line_height: text_line_height, - font: font.unwrap_or_else(|| crate::font::default()), + font: font.unwrap_or_else(crate::font::default), horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, shaping: text::Shaping::Advanced, @@ -422,7 +422,7 @@ pub fn overlay<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static bounds: Size::new(f32::MAX, f32::MAX), size: iced::Pixels(text_size), line_height, - font: font.unwrap_or_else(|| crate::font::default()), + font: font.unwrap_or_else(crate::font::default), horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, shaping: text::Shaping::Advanced, diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 26f69cce..f3388976 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -3,9 +3,10 @@ // SPDX-License-Identifier: MPL-2.0 AND MIT use super::menu::{self, Menu}; -use crate::widget::icon; +use crate::widget::icon::{self, Handle}; +use crate::{surface, Element}; use derive_setters::Setters; -use iced::Radians; +use iced::window; use iced_core::event::{self, Event}; use iced_core::text::{self, Paragraph, Text}; use iced_core::widget::tree::{self, Tree}; @@ -14,14 +15,24 @@ use iced_core::{ Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, }; use iced_widget::pick_list::{self, Catalog}; +use std::borrow::Cow; use std::ffi::OsStr; use std::hash::{DefaultHasher, Hash, Hasher}; +use std::marker::PhantomData; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, LazyLock, Mutex}; +pub type DropdownView = Arc Element<'static, Message> + Send + Sync>; +static AUTOSIZE_ID: LazyLock = + LazyLock::new(|| crate::widget::Id::new("cosmic-applet-autosize")); /// A widget for selecting a single value from a list of selections. #[derive(Setters)] -pub struct Dropdown<'a, S: AsRef, Message> { +pub struct Dropdown<'a, S: AsRef + Send + Sync + Clone + 'static, Message, AppMessage> +where + [S]: std::borrow::ToOwned, +{ #[setters(skip)] - on_selected: Box Message + 'a>, + on_selected: Arc Message + Send + Sync>, #[setters(skip)] selections: &'a [S], #[setters] @@ -38,9 +49,21 @@ pub struct Dropdown<'a, S: AsRef, Message> { text_line_height: text::LineHeight, #[setters(strip_option)] font: Option, + #[setters(skip)] + on_surface_action: Option Message + Send + Sync + 'static>>, + #[setters(skip)] + action_map: Option AppMessage + 'static + Send + Sync>>, + #[setters(strip_option)] + window_id: Option, + #[cfg(all(feature = "winit", feature = "wayland"))] + positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, } -impl<'a, S: AsRef, Message> Dropdown<'a, S, Message> { +impl<'a, S: AsRef + Send + Sync + Clone + 'static, Message: 'static, AppMessage: 'static> + Dropdown<'a, S, Message, AppMessage> +where + [S]: std::borrow::ToOwned, +{ /// The default gap. pub const DEFAULT_GAP: f32 = 4.0; @@ -52,10 +75,10 @@ impl<'a, S: AsRef, Message> Dropdown<'a, S, Message> { pub fn new( selections: &'a [S], selected: Option, - on_selected: impl Fn(usize) -> Message + 'a, + on_selected: impl Fn(usize) -> Message + 'static + Send + Sync, ) -> Self { Self { - on_selected: Box::new(on_selected), + on_selected: Arc::new(on_selected), selections, icons: &[], selected, @@ -65,12 +88,73 @@ impl<'a, S: AsRef, Message> Dropdown<'a, S, Message> { text_size: None, text_line_height: text::LineHeight::Relative(1.2), font: None, + window_id: None, + #[cfg(all(feature = "winit", feature = "wayland"))] + positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), + on_surface_action: None, + action_map: None, } } + + #[cfg(all(feature = "winit", feature = "wayland"))] + /// Handle dropdown requests for popup creation. + /// Intended to be used with [`crate::app::message::get_popup`] + pub fn with_popup( + mut self, + parent_id: window::Id, + on_surface_action: impl Fn(surface::Action) -> Message + Send + Sync + 'static, + action_map: impl Fn(Message) -> NewAppMessage + Send + Sync + 'static, + ) -> Dropdown<'a, S, Message, NewAppMessage> { + let Self { + on_selected, + selections, + icons, + selected, + width, + gap, + padding, + text_size, + text_line_height, + font, + positioner, + .. + } = self; + + Dropdown::<'a, S, Message, NewAppMessage> { + on_selected, + selections, + icons, + selected, + width, + gap, + padding, + text_size, + text_line_height, + font, + on_surface_action: Some(Arc::new(on_surface_action)), + action_map: Some(Arc::new(action_map)), + window_id: Some(parent_id), + positioner, + } + } + + #[cfg(all(feature = "winit", feature = "wayland"))] + pub fn with_positioner( + mut self, + positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, + ) -> Self { + self.positioner = positioner; + self + } } -impl<'a, S: AsRef, Message: 'a> Widget - for Dropdown<'a, S, Message> +impl< + S: AsRef + Send + Sync + Clone + 'static, + Message: 'static + Clone, + AppMessage: 'static + Clone, + > Widget for Dropdown<'_, S, Message, AppMessage> +where + [S]: std::borrow::ToOwned, { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -153,15 +237,26 @@ impl<'a, S: AsRef, Message: 'a> Widget, _viewport: &Rectangle, ) -> event::Status { - update( + update::( &event, layout, cursor, shell, - self.on_selected.as_ref(), + #[cfg(all(feature = "winit", feature = "wayland"))] + self.positioner.clone(), + self.on_selected.clone(), self.selected, self.selections, || tree.state.downcast_mut::(), + self.window_id, + self.on_surface_action.clone(), + self.action_map.clone(), + self.icons, + self.gap, + self.padding, + self.text_size, + self.font, + self.selected, ) } @@ -186,7 +281,7 @@ impl<'a, S: AsRef, Message: 'a> Widget, Message: 'a> Widget Option> { + #[cfg(all(feature = "winit", feature = "wayland"))] + if self.window_id.is_some() || self.on_surface_action.is_some() { + return None; + } + let state = tree.state.downcast_mut::(); overlay( @@ -225,8 +325,9 @@ impl<'a, S: AsRef, Message: 'a> Widget, Message: 'a> Widget, Message: 'a> From> - for crate::Element<'a, Message> +impl< + 'a, + S: AsRef + Send + Sync + Clone + 'static, + Message: 'static + std::clone::Clone, + AppMessage: 'static + std::clone::Clone, + > From> for crate::Element<'a, Message> +where + [S]: std::borrow::ToOwned, { - fn from(pick_list: Dropdown<'a, S, Message>) -> Self { + fn from(pick_list: Dropdown<'a, S, Message, AppMessage>) -> Self { Self::new(pick_list) } } /// The local state of a [`Dropdown`]. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct State { icon: Option, menu: menu::State, keyboard_modifiers: keyboard::Modifiers, - is_open: bool, - hovered_option: Option, + is_open: Arc, + hovered_option: Arc>>, hashes: Vec, selections: Vec, + popup_id: window::Id, } impl State { @@ -276,10 +384,11 @@ impl State { }, menu: menu::State::default(), keyboard_modifiers: keyboard::Modifiers::default(), - is_open: false, - hovered_option: None, + is_open: Arc::new(AtomicBool::new(false)), + hovered_option: Arc::new(Mutex::new(None)), selections: Vec::new(), hashes: Vec::new(), + popup_id: window::Id::unique(), } } } @@ -316,7 +425,7 @@ pub fn layout( bounds: Size::new(f32::MAX, f32::MAX), size: iced::Pixels(text_size), line_height: text_line_height, - font: font.unwrap_or_else(|| crate::font::default()), + font: font.unwrap_or_else(crate::font::default), horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, shaping: text::Shaping::Advanced, @@ -348,32 +457,136 @@ pub fn layout( /// Processes an [`Event`] and updates the [`State`] of a [`Dropdown`] /// accordingly. -#[allow(clippy::too_many_arguments)] -pub fn update<'a, S: AsRef, Message>( +#[allow(clippy::too_many_arguments, clippy::too_many_lines)] +pub fn update< + 'a, + S: AsRef + Send + Sync + Clone + 'static, + Message: Clone + 'static, + AppMessage: Clone + 'static, +>( event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, shell: &mut Shell<'_, Message>, - on_selected: &dyn Fn(usize) -> Message, + #[cfg(all(feature = "winit", feature = "wayland"))] + positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, + on_selected: Arc Message + Send + Sync + 'static>, selected: Option, selections: &[S], state: impl FnOnce() -> &'a mut State, + _window_id: Option, + on_surface_action: Option Message + Send + Sync + 'static>>, + action_map: Option AppMessage + Send + Sync + 'static>>, + icons: &[icon::Handle], + gap: f32, + padding: Padding, + text_size: Option, + font: Option, + selected_option: Option, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { let state = state(); - - if state.is_open { + let is_open = state.is_open.load(Ordering::Relaxed); + if is_open { // Event wasn't processed by overlay, so cursor was clicked either outside it's // bounds or on the drop-down, either way we close the overlay. - state.is_open = false; - + state.is_open.store(false, Ordering::Relaxed); + #[cfg(all(feature = "winit", feature = "wayland"))] + if let Some(on_close) = on_surface_action { + shell.publish(on_close(surface::action::destroy_popup(state.popup_id))); + } event::Status::Captured } else if cursor.is_over(layout.bounds()) { - state.is_open = true; - state.hovered_option = selected; + state.is_open.store(true, Ordering::Relaxed); + let mut hovered_guard = state.hovered_option.lock().unwrap(); + *hovered_guard = selected; + let id = window::Id::unique(); + state.popup_id = id; + #[cfg(all(feature = "winit", feature = "wayland"))] + if let Some(((on_surface_action, parent), action_map)) = + on_surface_action.zip(_window_id).zip(action_map) + { + use iced_runtime::platform_specific::wayland::popup::{ + SctkPopupSettings, SctkPositioner, + }; + let bounds = layout.bounds(); + let anchor_rect = Rectangle { + x: bounds.x as i32, + y: bounds.y as i32, + width: bounds.width as i32, + height: bounds.height as i32, + }; + let icon_width = if icons.is_empty() { 0.0 } else { 24.0 }; + let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 { + selection_paragraph.min_width().round() + }; + let pad_width = padding.horizontal().mul_add(2.0, 16.0); + let selections_width = selections + .iter() + .zip(state.selections.iter_mut()) + .map(|(label, selection)| measure(label.as_ref(), selection.raw())) + .fold(0.0, |next, current| current.max(next)); + + let icons: Cow<'static, [Handle]> = Cow::Owned(icons.to_vec()); + let selections: Cow<'static, [S]> = Cow::Owned(selections.to_vec()); + let state = state.clone(); + let on_close = surface::action::destroy_popup(id); + let on_surface_action_clone = on_surface_action.clone(); + let get_popup_action = surface::action::simple_popup::< + AppMessage, + Box< + dyn Fn() -> Element<'static, crate::Action> + + Send + + Sync + + 'static, + >, + >( + move || { + SctkPopupSettings { + parent, + id, + input_zone: None, + positioner: SctkPositioner { + size: Some((selections_width as u32 + gap as u32 + pad_width as u32 + icon_width as u32, 10)), + anchor_rect, + // TODO: left or right alignment based on direction? + anchor: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::BottomLeft, + gravity: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight, + reactive: true, + offset: (-padding.left as i32, 0), + constraint_adjustment: 9, + ..Default::default() + }, + parent_size: None, + grab: true, + close_with_children: true, + } + }, + Some(Box::new(move || { + let action_map = action_map.clone(); + let on_selected = on_selected.clone(); + let e: Element<'static, crate::Action> = + Element::from(menu_widget( + bounds, + &state, + gap, + padding, + text_size.unwrap_or(14.0), + selections.clone(), + icons.clone(), + selected_option, + Arc::new(move |i| on_selected.clone()(i)), + Some(on_surface_action_clone(on_close.clone())), + )) + .map(move |m| crate::Action::App(action_map.clone()(m))); + e + })), + ); + shell.publish(on_surface_action(get_popup_action)); + } event::Status::Captured } else { event::Status::Ignored @@ -383,11 +596,9 @@ pub fn update<'a, S: AsRef, Message>( delta: mouse::ScrollDelta::Lines { .. }, }) => { let state = state(); + let is_open = state.is_open.load(Ordering::Relaxed); - if state.keyboard_modifiers.command() - && cursor.is_over(layout.bounds()) - && !state.is_open - { + if state.keyboard_modifiers.command() && cursor.is_over(layout.bounds()) && !is_open { let next_index = selected.map(|index| index + 1).unwrap_or_default(); if selections.len() < next_index { @@ -423,9 +634,72 @@ pub fn mouse_interaction(layout: Layout<'_>, cursor: mouse::Cursor) -> mouse::In } } +#[cfg(all(feature = "winit", feature = "wayland"))] +/// Returns the current menu widget of a [`Dropdown`]. +#[allow(clippy::too_many_arguments)] +pub fn menu_widget< + S: AsRef + Send + Sync + Clone + 'static, + Message: 'static + std::clone::Clone, +>( + bounds: Rectangle, + state: &State, + gap: f32, + padding: Padding, + text_size: f32, + selections: Cow<'static, [S]>, + icons: Cow<'static, [icon::Handle]>, + selected_option: Option, + on_selected: Arc Message + Send + Sync + 'static>, + close_on_selected: Option, +) -> crate::Element<'static, Message> +where + [S]: std::borrow::ToOwned, +{ + let icon_width = if icons.is_empty() { 0.0 } else { 24.0 }; + let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 { + selection_paragraph.min_width().round() + }; + let selections_width = selections + .iter() + .zip(state.selections.iter()) + .map(|(label, selection)| measure(label.as_ref(), selection.raw())) + .fold(0.0, |next, current| current.max(next)); + let pad_width = padding.horizontal().mul_add(2.0, 16.0); + + let width = selections_width + gap + pad_width + icon_width; + let is_open = state.is_open.clone(); + let menu: Menu<'static, S, Message> = Menu::new( + state.menu.clone(), + selections, + icons, + state.hovered_option.clone(), + selected_option, + move |option| { + is_open.store(false, Ordering::Relaxed); + + (on_selected)(option) + }, + None, + close_on_selected, + ) + .width(width) + .padding(padding) + .text_size(text_size); + + crate::widget::autosize::autosize( + menu.popup(iced::Point::new(0., 0.), bounds.height), + AUTOSIZE_ID.clone(), + ) + .auto_height(true) + .auto_width(true) + .min_height(1.) + .min_width(width) + .into() +} + /// Returns the current overlay of a [`Dropdown`]. #[allow(clippy::too_many_arguments)] -pub fn overlay<'a, S: AsRef, Message: 'a>( +pub fn overlay<'a, S: AsRef + Send + Sync + Clone + 'static, Message: std::clone::Clone + 'a>( layout: Layout<'_>, _renderer: &crate::Renderer, state: &'a mut State, @@ -439,22 +713,27 @@ pub fn overlay<'a, S: AsRef, Message: 'a>( selected_option: Option, on_selected: &'a dyn Fn(usize) -> Message, translation: Vector, -) -> Option> { - if state.is_open { + close_on_selected: Option, +) -> Option> +where + [S]: std::borrow::ToOwned, +{ + if state.is_open.load(Ordering::Relaxed) { let bounds = layout.bounds(); let menu = Menu::new( - &mut state.menu, - selections, - icons, - &mut state.hovered_option, + state.menu.clone(), + Cow::Borrowed(selections), + Cow::Borrowed(icons), + state.hovered_option.clone(), selected_option, |option| { - state.is_open = false; + state.is_open.store(false, Ordering::Relaxed); (on_selected)(option) }, None, + close_on_selected, ) .width({ let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 { diff --git a/src/widget/flex_row/widget.rs b/src/widget/flex_row/widget.rs index 49955217..852c18cc 100644 --- a/src/widget/flex_row/widget.rs +++ b/src/widget/flex_row/widget.rs @@ -85,9 +85,7 @@ impl<'a, Message> FlexRow<'a, Message> { } } -impl<'a, Message: 'static + Clone> Widget - for FlexRow<'a, Message> -{ +impl Widget for FlexRow<'_, Message> { fn children(&self) -> Vec { self.children.iter().map(Tree::new).collect() } diff --git a/src/widget/grid/widget.rs b/src/widget/grid/widget.rs index 961ffb90..fc91ba29 100644 --- a/src/widget/grid/widget.rs +++ b/src/widget/grid/widget.rs @@ -44,6 +44,12 @@ pub struct Grid<'a, Message> { row: u16, } +impl Default for Grid<'_, Message> { + fn default() -> Self { + Self::new() + } +} + impl<'a, Message> Grid<'a, Message> { pub const fn new() -> Self { Self { @@ -106,7 +112,7 @@ impl<'a, Message> Grid<'a, Message> { } } -impl<'a, Message: 'static + Clone> Widget for Grid<'a, Message> { +impl Widget for Grid<'_, Message> { fn children(&self) -> Vec { self.children.iter().map(Tree::new).collect() } @@ -303,6 +309,12 @@ pub struct Assignment { pub(super) height: u16, } +impl Default for Assignment { + fn default() -> Self { + Self::new() + } +} + impl Assignment { pub const fn new() -> Self { Self { diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 1f70220f..32bfa278 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -120,8 +120,8 @@ pub struct HeaderBarWidget<'a, Message> { header_bar_inner: Element<'a, Message>, } -impl<'a, Message: Clone + 'static> Widget - for HeaderBarWidget<'a, Message> +impl Widget + for HeaderBarWidget<'_, Message> { fn diff(&mut self, tree: &mut tree::Tree) { tree.diff_children(&mut [&mut self.header_bar_inner]); @@ -306,7 +306,10 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { Density::Spacious => 48.0, Density::Standard => 48.0, }; - + let portion = ((start.len().max(end.len()) as f32 / center.len().max(1) as f32).round() + as u16) + .max(1); + let center_empty = center.is_empty() && self.title.is_empty(); // Creates the headerbar widget. let mut widget = widget::row::with_capacity(3) // If elements exist in the start region, append them here. @@ -316,7 +319,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .align_y(iced::Alignment::Center) .apply(widget::container) .align_x(iced::Alignment::Start) - .width(Length::Shrink), + .width(Length::FillPortion(portion)), ) // If elements exist in the center region, use them here. // This will otherwise use the title as a widget if a title was defined. @@ -338,7 +341,11 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .align_y(iced::Alignment::Center) .apply(widget::container) .align_x(iced::Alignment::End) - .width(Length::Shrink), + .width(if center_empty { + Length::Fill + } else { + Length::FillPortion(portion) + }), ) .align_y(iced::Alignment::Center) .height(Length::Fixed(height)) diff --git a/src/widget/icon/mod.rs b/src/widget/icon/mod.rs index 318b9fb1..45d5451d 100644 --- a/src/widget/icon/mod.rs +++ b/src/widget/icon/mod.rs @@ -84,7 +84,7 @@ impl Icon { self.height .unwrap_or_else(|| Length::Fixed(f32::from(self.size))), ) - .rotation(self.rotation.unwrap_or_else(Rotation::default)) + .rotation(self.rotation.unwrap_or_default()) .content_fit(self.content_fit) .into() }; @@ -100,7 +100,7 @@ impl Icon { self.height .unwrap_or_else(|| Length::Fixed(f32::from(self.size))), ) - .rotation(self.rotation.unwrap_or_else(Rotation::default)) + .rotation(self.rotation.unwrap_or_default()) .content_fit(self.content_fit) .symbolic(self.handle.symbolic) .into() diff --git a/src/widget/icon/named.rs b/src/widget/icon/named.rs index 40432c04..055b2e42 100644 --- a/src/widget/icon/named.rs +++ b/src/widget/icon/named.rs @@ -144,7 +144,7 @@ impl From for Icon { } } -impl<'a, Message: 'static> From for crate::Element<'a, Message> { +impl From for crate::Element<'_, Message> { fn from(builder: Named) -> Self { builder.icon().into() } diff --git a/src/widget/id_container.rs b/src/widget/id_container.rs index ae30289f..2070780b 100644 --- a/src/widget/id_container.rs +++ b/src/widget/id_container.rs @@ -47,8 +47,8 @@ where } } -impl<'a, Message, Theme, Renderer> Widget - for IdContainer<'a, Message, Theme, Renderer> +impl Widget + for IdContainer<'_, Message, Theme, Renderer> where Renderer: iced_core::Renderer, { diff --git a/src/widget/layer_container.rs b/src/widget/layer_container.rs index 016932b4..f442bc51 100644 --- a/src/widget/layer_container.rs +++ b/src/widget/layer_container.rs @@ -138,8 +138,7 @@ where } } -impl<'a, Message, Renderer> Widget - for LayerContainer<'a, Message, Renderer> +impl Widget for LayerContainer<'_, Message, Renderer> where Renderer: iced_core::Renderer, { diff --git a/src/widget/list/column.rs b/src/widget/list/column.rs index 39eb96a1..1a9b7348 100644 --- a/src/widget/list/column.rs +++ b/src/widget/list/column.rs @@ -24,7 +24,7 @@ pub struct ListColumn<'a, Message> { children: Vec>, } -impl<'a, Message: 'static> Default for ListColumn<'a, Message> { +impl Default for ListColumn<'_, Message> { fn default() -> Self { let cosmic_theme::Spacing { space_xxs, space_m, .. diff --git a/src/widget/menu.rs b/src/widget/menu.rs index d5ea3222..f5c9c461 100644 --- a/src/widget/menu.rs +++ b/src/widget/menu.rs @@ -55,6 +55,7 @@ //! pub mod action; + pub use action::MenuAction as Action; mod flex; diff --git a/src/widget/menu/key_bind.rs b/src/widget/menu/key_bind.rs index a6934ff1..8b4ed227 100644 --- a/src/widget/menu/key_bind.rs +++ b/src/widget/menu/key_bind.rs @@ -40,7 +40,7 @@ impl KeyBind { pub fn matches(&self, modifiers: Modifiers, key: &Key) -> bool { let key_eq = match (key, &self.key) { // CapsLock and Shift change the case of Key::Character, so we compare these in a case insensitive way - (Key::Character(a), Key::Character(b)) => a.eq_ignore_ascii_case(&b), + (Key::Character(a), Key::Character(b)) => a.eq_ignore_ascii_case(b), (a, b) => a.eq(b), }; key_eq diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 1f112925..283e5922 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -64,8 +64,8 @@ impl Default for MenuBarState { } } -pub(crate) fn menu_roots_children<'a, Message, Renderer>( - menu_roots: &Vec>, +pub(crate) fn menu_roots_children( + menu_roots: &Vec>, ) -> Vec where Renderer: renderer::Renderer, @@ -95,8 +95,8 @@ where } #[allow(invalid_reference_casting)] -pub(crate) fn menu_roots_diff<'a, Message, Renderer>( - menu_roots: &mut Vec>, +pub(crate) fn menu_roots_diff( + menu_roots: &mut Vec>, tree: &mut Tree, ) where Renderer: renderer::Renderer, @@ -280,8 +280,7 @@ where self } } -impl<'a, Message, Renderer> Widget - for MenuBar<'a, Message, Renderer> +impl Widget for MenuBar<'_, Message, Renderer> where Renderer: renderer::Renderer, { @@ -366,6 +365,8 @@ where if state.menu_states.is_empty() && view_cursor.is_over(layout.bounds()) { state.view_cursor = view_cursor; state.open = true; + // #[cfg(feature = "wayland")] + // TODO emit Message to open menu } } _ => (), @@ -437,6 +438,9 @@ where _renderer: &Renderer, translation: Vector, ) -> Option> { + // #[cfg(feature = "wayland")] + // return None; + let state = tree.state.downcast_ref::(); if !state.open { return None; diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index b64fba2c..1cd60dec 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -447,7 +447,7 @@ where pub(crate) style: &'b ::Style, pub(crate) position: Point, } -impl<'a, 'b, Message, Renderer> Menu<'a, 'b, Message, Renderer> +impl<'b, Message, Renderer> Menu<'_, 'b, Message, Renderer> where Renderer: renderer::Renderer, { @@ -455,8 +455,8 @@ where overlay::Element::new(Box::new(self)) } } -impl<'a, 'b, Message, Renderer> overlay::Overlay - for Menu<'a, 'b, Message, Renderer> +impl overlay::Overlay + for Menu<'_, '_, Message, Renderer> where Renderer: renderer::Renderer, { diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index 01ca3076..86ed0dfb 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -9,9 +9,9 @@ use std::rc::Rc; use iced_widget::core::{renderer, Element}; use crate::iced_core::{Alignment, Length}; -use crate::widget::icon; use crate::widget::menu::action::MenuAction; use crate::widget::menu::key_bind::KeyBind; +use crate::widget::{icon, Button}; use crate::{theme, widget}; /// Nested menu is essentially a tree of items, a menu is a collection of items @@ -192,14 +192,13 @@ pub enum MenuItem>> { /// - A button for the root menu item. pub fn menu_root<'a, Message, Renderer: renderer::Renderer>( label: impl Into> + 'a, -) -> iced::Element<'a, Message, crate::Theme, Renderer> +) -> Button<'a, Message> where Element<'a, Message, crate::Theme, Renderer>: From>, { widget::button::custom(widget::text(label)) .padding([4, 12]) .class(theme::Button::MenuRoot) - .into() } /// Create a list of menu items from a vector of `MenuItem`. diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 06dd4e85..75a3191c 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -97,6 +97,14 @@ pub mod aspect_ratio; #[cfg(feature = "autosize")] pub mod autosize; +pub(crate) mod responsive_container; + +#[cfg(feature = "surface-message")] +mod responsive_menu_bar; +#[cfg(feature = "surface-message")] +#[doc(inline)] +pub use responsive_menu_bar::responsive_menu_bar; + pub mod button; #[doc(inline)] pub use button::{Button, IconButton, LinkButton, TextButton}; @@ -335,9 +343,12 @@ pub use toggler::toggler; #[doc(inline)] pub use tooltip::{tooltip, Tooltip}; + +#[cfg(all(feature = "wayland", feature = "winit"))] +pub mod wayland; + pub mod tooltip { use crate::Element; - use std::borrow::Cow; pub use iced::widget::tooltip::Position; @@ -362,6 +373,10 @@ pub mod warning; #[doc(inline)] pub use warning::*; +pub mod wrapper; +#[doc(inline)] +pub use wrapper::*; + #[cfg(feature = "markdown")] #[doc(inline)] pub use iced::widget::markdown; diff --git a/src/widget/nav_bar_toggle.rs b/src/widget/nav_bar_toggle.rs index 1d807060..2a315683 100644 --- a/src/widget/nav_bar_toggle.rs +++ b/src/widget/nav_bar_toggle.rs @@ -25,7 +25,7 @@ pub fn nav_bar_toggle() -> NavBarToggle { } } -impl<'a, Message: 'static + Clone> From> for Element<'a, Message> { +impl From> for Element<'_, Message> { fn from(nav_bar_toggle: NavBarToggle) -> Self { let icon = if nav_bar_toggle.active { widget::icon::from_svg_bytes( diff --git a/src/widget/popover.rs b/src/widget/popover.rs index 974153d3..12a64d06 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -75,8 +75,8 @@ impl<'a, Message, Renderer> Popover<'a, Message, Renderer> { // TODO More options for positioning similar to GdkPopup, xdg_popup } -impl<'a, Message: Clone, Renderer> Widget - for Popover<'a, Message, Renderer> +impl Widget + for Popover<'_, Message, Renderer> where Renderer: iced_core::Renderer, { @@ -305,8 +305,8 @@ pub struct Overlay<'a, 'b, Message, Renderer> { pos: Point, } -impl<'a, 'b, Message, Renderer> overlay::Overlay - for Overlay<'a, 'b, Message, Renderer> +impl overlay::Overlay + for Overlay<'_, '_, Message, Renderer> where Message: Clone, Renderer: iced_core::Renderer, @@ -425,7 +425,7 @@ where ) -> Option> { self.content .as_widget_mut() - .overlay(&mut self.tree, layout, renderer, Default::default()) + .overlay(self.tree, layout, renderer, Default::default()) } } diff --git a/src/widget/radio.rs b/src/widget/radio.rs index 55d96192..ebb75ee2 100644 --- a/src/widget/radio.rs +++ b/src/widget/radio.rs @@ -155,7 +155,7 @@ where } } -impl<'a, Message, Renderer> Widget for Radio<'a, Message, Renderer> +impl Widget for Radio<'_, Message, Renderer> where Message: Clone, Renderer: iced_core::Renderer, diff --git a/src/widget/rectangle_tracker/mod.rs b/src/widget/rectangle_tracker/mod.rs index 876b1255..3e7753e9 100644 --- a/src/widget/rectangle_tracker/mod.rs +++ b/src/widget/rectangle_tracker/mod.rs @@ -209,7 +209,7 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let layout = self.container.layout( + self.container.layout( tree, renderer, if self.ignore_bounds { @@ -217,9 +217,7 @@ where } else { limits }, - ); - - layout + ) } fn operate( diff --git a/src/widget/responsive_container.rs b/src/widget/responsive_container.rs new file mode 100644 index 00000000..82b91824 --- /dev/null +++ b/src/widget/responsive_container.rs @@ -0,0 +1,299 @@ +//! Responsive Container, which will notify of size changes. + +use iced::{Limits, Size}; +use iced_core::event::{self, Event}; +use iced_core::layout; +use iced_core::mouse; +use iced_core::overlay; +use iced_core::renderer; +use iced_core::widget::{tree, Id, Tree}; +use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; + +pub(crate) fn responsive_container<'a, Message: 'static, Theme, E>( + content: E, + id: Id, + on_action: impl Fn(crate::surface::Action) -> Message + 'static, +) -> ResponsiveContainer<'a, Message, Theme, crate::Renderer> +where + E: Into>, + Theme: iced_widget::container::Catalog, + ::Class<'a>: From>, +{ + ResponsiveContainer::new(content, id, on_action) +} + +/// An element decorating some content. +/// +/// It is normally used for alignment purposes. +#[allow(missing_debug_implementations)] +pub struct ResponsiveContainer<'a, Message, Theme, Renderer> +where + Renderer: iced_core::Renderer, +{ + content: Element<'a, Message, Theme, Renderer>, + id: Id, + size: Option, + on_action: Box Message>, +} + +impl<'a, Message, Theme, Renderer> ResponsiveContainer<'a, Message, Theme, Renderer> +where + Renderer: iced_core::Renderer, +{ + /// Creates an empty [`IdContainer`]. + pub(crate) fn new( + content: T, + id: Id, + on_action: impl Fn(crate::surface::Action) -> Message + 'static, + ) -> Self + where + T: Into>, + { + ResponsiveContainer { + content: content.into(), + id, + size: None, + on_action: Box::new(on_action), + } + } + + pub(crate) fn size(mut self, size: Size) -> Self { + self.size = Some(size); + self + } +} + +impl Widget + for ResponsiveContainer<'_, Message, Theme, Renderer> +where + Renderer: iced_core::Renderer, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(State::new()) + } + + fn children(&self) -> Vec { + vec![Tree::new(&self.content)] + } + + fn diff(&mut self, tree: &mut Tree) { + tree.children[0].diff(&mut self.content); + } + + fn size(&self) -> iced_core::Size { + self.content.as_widget().size() + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let state = tree.state.downcast_mut::(); + let unrestricted_size = self.size.unwrap_or_else(|| { + let node = + self.content + .as_widget() + .layout(&mut tree.children[0], renderer, &Limits::NONE); + node.size() + }); + + let max_size = limits.max(); + let old_max = state.limits.max(); + state.needs_update = (unrestricted_size.width > max_size.width) + ^ (state.size.width > old_max.width) + || (unrestricted_size.height > max_size.height) ^ (state.size.height > old_max.height); + if state.needs_update { + state.limits = *limits; + state.size = unrestricted_size; + } + + let node = self + .content + .as_widget() + .layout(&mut tree.children[0], renderer, limits); + let size = node.size(); + layout::Node::with_children(size, vec![node]) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn iced_core::widget::Operation<()>, + ) { + operation.container(Some(&self.id), layout.bounds(), &mut |operation| { + self.content.as_widget().operate( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + operation, + ); + }); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) -> event::Status { + let state = tree.state.downcast_mut::(); + + if state.needs_update { + shell.publish((self.on_action)( + crate::surface::Action::ResponsiveMenuBar { + menu_bar: self.id.clone(), + limits: state.limits, + size: state.size, + }, + )); + state.needs_update = false; + } + + self.content.as_widget_mut().on_event( + &mut tree.children[0], + event.clone(), + layout.children().next().unwrap(), + cursor_position, + renderer, + clipboard, + shell, + viewport, + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + let content_layout = layout.children().next().unwrap(); + self.content.as_widget().mouse_interaction( + &tree.children[0], + content_layout, + cursor_position, + viewport, + renderer, + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + renderer_style: &renderer::Style, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + viewport: &Rectangle, + ) { + let content_layout = layout.children().next().unwrap(); + self.content.as_widget().draw( + &tree.children[0], + renderer, + theme, + renderer_style, + content_layout, + cursor_position, + viewport, + ); + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + translation: Vector, + ) -> Option> { + self.content.as_widget_mut().overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + translation, + ) + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, + ) { + let content_layout = layout.children().next().unwrap(); + self.content.as_widget().drag_destinations( + &state.children[0], + content_layout, + renderer, + dnd_rectangles, + ); + } + + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn set_id(&mut self, id: crate::widget::Id) { + self.id = id; + } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: Layout<'_>, + state: &Tree, + p: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + let c_layout = layout.children().next().unwrap(); + let c_state = &state.children[0]; + self.content.as_widget().a11y_nodes(c_layout, c_state, p) + } +} + +impl<'a, Message, Theme, Renderer> From> + for Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Renderer: 'a + iced_core::Renderer, + Theme: 'a, +{ + fn from( + c: ResponsiveContainer<'a, Message, Theme, Renderer>, + ) -> Element<'a, Message, Theme, Renderer> { + Element::new(c) + } +} + +#[derive(Debug, Clone, Copy)] +struct State { + limits: Limits, + size: Size, + needs_update: bool, +} + +impl State { + fn new() -> Self { + Self { + limits: Limits::NONE, + size: Size::new(0., 0.), + needs_update: false, + } + } +} diff --git a/src/widget/responsive_menu_bar.rs b/src/widget/responsive_menu_bar.rs new file mode 100644 index 00000000..38857100 --- /dev/null +++ b/src/widget/responsive_menu_bar.rs @@ -0,0 +1,78 @@ +use std::collections::HashMap; + +use apply::Apply; + +use crate::{ + widget::{button, icon, responsive_container}, + Core, Element, +}; + +use super::menu; + +/// # Panics +/// +/// Will panic if the menu bar collapses without tracking the size +pub fn responsive_menu_bar<'a, Message: Clone + 'static, A: menu::Action>( + core: &Core, + key_binds: &HashMap, + id: crate::widget::Id, + action_message: impl Fn(crate::surface::Action) -> Message + 'static, + trees: Vec<( + std::borrow::Cow<'static, str>, + Vec>>, + )>, +) -> Element<'a, Message> { + use crate::widget::id_container; + + let menu_bar_size = core.menu_bars.get(&id); + + #[allow(clippy::if_not_else)] + if !menu_bar_size.is_some_and(|(limits, size)| { + let max_size = limits.max(); + max_size.width < size.width + }) { + responsive_container::responsive_container( + id_container( + menu::bar( + trees + .into_iter() + .map(|mt| { + menu::Tree::<_>::with_children( + menu::root(mt.0), + menu::items(key_binds, mt.1), + ) + }) + .collect(), + ), + crate::widget::Id::new(format!("menu_bar_expanded_{id}")), + ), + id, + action_message, + ) + .apply(Element::from) + } else { + responsive_container::responsive_container( + id_container( + menu::bar(vec![menu::Tree::<_>::with_children( + Element::from( + button::icon(icon::from_name("open-menu-symbolic")) + .padding([4, 12]) + .class(crate::theme::Button::MenuRoot), + ), + menu::items( + key_binds, + trees + .into_iter() + .map(|mt| menu::Item::Folder(mt.0, mt.1)) + .collect(), + ), + )]), + crate::widget::Id::new(format!("menu_bar_collapsed_{id}")), + ), + id, + action_message, + ) + .size(menu_bar_size.unwrap().1) + .apply(Element::from) + } +} diff --git a/src/widget/segmented_button/horizontal.rs b/src/widget/segmented_button/horizontal.rs index a859b2ca..ccf4c8ae 100644 --- a/src/widget/segmented_button/horizontal.rs +++ b/src/widget/segmented_button/horizontal.rs @@ -30,8 +30,8 @@ where SegmentedButton::new(model) } -impl<'a, SelectionMode, Message> SegmentedVariant - for SegmentedButton<'a, Horizontal, SelectionMode, Message> +impl SegmentedVariant + for SegmentedButton<'_, Horizontal, SelectionMode, Message> where Model: Selectable, SelectionMode: Default, diff --git a/src/widget/segmented_button/model/entity.rs b/src/widget/segmented_button/model/entity.rs index 77f591b9..02a7d371 100644 --- a/src/widget/segmented_button/model/entity.rs +++ b/src/widget/segmented_button/model/entity.rs @@ -15,7 +15,7 @@ pub struct EntityMut<'a, SelectionMode: Default> { pub(super) model: &'a mut Model, } -impl<'a, SelectionMode: Default> EntityMut<'a, SelectionMode> +impl EntityMut<'_, SelectionMode> where Model: Selectable, { diff --git a/src/widget/segmented_button/vertical.rs b/src/widget/segmented_button/vertical.rs index d8ae0be9..3f5d5645 100644 --- a/src/widget/segmented_button/vertical.rs +++ b/src/widget/segmented_button/vertical.rs @@ -30,8 +30,8 @@ where SegmentedButton::new(model) } -impl<'a, SelectionMode, Message> SegmentedVariant - for SegmentedButton<'a, Vertical, SelectionMode, Message> +impl SegmentedVariant + for 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 e40465d0..0e433aec 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -547,8 +547,8 @@ where } } -impl<'a, Variant, SelectionMode, Message> Widget - for SegmentedButton<'a, Variant, SelectionMode, Message> +impl Widget + for SegmentedButton<'_, Variant, SelectionMode, Message> where Self: SegmentedVariant, Model: Selectable, @@ -562,7 +562,7 @@ where if let Some(ref context_menu) = self.context_menu { let mut tree = Tree::empty(); tree.state = tree::State::new(MenuBarState::default()); - tree.children = menu_roots_children(&context_menu); + tree.children = menu_roots_children(context_menu); children.push(tree); } @@ -719,7 +719,7 @@ where let on_dnd_enter = self.on_dnd_enter .as_ref() - .zip(entity.clone()) + .zip(entity) .map(|(on_enter, entity)| { move |_, _, mime_types| on_enter(entity, mime_types) }); diff --git a/src/widget/settings/section.rs b/src/widget/settings/section.rs index e32251ef..71b9a92e 100644 --- a/src/widget/settings/section.rs +++ b/src/widget/settings/section.rs @@ -20,9 +20,7 @@ pub fn section<'a, Message: 'static>() -> Section<'a, Message> { } /// A section with a pre-defined list column. -pub fn with_column<'a, Message: 'static>( - children: ListColumn<'a, Message>, -) -> Section<'a, Message> { +pub fn with_column(children: ListColumn<'_, Message>) -> Section<'_, Message> { Section { title: Cow::Borrowed(""), children, diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index 10c19125..8739342d 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -9,12 +9,10 @@ use crate::{ Element, }; use apply::Apply; -use derive_setters::Setters; -use iced::{alignment::Horizontal, Border, Shadow}; use iced::{Alignment, Length}; -use std::marker::PhantomData; +use iced::{Border, Shadow}; +use std::borrow::Cow; use std::ops::{Add, Sub}; -use std::{borrow::Cow, fmt::Display}; /// Horizontal spin button widget. pub fn spin_button<'a, T, M>( @@ -153,9 +151,7 @@ where } } -fn horizontal_variant<'a, T, Message>( - spin_button: SpinButton<'a, T, Message>, -) -> Element<'a, Message> +fn horizontal_variant(spin_button: SpinButton<'_, T, Message>) -> Element<'_, Message> where Message: Clone + 'static, T: Copy + Sub + Add + PartialOrd, @@ -193,7 +189,7 @@ where .into() } -fn vertical_variant<'a, T, Message>(spin_button: SpinButton<'a, T, Message>) -> Element<'a, Message> +fn vertical_variant(spin_button: SpinButton<'_, T, Message>) -> Element<'_, Message> where Message: Clone + 'static, T: Copy + Sub + Add + PartialOrd, diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 8d46b839..d6c11cf6 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -18,7 +18,6 @@ use super::style::StyleSheet; pub use super::value::Value; use apply::Apply; -use cosmic_theme::Theme; use iced::clipboard::dnd::{DndAction, DndEvent, OfferEvent, SourceEvent}; use iced::clipboard::mime::AsMimeTypes; use iced::Limits; @@ -40,10 +39,6 @@ use iced_core::{ Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size, Vector, Widget, }; -#[cfg(feature = "wayland")] -use iced_renderer::core::event::{wayland, PlatformSpecific}; -#[cfg(feature = "wayland")] -use iced_runtime::platform_specific; use iced_runtime::{task, Action, Task}; thread_local! { @@ -200,7 +195,7 @@ pub struct TextInput<'a, Message> { error: Option>, on_input: Option Message + 'a>>, on_paste: Option Message + 'a>>, - on_submit: Option, + on_submit: Option Message + 'a>>, on_toggle_edit: Option Message + 'a>>, leading_icon: Option>, trailing_icon: Option>, @@ -211,6 +206,8 @@ pub struct TextInput<'a, Message> { line_height: text::LineHeight, helper_line_height: text::LineHeight, always_active: bool, + /// The text input tracks and manages the input value in its state. + manage_value: bool, } impl<'a, Message> TextInput<'a, Message> @@ -255,6 +252,7 @@ where label: None, helper_text: None, always_active: false, + manage_value: false, } } @@ -340,14 +338,24 @@ where /// Sets the message that should be produced when the [`TextInput`] is /// focused and the enter key is pressed. - pub fn on_submit(self, message: Message) -> Self { - self.on_submit_maybe(Some(message)) + pub fn on_submit(self, callback: F) -> Self + where + F: 'a + Fn(String) -> Message, + { + self.on_submit_maybe(Some(Box::new(callback))) } /// Maybe sets the message that should be produced when the [`TextInput`] is /// focused and the enter key is pressed. - pub fn on_submit_maybe(mut self, message: Option) -> Self { - self.on_submit = message; + pub fn on_submit_maybe(mut self, callback: Option) -> Self + where + F: 'a + Fn(String) -> Message, + { + if let Some(callback) = callback { + self.on_submit = Some(Box::new(callback)); + } else { + self.on_submit = None; + } self } @@ -416,6 +424,12 @@ where self } + /// Sets the text input to manage its input value or not + pub fn manage_value(mut self, manage_value: bool) -> Self { + self.manage_value = true; + self + } + /// Draws the [`TextInput`] with the given [`Renderer`], overriding its /// [`Value`] if provided. /// @@ -507,7 +521,7 @@ where } } -impl<'a, Message> Widget for TextInput<'a, Message> +impl Widget for TextInput<'_, Message> where Message: Clone + 'static, { @@ -526,9 +540,14 @@ where fn diff(&mut self, tree: &mut Tree) { let state = tree.state.downcast_mut::(); - + if !self.manage_value || !self.value.is_empty() && state.tracked_value != self.value { + state.tracked_value = self.value.clone(); + } else if self.value.is_empty() { + self.value = state.tracked_value.clone(); + // std::mem::swap(&mut state.tracked_value, &mut self.value); + } // Unfocus text input if it becomes disabled - if self.on_input.is_none() { + if self.on_input.is_none() && !self.manage_value { state.last_click = None; state.is_focused = None; state.is_pasting = None; @@ -581,13 +600,10 @@ where // if the previous state was at the end of the text, keep it there let old_value = Value::new(&old_value); if state.is_focused.is_some() { - match state.cursor.state(&old_value) { - cursor::State::Index(index) => { - if index == old_value.len() { - state.cursor.move_to(self.value.len()); - } + if let cursor::State::Index(index) = state.cursor.state(&old_value) { + if index == old_value.len() { + state.cursor.move_to(self.value.len()); } - _ => {} }; } @@ -597,6 +613,11 @@ where state.is_focused = None; } + // Stop pasting if input becomes disabled + if !self.manage_value && self.on_input.is_none() { + state.is_pasting = None; + } + let mut children: Vec<_> = self .leading_icon .iter_mut() @@ -779,7 +800,7 @@ where } } - if tree.children.len() > 0 { + if !tree.children.is_empty() { let index = tree.children.len() - 1; if let (Some(trailing_icon), Some(tree)) = (self.trailing_icon.as_mut(), tree.children.get_mut(index)) @@ -824,13 +845,14 @@ where self.is_editable, self.on_input.as_deref(), self.on_paste.as_deref(), - &self.on_submit, + self.on_submit.as_deref(), self.on_toggle_edit.as_deref(), || tree.state.downcast_mut::(), self.on_create_dnd_source.as_deref(), dnd_id, line_height, layout, + self.manage_value, ) } @@ -856,7 +878,7 @@ where &self.placeholder, self.size, self.font, - self.on_input.is_none(), + self.on_input.is_none() && !self.manage_value, self.is_secure, self.leading_icon.as_ref(), self.trailing_icon.as_ref(), @@ -925,7 +947,11 @@ where } let mut children = layout.children(); let layout = children.next().unwrap(); - mouse_interaction(layout, cursor_position, self.on_input.is_none()) + mouse_interaction( + layout, + cursor_position, + self.on_input.is_none() && !self.manage_value, + ) } fn id(&self) -> Option { @@ -1236,13 +1262,14 @@ pub fn update<'a, Message: 'static>( is_editable: bool, on_input: Option<&dyn Fn(String) -> Message>, on_paste: Option<&dyn Fn(String) -> Message>, - on_submit: &Option, + on_submit: Option<&dyn Fn(String) -> Message>, on_toggle_edit: Option<&dyn Fn(bool) -> Message>, state: impl FnOnce() -> &'a mut State, #[allow(unused_variables)] on_start_dnd_source: Option<&dyn Fn(State) -> Message>, #[allow(unused_variables)] dnd_id: u128, line_height: text::LineHeight, layout: Layout<'_>, + manage_value: bool, ) -> event::Status where Message: Clone, @@ -1264,7 +1291,7 @@ where | Event::Touch(touch::Event::FingerPressed { .. }) => { let state = state(); - let click_position = if on_input.is_some() { + let click_position = if on_input.is_some() || manage_value { cursor.position_over(layout.bounds()) } else { None @@ -1299,7 +1326,7 @@ where // single click that is on top of the selected text // is the click on selected text? - if let Some(on_input) = on_input { + if manage_value || on_input.is_some() { let left = start.min(end); let right = end.max(start); @@ -1339,8 +1366,11 @@ where let contents = editor.contents(); let unsecured_value = Value::new(&contents); - let message = (on_input)(contents); - shell.publish(message); + 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())); } @@ -1349,7 +1379,7 @@ where iced_core::clipboard::start_dnd( clipboard, false, - id.map(|id| iced_core::clipboard::DndSource::Widget(id)), + id.map(iced_core::clipboard::DndSource::Widget), Some(iced_core::clipboard::IconSurface::new( Element::from( TextInput::<'static, ()>::new("", input_text.clone()) @@ -1531,7 +1561,7 @@ where let state = state(); if let Some(focus) = &mut state.is_focused { - let Some(on_input) = on_input else { + if !manage_value && on_input.is_none() { return event::Status::Ignored; }; @@ -1545,8 +1575,8 @@ where match key { keyboard::Key::Named(keyboard::key::Named::Enter) => { - if let Some(on_submit) = on_submit.clone() { - shell.publish(on_submit); + if let Some(on_submit) = on_submit { + shell.publish((on_submit)(unsecured_value.to_string())); } } keyboard::Key::Named(keyboard::key::Named::Backspace) => { @@ -1566,9 +1596,11 @@ where let contents = editor.contents(); let unsecured_value = Value::new(&contents); - let message = (on_input)(editor.contents()); - shell.publish(message); - + state.tracked_value = unsecured_value.clone(); + if let Some(on_input) = on_input { + let message = (on_input)(editor.contents()); + shell.publish(message); + } let value = if is_secure { unsecured_value.secure() } else { @@ -1592,8 +1624,12 @@ where editor.delete(); let contents = editor.contents(); let unsecured_value = Value::new(&contents); - let message = (on_input)(contents); - shell.publish(message); + if let Some(on_input) = on_input { + let message = (on_input)(contents); + state.tracked_value = unsecured_value.clone(); + shell.publish(message); + } + let value = if is_secure { unsecured_value.secure() } else { @@ -1671,10 +1707,12 @@ where let mut editor = Editor::new(value, &mut state.cursor); editor.delete(); - - let message = (on_input)(editor.contents()); - - shell.publish(message); + let content = editor.contents(); + state.tracked_value = Value::new(&content); + if let Some(on_input) = on_input { + let message = (on_input)(content); + shell.publish(message); + } } } keyboard::Key::Character(c) @@ -1699,13 +1737,16 @@ where let contents = editor.contents(); let unsecured_value = Value::new(&contents); - let message = if let Some(paste) = &on_paste { - (paste)(contents) - } else { - (on_input)(contents) - }; - shell.publish(message); + state.tracked_value = unsecured_value.clone(); + if let Some(on_input) = on_input { + let message = if let Some(paste) = &on_paste { + (paste)(contents) + } else { + (on_input)(contents) + }; + shell.publish(message); + } state.is_pasting = Some(content); let value = if is_secure { @@ -1750,8 +1791,11 @@ where } let contents = editor.contents(); let unsecured_value = Value::new(&contents); - let message = (on_input)(contents); - shell.publish(message); + state.tracked_value = unsecured_value.clone(); + if let Some(on_input) = on_input { + let message = (on_input)(contents); + shell.publish(message); + } focus.updated_at = Instant::now(); LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at)); @@ -1926,7 +1970,7 @@ where editor.paste(Value::new(content.as_str())); let contents = editor.contents(); let unsecured_value = Value::new(&contents); - + state.tracked_value = unsecured_value.clone(); if let Some(on_paste) = on_paste.as_ref() { let message = (on_paste)(contents); shell.publish(message); @@ -2408,6 +2452,7 @@ pub(crate) struct DndOfferState; #[derive(Debug, Default, Clone)] #[must_use] pub struct State { + pub tracked_value: Value, pub value: crate::Plain, pub placeholder: crate::Plain, pub label: crate::Plain, @@ -2482,6 +2527,7 @@ impl State { /// Creates a new [`State`], representing a focused [`TextInput`]. pub fn focused(is_secure: bool, is_read_only: bool) -> Self { Self { + tracked_value: Value::default(), is_secure, value: crate::Plain::default(), placeholder: crate::Plain::default(), diff --git a/src/widget/text_input/value.rs b/src/widget/text_input/value.rs index b18ea2ca..dee3f110 100644 --- a/src/widget/text_input/value.rs +++ b/src/widget/text_input/value.rs @@ -8,7 +8,7 @@ use unicode_segmentation::UnicodeSegmentation; /// /// [`TextInput`]: crate::widget::TextInput // TODO: Reduce allocations, cache results (?) -#[derive(Debug, Clone)] +#[derive(Default, Debug, Clone, PartialEq)] pub struct Value { graphemes: Vec, } diff --git a/src/widget/toaster/mod.rs b/src/widget/toaster/mod.rs index 2ed8289e..1acbce0c 100644 --- a/src/widget/toaster/mod.rs +++ b/src/widget/toaster/mod.rs @@ -8,7 +8,7 @@ use std::rc::Rc; use crate::widget::container; use crate::widget::Column; -use iced::{Padding, Task}; +use iced::Task; use iced_core::Element; use slotmap::new_key_type; use slotmap::SlotMap; diff --git a/src/widget/toaster/widget.rs b/src/widget/toaster/widget.rs index 7a7f6949..bde5c890 100644 --- a/src/widget/toaster/widget.rs +++ b/src/widget/toaster/widget.rs @@ -35,8 +35,8 @@ impl<'a, Message, Theme, Renderer> Toaster<'a, Message, Theme, Renderer> { } } -impl<'a, Message, Theme, Renderer> Widget - for Toaster<'a, Message, Theme, Renderer> +impl Widget + for Toaster<'_, Message, Theme, Renderer> where Renderer: iced_core::Renderer, { @@ -191,8 +191,8 @@ where } } -impl<'a, 'b, Message, Theme, Renderer> Overlay - for ToasterOverlay<'a, 'b, Message, Theme, Renderer> +impl Overlay + for ToasterOverlay<'_, '_, Message, Theme, Renderer> where Renderer: renderer::Renderer, { diff --git a/src/widget/wayland/mod.rs b/src/widget/wayland/mod.rs new file mode 100644 index 00000000..7c53d374 --- /dev/null +++ b/src/widget/wayland/mod.rs @@ -0,0 +1 @@ +pub mod tooltip; diff --git a/src/widget/wayland/tooltip/mod.rs b/src/widget/wayland/tooltip/mod.rs new file mode 100644 index 00000000..79a2fda9 --- /dev/null +++ b/src/widget/wayland/tooltip/mod.rs @@ -0,0 +1,76 @@ +//! Change the apperance of a tooltip. + +pub mod widget; + +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use iced_core::{border::Radius, Background, Color, Vector}; + +use crate::theme::THEME; + +/// The appearance of a tooltip. +#[must_use] +#[derive(Debug, Clone, Copy)] +pub struct Style { + /// The amount of offset to apply to the shadow of the tooltip. + pub shadow_offset: Vector, + + /// The [`Background`] of the tooltip. + pub background: Option, + + /// The border radius of the tooltip. + pub border_radius: Radius, + + /// The border width of the tooltip. + pub border_width: f32, + + /// The border [`Color`] of the tooltip. + pub border_color: Color, + + /// An outline placed around the border. + pub outline_width: f32, + + /// The [`Color`] of the outline. + pub outline_color: Color, + + /// The icon [`Color`] of the tooltip. + pub icon_color: Option, + + /// The text [`Color`] of the tooltip. + pub text_color: Color, +} + +impl Style { + // TODO: `Radius` is not `const fn` compatible. + pub fn new() -> Self { + let rad_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0; + Self { + shadow_offset: Vector::new(0.0, 0.0), + background: None, + border_radius: Radius::from(rad_0), + border_width: 0.0, + border_color: Color::TRANSPARENT, + outline_width: 0.0, + outline_color: Color::TRANSPARENT, + icon_color: None, + text_color: Color::BLACK, + } + } +} + +impl std::default::Default for Style { + fn default() -> Self { + Self::new() + } +} + +// TODO update to match other styles +/// A set of rules that dictate the style of a tooltip. +pub trait Catalog { + /// The supported style of the [`StyleSheet`]. + type Class: Default; + + /// Produces the active [`Appearance`] of a tooltip. + fn style(&self, style: &Self::Class) -> Style; +} diff --git a/src/widget/wayland/tooltip/widget.rs b/src/widget/wayland/tooltip/widget.rs new file mode 100644 index 00000000..033ad0f3 --- /dev/null +++ b/src/widget/wayland/tooltip/widget.rs @@ -0,0 +1,684 @@ +// Copyright 2019 H�ctor Ram�n, Iced contributors +// Copyright 2023 System76 +// SPDX-License-Identifier: MIT + +//! Allow your users to perform actions by pressing a button. +//! +//! A [`Tooltip`] has some local [`State`]. + +use std::any::Any; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use iced::Task; +use iced_runtime::core::widget::Id; + +use iced_core::event::{self, Event}; +use iced_core::renderer; +use iced_core::touch; +use iced_core::widget::tree::{self, Tree}; +use iced_core::widget::Operation; +use iced_core::{layout, svg}; +use iced_core::{mouse, Border}; +use iced_core::{overlay, Shadow}; +use iced_core::{ + Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget, +}; + +pub use super::{Catalog, Style}; + +/// Internally defines different button widget variants. +enum Variant { + Normal, + Image { + close_icon: svg::Handle, + on_remove: Option, + }, +} + +/// A generic button which emits a message when pressed. +#[allow(missing_debug_implementations)] +#[must_use] +pub struct Tooltip<'a, Message, TopLevelMessage> { + id: Id, + #[cfg(feature = "a11y")] + name: Option>, + #[cfg(feature = "a11y")] + description: Option>, + #[cfg(feature = "a11y")] + label: Option>, + content: crate::Element<'a, Message>, + on_leave: Message, + on_surface_action: Box Message>, + width: Length, + height: Length, + padding: Padding, + selected: bool, + style: crate::theme::Tooltip, + delay: Option, + settings: Option< + Arc< + dyn Fn(Rectangle) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + + Send + + Sync + + 'static, + >, + >, + view: Arc< + dyn Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, + >, +} + +impl<'a, Message, TopLevelMessage> Tooltip<'a, Message, TopLevelMessage> { + /// Creates a new [`Tooltip`] with the given content. + pub fn new( + content: impl Into>, + settings: Option< + impl Fn(Rectangle) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + + Send + + Sync + + 'static, + >, + view: impl Fn() -> crate::Element<'static, crate::Action> + + Send + + Sync + + 'static, + on_leave: Message, + on_surface_action: impl Fn(crate::surface::Action) -> Message + 'static, + ) -> Self { + Self { + id: Id::unique(), + #[cfg(feature = "a11y")] + name: None, + #[cfg(feature = "a11y")] + description: None, + #[cfg(feature = "a11y")] + label: None, + content: content.into(), + width: Length::Shrink, + height: Length::Shrink, + padding: Padding::new(0.0), + selected: false, + style: crate::theme::Tooltip::default(), + on_leave, + on_surface_action: Box::new(on_surface_action), + delay: None, + settings: if let Some(s) = settings { + Some(Arc::new(s)) + } else { + None + }, + view: Arc::new(view), + } + } + + pub fn delay(mut self, dur: Duration) -> Self { + self.delay = Some(dur); + self + } + + /// Sets the [`Id`] of the [`Tooltip`]. + pub fn id(mut self, id: Id) -> Self { + self.id = id; + self + } + + /// Sets the width of the [`Tooltip`]. + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Sets the height of the [`Tooltip`]. + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } + + /// Sets the [`Padding`] of the [`Tooltip`]. + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + /// Sets the widget to a selected state. + /// + /// Displays a selection indicator on image buttons. + pub fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + + self + } + + /// Sets the style variant of this [`Tooltip`]. + pub fn class(mut self, style: crate::theme::Tooltip) -> Self { + self.style = style; + self + } + + #[cfg(feature = "a11y")] + /// Sets the name of the [`Tooltip`]. + pub fn name(mut self, name: impl Into>) -> Self { + self.name = Some(name.into()); + self + } + + #[cfg(feature = "a11y")] + /// Sets the description of the [`Tooltip`]. + pub fn description_widget(mut self, description: &T) -> Self { + self.description = Some(iced_accessibility::Description::Id( + description.description(), + )); + self + } + + #[cfg(feature = "a11y")] + /// Sets the description of the [`Tooltip`]. + pub fn description(mut self, description: impl Into>) -> Self { + self.description = Some(iced_accessibility::Description::Text(description.into())); + self + } + + #[cfg(feature = "a11y")] + /// Sets the label of the [`Tooltip`]. + pub fn label(mut self, label: &dyn iced_accessibility::Labels) -> Self { + self.label = Some(label.label().into_iter().map(|l| l.into()).collect()); + self + } +} + +impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> + Widget for Tooltip<'a, Message, TopLevelMessage> +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(State::default()) + } + + fn children(&self) -> Vec { + vec![Tree::new(&self.content)] + } + + fn diff(&mut self, tree: &mut Tree) { + tree.diff_children(std::slice::from_mut(&mut self.content)); + } + + fn size(&self) -> iced_core::Size { + iced_core::Size::new(self.width, self.height) + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &crate::Renderer, + limits: &layout::Limits, + ) -> layout::Node { + layout( + renderer, + limits, + self.width, + self.height, + self.padding, + |renderer, limits| { + self.content + .as_widget() + .layout(&mut tree.children[0], renderer, limits) + }, + ) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &crate::Renderer, + operation: &mut dyn Operation<()>, + ) { + operation.container(None, layout.bounds(), &mut |operation| { + self.content.as_widget().operate( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + operation, + ); + }); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &crate::Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) -> event::Status { + let status = update( + self.id.clone(), + event.clone(), + layout, + cursor, + shell, + self.settings.as_ref(), + &self.view, + self.delay, + &self.on_leave, + &self.on_surface_action, + || tree.state.downcast_mut::(), + ); + status.merge(self.content.as_widget_mut().on_event( + &mut tree.children[0], + event, + layout.children().next().unwrap(), + cursor, + renderer, + clipboard, + shell, + viewport, + )) + } + + #[allow(clippy::too_many_lines)] + fn draw( + &self, + tree: &Tree, + renderer: &mut crate::Renderer, + theme: &crate::Theme, + renderer_style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + if !viewport.intersects(&bounds) { + return; + } + let content_layout = layout.children().next().unwrap(); + + let state = tree.state.downcast_ref::(); + + let styling = theme.style(&self.style); + + let icon_color = styling.icon_color.unwrap_or(renderer_style.icon_color); + + draw::<_, crate::Theme>( + renderer, + bounds, + *viewport, + &styling, + |renderer, _styling| { + self.content.as_widget().draw( + &tree.children[0], + renderer, + theme, + &renderer::Style { + icon_color, + text_color: styling.text_color, + scale_factor: renderer_style.scale_factor, + }, + content_layout, + cursor, + &viewport.intersection(&bounds).unwrap_or_default(), + ); + }, + ); + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &crate::Renderer, + ) -> mouse::Interaction { + self.content.as_widget().mouse_interaction( + &tree.children[0], + layout, + cursor, + viewport, + renderer, + ) + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &crate::Renderer, + mut translation: Vector, + ) -> Option> { + let position = layout.bounds().position(); + translation.x += position.x; + translation.y += position.y; + self.content.as_widget_mut().overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + translation, + ) + } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: Layout<'_>, + state: &Tree, + p: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + let c_layout = layout.children().next().unwrap(); + + self.content.as_widget().a11y_nodes(c_layout, state, p) + } + + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn set_id(&mut self, id: Id) { + self.id = id; + } +} + +impl<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static> + From> for crate::Element<'a, Message> +{ + fn from(button: Tooltip<'a, Message, TopLevelMessage>) -> Self { + Self::new(button) + } +} + +/// The local state of a [`Tooltip`]. +#[derive(Debug, Clone, Default)] +#[allow(clippy::struct_field_names)] +pub struct State { + is_hovered: Arc>, +} + +impl State { + /// Returns whether the [`Tooltip`] is currently hovered or not. + pub fn is_hovered(self) -> bool { + let guard = self.is_hovered.lock().unwrap(); + *guard + } +} + +/// Processes the given [`Event`] and updates the [`State`] of a [`Tooltip`] +/// accordingly. +#[allow(clippy::needless_pass_by_value)] +pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>( + _id: Id, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + shell: &mut Shell<'_, Message>, + settings: Option< + &Arc< + dyn Fn(Rectangle) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + + Send + + Sync + + 'static, + >, + >, + view: &Arc< + dyn Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, + >, + delay: Option, + on_leave: &Message, + on_surface_action: &dyn Fn(crate::surface::Action) -> Message, + state: impl FnOnce() -> &'a mut State, +) -> event::Status { + match event { + Event::Touch(touch::Event::FingerLifted { .. }) => { + let state = state(); + let mut guard = state.is_hovered.lock().unwrap(); + if *guard { + *guard = false; + + shell.publish(on_leave.clone()); + + return event::Status::Captured; + } + } + + Event::Touch(touch::Event::FingerLost { .. }) | Event::Mouse(mouse::Event::CursorLeft) => { + let state = state(); + let mut guard = state.is_hovered.lock().unwrap(); + + if *guard { + *guard = false; + + shell.publish(on_leave.clone()); + } + } + + Event::Mouse(mouse::Event::CursorMoved { .. }) => { + let state = state(); + let bounds = layout.bounds(); + let is_hovered = state.is_hovered.clone(); + let mut guard = state.is_hovered.lock().unwrap(); + + if *guard { + *guard = cursor.is_over(bounds); + if !*guard { + shell.publish(on_leave.clone()); + } + } else { + *guard = cursor.is_over(bounds); + if *guard { + if let Some(settings) = settings { + if let Some(delay) = delay { + let s = settings.clone(); + let view = view.clone(); + let bounds = layout.bounds(); + + let sm = crate::surface::Action::Task(Arc::new(move || { + let s = s.clone(); + let view = view.clone(); + let is_hovered = is_hovered.clone(); + Task::future(async move { + #[cfg(feature = "tokio")] + { + _ = tokio::time::sleep(delay).await; + } + #[cfg(feature = "async-std")] + { + _ = async_std::task::sleep(delay).await; + } + let is_hovered = is_hovered.clone(); + let g = is_hovered.lock().unwrap(); + if !*g { + return crate::surface::Action::Ignore; + } + let boxed: Box< + dyn Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + + Send + + Sync + + 'static, + > = Box::new(move || s(bounds)); + let boxed: Box = + Box::new(boxed); + crate::surface::Action::Popup( + Arc::new(boxed), + Some({ + let boxed: Box< + dyn Fn() -> crate::Element< + 'static, + crate::Action, + > + Send + + Sync + + 'static, + > = Box::new(move || view()); + let boxed: Box = + Box::new(boxed); + Arc::new(boxed) + }), + ) + }) + })); + + shell.publish((on_surface_action)(sm)); + } else { + let s = settings.clone(); + let view = view.clone(); + let bounds = layout.bounds(); + + let boxed: Box< + dyn Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + + Send + + Sync + + 'static, + > = Box::new(move || s(bounds)); + let boxed: Box = Box::new(boxed); + + let sm = crate::surface::Action::Popup( + Arc::new(boxed), + Some({ + let boxed: Box< + dyn Fn() -> crate::Element< + 'static, + crate::Action, + > + Send + + Sync + + 'static, + > = Box::new(move || view()); + let boxed: Box = + Box::new(boxed); + Arc::new(boxed) + }), + ); + shell.publish((on_surface_action)(sm)); + } + } + } + } + } + _ => {} + } + + event::Status::Ignored +} + +#[allow(clippy::too_many_arguments)] +pub fn draw( + renderer: &mut Renderer, + bounds: Rectangle, + viewport_bounds: Rectangle, + styling: &super::Style, + draw_contents: impl FnOnce(&mut Renderer, &Style), +) where + Theme: super::Catalog, +{ + let doubled_border_width = styling.border_width * 2.0; + let doubled_outline_width = styling.outline_width * 2.0; + + if styling.outline_width > 0.0 { + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x - styling.border_width - styling.outline_width, + y: bounds.y - styling.border_width - styling.outline_width, + width: bounds.width + doubled_border_width + doubled_outline_width, + height: bounds.height + doubled_border_width + doubled_outline_width, + }, + border: Border { + width: styling.outline_width, + color: styling.outline_color, + radius: styling.border_radius, + }, + shadow: Shadow::default(), + }, + Color::TRANSPARENT, + ); + } + + if styling.background.is_some() || styling.border_width > 0.0 { + if styling.shadow_offset != Vector::default() { + // TODO: Implement proper shadow support + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x + styling.shadow_offset.x, + y: bounds.y + styling.shadow_offset.y, + width: bounds.width, + height: bounds.height, + }, + border: Border { + radius: styling.border_radius, + ..Default::default() + }, + shadow: Shadow::default(), + }, + Background::Color([0.0, 0.0, 0.0, 0.5].into()), + ); + } + + // Draw the button background first. + if let Some(background) = styling.background { + renderer.fill_quad( + renderer::Quad { + bounds, + border: Border { + radius: styling.border_radius, + ..Default::default() + }, + shadow: Shadow::default(), + }, + background, + ); + } + + // Then draw the button contents onto the background. + draw_contents(renderer, styling); + + let mut clipped_bounds = viewport_bounds.intersection(&bounds).unwrap_or_default(); + clipped_bounds.height += styling.border_width; + + renderer.with_layer(clipped_bounds, |renderer| { + // Finish by drawing the border above the contents. + renderer.fill_quad( + renderer::Quad { + bounds, + border: Border { + width: styling.border_width, + color: styling.border_color, + radius: styling.border_radius, + }, + shadow: Shadow::default(), + }, + Color::TRANSPARENT, + ); + }); + } else { + draw_contents(renderer, styling); + } +} + +/// Computes the layout of a [`Tooltip`]. +pub fn layout( + renderer: &Renderer, + limits: &layout::Limits, + width: Length, + height: Length, + padding: Padding, + layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, +) -> layout::Node { + let limits = limits.width(width).height(height); + + let mut content = layout_content(renderer, &limits.shrink(padding)); + let padding = padding.fit(content.size(), limits.max()); + let size = limits + .shrink(padding) + .resolve(width, height, content.size()) + .expand(padding); + + content = content.move_to(Point::new(padding.left, padding.top)); + + layout::Node::with_children(size, vec![content]) +} diff --git a/src/widget/wrapper.rs b/src/widget/wrapper.rs new file mode 100644 index 00000000..219254c7 --- /dev/null +++ b/src/widget/wrapper.rs @@ -0,0 +1,220 @@ +use std::{ + cell::RefCell, + rc::Rc, + thread::{self, ThreadId}, +}; + +use crate::Element; +use iced::{event, Length, Rectangle, Size}; +use iced_core::{id::Id, widget, widget::tree, Widget}; + +#[derive(Debug)] +pub struct RcWrapper { + pub(crate) data: Rc>, + pub(crate) thread_id: ThreadId, +} + +impl Clone for RcWrapper { + fn clone(&self) -> Self { + Self { + data: self.data.clone(), + thread_id: self.thread_id, + } + } +} + +unsafe impl Send for RcWrapper {} +unsafe impl Sync for RcWrapper {} + +impl RcWrapper { + pub fn new(element: T) -> Self { + Self { + data: Rc::new(RefCell::new(element)), + thread_id: thread::current().id(), + } + } + + /// # Panics + /// + /// Will panic if used outside of original thread. + pub fn with_data(&self, f: impl FnOnce(&T) -> O) -> O { + assert_eq!(self.thread_id, thread::current().id()); + let my_ref: &T = &RefCell::borrow(self.data.as_ref()); + f(my_ref) + } + + /// # Panics + /// + /// Will panic if used outside of original thread. + pub fn with_data_mut(&self, f: impl FnOnce(&mut T) -> O) -> O { + assert_eq!(self.thread_id, thread::current().id()); + let my_refmut: &mut T = &mut RefCell::borrow_mut(self.data.as_ref()); + f(my_refmut) + } + + /// # Panics + /// + /// Will panic if used outside of original thread. + pub(crate) unsafe fn as_ptr(&self) -> *mut T { + assert_eq!(self.thread_id, thread::current().id()); + RefCell::as_ptr(self.data.as_ref()) + } +} + +#[derive(Clone)] +pub struct RcElementWrapper { + pub(crate) element: RcWrapper>, +} + +impl RcElementWrapper { + #[must_use] + pub fn new(element: Element<'static, M>) -> Self { + RcElementWrapper { + element: RcWrapper::new(element), + } + } +} + +impl Widget for RcElementWrapper { + fn size(&self) -> Size { + self.element.with_data(|e| e.as_widget().size()) + } + + fn size_hint(&self) -> Size { + self.element.with_data(move |e| e.as_widget().size_hint()) + } + + fn layout( + &self, + tree: &mut tree::Tree, + renderer: &crate::Renderer, + limits: &crate::iced_core::layout::Limits, + ) -> crate::iced_core::layout::Node { + self.element + .with_data_mut(|e| e.as_widget_mut().layout(tree, renderer, limits)) + } + + fn draw( + &self, + tree: &tree::Tree, + renderer: &mut crate::Renderer, + theme: &crate::Theme, + style: &crate::iced_core::renderer::Style, + layout: crate::iced_core::Layout<'_>, + cursor: crate::iced_core::mouse::Cursor, + viewport: &Rectangle, + ) { + self.element.with_data(move |e| { + e.as_widget() + .draw(tree, renderer, theme, style, layout, cursor, viewport); + }); + } + + fn tag(&self) -> tree::Tag { + self.element.with_data(|e| e.as_widget().tag()) + } + + fn state(&self) -> tree::State { + self.element.with_data(|e| e.as_widget().state()) + } + + fn children(&self) -> Vec { + self.element.with_data(|e| e.as_widget().children()) + } + + fn diff(&mut self, tree: &mut tree::Tree) { + self.element.with_data_mut(|e| e.as_widget_mut().diff(tree)); + } + + fn operate( + &self, + state: &mut tree::Tree, + layout: crate::iced_core::Layout<'_>, + renderer: &crate::Renderer, + operation: &mut dyn widget::Operation, + ) { + self.element.with_data(|e| { + e.as_widget().operate(state, layout, renderer, operation); + }); + } + + fn on_event( + &mut self, + state: &mut tree::Tree, + event: crate::iced::Event, + layout: crate::iced_core::Layout<'_>, + cursor: crate::iced_core::mouse::Cursor, + renderer: &crate::Renderer, + clipboard: &mut dyn crate::iced_core::Clipboard, + shell: &mut crate::iced_core::Shell<'_, M>, + viewport: &Rectangle, + ) -> event::Status { + self.element.with_data_mut(|e| { + e.as_widget_mut().on_event( + state, event, layout, cursor, renderer, clipboard, shell, viewport, + ) + }) + } + + fn mouse_interaction( + &self, + state: &tree::Tree, + layout: crate::iced_core::Layout<'_>, + cursor: crate::iced_core::mouse::Cursor, + viewport: &Rectangle, + renderer: &crate::Renderer, + ) -> crate::iced_core::mouse::Interaction { + self.element.with_data(|e| { + e.as_widget() + .mouse_interaction(state, layout, cursor, viewport, renderer) + }) + } + + fn overlay<'a>( + &'a mut self, + state: &'a mut tree::Tree, + layout: crate::iced_core::Layout<'_>, + renderer: &crate::Renderer, + translation: crate::iced_core::Vector, + ) -> Option> { + assert_eq!(self.element.thread_id, thread::current().id()); + Rc::get_mut(&mut self.element.data).and_then(|e| { + e.get_mut() + .as_widget_mut() + .overlay(state, layout, renderer, translation) + }) + } + + fn id(&self) -> Option { + self.element.with_data_mut(|e| e.as_widget_mut().id()) + } + + fn set_id(&mut self, id: Id) { + self.element.with_data_mut(|e| e.as_widget_mut().set_id(id)); + } + + fn drag_destinations( + &self, + state: &tree::Tree, + layout: crate::iced_core::Layout<'_>, + renderer: &crate::Renderer, + dnd_rectangles: &mut crate::iced_core::clipboard::DndDestinationRectangles, + ) { + self.element.with_data_mut(|e| { + e.as_widget_mut() + .drag_destinations(state, layout, renderer, dnd_rectangles); + }); + } +} + +impl From> for Element<'static, Message> { + fn from(wrapper: RcElementWrapper) -> Self { + Element::new(wrapper) + } +} + +impl From> for RcElementWrapper { + fn from(e: Element<'static, Message>) -> Self { + RcElementWrapper::new(e) + } +} From b2b6d90fb63bac9e032f95e1523eef226b886f02 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 18 Mar 2025 15:20:16 +0100 Subject: [PATCH 142/556] fix(text_input): fixing multiple issues --- src/widget/text_input/input.rs | 109 ++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 48 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index d6c11cf6..869d53eb 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -1247,7 +1247,7 @@ pub fn layout( #[allow(clippy::missing_panics_doc)] #[allow(clippy::cast_lossless)] #[allow(clippy::cast_possible_truncation)] -pub fn update<'a, Message: 'static>( +pub fn update<'a, Message: Clone + 'static>( id: Option, event: Event, text_layout: Layout<'_>, @@ -1270,10 +1270,7 @@ pub fn update<'a, Message: 'static>( line_height: text::LineHeight, layout: Layout<'_>, manage_value: bool, -) -> event::Status -where - Message: Clone, -{ +) -> event::Status { let update_cache = |state, value| { replace_paragraph(state, layout, value, font, iced::Pixels(size), line_height); }; @@ -1286,6 +1283,8 @@ where let unsecured_value = value; let value = &mut secured_value; + // NOTE: Clicks must be captured to prevent mouse areas behind them handling the same clicks. + match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { @@ -1309,7 +1308,6 @@ where } if let Some(cursor_position) = click_position { - let text_layout = layout.children().next().unwrap(); let target = cursor_position.x - text_layout.bounds().x; let click = @@ -1463,19 +1461,17 @@ where && matches!(state.dragging_state, None | Some(DraggingState::Selection)) { state.is_read_only = false; + + let now = Instant::now(); + LAST_FOCUS_UPDATE.with(|x| x.set(now)); + state.is_focused = Some(Focus { + updated_at: now, + now, + }); + if let Some(on_toggle_edit) = on_toggle_edit { let message = (on_toggle_edit)(!state.is_read_only); shell.publish(message); - - let now = Instant::now(); - LAST_FOCUS_UPDATE.with(|x| x.set(now)); - state.is_focused = Some(Focus { - updated_at: now, - now, - }); - - state.move_cursor_to_end(); - return event::Status::Captured; } } @@ -1483,61 +1479,77 @@ where return event::Status::Captured; } + let mut is_trailing_clicked = false; if is_editable { if let Some(trailing_layout) = trailing_icon_layout { is_trailing_clicked = cursor.is_over(trailing_layout.bounds()); + if is_trailing_clicked { + if on_toggle_edit.is_some() { + let Some(pos) = cursor.position() else { + return event::Status::Ignored; + }; - if is_trailing_clicked && on_toggle_edit.is_some() { - let Some(pos) = cursor.position() else { - return event::Status::Ignored; - }; + let click = + mouse::Click::new(pos, mouse::Button::Left, state.last_click); - let click = mouse::Click::new(pos, mouse::Button::Left, state.last_click); + match ( + &state.dragging_state, + click.kind(), + state.cursor().state(value), + ) { + (None, click::Kind::Single, _) => { + state.is_read_only = !state.is_read_only; + if let Some(on_toggle_edit) = on_toggle_edit { + let message = (on_toggle_edit)(!state.is_read_only); + shell.publish(message); - match ( - &state.dragging_state, - click.kind(), - state.cursor().state(value), - ) { - (None, click::Kind::Single, _) => { - state.is_read_only = !state.is_read_only; - if let Some(on_toggle_edit) = on_toggle_edit { - let message = (on_toggle_edit)(!state.is_read_only); - shell.publish(message); + let now = Instant::now(); + LAST_FOCUS_UPDATE.with(|x| x.set(now)); + state.is_focused = Some(Focus { + updated_at: now, + now, + }); - let now = Instant::now(); - LAST_FOCUS_UPDATE.with(|x| x.set(now)); - state.is_focused = Some(Focus { - updated_at: now, - now, - }); - - state.move_cursor_to_end(); - - return event::Status::Captured; + state.move_cursor_to_end(); + } + } + _ => { + state.dragging_state = None; } } - _ => { - state.dragging_state = None; - } } + + return event::Status::Captured; } } } + // Condition met when mouse click is completely outside of the widget. if !is_trailing_clicked && click_position.is_none() { state.is_focused = None; state.dragging_state = None; state.is_pasting = None; - state.keyboard_modifiers = keyboard::Modifiers::default(); + state.is_read_only = true; + + // Ensure clicks outside emit the toggle edit message. + if let Some(on_toggle_edit) = on_toggle_edit { + let message = (on_toggle_edit)(false); + shell.publish(message); + } } } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }) => { let state = state(); state.dragging_state = None; + + return if cursor.is_over(layout.bounds()) { + event::Status::Captured + } else { + event::Status::Ignored + }; } Event::Mouse(mouse::Event::CursorMoved { position }) | Event::Touch(touch::Event::FingerMoved { position, .. }) => { @@ -1769,10 +1781,11 @@ where state.keyboard_modifiers = keyboard::Modifiers::default(); } + keyboard::Key::Named( - keyboard::key::Named::Tab - | keyboard::key::Named::ArrowUp - | keyboard::key::Named::ArrowDown, + keyboard::key::Named::ArrowUp + | keyboard::key::Named::ArrowDown + | keyboard::key::Named::Tab, ) => { return event::Status::Ignored; } From e29ce0d4c1a3ff09913e4f0d3bfd5553fe5a770b Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 19 Mar 2025 12:27:21 -0600 Subject: [PATCH 143/556] Fix compilation with process feature on other Unixes --- src/process.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/process.rs b/src/process.rs index f76dad7e..b1a3ae03 100644 --- a/src/process.rs +++ b/src/process.rs @@ -32,7 +32,7 @@ pub async fn spawn(mut command: Command) -> Option { .stderr(Stdio::null()); // Handle Linux - #[cfg(target_os = "linux")] + #[cfg(all(unix, not(target_os = "macos")))] let Ok((read, write)) = rustix::pipe::pipe_with(rustix::pipe::PipeFlags::CLOEXEC) else { return None; }; From 202684d0217edbd7b7cc02e51883fa4a9d42f449 Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Wed, 19 Mar 2025 15:46:12 -0400 Subject: [PATCH 144/556] chore: update fde and iced --- Cargo.toml | 2 +- iced | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7649e5d4..c5ea0292 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -134,7 +134,7 @@ zbus = { version = "4.2.1", default-features = false, optional = true } [target.'cfg(unix)'.dependencies] freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } -freedesktop-desktop-entry = { version = "0.7.8", optional = true } +freedesktop-desktop-entry = { version = "0.7.9", optional = true } shlex = { version = "1.3.0", optional = true } [dependencies.cosmic-theme] diff --git a/iced b/iced index 07880754..9f1dfc55 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 0788075435aa7a6532327b9435fcfc7a12e1b6a6 +Subproject commit 9f1dfc553c4d3c5edbfbd2aaca619760d6efb03d From 91eae67dd59c70283590253d5a98688093017ebe Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 19 Mar 2025 14:36:53 -0600 Subject: [PATCH 145/556] Update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 9f1dfc55..69da0aba 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 9f1dfc553c4d3c5edbfbd2aaca619760d6efb03d +Subproject commit 69da0abad9b708f4191ac5c7a9fdc0b733c4ad2d From a3525ef56e65b8bd198e5f1e837499673c3f6078 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 20 Mar 2025 10:10:02 -0400 Subject: [PATCH 146/556] refactor: track virtual offset in the layout --- iced | 2 +- src/widget/autosize.rs | 30 ++++++++++---- src/widget/button/widget.rs | 34 ++++++++++++---- src/widget/dropdown/widget.rs | 4 +- src/widget/flex_row/widget.rs | 41 ++++++++++++------- src/widget/grid/widget.rs | 59 +++++++++++++++++++--------- src/widget/id_container.rs | 30 ++++++++++---- src/widget/responsive_container.rs | 30 ++++++++++---- src/widget/wayland/tooltip/widget.rs | 48 +++++++++++++++------- 9 files changed, 200 insertions(+), 78 deletions(-) diff --git a/iced b/iced index 69da0aba..07f66698 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 69da0abad9b708f4191ac5c7a9fdc0b733c4ad2d +Subproject commit 07f666981d2d90cf7978f3fd90c0a9dfe22e1f0e diff --git a/src/widget/autosize.rs b/src/widget/autosize.rs index 6c15750d..3f608351 100644 --- a/src/widget/autosize.rs +++ b/src/widget/autosize.rs @@ -140,7 +140,11 @@ where operation.container(Some(&self.id), layout.bounds(), &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, operation, ); @@ -171,7 +175,11 @@ where self.content.as_widget_mut().on_event( &mut tree.children[0], event.clone(), - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), cursor_position, renderer, clipboard, @@ -191,7 +199,7 @@ where let content_layout = layout.children().next().unwrap(); self.content.as_widget().mouse_interaction( &tree.children[0], - content_layout, + content_layout.with_virtual_offset(layout.virtual_offset()), cursor_position, viewport, renderer, @@ -214,7 +222,7 @@ where renderer, theme, renderer_style, - content_layout, + content_layout.with_virtual_offset(layout.virtual_offset()), cursor_position, viewport, ); @@ -229,7 +237,11 @@ where ) -> Option> { self.content.as_widget_mut().overlay( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, translation, ) @@ -245,7 +257,7 @@ where let content_layout = layout.children().next().unwrap(); self.content.as_widget().drag_destinations( &state.children[0], - content_layout, + content_layout.with_virtual_offset(layout.virtual_offset()), renderer, dnd_rectangles, ); @@ -269,7 +281,11 @@ where ) -> iced_accessibility::A11yTree { let c_layout = layout.children().next().unwrap(); let c_state = &state.children[0]; - self.content.as_widget().a11y_nodes(c_layout, c_state, p) + self.content.as_widget().a11y_nodes( + c_layout.with_virtual_offset(layout.virtual_offset()), + c_state, + p, + ) } } diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index c3e51495..3b6d5577 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -271,7 +271,11 @@ impl<'a, Message: 'a + Clone> Widget operation.container(None, layout.bounds(), &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, operation, ); @@ -315,7 +319,11 @@ impl<'a, Message: 'a + Clone> Widget if self.content.as_widget_mut().on_event( &mut tree.children[0], event.clone(), - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -416,7 +424,7 @@ impl<'a, Message: 'a + Clone> Widget text_color, scale_factor: renderer_style.scale_factor, }, - content_layout, + content_layout.with_virtual_offset(layout.virtual_offset()), cursor, &viewport.intersection(&bounds).unwrap_or_default(), ); @@ -533,7 +541,11 @@ impl<'a, Message: 'a + Clone> Widget _viewport: &Rectangle, _renderer: &crate::Renderer, ) -> mouse::Interaction { - mouse_interaction(layout, cursor, self.on_press.is_some()) + mouse_interaction( + layout.with_virtual_offset(layout.virtual_offset()), + cursor, + self.on_press.is_some(), + ) } fn overlay<'b>( @@ -548,7 +560,11 @@ impl<'a, Message: 'a + Clone> Widget translation.y += position.y; self.content.as_widget_mut().overlay( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, translation, ) @@ -614,9 +630,11 @@ impl<'a, Message: 'a + Clone> Widget node.set_default_action_verb(DefaultActionVerb::Click); if let Some(child_tree) = child_tree.map(|child_tree| { - self.content - .as_widget() - .a11y_nodes(child_layout, child_tree, p) + self.content.as_widget().a11y_nodes( + child_layout.with_virtual_offset(layout.virtual_offset()), + child_tree, + p, + ) }) { A11yTree::node_with_child_tree(A11yNode::new(node, self.id.clone()), child_tree) } else { diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index f3388976..f6cecea7 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -535,6 +535,8 @@ pub fn update< let state = state.clone(); let on_close = surface::action::destroy_popup(id); let on_surface_action_clone = on_surface_action.clone(); + let translation = layout.virtual_offset(); + dbg!(translation); let get_popup_action = surface::action::simple_popup::< AppMessage, Box< @@ -556,7 +558,7 @@ pub fn update< anchor: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::BottomLeft, gravity: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight, reactive: true, - offset: (-padding.left as i32, 0), + offset: ((-padding.left - translation.x) as i32, -translation.y as i32), constraint_adjustment: 9, ..Default::default() }, diff --git a/src/widget/flex_row/widget.rs b/src/widget/flex_row/widget.rs index 852c18cc..e343757a 100644 --- a/src/widget/flex_row/widget.rs +++ b/src/widget/flex_row/widget.rs @@ -137,10 +137,13 @@ impl Widget for FlexR .iter() .zip(&mut tree.children) .zip(layout.children()) - .for_each(|((child, state), layout)| { - child - .as_widget() - .operate(state, layout, renderer, operation); + .for_each(|((child, state), c_layout)| { + child.as_widget().operate( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + operation, + ); }); }); } @@ -160,11 +163,11 @@ impl Widget for FlexR .iter_mut() .zip(&mut tree.children) .zip(layout.children()) - .map(|((child, state), layout)| { + .map(|((child, state), c_layout)| { child.as_widget_mut().on_event( state, event.clone(), - layout, + c_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -187,10 +190,14 @@ impl Widget for FlexR .iter() .zip(&tree.children) .zip(layout.children()) - .map(|((child, state), layout)| { - child - .as_widget() - .mouse_interaction(state, layout, cursor, viewport, renderer) + .map(|((child, state), c_layout)| { + child.as_widget().mouse_interaction( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, + renderer, + ) }) .max() .unwrap_or_default() @@ -206,15 +213,21 @@ impl Widget for FlexR cursor: mouse::Cursor, viewport: &Rectangle, ) { - for ((child, state), layout) in self + for ((child, state), c_layout) in self .children .iter() .zip(&tree.children) .zip(layout.children()) { - child - .as_widget() - .draw(state, renderer, theme, style, layout, cursor, viewport); + child.as_widget().draw( + state, + renderer, + theme, + style, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, + ); } } diff --git a/src/widget/grid/widget.rs b/src/widget/grid/widget.rs index fc91ba29..f09b3739 100644 --- a/src/widget/grid/widget.rs +++ b/src/widget/grid/widget.rs @@ -166,10 +166,13 @@ impl Widget for Grid< .iter() .zip(&mut tree.children) .zip(layout.children()) - .for_each(|((child, state), layout)| { - child - .as_widget() - .operate(state, layout, renderer, operation); + .for_each(|((child, state), c_layout)| { + child.as_widget().operate( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + operation, + ); }); }); } @@ -189,11 +192,11 @@ impl Widget for Grid< .iter_mut() .zip(&mut tree.children) .zip(layout.children()) - .map(|((child, state), layout)| { + .map(|((child, state), c_layout)| { child.as_widget_mut().on_event( state, event.clone(), - layout, + c_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -216,10 +219,14 @@ impl Widget for Grid< .iter() .zip(&tree.children) .zip(layout.children()) - .map(|((child, state), layout)| { - child - .as_widget() - .mouse_interaction(state, layout, cursor, viewport, renderer) + .map(|((child, state), c_layout)| { + child.as_widget().mouse_interaction( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, + renderer, + ) }) .max() .unwrap_or_default() @@ -235,15 +242,21 @@ impl Widget for Grid< cursor: mouse::Cursor, viewport: &Rectangle, ) { - for ((child, state), layout) in self + for ((child, state), c_layout) in self .children .iter() .zip(&tree.children) .zip(layout.children()) { - child - .as_widget() - .draw(state, renderer, theme, style, layout, cursor, viewport); + child.as_widget().draw( + state, + renderer, + theme, + style, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, + ); } } @@ -271,7 +284,13 @@ impl Widget for Grid< .iter() .zip(layout.children()) .zip(state.children.iter()) - .map(|((c, c_layout), state)| c.as_widget().a11y_nodes(c_layout, state, p)), + .map(|((c, c_layout), state)| { + c.as_widget().a11y_nodes( + c_layout.with_virtual_offset(layout.virtual_offset()), + state, + p, + ) + }), ) } @@ -282,14 +301,18 @@ impl Widget for Grid< renderer: &Renderer, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { - for ((e, layout), state) in self + for ((e, c_layout), state) in self .children .iter() .zip(layout.children()) .zip(state.children.iter()) { - e.as_widget() - .drag_destinations(state, layout, renderer, dnd_rectangles); + e.as_widget().drag_destinations( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + dnd_rectangles, + ); } } } diff --git a/src/widget/id_container.rs b/src/widget/id_container.rs index 2070780b..7f6bc97e 100644 --- a/src/widget/id_container.rs +++ b/src/widget/id_container.rs @@ -88,7 +88,11 @@ where operation.container(Some(&self.id), layout.bounds(), &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, operation, ); @@ -109,7 +113,11 @@ where self.content.as_widget_mut().on_event( &mut tree.children[0], event.clone(), - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), cursor_position, renderer, clipboard, @@ -129,7 +137,7 @@ where let content_layout = layout.children().next().unwrap(); self.content.as_widget().mouse_interaction( &tree.children[0], - content_layout, + content_layout.with_virtual_offset(layout.virtual_offset()), cursor_position, viewport, renderer, @@ -152,7 +160,7 @@ where renderer, theme, renderer_style, - content_layout, + content_layout.with_virtual_offset(layout.virtual_offset()), cursor_position, viewport, ); @@ -167,7 +175,11 @@ where ) -> Option> { self.content.as_widget_mut().overlay( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, translation, ) @@ -183,7 +195,7 @@ where let content_layout = layout.children().next().unwrap(); self.content.as_widget().drag_destinations( &state.children[0], - content_layout, + content_layout.with_virtual_offset(layout.virtual_offset()), renderer, dnd_rectangles, ); @@ -207,7 +219,11 @@ where ) -> iced_accessibility::A11yTree { let c_layout = layout.children().next().unwrap(); let c_state = &state.children[0]; - self.content.as_widget().a11y_nodes(c_layout, c_state, p) + self.content.as_widget().a11y_nodes( + c_layout.with_virtual_offset(layout.virtual_offset()), + c_state, + p, + ) } } diff --git a/src/widget/responsive_container.rs b/src/widget/responsive_container.rs index 82b91824..2f15a889 100644 --- a/src/widget/responsive_container.rs +++ b/src/widget/responsive_container.rs @@ -131,7 +131,11 @@ where operation.container(Some(&self.id), layout.bounds(), &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, operation, ); @@ -165,7 +169,11 @@ where self.content.as_widget_mut().on_event( &mut tree.children[0], event.clone(), - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), cursor_position, renderer, clipboard, @@ -185,7 +193,7 @@ where let content_layout = layout.children().next().unwrap(); self.content.as_widget().mouse_interaction( &tree.children[0], - content_layout, + content_layout.with_virtual_offset(layout.virtual_offset()), cursor_position, viewport, renderer, @@ -208,7 +216,7 @@ where renderer, theme, renderer_style, - content_layout, + content_layout.with_virtual_offset(layout.virtual_offset()), cursor_position, viewport, ); @@ -223,7 +231,11 @@ where ) -> Option> { self.content.as_widget_mut().overlay( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, translation, ) @@ -239,7 +251,7 @@ where let content_layout = layout.children().next().unwrap(); self.content.as_widget().drag_destinations( &state.children[0], - content_layout, + content_layout.with_virtual_offset(layout.virtual_offset()), renderer, dnd_rectangles, ); @@ -263,7 +275,11 @@ where ) -> iced_accessibility::A11yTree { let c_layout = layout.children().next().unwrap(); let c_state = &state.children[0]; - self.content.as_widget().a11y_nodes(c_layout, c_state, p) + self.content.as_widget().a11y_nodes( + c_layout.with_virtual_offset(layout.virtual_offset()), + c_state, + p, + ) } } diff --git a/src/widget/wayland/tooltip/widget.rs b/src/widget/wayland/tooltip/widget.rs index 033ad0f3..35dd14de 100644 --- a/src/widget/wayland/tooltip/widget.rs +++ b/src/widget/wayland/tooltip/widget.rs @@ -240,7 +240,11 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> operation.container(None, layout.bounds(), &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, operation, ); @@ -271,16 +275,22 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> &self.on_surface_action, || tree.state.downcast_mut::(), ); - status.merge(self.content.as_widget_mut().on_event( - &mut tree.children[0], - event, - layout.children().next().unwrap(), - cursor, - renderer, - clipboard, - shell, - viewport, - )) + status.merge( + self.content.as_widget_mut().on_event( + &mut tree.children[0], + event, + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), + cursor, + renderer, + clipboard, + shell, + viewport, + ), + ) } #[allow(clippy::too_many_lines)] @@ -321,7 +331,7 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> text_color: styling.text_color, scale_factor: renderer_style.scale_factor, }, - content_layout, + content_layout.with_virtual_offset(layout.virtual_offset()), cursor, &viewport.intersection(&bounds).unwrap_or_default(), ); @@ -339,7 +349,7 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> ) -> mouse::Interaction { self.content.as_widget().mouse_interaction( &tree.children[0], - layout, + layout.children().next().unwrap(), cursor, viewport, renderer, @@ -358,7 +368,11 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> translation.y += position.y; self.content.as_widget_mut().overlay( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, translation, ) @@ -374,7 +388,11 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> ) -> iced_accessibility::A11yTree { let c_layout = layout.children().next().unwrap(); - self.content.as_widget().a11y_nodes(c_layout, state, p) + self.content.as_widget().a11y_nodes( + c_layout.with_virtual_offset(layout.virtual_offset()), + state, + p, + ) } fn id(&self) -> Option { From cb6ea4c7c39d99001d9f72757a947335929a60e3 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 21 Mar 2025 01:53:49 -0400 Subject: [PATCH 147/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 07f66698..55f6d5c1 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 07f666981d2d90cf7978f3fd90c0a9dfe22e1f0e +Subproject commit 55f6d5c10e0328a04ce584e454de1b831bebe011 From 77c3a8ed901dde64b695c0b4639830bae504b417 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 19 Mar 2025 04:04:30 +0100 Subject: [PATCH 148/556] feat(task): add stream function --- Cargo.toml | 1 + src/task.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index c5ea0292..3eea0bb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,6 +106,7 @@ cosmic-config = { path = "cosmic-config" } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } css-color = "0.2.5" derive_setters = "0.1.5" +futures = "0.3" image = { version = "0.25.5", default-features = false, features = [ "jpeg", "png", diff --git a/src/task.rs b/src/task.rs index a730b37e..35dcf4de 100644 --- a/src/task.rs +++ b/src/task.rs @@ -3,6 +3,7 @@ //! Create asynchronous actions to be performed in the background. +use futures::stream::{Stream, StreamExt}; use std::future::Future; /// Yields a task which contains a batch of tasks. @@ -23,3 +24,10 @@ pub fn future, Y: 'static>( pub fn message, Y: 'static>(message: X) -> iced::Task { future(async move { message.into() }) } + +/// Yields a task which will run a stream on the runtime executor. +pub fn stream + 'static, Y: 'static>( + stream: impl Stream + Send + 'static, +) -> iced::Task { + iced::Task::stream(stream.map(Into::into)) +} From 2afa192573e8a8ede86a15ed6c34120884fa9722 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 19 Mar 2025 19:23:34 +0100 Subject: [PATCH 149/556] fix: Rust 2024 errors --- src/malloc.rs | 2 +- src/widget/segmented_button/widget.rs | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/malloc.rs b/src/malloc.rs index 0d271447..a56323fe 100644 --- a/src/malloc.rs +++ b/src/malloc.rs @@ -5,7 +5,7 @@ use std::os::raw::c_int; const M_MMAP_THRESHOLD: c_int = -3; -extern "C" { +unsafe extern "C" { fn malloc_trim(pad: usize); fn mallopt(param: c_int, value: c_int) -> c_int; diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 0e433aec..9bdc9ad0 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -6,29 +6,29 @@ use crate::iced_core::id::Internal; use crate::theme::{SegmentedButton as Style, THEME}; use crate::widget::dnd_destination::DragId; use crate::widget::menu::{ - self, menu_roots_children, menu_roots_diff, CloseCondition, ItemHeight, ItemWidth, - MenuBarState, PathHighlight, + self, CloseCondition, ItemHeight, ItemWidth, MenuBarState, PathHighlight, menu_roots_children, + menu_roots_diff, }; -use crate::widget::{icon, Icon}; +use crate::widget::{Icon, icon}; use crate::{Element, Renderer}; use derive_setters::Setters; use iced::clipboard::dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent}; use iced::clipboard::mime::AllowedMimeTypes; use iced::touch::Finger; use iced::{ - alignment, event, keyboard, mouse, touch, Alignment, Background, Color, Event, Length, Padding, - Rectangle, Size, Task, Vector, + Alignment, Background, Color, Event, Length, Padding, Rectangle, Size, Task, Vector, alignment, + event, keyboard, mouse, touch, }; use iced_core::mouse::ScrollDelta; use iced_core::text::{LineHeight, Paragraph, Renderer as TextRenderer, Shaping, Wrapping}; use iced_core::widget::{self, operation, tree}; -use iced_core::{layout, renderer, widget::Tree, Clipboard, Layout, Shell, Widget}; use iced_core::{Border, Gradient, Point, Renderer as IcedRenderer, Shadow, Text}; -use iced_runtime::{task, Action}; +use iced_core::{Clipboard, Layout, Shell, Widget, layout, renderer, widget::Tree}; +use iced_runtime::{Action, task}; use slotmap::{Key, SecondaryMap}; use std::borrow::Cow; -use std::collections::hash_map::DefaultHasher; use std::collections::HashSet; +use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use std::mem; @@ -537,7 +537,7 @@ where pub fn get_drag_id(&self) -> u128 { self.drag_id.map_or_else( || { - u128::from(match &self.id.0 .0 { + u128::from(match &self.id.0.0 { Internal::Unique(id) | Internal::Custom(id, _) => *id, Internal::Set(_) => panic!("Invalid Id assigned to dnd destination."), }) @@ -1180,7 +1180,7 @@ where } Background::Gradient(g) => { - let Gradient::Linear(mut l) = g; + let Gradient::Linear(l) = g; for c in &mut l.stops { let Some(stop) = c else { continue; From ab887aeeac3b3d24c80084407143afb8e7acc2c8 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 19 Mar 2025 19:24:09 +0100 Subject: [PATCH 150/556] feat: add `cosmic::theme::spacing()` --- examples/application/src/main.rs | 1 + src/theme/mod.rs | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index bcffc316..50329298 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -205,6 +205,7 @@ impl cosmic::Application for App { .on_input(Message::Input2) .on_clear(Message::Ignore), ] + .spacing(cosmic::theme::spacing().space_s) .width(iced::Length::Fill) .height(iced::Length::Shrink) .align_x(iced::Alignment::Center), diff --git a/src/theme/mod.rs b/src/theme/mod.rs index b3cdaf81..f2e42036 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -6,11 +6,12 @@ #[cfg(feature = "xdg-portal")] pub mod portal; pub mod style; +use cosmic_theme::Spacing; use cosmic_theme::ThemeMode; pub use style::*; -use cosmic_config::config_subscription; use cosmic_config::CosmicConfigEntry; +use cosmic_config::config_subscription; use cosmic_theme::Component; use cosmic_theme::LayeredTheme; use iced_futures::Subscription; @@ -63,6 +64,12 @@ pub fn active_type() -> ThemeType { THEME.lock().unwrap().theme_type.clone() } +/// Preferred interface spacing parameters defined by the active theme. +#[inline(always)] +pub fn spacing() -> Spacing { + active().cosmic().spacing +} + /// Whether the active theme has a dark preference. #[must_use] pub fn is_dark() -> bool { From 92b2756e2660e6c992486c1d627b031956e0b1bb Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 19 Mar 2025 20:29:11 +0100 Subject: [PATCH 151/556] chore: update dependencies; including ron 0.9 --- Cargo.toml | 49 +++++++++++++++----------------- cosmic-config/Cargo.toml | 20 ++++++------- cosmic-theme/Cargo.toml | 14 ++++----- examples/about/Cargo.toml | 6 ++-- examples/applet/Cargo.toml | 6 ++-- examples/application/Cargo.toml | 4 +-- examples/calendar/Cargo.toml | 4 +-- examples/config/Cargo.toml | 2 +- examples/context-menu/Cargo.toml | 4 +-- examples/cosmic/Cargo.toml | 19 +++++++++---- examples/image-button/Cargo.toml | 4 +-- examples/menu/Cargo.toml | 4 +-- examples/nav-context/Cargo.toml | 4 +-- examples/open-dialog/Cargo.toml | 8 +++--- examples/text-input/Cargo.toml | 4 +-- 15 files changed, 79 insertions(+), 73 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3eea0bb9..41c1d7e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "libcosmic" version = "0.1.0" -edition = "2021" -rust-version = "1.80" +edition = "2024" +rust-version = "1.85" [lib] name = "cosmic" @@ -97,41 +97,38 @@ async-std = [ [dependencies] apply = "0.3.0" -ashpd = { version = "0.9.1", default-features = false, optional = true } +ashpd = { version = "0.9.2", default-features = false, optional = true } async-fs = { version = "2.1", optional = true } -async-std = { version = "1.10", optional = true } +async-std = { version = "1.13", optional = true } cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "178eb0b", optional = true } -chrono = "0.4.35" +chrono = "0.4.40" cosmic-config = { path = "cosmic-config" } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } -css-color = "0.2.5" -derive_setters = "0.1.5" +css-color = "0.2.8" +derive_setters = "0.1.6" futures = "0.3" image = { version = "0.25.5", default-features = false, features = [ "jpeg", "png", ] } -lazy_static = "1.4.0" -libc = { version = "0.2.155", optional = true } -license = { version = "3.5.1", optional = true } +lazy_static = "1.5.0" +libc = { version = "0.2.171", optional = true } +license = { version = "3.6.0", optional = true } mime = { version = "0.3.17", optional = true } -palette = "0.7.3" -rfd = { version = "0.14.0", default-features = false, features = [ +palette = "0.7.6" +rfd = { version = "0.14.1", default-features = false, features = [ "xdg-portal", ], optional = true } -rustix = { version = "0.38.34", features = [ - "pipe", - "process", -], optional = true } -serde = { version = "1.0.180", features = ["derive"] } -slotmap = "1.0.6" -smol = { version = "2.0.0", optional = true } -thiserror = "1.0.44" -tokio = { version = "1.24.2", optional = true } +rustix = { version = "1.0", features = ["pipe", "process"], optional = true } +serde = { version = "1.0.219", features = ["derive"] } +slotmap = "1.0.7" +smol = { version = "2.0.2", optional = true } +thiserror = "2.0.12" +tokio = { version = "1.44.1", optional = true } tracing = "0.1.41" -unicode-segmentation = "1.6" -url = "2.4.0" -zbus = { version = "4.2.1", default-features = false, optional = true } +unicode-segmentation = "1.12" +url = "2.5.4" +zbus = { version = "4.4.0", default-features = false, optional = true } [target.'cfg(unix)'.dependencies] freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } @@ -190,7 +187,7 @@ git = "https://github.com/pop-os/cosmic-panel" optional = true [dependencies.ron] -version = "0.8" +version = "0.9" optional = true [dependencies.taffy] @@ -208,7 +205,7 @@ members = [ exclude = ["iced"] [workspace.dependencies] -dirs = "5.0.1" +dirs = "6.0.0" [patch."https://github.com/pop-os/libcosmic"] diff --git a/cosmic-config/Cargo.toml b/cosmic-config/Cargo.toml index 4a9d4944..3fa7eb9d 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -11,24 +11,24 @@ subscription = ["iced_futures"] [dependencies] cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } -zbus = { version = "4.2.1", default-features = false, optional = true } +zbus = { version = "4.4.0", default-features = false, optional = true } atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" } -calloop = { version = "0.14.0", optional = true } -notify = "6.0.0" -ron = "0.9.0-alpha.0" -serde = "1.0.152" +calloop = { version = "0.14.2", optional = true } +notify = "8.0.0" +ron = "0.9.0" +serde = "1.0.219" cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true } iced = { path = "../iced/", default-features = false, optional = true } iced_futures = { path = "../iced/futures/", default-features = false, optional = true } -once_cell = "1.19.0" +once_cell = "1.21.1" futures-util = { version = "0.3", optional = true } dirs.workspace = true -tokio = { version = "1.0", optional = true, features = ["time"] } -async-std = { version = "1.10", optional = true } +tokio = { version = "1.44", optional = true, features = ["time"] } +async-std = { version = "1.13", optional = true } tracing = "0.1" [target.'cfg(unix)'.dependencies] -xdg = "2.1" +xdg = "2.5" [target.'cfg(windows)'.dependencies] -known-folders = "1.1.0" +known-folders = "1.2.0" diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index 2034197e..483014f6 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -15,18 +15,18 @@ export = ["serde_json"] no-default = [] [dependencies] -palette = { version = "0.7.3", features = ["serializing"] } +palette = { version = "0.7.6", features = ["serializing"] } almost = "0.2" -serde = { version = "1.0.129", features = ["derive"] } -serde_json = { version = "1.0.64", optional = true, features = [ +serde = { version = "1.0.219", features = ["derive"] } +serde_json = { version = "1.0.140", optional = true, features = [ "preserve_order", ] } -ron = "0.9.0-alpha.0" -lazy_static = "1.4.0" -csscolorparser = { version = "0.6.2", features = ["serde"] } +ron = "0.9.0" +lazy_static = "1.5.0" +csscolorparser = { version = "0.7.0", features = ["serde"] } cosmic-config = { path = "../cosmic-config/", default-features = false, features = [ "subscription", "macro", ] } dirs.workspace = true -thiserror = "1.0.5" +thiserror = "2.0.12" diff --git a/examples/about/Cargo.toml b/examples/about/Cargo.toml index b257999c..cf067095 100644 --- a/examples/about/Cargo.toml +++ b/examples/about/Cargo.toml @@ -4,10 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] -tracing = "0.1.37" -tracing-subscriber = "0.3.17" +tracing = "0.1.41" +tracing-subscriber = "0.3.19" tracing-log = "0.2.0" -open = "5.3.0" +open = "5.3.2" [dependencies.libcosmic] path = "../../" diff --git a/examples/applet/Cargo.toml b/examples/applet/Cargo.toml index 0a9f2b32..c39ca288 100644 --- a/examples/applet/Cargo.toml +++ b/examples/applet/Cargo.toml @@ -7,10 +7,10 @@ edition = "2021" [dependencies] once_cell = "1" -rust-embed = "8.0.0" +rust-embed = "8.6.0" tracing = "0.1" -env_logger = "0.10.0" -log = "0.4.17" +env_logger = "0.10.2" +log = "0.4.26" [dependencies.libcosmic] git = "https://github.com/pop-os/libcosmic" diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index 23413086..3c5ce8e2 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -8,8 +8,8 @@ default = ["wayland"] wayland = ["libcosmic/wayland"] [dependencies] -tracing = "0.1.37" -tracing-subscriber = "0.3.17" +tracing = "0.1.41" +tracing-subscriber = "0.3.19" tracing-log = "0.2.0" [dependencies.libcosmic] diff --git a/examples/calendar/Cargo.toml b/examples/calendar/Cargo.toml index 8eadab14..18bc6b49 100644 --- a/examples/calendar/Cargo.toml +++ b/examples/calendar/Cargo.toml @@ -6,9 +6,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -chrono = "0.4.35" +chrono = "0.4.40" [dependencies.libcosmic] path = "../../" default-features = false -features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] \ No newline at end of file +features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/config/Cargo.toml b/examples/config/Cargo.toml index 40e118ad..4f20144c 100644 --- a/examples/config/Cargo.toml +++ b/examples/config/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] cosmic-config = { path = "../../cosmic-config" } -ron = "0.9.0-alpha.0" +ron = "0.9.0" diff --git a/examples/context-menu/Cargo.toml b/examples/context-menu/Cargo.toml index 133c642e..5b9ad020 100644 --- a/examples/context-menu/Cargo.toml +++ b/examples/context-menu/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -tracing = "0.1.37" -tracing-subscriber = "0.3.17" +tracing = "0.1.41" +tracing-subscriber = "0.3.19" tracing-log = "0.2.0" [dependencies.libcosmic] diff --git a/examples/cosmic/Cargo.toml b/examples/cosmic/Cargo.toml index a7daf0ad..695f0c37 100644 --- a/examples/cosmic/Cargo.toml +++ b/examples/cosmic/Cargo.toml @@ -7,12 +7,21 @@ publish = false [dependencies] apply = "0.3.0" -fraction = "0.14.0" -libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "dbus-config", "a11y", "wgpu", "xdg-portal"] } -once_cell = "1.18" -slotmap = "1.0.6" +fraction = "0.15.3" +libcosmic = { path = "../..", features = [ + "debug", + "winit", + "tokio", + "single-instance", + "dbus-config", + "a11y", + "wgpu", + "xdg-portal", +] } +once_cell = "1.21" +slotmap = "1.0.7" env_logger = "0.10" -log = "0.4.17" +log = "0.4.26" [dependencies.cosmic-time] git = "https://github.com/pop-os/cosmic-time" diff --git a/examples/image-button/Cargo.toml b/examples/image-button/Cargo.toml index cb3ac811..110be619 100644 --- a/examples/image-button/Cargo.toml +++ b/examples/image-button/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -tracing = "0.1.37" -tracing-subscriber = "0.3.17" +tracing = "0.1.41" +tracing-subscriber = "0.3.19" [dependencies.libcosmic] path = "../../" diff --git a/examples/menu/Cargo.toml b/examples/menu/Cargo.toml index 4348ca0b..c83a216d 100644 --- a/examples/menu/Cargo.toml +++ b/examples/menu/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -tracing = "0.1.37" -tracing-subscriber = "0.3.17" +tracing = "0.1.41" +tracing-subscriber = "0.3.19" tracing-log = "0.2.0" [dependencies.libcosmic] diff --git a/examples/nav-context/Cargo.toml b/examples/nav-context/Cargo.toml index d0f3bce5..a1b95413 100644 --- a/examples/nav-context/Cargo.toml +++ b/examples/nav-context/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -tracing = "0.1.37" -tracing-subscriber = "0.3.17" +tracing = "0.1.41" +tracing-subscriber = "0.3.19" tracing-log = "0.2.0" [dependencies.libcosmic] diff --git a/examples/open-dialog/Cargo.toml b/examples/open-dialog/Cargo.toml index a102f533..3fa07d42 100644 --- a/examples/open-dialog/Cargo.toml +++ b/examples/open-dialog/Cargo.toml @@ -10,10 +10,10 @@ xdg-portal = ["libcosmic/xdg-portal"] [dependencies] apply = "0.3.0" -tokio = { version = "1.31", features = ["full"] } -tracing = "0.1.37" -tracing-subscriber = "0.3.17" -url = "2.4.0" +tokio = { version = "1.44", features = ["full"] } +tracing = "0.1.41" +tracing-subscriber = "0.3.19" +url = "2.5.4" [dependencies.libcosmic] features = ["debug", "winit", "multi-window", "wayland", "tokio"] diff --git a/examples/text-input/Cargo.toml b/examples/text-input/Cargo.toml index e84f9eec..1cc35d1d 100644 --- a/examples/text-input/Cargo.toml +++ b/examples/text-input/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -tracing = "0.1.37" -tracing-subscriber = "0.3.17" +tracing = "0.1.41" +tracing-subscriber = "0.3.19" tracing-log = "0.2.0" [dependencies.libcosmic] From c538d672df2ac0e4e38d8d642b161c48b50f3ddb Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 19 Mar 2025 16:43:43 +0100 Subject: [PATCH 152/556] improv(text_input): optimize, fix, and improve the text inputs --- src/widget/text_input/cursor.rs | 27 +- src/widget/text_input/editor.rs | 2 + src/widget/text_input/input.rs | 462 ++++++++++++++++++-------------- src/widget/text_input/value.rs | 9 + 4 files changed, 291 insertions(+), 209 deletions(-) diff --git a/src/widget/text_input/cursor.rs b/src/widget/text_input/cursor.rs index a42596e1..42f52da1 100644 --- a/src/widget/text_input/cursor.rs +++ b/src/widget/text_input/cursor.rs @@ -27,6 +27,7 @@ pub enum State { } impl Default for Cursor { + #[inline] fn default() -> Self { Self { state: State::Index(0), @@ -37,6 +38,7 @@ impl Default for Cursor { impl Cursor { /// Returns the [`State`] of the [`Cursor`]. #[must_use] + #[inline(never)] pub fn state(&self, value: &Value) -> State { match self.state { State::Index(index) => State::Index(index.min(value.len())), @@ -57,6 +59,7 @@ impl Cursor { /// /// `start` is guaranteed to be <= than `end`. #[must_use] + #[inline] pub fn selection(&self, value: &Value) -> Option<(usize, usize)> { match self.state(value) { State::Selection { start, end } => Some((start.min(end), start.max(end))), @@ -64,18 +67,22 @@ impl Cursor { } } + #[inline] pub(crate) fn move_to(&mut self, position: usize) { self.state = State::Index(position); } + #[inline] pub(crate) fn move_right(&mut self, value: &Value) { self.move_right_by_amount(value, 1); } + #[inline] pub(crate) fn move_right_by_words(&mut self, value: &Value) { self.move_to(value.next_end_of_word(self.right(value))); } + #[inline] pub(crate) fn move_right_by_amount(&mut self, value: &Value, amount: usize) { match self.state(value) { State::Index(index) => self.move_to(index.saturating_add(amount).min(value.len())), @@ -83,6 +90,7 @@ impl Cursor { } } + #[inline] pub(crate) fn move_left(&mut self, value: &Value) { match self.state(value) { State::Index(index) if index > 0 => self.move_to(index - 1), @@ -91,18 +99,21 @@ impl Cursor { } } + #[inline] pub(crate) fn move_left_by_words(&mut self, value: &Value) { self.move_to(value.previous_start_of_word(self.left(value))); } + #[inline] pub(crate) fn select_range(&mut self, start: usize, end: usize) { - if start == end { - self.state = State::Index(start); + self.state = if start == end { + State::Index(start) } else { - self.state = State::Selection { start, end }; - } + State::Selection { start, end } + }; } + #[inline] pub(crate) fn select_left(&mut self, value: &Value) { match self.state(value) { State::Index(index) if index > 0 => self.select_range(index, index - 1), @@ -111,6 +122,7 @@ impl Cursor { } } + #[inline] pub(crate) fn select_right(&mut self, value: &Value) { match self.state(value) { State::Index(index) if index < value.len() => self.select_range(index, index + 1), @@ -121,6 +133,7 @@ impl Cursor { } } + #[inline] pub(crate) fn select_left_by_words(&mut self, value: &Value) { match self.state(value) { State::Index(index) => self.select_range(index, value.previous_start_of_word(index)), @@ -130,6 +143,7 @@ impl Cursor { } } + #[inline] pub(crate) fn select_right_by_words(&mut self, value: &Value) { match self.state(value) { State::Index(index) => self.select_range(index, value.next_end_of_word(index)), @@ -139,10 +153,12 @@ impl Cursor { } } + #[inline] pub(crate) fn select_all(&mut self, value: &Value) { self.select_range(0, value.len()); } + #[inline] pub(crate) fn start(&self, value: &Value) -> usize { let start = match self.state { State::Index(index) => index, @@ -152,6 +168,7 @@ impl Cursor { start.min(value.len()) } + #[inline] pub(crate) fn end(&self, value: &Value) -> usize { let end = match self.state { State::Index(index) => index, @@ -161,6 +178,7 @@ impl Cursor { end.min(value.len()) } + #[inline] fn left(&self, value: &Value) -> usize { match self.state(value) { State::Index(index) => index, @@ -168,6 +186,7 @@ impl Cursor { } } + #[inline] fn right(&self, value: &Value) -> usize { match self.state(value) { State::Index(index) => index, diff --git a/src/widget/text_input/editor.rs b/src/widget/text_input/editor.rs index 301820f4..b8144761 100644 --- a/src/widget/text_input/editor.rs +++ b/src/widget/text_input/editor.rs @@ -10,11 +10,13 @@ pub struct Editor<'a> { } impl<'a> Editor<'a> { + #[inline] pub fn new(value: &'a mut Value, cursor: &'a mut Cursor) -> Editor<'a> { Editor { value, cursor } } #[must_use] + #[inline] pub fn contents(&self) -> String { self.value.to_string() } diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 869d53eb..52fde469 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -18,9 +18,9 @@ use super::style::StyleSheet; pub use super::value::Value; use apply::Apply; +use iced::Limits; use iced::clipboard::dnd::{DndAction, DndEvent, OfferEvent, SourceEvent}; use iced::clipboard::mime::AsMimeTypes; -use iced::Limits; use iced_core::event::{self, Event}; use iced_core::mouse::{self, click}; use iced_core::overlay::Group; @@ -28,18 +28,18 @@ use iced_core::renderer::{self, Renderer as CoreRenderer}; use iced_core::text::{self, Paragraph, Renderer, Text}; use iced_core::time::{Duration, Instant}; use iced_core::touch; +use iced_core::widget::Id; use iced_core::widget::operation::{self, Operation}; use iced_core::widget::tree::{self, Tree}; -use iced_core::widget::Id; use iced_core::window; -use iced_core::{alignment, Background}; -use iced_core::{keyboard, Border, Shadow}; -use iced_core::{layout, overlay}; +use iced_core::{Background, alignment}; +use iced_core::{Border, Shadow, keyboard}; use iced_core::{ Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size, Vector, Widget, }; -use iced_runtime::{task, Action, Task}; +use iced_core::{layout, overlay}; +use iced_runtime::{Action, Task, task}; thread_local! { // Prevents two inputs from being focused at the same time. @@ -59,7 +59,7 @@ where TextInput::new(placeholder, value) } -/// A text label whiich can transform into a text input on activation. +/// A text label which can transform into a text input on activation. pub fn editable_input<'a, Message: Clone + 'static>( placeholder: impl Into>, text: impl Into>, @@ -182,7 +182,7 @@ pub struct TextInput<'a, Message> { placeholder: Cow<'a, str>, value: Value, is_secure: bool, - is_editable: bool, + is_editable_variant: bool, is_read_only: bool, select_on_focus: bool, font: Option<::Font>, @@ -193,8 +193,11 @@ pub struct TextInput<'a, Message> { label: Option>, helper_text: Option>, error: Option>, + on_focus: Option, + on_unfocus: Option, on_input: Option Message + 'a>>, on_paste: Option Message + 'a>>, + on_tab: Option, on_submit: Option Message + 'a>>, on_toggle_edit: Option Message + 'a>>, leading_icon: Option>, @@ -228,7 +231,7 @@ where placeholder: placeholder.into(), value: Value::new(v.as_ref()), is_secure: false, - is_editable: false, + is_editable_variant: false, is_read_only: false, select_on_focus: false, font: None, @@ -237,9 +240,12 @@ where size: None, helper_size: 10.0, helper_line_height: text::LineHeight::Absolute(14.0.into()), + on_focus: None, + on_unfocus: None, on_input: None, on_paste: None, on_submit: None, + on_tab: None, on_toggle_edit: None, leading_icon: None, trailing_icon: None, @@ -256,6 +262,7 @@ where } } + #[inline] fn dnd_id(&self) -> u128 { match &self.id.0 { iced_core::id::Internal::Custom(id, _) | iced_core::id::Internal::Unique(id) => { @@ -267,7 +274,8 @@ where /// Sets the input to be always active. /// This makes it behave as if it was always focused. - pub fn always_active(mut self) -> Self { + #[inline] + pub const fn always_active(mut self) -> Self { self.always_active = true; self } @@ -285,6 +293,7 @@ where } /// Sets the [`Id`] of the [`TextInput`]. + #[inline] pub fn id(mut self, id: Id) -> Self { self.id = id; self @@ -303,66 +312,85 @@ where } /// Converts the [`TextInput`] into a secure password input. - pub fn password(mut self) -> Self { + #[inline] + pub const fn password(mut self) -> Self { self.is_secure = true; self } - pub fn editable(mut self) -> Self { - self.is_editable = true; + /// Applies behaviors unique to the `editable_input` variable. + #[inline] + pub(crate) const fn editable(mut self) -> Self { + self.is_editable_variant = true; self } - pub fn editing(mut self, enable: bool) -> Self { + #[inline] + pub const fn editing(mut self, enable: bool) -> Self { self.is_read_only = !enable; self } /// Selects all text when the text input is focused - pub fn select_on_focus(mut self, select_on_focus: bool) -> Self { + #[inline] + pub const fn select_on_focus(mut self, select_on_focus: bool) -> Self { self.select_on_focus = select_on_focus; self } + /// Emits a message when an unfocused text input has been focused by click. + /// + /// This will not trigger if the input was focused externally by the application. + #[inline] + pub fn on_focus(mut self, on_focus: Message) -> Self { + self.on_focus = Some(on_focus); + self + } + + /// Emits a message when a focused text input has been unfocused via the Tab or Esc key. + /// + /// This will not trigger if the input was unfocused externally by the application. + #[inline] + pub fn on_unfocus(mut self, on_unfocus: Message) -> Self { + self.on_unfocus = Some(on_unfocus); + self + } + /// Sets the message that should be produced when some text is typed into /// the [`TextInput`]. /// /// If this method is not called, the [`TextInput`] will be disabled. - pub fn on_input(mut self, callback: F) -> Self - where - F: 'a + Fn(String) -> Message, - { + pub fn on_input(mut self, callback: impl Fn(String) -> Message + 'a) -> Self { self.on_input = Some(Box::new(callback)); self } - /// Sets the message that should be produced when the [`TextInput`] is - /// focused and the enter key is pressed. - pub fn on_submit(self, callback: F) -> Self - where - F: 'a + Fn(String) -> Message, - { - self.on_submit_maybe(Some(Box::new(callback))) - } - - /// Maybe sets the message that should be produced when the [`TextInput`] is - /// focused and the enter key is pressed. - pub fn on_submit_maybe(mut self, callback: Option) -> Self - where - F: 'a + Fn(String) -> Message, - { - if let Some(callback) = callback { - self.on_submit = Some(Box::new(callback)); - } else { - self.on_submit = None; - } + /// Emits a message when a focused text input receives the Enter/Return key. + pub fn on_submit(mut self, callback: impl Fn(String) -> Message + 'a) -> Self { + self.on_submit = Some(Box::new(callback)); self } - pub fn on_toggle_edit(mut self, callback: F) -> Self - where - F: 'a + Fn(bool) -> Message, - { + /// Optionally emits a message when a focused text input receives the Enter/Return key. + pub fn on_submit_maybe(self, callback: Option Message + 'a>) -> Self { + if let Some(callback) = callback { + self.on_submit(callback) + } else { + self + } + } + + /// Emits a message when the Tab key has been captured, which prevents focus from changing. + /// + /// If you do no want to capture the Tab key, use [`TextInput::on_unfocus`] instead. + #[inline] + pub fn on_tab(mut self, on_tab: Message) -> Self { + self.on_tab = Some(on_tab); + self + } + + /// Emits a message when the editable state of the input changes. + pub fn on_toggle_edit(mut self, callback: impl Fn(bool) -> Message + 'a) -> Self { self.on_toggle_edit = Some(Box::new(callback)); self } @@ -377,12 +405,17 @@ where /// Sets the [`Font`] of the [`TextInput`]. /// /// [`Font`]: text::Renderer::Font - pub fn font(mut self, font: ::Font) -> Self { + #[inline] + pub const fn font( + mut self, + font: ::Font, + ) -> Self { self.font = Some(font); self } /// Sets the start [`Icon`] of the [`TextInput`]. + #[inline] pub fn leading_icon( mut self, icon: Element<'a, Message, crate::Theme, crate::Renderer>, @@ -392,6 +425,7 @@ where } /// Sets the end [`Icon`] of the [`TextInput`]. + #[inline] pub fn trailing_icon( mut self, icon: Element<'a, Message, crate::Theme, crate::Renderer>, @@ -407,7 +441,7 @@ where } /// Sets the [`Padding`] of the [`TextInput`]. - pub fn padding>(mut self, padding: P) -> Self { + pub fn padding(mut self, padding: impl Into) -> Self { self.padding = padding.into(); self } @@ -425,8 +459,9 @@ where } /// Sets the text input to manage its input value or not - pub fn manage_value(mut self, manage_value: bool) -> Self { - self.manage_value = true; + #[inline] + pub const fn manage_value(mut self, manage_value: bool) -> Self { + self.manage_value = manage_value; self } @@ -435,6 +470,7 @@ where /// /// [`Renderer`]: text::Renderer #[allow(clippy::too_many_arguments)] + #[inline] pub fn draw( &self, tree: &Tree, @@ -484,13 +520,15 @@ where /// Sets the window id of the [`TextInput`] and the window id of the drag icon. /// Both ids are required to be unique. /// This is required for the dnd to work. - pub fn surface_ids(mut self, window_id: (window::Id, window::Id)) -> Self { + #[inline] + pub const fn surface_ids(mut self, window_id: (window::Id, window::Id)) -> Self { self.surface_ids = Some(window_id); self } /// Sets the mode of this [`TextInput`] to be a drag and drop icon. - pub fn dnd_icon(mut self, dnd_icon: bool) -> Self { + #[inline] + pub const fn dnd_icon(mut self, dnd_icon: bool) -> Self { self.dnd_icon = dnd_icon; self } @@ -508,6 +546,7 @@ 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 @@ -525,10 +564,12 @@ impl Widget for TextInput<'_, M where Message: Clone + 'static, { + #[inline] fn tag(&self) -> tree::Tag { tree::Tag::of::() } + #[inline] fn state(&self) -> tree::State { tree::State::new(State::new( self.is_secure, @@ -540,6 +581,7 @@ where fn diff(&mut self, tree: &mut Tree) { let state = tree.state.downcast_mut::(); + if !self.manage_value || !self.value.is_empty() && state.tracked_value != self.value { state.tracked_value = self.value.clone(); } else if self.value.is_empty() { @@ -586,8 +628,6 @@ where state.dirty = true; } - self.is_read_only = state.is_read_only; - if self.always_active && state.is_focused.is_none() { let now = Instant::now(); LAST_FOCUS_UPDATE.with(|x| x.set(now)); @@ -607,12 +647,18 @@ where }; } - if !state.is_focused.as_ref().map_or(false, |f| { - f.updated_at == LAST_FOCUS_UPDATE.with(|f| f.get()) - }) { - state.is_focused = None; + if !state + .is_focused + .as_ref() + .map(|f| f.updated_at == LAST_FOCUS_UPDATE.with(|f| f.get())) + .unwrap_or_default() + { + state.unfocus(); + // state.is_read_only = true; } + self.is_read_only = state.is_read_only; + // Stop pasting if input becomes disabled if !self.manage_value && self.on_input.is_none() { state.is_pasting = None; @@ -635,6 +681,7 @@ where .collect() } + #[inline] fn size(&self) -> Size { Size { width: self.width, @@ -790,7 +837,8 @@ where let size = self.size.unwrap_or_else(|| renderer.default_size().0); let line_height = self.line_height; - if self.is_editable { + // Disables editing of the editable variant when clicking outside of it. + if self.is_editable_variant { if let Some(ref on_edit) = self.on_toggle_edit { let state = tree.state.downcast_mut::(); if !state.is_read_only && state.is_focused.is_none() { @@ -800,34 +848,38 @@ where } } + // Calculates the layout of the trailing icon button element. if !tree.children.is_empty() { let index = tree.children.len() - 1; if let (Some(trailing_icon), Some(tree)) = (self.trailing_icon.as_mut(), tree.children.get_mut(index)) { - let children = text_layout.children(); - trailing_icon_layout = Some(children.last().unwrap()); + trailing_icon_layout = Some(text_layout.children().last().unwrap()); - if let Some(trailing_layout) = trailing_icon_layout { - if cursor_position.is_over(trailing_layout.bounds()) { - let res = trailing_icon.as_widget_mut().on_event( - tree, - event.clone(), - trailing_layout, - cursor_position, - renderer, - clipboard, - shell, - viewport, - ); + // Enable custom buttons defined on the trailing icon position to be handled. + if !self.is_editable_variant { + if let Some(trailing_layout) = trailing_icon_layout { + if cursor_position.is_over(trailing_layout.bounds()) { + let res = trailing_icon.as_widget_mut().on_event( + tree, + event.clone(), + trailing_layout, + cursor_position, + renderer, + clipboard, + shell, + viewport, + ); - if res == event::Status::Captured { - return res; + if res == event::Status::Captured { + return res; + } } } } } } + let dnd_id = self.dnd_id(); let id = Widget::id(self); update( @@ -841,11 +893,14 @@ where &mut self.value, size, font, + self.is_editable_variant, self.is_secure, - self.is_editable, + self.on_focus.as_ref(), + self.on_unfocus.as_ref(), self.on_input.as_deref(), self.on_paste.as_deref(), self.on_submit.as_deref(), + self.on_tab.as_ref(), self.on_toggle_edit.as_deref(), || tree.state.downcast_mut::(), self.on_create_dnd_source.as_deref(), @@ -856,6 +911,7 @@ where ) } + #[inline] fn draw( &self, tree: &Tree, @@ -908,9 +964,7 @@ where if let (Some(leading_icon), Some(tree)) = (self.leading_icon.as_ref(), state.children.get(index)) { - let mut children = layout.children(); - children.next(); - let leading_icon_layout = children.next().unwrap(); + let leading_icon_layout = layout.children().nth(1).unwrap(); if cursor_position.is_over(leading_icon_layout.bounds()) { return leading_icon.as_widget().mouse_interaction( @@ -954,19 +1008,21 @@ where ) } + #[inline] fn id(&self) -> Option { Some(self.id.clone()) } + #[inline] fn set_id(&mut self, id: Id) { self.id = id; } fn drag_destinations( &self, - state: &Tree, + _state: &Tree, layout: Layout<'_>, - renderer: &crate::Renderer, + _renderer: &crate::Renderer, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { if let Some(input) = layout.children().last() { @@ -1251,18 +1307,21 @@ pub fn update<'a, Message: Clone + 'static>( id: Option, event: Event, text_layout: Layout<'_>, - trailing_icon_layout: Option>, + edit_button_layout: Option>, cursor: mouse::Cursor, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, value: &mut Value, size: f32, font: ::Font, + is_editable_variant: bool, is_secure: bool, - is_editable: bool, + on_focus: Option<&Message>, + on_unfocus: Option<&Message>, on_input: Option<&dyn Fn(String) -> Message>, on_paste: Option<&dyn Fn(String) -> Message>, on_submit: Option<&dyn Fn(String) -> Message>, + on_tab: Option<&Message>, on_toggle_edit: Option<&dyn Fn(bool) -> Message>, state: impl FnOnce() -> &'a mut State, #[allow(unused_variables)] on_start_dnd_source: Option<&dyn Fn(State) -> Message>, @@ -1285,9 +1344,15 @@ pub fn update<'a, Message: Clone + 'static>( // NOTE: Clicks must be captured to prevent mouse areas behind them handling the same clicks. + /// Mark a branch as cold + #[inline] + #[cold] + fn cold() {} + match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { + cold(); let state = state(); let click_position = if on_input.is_some() || manage_value { @@ -1296,18 +1361,30 @@ pub fn update<'a, Message: Clone + 'static>( None }; - if click_position.is_some() { - state.is_focused = state.is_focused.or_else(|| { - let now = Instant::now(); - LAST_FOCUS_UPDATE.with(|x| x.set(now)); - Some(Focus { - updated_at: now, - now, - }) - }); - } - 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 is_editable_variant { + state.is_read_only = !state.is_read_only; + state.move_cursor_to_end(); + + if let Some(on_toggle_edit) = on_toggle_edit { + shell.publish(on_toggle_edit(!state.is_read_only)); + } + + let now = Instant::now(); + LAST_FOCUS_UPDATE.with(|x| x.set(now)); + state.is_focused = Some(Focus { + updated_at: now, + now, + }); + } + + return event::Status::Captured; + } + let target = cursor_position.x - text_layout.bounds().x; let click = @@ -1324,7 +1401,7 @@ pub fn update<'a, Message: Clone + 'static>( // single click that is on top of the selected text // is the click on selected text? - if manage_value || on_input.is_some() { + if on_input.is_some() || manage_value { let left = start.min(end); let right = end.max(start); @@ -1393,40 +1470,14 @@ pub fn update<'a, Message: Clone + 'static>( update_cache(state, &unsecured_value); } else { update_cache(state, value); - // existing logic for setting the selection - let position = if target > 0.0 { - find_cursor_position(text_layout.bounds(), value, state, target) - } else { - None - }; - - state.cursor.move_to(position.unwrap_or(0)); - state.dragging_state = Some(DraggingState::Selection); + state.setting_selection(value, text_layout.bounds(), target); } } else { - // existing logic for setting the selection - let position = if target > 0.0 { - update_cache(state, value); - find_cursor_position(text_layout.bounds(), value, state, target) - } else { - None - }; - - state.cursor.move_to(position.unwrap_or(0)); - state.dragging_state = Some(DraggingState::Selection); + state.setting_selection(value, text_layout.bounds(), target); } } (None, click::Kind::Single, _) => { - // existing logic for setting the selection - let position = if target > 0.0 { - update_cache(state, value); - find_cursor_position(text_layout.bounds(), value, state, target) - } else { - None - }; - - state.cursor.move_to(position.unwrap_or(0)); - state.dragging_state = Some(DraggingState::Selection); + state.setting_selection(value, text_layout.bounds(), target); } (None | Some(DraggingState::Selection), click::Kind::Double, _) => { update_cache(state, value); @@ -1455,93 +1506,41 @@ pub fn update<'a, Message: Clone + 'static>( } } - // Enable write mode when an editable input label is clicked - if is_editable - && state.is_read_only + // Focus on click of the text input, and ensure that the input is writable. + if state.is_focused.is_none() && matches!(state.dragging_state, None | Some(DraggingState::Selection)) { - state.is_read_only = false; + if let Some(on_focus) = on_focus { + shell.publish(on_focus.clone()); + } + + if state.is_read_only { + state.is_read_only = false; + if let Some(on_toggle_edit) = on_toggle_edit { + let message = (on_toggle_edit)(true); + shell.publish(message); + } + } let now = Instant::now(); LAST_FOCUS_UPDATE.with(|x| x.set(now)); + state.is_focused = Some(Focus { updated_at: now, now, }); - - if let Some(on_toggle_edit) = on_toggle_edit { - let message = (on_toggle_edit)(!state.is_read_only); - shell.publish(message); - } } state.last_click = Some(click); return event::Status::Captured; - } - - let mut is_trailing_clicked = false; - if is_editable { - if let Some(trailing_layout) = trailing_icon_layout { - is_trailing_clicked = cursor.is_over(trailing_layout.bounds()); - if is_trailing_clicked { - if on_toggle_edit.is_some() { - let Some(pos) = cursor.position() else { - return event::Status::Ignored; - }; - - let click = - mouse::Click::new(pos, mouse::Button::Left, state.last_click); - - match ( - &state.dragging_state, - click.kind(), - state.cursor().state(value), - ) { - (None, click::Kind::Single, _) => { - state.is_read_only = !state.is_read_only; - if let Some(on_toggle_edit) = on_toggle_edit { - let message = (on_toggle_edit)(!state.is_read_only); - shell.publish(message); - - let now = Instant::now(); - LAST_FOCUS_UPDATE.with(|x| x.set(now)); - state.is_focused = Some(Focus { - updated_at: now, - now, - }); - - state.move_cursor_to_end(); - } - } - _ => { - state.dragging_state = None; - } - } - } - - return event::Status::Captured; - } - } - } - - // Condition met when mouse click is completely outside of the widget. - if !is_trailing_clicked && click_position.is_none() { - state.is_focused = None; - state.dragging_state = None; - state.is_pasting = None; - state.keyboard_modifiers = keyboard::Modifiers::default(); - state.is_read_only = true; - - // Ensure clicks outside emit the toggle edit message. - if let Some(on_toggle_edit) = on_toggle_edit { - let message = (on_toggle_edit)(false); - shell.publish(message); - } + } else { + state.unfocus(); } } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }) => { + cold(); let state = state(); state.dragging_state = None; @@ -1573,14 +1572,10 @@ pub fn update<'a, Message: Clone + 'static>( let state = state(); if let Some(focus) = &mut state.is_focused { - if !manage_value && on_input.is_none() { + if state.is_read_only || (!manage_value && on_input.is_none()) { return event::Status::Ignored; }; - if state.is_read_only { - return event::Status::Ignored; - } - let modifiers = state.keyboard_modifiers; focus.updated_at = Instant::now(); LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at)); @@ -1750,6 +1745,7 @@ pub fn update<'a, Message: Clone + 'static>( 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 = if let Some(paste) = &on_paste { (paste)(contents) @@ -1759,6 +1755,7 @@ pub fn update<'a, Message: Clone + 'static>( shell.publish(message); } + state.is_pasting = Some(content); let value = if is_secure { @@ -1775,17 +1772,34 @@ pub fn update<'a, Message: Clone + 'static>( state.cursor.select_all(value); } keyboard::Key::Named(keyboard::key::Named::Escape) => { - state.is_focused = None; - state.dragging_state = None; - state.is_pasting = None; + state.unfocus(); + state.is_read_only = true; - state.keyboard_modifiers = keyboard::Modifiers::default(); + if let Some(on_unfocus) = on_unfocus { + shell.publish(on_unfocus.clone()); + } + } + + keyboard::Key::Named(keyboard::key::Named::Tab) => { + if let Some(on_tab) = on_tab { + // Allow the application to decide how the event is handled. + // This could be to connect the text input to another text input. + // Or to connect the text input to a button. + shell.publish(on_tab.clone()); + } else { + state.unfocus(); + state.is_read_only = true; + + if let Some(on_unfocus) = on_unfocus { + shell.publish(on_unfocus.clone()); + } + + return event::Status::Ignored; + }; } keyboard::Key::Named( - keyboard::key::Named::ArrowUp - | keyboard::key::Named::ArrowDown - | keyboard::key::Named::Tab, + keyboard::key::Named::ArrowUp | keyboard::key::Named::ArrowDown, ) => { return event::Status::Ignored; } @@ -1819,8 +1833,6 @@ pub fn update<'a, Message: Clone + 'static>( unsecured_value }; update_cache(state, &value); - - return event::Status::Captured; } } _ => {} @@ -1869,8 +1881,10 @@ pub fn update<'a, Message: Clone + 'static>( } #[cfg(feature = "wayland")] Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => { + cold(); let state = state(); if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) { + // TODO: restore value in text input state.dragging_state = None; return event::Status::Captured; } @@ -1885,6 +1899,7 @@ pub fn update<'a, Message: Clone + 'static>( surface, }, )) if rectangle == Some(dnd_id) => { + cold(); let state = state(); let is_clicked = text_layout.bounds().contains(Point { x: x as f32, @@ -1934,6 +1949,7 @@ pub fn update<'a, Message: Clone + 'static>( } #[cfg(feature = "wayland")] Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if rectangle == Some(dnd_id) => { + cold(); let state = state(); if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() { let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES @@ -1953,6 +1969,7 @@ pub fn update<'a, Message: Clone + 'static>( rectangle, OfferEvent::Leave | OfferEvent::LeaveDestination, )) if rectangle == Some(dnd_id) => { + cold(); let state = state(); // ASHLEY TODO we should be able to reset but for now we don't if we are handling a // drop @@ -1968,6 +1985,7 @@ pub fn update<'a, Message: Clone + 'static>( Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type })) if rectangle == Some(dnd_id) => { + cold(); let state = state(); if let DndOfferState::Dropped = state.dnd_offer.clone() { state.dnd_offer = DndOfferState::None; @@ -2560,18 +2578,21 @@ impl State { } /// Returns whether the [`TextInput`] is currently focused or not. + #[inline] #[must_use] pub fn is_focused(&self) -> bool { self.is_focused.is_some() } /// Returns the [`Cursor`] of the [`TextInput`]. + #[inline] #[must_use] pub fn cursor(&self) -> Cursor { self.cursor } /// Focuses the [`TextInput`]. + #[cold] pub fn focus(&mut self) { let now = Instant::now(); LAST_FOCUS_UPDATE.with(|x| x.set(now)); @@ -2589,63 +2610,90 @@ impl State { } /// Unfocuses the [`TextInput`]. - pub fn unfocus(&mut self) { + #[cold] + pub(super) fn unfocus(&mut self) { self.is_focused = None; + self.dragging_state = None; + self.is_pasting = None; + self.keyboard_modifiers = keyboard::Modifiers::default(); } /// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text. + #[inline] pub fn move_cursor_to_front(&mut self) { self.cursor.move_to(0); } /// Moves the [`Cursor`] of the [`TextInput`] to the end of the input text. + #[inline] pub fn move_cursor_to_end(&mut self) { self.cursor.move_to(usize::MAX); } /// Moves the [`Cursor`] of the [`TextInput`] to an arbitrary location. + #[inline] pub fn move_cursor_to(&mut self, position: usize) { self.cursor.move_to(position); } /// Selects all the content of the [`TextInput`]. + #[inline] pub fn select_all(&mut self) { self.cursor.select_range(0, usize::MAX); } + + pub(super) fn setting_selection(&mut self, value: &Value, bounds: Rectangle, target: f32) { + let position = if target > 0.0 { + find_cursor_position(bounds, value, self, target) + } else { + None + }; + + self.cursor.move_to(position.unwrap_or(0)); + self.dragging_state = Some(DraggingState::Selection); + } } impl operation::Focusable for State { + #[inline] fn is_focused(&self) -> bool { Self::is_focused(self) } + #[inline] fn focus(&mut self) { Self::focus(self); } + #[inline] fn unfocus(&mut self) { Self::unfocus(self); } } impl operation::TextInput for State { + #[inline] fn move_cursor_to_front(&mut self) { Self::move_cursor_to_front(self); } + #[inline] fn move_cursor_to_end(&mut self) { Self::move_cursor_to_end(self); } + #[inline] fn move_cursor_to(&mut self, position: usize) { Self::move_cursor_to(self, position); } + #[inline] fn select_all(&mut self) { Self::select_all(self); } } +#[inline(never)] fn measure_cursor_and_scroll_offset( paragraph: &impl text::Paragraph, text_bounds: Rectangle, @@ -2662,6 +2710,7 @@ fn measure_cursor_and_scroll_offset( /// Computes the position of the text cursor at the given X coordinate of /// a [`TextInput`]. +#[inline(never)] fn find_cursor_position( text_bounds: Rectangle, value: &Value, @@ -2686,6 +2735,7 @@ fn find_cursor_position( ) } +#[inline(never)] fn replace_paragraph( state: &mut State, layout: Layout<'_>, @@ -2715,6 +2765,7 @@ const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500; mod platform { use iced_core::keyboard; + #[inline] pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool { if cfg!(target_os = "macos") { modifiers.alt() @@ -2724,6 +2775,7 @@ mod platform { } } +#[inline(never)] fn offset(text_bounds: Rectangle, value: &Value, state: &State) -> f32 { if state.is_focused() { let cursor = state.cursor(); diff --git a/src/widget/text_input/value.rs b/src/widget/text_input/value.rs index dee3f110..60647db3 100644 --- a/src/widget/text_input/value.rs +++ b/src/widget/text_input/value.rs @@ -27,12 +27,14 @@ impl Value { /// /// A [`Value`] is empty when it contains no graphemes. #[must_use] + #[inline] pub fn is_empty(&self) -> bool { self.len() == 0 } /// Returns the total amount of graphemes in the [`Value`]. #[must_use] + #[inline] pub fn len(&self) -> usize { self.graphemes.len() } @@ -75,6 +77,7 @@ impl Value { /// Returns a new [`Value`] containing the graphemes from `start` until the /// given `end`. #[must_use] + #[inline] pub fn select(&self, start: usize, end: usize) -> Self { let graphemes = self.graphemes[start.min(self.len())..end.min(self.len())].to_vec(); @@ -84,6 +87,7 @@ impl Value { /// Returns a new [`Value`] containing the graphemes until the given /// `index`. #[must_use] + #[inline] pub fn until(&self, index: usize) -> Self { let graphemes = self.graphemes[..index.min(self.len())].to_vec(); @@ -91,6 +95,7 @@ impl Value { } /// Inserts a new `char` at the given grapheme `index`. + #[inline] pub fn insert(&mut self, index: usize, c: char) { self.graphemes.insert(index, c.to_string()); @@ -100,6 +105,7 @@ impl Value { } /// Inserts a bunch of graphemes at the given grapheme `index`. + #[inline] pub fn insert_many(&mut self, index: usize, mut value: Value) { let _ = self .graphemes @@ -107,11 +113,13 @@ impl Value { } /// Removes the grapheme at the given `index`. + #[inline] pub fn remove(&mut self, index: usize) { let _ = self.graphemes.remove(index); } /// Removes the graphemes from `start` to `end`. + #[inline] pub fn remove_many(&mut self, start: usize, end: usize) { let _ = self.graphemes.splice(start..end, std::iter::empty()); } @@ -129,6 +137,7 @@ impl Value { } impl ToString for Value { + #[inline] fn to_string(&self) -> String { self.graphemes.concat() } From 8cf372c9b9359effacf07ecc09e6bbfc94a06e57 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 21 Mar 2025 03:17:59 +0100 Subject: [PATCH 153/556] perf: inline public getters/setters, and use non-generic inner functions To reduce compile-times and avoid some overhead to binary size, this will modify some of our generic functions to use non-generic inner functions where possible. The inner functions are marked carefully with `#[inline(never)]` to prevent being inlined by LLVM at their callsites While looking for generic functions to optimize, I have also taken the opportunity to annotate public non-generic getters and setters with `#[inline]` to ensure that LLVM will inline them across crate boundaries. By default, only generic functions are automatically inlined, and only when enabling fat LTO are constant functions reliably inlined across crate boundaries. --- cosmic-config/src/dbus.rs | 1 + cosmic-config/src/lib.rs | 12 +- cosmic-config/src/subscription.rs | 2 + cosmic-theme/src/lib.rs | 2 +- cosmic-theme/src/model/cosmic_palette.rs | 7 + cosmic-theme/src/model/derivation.rs | 5 + cosmic-theme/src/model/layout.rs | 5 +- cosmic-theme/src/model/mode.rs | 5 + cosmic-theme/src/model/theme.rs | 115 +++++++++++++ cosmic-theme/src/output/gtk4_output.rs | 6 + cosmic-theme/src/output/mod.rs | 3 + cosmic-theme/src/output/vs_code.rs | 2 + src/app/cosmic.rs | 16 +- src/app/mod.rs | 13 +- src/config/mod.rs | 1 + src/core.rs | 54 ++++-- src/dbus_activation.rs | 8 +- src/desktop.rs | 1 + src/font.rs | 5 + src/icon_theme.rs | 2 + src/keyboard_nav.rs | 3 +- src/malloc.rs | 2 + src/process.rs | 3 +- src/surface/mod.rs | 1 + src/theme/mod.rs | 20 ++- src/theme/portal.rs | 5 +- src/widget/aspect_ratio.rs | 11 +- src/widget/autosize.rs | 7 + src/widget/button/icon.rs | 6 +- src/widget/button/image.rs | 8 +- src/widget/button/link.rs | 1 + src/widget/button/mod.rs | 4 +- src/widget/button/text.rs | 3 +- src/widget/button/widget.rs | 30 +++- src/widget/calendar.rs | 7 +- src/widget/context_drawer/widget.rs | 158 ++++++++++-------- src/widget/flex_row/widget.rs | 5 +- src/widget/frames.rs | 52 +++--- src/widget/grid/widget.rs | 1 + src/widget/header_bar.rs | 5 +- src/widget/icon/handle.rs | 35 ++-- src/widget/icon/named.rs | 5 + src/widget/layer_container.rs | 9 + src/widget/list/column.rs | 51 +++--- src/widget/nav_bar.rs | 10 +- src/widget/nav_bar_toggle.rs | 2 +- src/widget/rectangle_tracker/subscription.rs | 10 +- src/widget/segmented_button/model/builder.rs | 9 + src/widget/segmented_button/model/entity.rs | 13 +- src/widget/segmented_button/model/mod.rs | 32 +++- .../segmented_button/model/selection.rs | 9 + src/widget/segmented_button/widget.rs | 4 +- src/widget/settings/item.rs | 47 ++++-- src/widget/text.rs | 117 +++++++++---- src/widget/toaster/mod.rs | 7 +- 55 files changed, 702 insertions(+), 255 deletions(-) diff --git a/cosmic-config/src/dbus.rs b/cosmic-config/src/dbus.rs index e689acc6..e66d8556 100644 --- a/cosmic-config/src/dbus.rs +++ b/cosmic-config/src/dbus.rs @@ -20,6 +20,7 @@ pub struct Watcher { impl Deref for Watcher { type Target = ConfigProxy<'static>; + #[inline] fn deref(&self) -> &Self::Target { &self.proxy } diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 0c0f4db7..beab95fb 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -40,6 +40,7 @@ pub enum Error { } impl fmt::Display for Error { + #[cold] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::AtomicWrites(err) => err.fmt(f), @@ -61,6 +62,7 @@ impl Error { /// Whether the reason for the missing config is caused by an error. /// /// Useful for determining if it is appropriate to log as an error. + #[inline] pub fn is_err(&self) -> bool { !matches!(self, Self::NoConfigDirectory | Self::NotFound) } @@ -134,11 +136,6 @@ fn sanitize_name(name: &str) -> Result<&Path, Error> { } impl Config { - /// Get the config for the libcosmic toolkit - pub fn libcosmic() -> Result { - Self::new("com.system76.libcosmic", 1) - } - /// Get a system config for the given name and config version pub fn system(name: &str, version: u64) -> Result { let path = sanitize_name(name)?.join(format!("v{version}")); @@ -235,6 +232,7 @@ impl Config { } // Start a transaction (to set multiple configs at the same time) + #[inline] pub fn transaction(&self) -> ConfigTransaction { ConfigTransaction { config: self, @@ -368,7 +366,7 @@ pub struct ConfigTransaction<'a> { updates: Mutex>, } -impl<'a> ConfigTransaction<'a> { +impl ConfigTransaction<'_> { /// Apply all pending changes from ConfigTransaction //TODO: apply all changes at once pub fn commit(self) -> Result<(), Error> { @@ -386,7 +384,7 @@ impl<'a> ConfigTransaction<'a> { // Setting any setting in this way will do one transaction for all settings // when commit finishes that transaction -impl<'a> ConfigSet for ConfigTransaction<'a> { +impl ConfigSet for ConfigTransaction<'_> { fn set(&self, key: &str, value: T) -> Result<(), Error> { //TODO: sanitize key (no slashes, cannot be . or ..) let key_path = self.config.key_path(key)?; diff --git a/cosmic-config/src/subscription.rs b/cosmic-config/src/subscription.rs index 7468af0a..64255954 100644 --- a/cosmic-config/src/subscription.rs +++ b/cosmic-config/src/subscription.rs @@ -16,6 +16,7 @@ pub enum ConfigUpdate { Failed, } +#[cold] pub fn config_subscription< I: 'static + Copy + Send + Sync + Hash, T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry, @@ -27,6 +28,7 @@ pub fn config_subscription< iced_futures::Subscription::run_with_id(id, watcher_stream(config_id, config_version, false)) } +#[cold] pub fn config_state_subscription< I: 'static + Copy + Send + Sync + Hash, T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry, diff --git a/cosmic-theme/src/lib.rs b/cosmic-theme/src/lib.rs index c30234b1..5d59ccda 100644 --- a/cosmic-theme/src/lib.rs +++ b/cosmic-theme/src/lib.rs @@ -19,6 +19,6 @@ pub mod composite; pub mod steps; /// name of cosmic theme -pub const NAME: &'static str = "com.system76.CosmicTheme"; +pub const NAME: &str = "com.system76.CosmicTheme"; pub use palette; diff --git a/cosmic-theme/src/model/cosmic_palette.rs b/cosmic-theme/src/model/cosmic_palette.rs index 3b916c19..6a189089 100644 --- a/cosmic-theme/src/model/cosmic_palette.rs +++ b/cosmic-theme/src/model/cosmic_palette.rs @@ -26,6 +26,7 @@ pub enum CosmicPalette { impl CosmicPalette { /// extract the inner palette + #[inline] pub fn inner(self) -> CosmicPaletteInner { match self { CosmicPalette::Dark(p) => p, @@ -37,6 +38,7 @@ impl CosmicPalette { } impl AsMut for CosmicPalette { + #[inline] fn as_mut(&mut self) -> &mut CosmicPaletteInner { match self { CosmicPalette::Dark(p) => p, @@ -48,6 +50,7 @@ impl AsMut for CosmicPalette { } impl AsRef for CosmicPalette { + #[inline] fn as_ref(&self) -> &CosmicPaletteInner { match self { CosmicPalette::Dark(p) => p, @@ -60,6 +63,7 @@ impl AsRef for CosmicPalette { impl CosmicPalette { /// check if the palette is dark + #[inline] pub fn is_dark(&self) -> bool { match self { CosmicPalette::Dark(_) | CosmicPalette::HighContrastDark(_) => true, @@ -68,6 +72,7 @@ impl CosmicPalette { } /// check if the palette is high_contrast + #[inline] pub fn is_high_contrast(&self) -> bool { match self { CosmicPalette::HighContrastLight(_) | CosmicPalette::HighContrastDark(_) => true, @@ -77,6 +82,7 @@ impl CosmicPalette { } impl Default for CosmicPalette { + #[inline] fn default() -> Self { CosmicPalette::Dark(Default::default()) } @@ -164,6 +170,7 @@ pub struct CosmicPaletteInner { impl CosmicPalette { /// name of the palette + #[inline] pub fn name(&self) -> &str { match &self { CosmicPalette::Dark(p) => &p.name, diff --git a/cosmic-theme/src/model/derivation.rs b/cosmic-theme/src/model/derivation.rs index 92761422..f4147c2c 100644 --- a/cosmic-theme/src/model/derivation.rs +++ b/cosmic-theme/src/model/derivation.rs @@ -77,26 +77,31 @@ pub struct Component { #[allow(clippy::must_use_candidate)] #[allow(clippy::doc_markdown)] impl Component { + #[inline] /// get @hover_state_color pub fn hover_state_color(&self) -> Srgba { self.hover } + #[inline] /// get @pressed_state_color pub fn pressed_state_color(&self) -> Srgba { self.pressed } + #[inline] /// get @selected_state_color pub fn selected_state_color(&self) -> Srgba { self.selected } + #[inline] /// get @selected_state_text_color pub fn selected_state_text_color(&self) -> Srgba { self.selected_text } + #[inline] /// get @focus_color pub fn focus_color(&self) -> Srgba { self.focus diff --git a/cosmic-theme/src/model/layout.rs b/cosmic-theme/src/model/layout.rs index 79456dc6..a476b630 100644 --- a/cosmic-theme/src/model/layout.rs +++ b/cosmic-theme/src/model/layout.rs @@ -1,5 +1,4 @@ #[derive(Default)] pub struct Layout { - corner_radii: [u32;4], - -} \ No newline at end of file + corner_radii: [u32; 4], +} diff --git a/cosmic-theme/src/model/mode.rs b/cosmic-theme/src/model/mode.rs index f57c653c..ce166979 100644 --- a/cosmic-theme/src/model/mode.rs +++ b/cosmic-theme/src/model/mode.rs @@ -16,6 +16,7 @@ pub struct ThemeMode { } impl Default for ThemeMode { + #[inline] fn default() -> Self { Self { is_dark: true, @@ -25,15 +26,19 @@ impl Default for ThemeMode { } impl ThemeMode { + #[inline] /// Check if the theme is currently using dark mode pub fn is_dark(config: &Config) -> Result { config.get::("is_dark") } + #[inline] + /// The current version of the theme mode config. pub const fn version() -> u64 { Self::VERSION } + #[inline] /// Get the config for the theme mode pub fn config() -> Result { Config::new(THEME_MODE_ID, Self::VERSION) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index a36bbda7..d159b405 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -103,6 +103,7 @@ pub struct Theme { } impl Default for Theme { + #[inline] fn default() -> Self { Self::preferred_theme() } @@ -121,36 +122,43 @@ impl Theme { NAME } + #[inline] /// Get the config for the current dark theme pub fn dark_config() -> Result { Config::new(DARK_THEME_ID, Self::VERSION) } + #[inline] /// Get the config for the current light theme pub fn light_config() -> Result { Config::new(LIGHT_THEME_ID, Self::VERSION) } + #[inline] /// get the built in light theme pub fn light_default() -> Self { LIGHT_PALETTE.clone().into() } + #[inline] /// get the built in dark theme pub fn dark_default() -> Self { DARK_PALETTE.clone().into() } + #[inline] /// get the built in high contrast dark theme pub fn high_contrast_dark_default() -> Self { CosmicPalette::HighContrastDark(DARK_PALETTE.as_ref().clone()).into() } + #[inline] /// get the built in high contrast light theme pub fn high_contrast_light_default() -> Self { CosmicPalette::HighContrastLight(LIGHT_PALETTE.as_ref().clone()).into() } + #[inline] /// Convert the theme to a high-contrast variant pub fn to_high_contrast(&self) -> Self { todo!(); @@ -159,6 +167,7 @@ impl Theme { // TODO convenient getter functions for each named color variable #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @accent_color pub fn accent_color(&self) -> Srgba { self.accent.base @@ -166,6 +175,7 @@ impl Theme { #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @success_color pub fn success_color(&self) -> Srgba { self.success.base @@ -173,6 +183,7 @@ impl Theme { #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @destructive_color pub fn destructive_color(&self) -> Srgba { self.destructive.base @@ -180,6 +191,7 @@ impl Theme { #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @warning_color pub fn warning_color(&self) -> Srgba { self.warning.base @@ -187,6 +199,7 @@ impl Theme { #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @small_widget_divider pub fn small_widget_divider(&self) -> Srgba { let mut neutral_9 = self.palette.neutral_9; @@ -197,42 +210,55 @@ impl Theme { // Containers #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @bg_color pub fn bg_color(&self) -> Srgba { self.background.base } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @bg_component_color pub fn bg_component_color(&self) -> Srgba { self.background.component.base } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @primary_container_color pub fn primary_container_color(&self) -> Srgba { self.primary.base } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @primary_component_color pub fn primary_component_color(&self) -> Srgba { self.primary.component.base } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @secondary_container_color pub fn secondary_container_color(&self) -> Srgba { self.secondary.base } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @secondary_component_color pub fn secondary_component_color(&self) -> Srgba { self.secondary.component.base } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @button_bg_color pub fn button_bg_color(&self) -> Srgba { self.button.base @@ -241,90 +267,119 @@ impl Theme { // Text #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @on_bg_color pub fn on_bg_color(&self) -> Srgba { self.background.on } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @on_bg_component_color pub fn on_bg_component_color(&self) -> Srgba { self.background.component.on } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @on_primary_color pub fn on_primary_container_color(&self) -> Srgba { self.primary.on } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @on_primary_component_color pub fn on_primary_component_color(&self) -> Srgba { self.primary.component.on } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @on_secondary_color pub fn on_secondary_container_color(&self) -> Srgba { self.secondary.on } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @on_secondary_component_color pub fn on_secondary_component_color(&self) -> Srgba { self.secondary.component.on } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @accent_text_color pub fn accent_text_color(&self) -> Srgba { self.accent_text.unwrap_or(self.accent.base) } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @success_text_color pub fn success_text_color(&self) -> Srgba { self.success.base } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @warning_text_color pub fn warning_text_color(&self) -> Srgba { self.warning.base } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @destructive_text_color pub fn destructive_text_color(&self) -> Srgba { self.destructive.base } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @on_accent_color pub fn on_accent_color(&self) -> Srgba { self.accent.on } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @on_success_color pub fn on_success_color(&self) -> Srgba { self.success.on } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @on_warning_color pub fn on_warning_color(&self) -> Srgba { self.warning.on } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @on_destructive_color pub fn on_destructive_color(&self) -> Srgba { self.destructive.on } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @button_color pub fn button_color(&self) -> Srgba { self.button.on @@ -333,36 +388,47 @@ impl Theme { // Borders and Dividers #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @bg_divider pub fn bg_divider(&self) -> Srgba { self.background.divider } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @bg_component_divider pub fn bg_component_divider(&self) -> Srgba { self.background.component.divider } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @primary_container_divider pub fn primary_container_divider(&self) -> Srgba { self.primary.divider } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @primary_component_divider pub fn primary_component_divider(&self) -> Srgba { self.primary.component.divider } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @secondary_container_divider pub fn secondary_container_divider(&self) -> Srgba { self.secondary.divider } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @button_divider pub fn button_divider(&self) -> Srgba { self.button.divider @@ -370,6 +436,7 @@ impl Theme { #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @window_header_bg pub fn window_header_bg(&self) -> Srgba { self.background.base @@ -377,60 +444,79 @@ impl Theme { #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @space_none pub fn space_none(&self) -> u16 { self.spacing.space_none } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @space_xxxs pub fn space_xxxs(&self) -> u16 { self.spacing.space_xxxs } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @space_xxs pub fn space_xxs(&self) -> u16 { self.spacing.space_xxs } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @space_xs pub fn space_xs(&self) -> u16 { self.spacing.space_xs } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @space_s pub fn space_s(&self) -> u16 { self.spacing.space_s } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @space_m pub fn space_m(&self) -> u16 { self.spacing.space_m } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @space_l pub fn space_l(&self) -> u16 { self.spacing.space_l } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @space_xl pub fn space_xl(&self) -> u16 { self.spacing.space_xl } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @space_xxl pub fn space_xxl(&self) -> u16 { self.spacing.space_xxl } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @space_xxxl pub fn space_xxxl(&self) -> u16 { self.spacing.space_xxxl @@ -438,36 +524,47 @@ impl Theme { #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @radius_0 pub fn radius_0(&self) -> [f32; 4] { self.corner_radii.radius_0 } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @radius_xs pub fn radius_xs(&self) -> [f32; 4] { self.corner_radii.radius_xs } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @radius_s pub fn radius_s(&self) -> [f32; 4] { self.corner_radii.radius_s } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @radius_m pub fn radius_m(&self) -> [f32; 4] { self.corner_radii.radius_m } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @radius_l pub fn radius_l(&self) -> [f32; 4] { self.corner_radii.radius_l } + #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @radius_xl pub fn radius_xl(&self) -> [f32; 4] { self.corner_radii.radius_xl @@ -475,6 +572,7 @@ impl Theme { #[must_use] #[allow(clippy::doc_markdown)] + #[inline] /// get @shade_color pub fn shade_color(&self) -> Srgba { self.shade @@ -631,6 +729,7 @@ impl Default for ThemeBuilder { } impl ThemeBuilder { + #[inline] /// Get a builder that is initialized with the default dark theme pub fn dark() -> Self { Self { @@ -639,6 +738,7 @@ impl ThemeBuilder { } } + #[inline] /// Get a builder that is initialized with the default light theme pub fn light() -> Self { Self { @@ -647,6 +747,7 @@ impl ThemeBuilder { } } + #[inline] /// Get a builder that is initialized with the default dark high contrast theme pub fn dark_high_contrast() -> Self { let palette: CosmicPalette = DARK_PALETTE.to_owned(); @@ -656,6 +757,7 @@ impl ThemeBuilder { } } + #[inline] /// Get a builder that is initialized with the default light high contrast theme pub fn light_high_contrast() -> Self { let palette: CosmicPalette = LIGHT_PALETTE.to_owned(); @@ -665,6 +767,7 @@ impl ThemeBuilder { } } + #[inline] /// Get a builder that is initialized with the provided palette pub fn palette(palette: CosmicPalette) -> Self { Self { @@ -673,60 +776,70 @@ impl ThemeBuilder { } } + #[inline] /// set the spacing of the builder pub fn spacing(mut self, spacing: Spacing) -> Self { self.spacing = spacing; self } + #[inline] /// set the corner radii of the builder pub fn corner_radii(mut self, corner_radii: CornerRadii) -> Self { self.corner_radii = corner_radii; self } + #[inline] /// apply a neutral tint to the palette pub fn neutral_tint(mut self, tint: Srgb) -> Self { self.neutral_tint = Some(tint); self } + #[inline] /// apply a text tint to the palette pub fn text_tint(mut self, tint: Srgb) -> Self { self.text_tint = Some(tint); self } + #[inline] /// apply a background color to the palette pub fn bg_color(mut self, c: Srgba) -> Self { self.bg_color = Some(c); self } + #[inline] /// apply a primary container background color to the palette pub fn primary_container_bg(mut self, c: Srgba) -> Self { self.primary_container_bg = Some(c); self } + #[inline] /// apply a accent color to the palette pub fn accent(mut self, c: Srgb) -> Self { self.accent = Some(c); self } + #[inline] /// apply a success color to the palette pub fn success(mut self, c: Srgb) -> Self { self.success = Some(c); self } + #[inline] /// apply a warning color to the palette pub fn warning(mut self, c: Srgb) -> Self { self.warning = Some(c); self } + #[inline] /// apply a destructive color to the palette pub fn destructive(mut self, c: Srgb) -> Self { self.destructive = Some(c); @@ -1139,11 +1252,13 @@ impl ThemeBuilder { theme } + #[inline] /// Get the builder for the dark config pub fn dark_config() -> Result { Config::new(DARK_THEME_BUILDER_ID, Self::VERSION) } + #[inline] /// Get the builder for the light config pub fn light_config() -> Result { Config::new(LIGHT_THEME_BUILDER_ID, Self::VERSION) diff --git a/cosmic-theme/src/output/gtk4_output.rs b/cosmic-theme/src/output/gtk4_output.rs index d0ab0c0e..c172e4ec 100644 --- a/cosmic-theme/src/output/gtk4_output.rs +++ b/cosmic-theme/src/output/gtk4_output.rs @@ -11,6 +11,7 @@ use super::{to_rgba, OutputError}; impl Theme { #[must_use] + #[cold] /// turn the theme into css pub fn as_gtk4(&self) -> String { let Self { @@ -145,6 +146,7 @@ impl Theme { /// # Errors /// /// Returns an `OutputError` if there is an error writing the CSS file. + #[cold] pub fn write_gtk4(&self) -> Result<(), OutputError> { let css_str = self.as_gtk4(); let Some(config_dir) = dirs::config_dir() else { @@ -174,6 +176,7 @@ impl Theme { /// # Errors /// /// Returns an `OutputError` if there is an error applying the CSS file. + #[cold] pub fn apply_gtk(is_dark: bool) -> Result<(), OutputError> { let Some(config_dir) = dirs::config_dir() else { return Err(OutputError::MissingConfigDir); @@ -213,6 +216,7 @@ impl Theme { /// # Errors /// /// Returns an `OutputError` if there is an error resetting the CSS file. + #[cold] pub fn reset_gtk() -> Result<(), OutputError> { let Some(config_dir) = dirs::config_dir() else { return Err(OutputError::MissingConfigDir); @@ -229,6 +233,7 @@ impl Theme { res } + #[cold] fn backup_non_cosmic_css(path: &Path, cosmic_css: &Path) -> io::Result<()> { if !Self::is_cosmic_css(path, cosmic_css)?.unwrap_or(true) { let backup_path = path.with_extension("css.bak"); @@ -237,6 +242,7 @@ impl Theme { Ok(()) } + #[cold] fn reset_cosmic_css(path: &Path, cosmic_css: &Path) -> io::Result<()> { if Self::is_cosmic_css(path, cosmic_css)?.unwrap_or_default() { fs::remove_file(path)?; diff --git a/cosmic-theme/src/output/mod.rs b/cosmic-theme/src/output/mod.rs index f2822333..f2eb6b4b 100644 --- a/cosmic-theme/src/output/mod.rs +++ b/cosmic-theme/src/output/mod.rs @@ -19,6 +19,7 @@ pub enum OutputError { } impl Theme { + #[inline] pub fn apply_exports(&self) -> Result<(), OutputError> { let gtk_res = Theme::apply_gtk(self.is_dark); let vs_res = self.clone().apply_vs_code(); @@ -27,12 +28,14 @@ impl Theme { Ok(()) } + #[inline] pub fn write_exports(&self) -> Result<(), OutputError> { let gtk_res = self.write_gtk4(); gtk_res?; Ok(()) } + #[inline] pub fn reset_exports() -> Result<(), OutputError> { let gtk_res = Theme::reset_gtk(); let vs_res = Theme::reset_vs_code(); diff --git a/cosmic-theme/src/output/vs_code.rs b/cosmic-theme/src/output/vs_code.rs index 11403dbe..5c770cd6 100644 --- a/cosmic-theme/src/output/vs_code.rs +++ b/cosmic-theme/src/output/vs_code.rs @@ -266,6 +266,7 @@ impl From for VsTheme { } 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)?; @@ -289,6 +290,7 @@ impl Theme { Ok(()) } + #[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"); diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 919e6042..ec129d33 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -6,16 +6,16 @@ use std::collections::HashMap; use std::sync::Arc; use super::{Action, Application, ApplicationExt, Subscription}; -use crate::theme::{Theme, ThemeType, THEME}; -use crate::{keyboard_nav, Core, Element}; +use crate::theme::{THEME, Theme, ThemeType}; +use crate::{Core, Element, keyboard_nav}; #[cfg(feature = "wayland")] use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use cosmic_theme::ThemeMode; -#[cfg(feature = "wayland")] -use iced::event::wayland; #[cfg(not(any(feature = "multi-window", feature = "wayland")))] use iced::Application as IcedApplication; -use iced::{window, Task}; +#[cfg(feature = "wayland")] +use iced::event::wayland; +use iced::{Task, window}; use iced_futures::event::listen_with; use palette::color_difference::EuclideanDistance; @@ -243,6 +243,7 @@ where } #[allow(clippy::too_many_lines)] + #[cold] pub fn subscription(&self) -> Subscription> { let window_events = listen_with(|event, _, id| { match event { @@ -410,6 +411,7 @@ where impl Cosmic { #[allow(clippy::unused_self)] + #[cold] pub fn close(&mut self) -> iced::Task> { if let Some(id) = self.app.core().main_window_id() { iced::window::close(id) @@ -490,10 +492,10 @@ impl Cosmic { Action::KeyboardNav(message) => match message { keyboard_nav::Action::FocusNext => { - return iced::widget::focus_next().map(crate::Action::Cosmic) + return iced::widget::focus_next().map(crate::Action::Cosmic); } keyboard_nav::Action::FocusPrevious => { - return iced::widget::focus_previous().map(crate::Action::Cosmic) + return iced::widget::focus_previous().map(crate::Action::Cosmic); } keyboard_nav::Action::Escape => return self.app.on_escape(), keyboard_nav::Action::Search => return self.app.on_search(), diff --git a/src/app/mod.rs b/src/app/mod.rs index 4af3f348..2f4192b6 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -17,10 +17,10 @@ pub mod settings; pub type Task = iced::Task>; +pub use crate::Core; use crate::prelude::*; use crate::theme::THEME; use crate::widget::{container, horizontal_space, id_container, menu, nav_bar, popover}; -pub use crate::Core; use apply::Apply; use context_drawer::ContextDrawer; use iced::window; @@ -28,6 +28,7 @@ use iced::{Length, Subscription}; pub use settings::Settings; use std::borrow::Cow; +#[cold] pub(crate) fn iced_settings( settings: Settings, flags: App::Flags, @@ -681,11 +682,10 @@ 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 = 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 { @@ -811,6 +811,7 @@ const EMBEDDED_FONTS: &[&[u8]] = &[ include_bytes!("../../res/noto/NotoSansMono-Bold.ttf"), ]; +#[cold] fn preload_fonts() { let mut font_system = iced::advanced::graphics::text::font_system() .write() diff --git a/src/config/mod.rs b/src/config/mod.rs index 1e82becd..dedadbc2 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -134,6 +134,7 @@ impl Default for CosmicTk { } impl CosmicTk { + #[inline] pub fn config() -> Result { Config::new(ID, Self::VERSION) } diff --git a/src/core.rs b/src/core.rs index fdeeba5b..744b33b7 100644 --- a/src/core.rs +++ b/src/core.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use crate::widget::nav_bar; use cosmic_config::CosmicConfigEntry; use cosmic_theme::ThemeMode; -use iced::{window, Limits, Size}; +use iced::{Limits, Size, window}; use iced_core::window::Id; use palette::Srgba; use slotmap::Key; @@ -161,45 +161,52 @@ impl Default for Core { impl Core { /// Whether the window is too small for the nav bar + main content. #[must_use] - pub fn is_condensed(&self) -> bool { + #[inline] + pub const fn is_condensed(&self) -> bool { self.is_condensed } /// The scaling factor used by the application. #[must_use] - pub fn scale_factor(&self) -> f32 { + #[inline] + pub const fn scale_factor(&self) -> f32 { self.scale_factor } /// Enable or disable keyboard navigation - pub fn set_keyboard_nav(&mut self, enabled: bool) { + #[inline] + pub const fn set_keyboard_nav(&mut self, enabled: bool) { self.keyboard_nav = enabled; } - #[must_use] /// Enable or disable keyboard navigation - pub fn keyboard_nav(&self) -> bool { + #[must_use] + #[inline] + pub const fn keyboard_nav(&self) -> bool { self.keyboard_nav } /// Changes the scaling factor used by the application. + #[cold] pub(crate) fn set_scale_factor(&mut self, factor: f32) { self.scale_factor = factor; self.is_condensed_update(); } /// Set header bar title + #[inline] pub fn set_header_title(&mut self, title: String) { self.window.header_title = title; } + #[inline] /// Whether to show or hide the main window's content. pub(crate) fn show_content(&self) -> bool { !self.is_condensed || !self.nav_bar.toggled_condensed } - /// Call this whenever the scaling factor or window width has changed. #[allow(clippy::cast_precision_loss)] + /// Call this whenever the scaling factor or window width has changed. fn is_condensed_update(&mut self) { // Nav bar (280px) + padding (8px) + content (360px) let mut breakpoint = 280.0 + 8.0 + 360.0; @@ -212,6 +219,7 @@ impl Core { self.nav_bar_update(); } + #[inline] fn condensed_conflict(&self) -> bool { // There is a conflict if the view is condensed and both the nav bar and context drawer are open on the same layer self.is_condensed @@ -220,6 +228,7 @@ impl Core { && !self.window.context_is_overlay } + #[inline] pub(crate) fn context_width(&self, has_nav: bool) -> f32 { let window_width = self.window.width / self.scale_factor; @@ -230,12 +239,14 @@ impl Core { reserved_width += 280.0 + 8.0; } + #[allow(clippy::manual_clamp)] // This logic is to ensure the context drawer does not take up too much of the content's space // The minimum width is 344px and the maximum with is 480px // We want to keep the content at least 360px until going down to the minimum width (window_width - reserved_width).min(480.0).max(344.0) } + #[cold] pub fn set_show_context(&mut self, show: bool) { self.window.show_context = show; self.is_condensed_update(); @@ -246,38 +257,46 @@ impl Core { } } + #[inline] pub fn main_window_is(&self, id: iced::window::Id) -> bool { self.main_window_id().is_some_and(|main_id| main_id == id) } /// Whether the nav panel is visible or not #[must_use] - pub fn nav_bar_active(&self) -> bool { + #[inline] + pub const fn nav_bar_active(&self) -> bool { self.nav_bar.active } + #[inline] pub fn nav_bar_toggle(&mut self) { self.nav_bar.toggled = !self.nav_bar.toggled; self.nav_bar_set_toggled_condensed(self.nav_bar.toggled); } + #[inline] pub fn nav_bar_toggle_condensed(&mut self) { self.nav_bar_set_toggled_condensed(!self.nav_bar.toggled_condensed); } - pub(crate) fn nav_bar_context(&self) -> nav_bar::Id { + #[inline] + pub(crate) const fn nav_bar_context(&self) -> nav_bar::Id { self.nav_bar.context_id } + #[inline] pub(crate) fn nav_bar_set_context(&mut self, id: nav_bar::Id) { self.nav_bar.context_id = id; } + #[inline] pub fn nav_bar_set_toggled(&mut self, toggled: bool) { self.nav_bar.toggled = toggled; self.nav_bar_set_toggled_condensed(self.nav_bar.toggled); } + #[cold] pub(crate) fn nav_bar_set_toggled_condensed(&mut self, toggled: bool) { self.nav_bar.toggled_condensed = toggled; self.nav_bar_update(); @@ -293,6 +312,7 @@ impl Core { } } + #[inline] pub(crate) fn nav_bar_update(&mut self) { self.nav_bar.active = if self.is_condensed { self.nav_bar.toggled_condensed @@ -301,25 +321,29 @@ impl Core { }; } + #[inline] /// Set the height of the main window. - pub(crate) fn set_window_height(&mut self, new_height: f32) { + pub(crate) const fn set_window_height(&mut self, new_height: f32) { self.window.height = new_height; } + #[inline] /// Set the width of the main window. pub(crate) fn set_window_width(&mut self, new_width: f32) { self.window.width = new_width; self.is_condensed_update(); } + #[inline] /// Get the current system theme - pub fn system_theme(&self) -> &Theme { + pub const fn system_theme(&self) -> &Theme { &self.system_theme } + #[inline] #[must_use] /// Get the current system theme mode - pub fn system_theme_mode(&self) -> ThemeMode { + pub const fn system_theme_mode(&self) -> ThemeMode { self.system_theme_mode } @@ -359,12 +383,14 @@ impl Core { /// Get the current focused window if it exists #[must_use] - pub fn focused_window(&self) -> Option { + #[inline] + pub const fn focused_window(&self) -> Option { self.focused_window } /// Whether the application should use a dark theme, according to the system #[must_use] + #[inline] pub fn system_is_dark(&self) -> bool { self.portal_is_dark .unwrap_or(self.system_theme_mode.is_dark) @@ -372,11 +398,13 @@ impl Core { /// The [`Id`] of the main window #[must_use] + #[inline] pub fn main_window_id(&self) -> Option { self.main_window.filter(|id| iced::window::Id::NONE != *id) } /// Reset the tracked main window to a new value + #[inline] pub fn set_main_window_id(&mut self, mut id: Option) -> Option { std::mem::swap(&mut self.main_window, &mut id); id diff --git a/src/dbus_activation.rs b/src/dbus_activation.rs index 84b5d000..4fffa422 100644 --- a/src/dbus_activation.rs +++ b/src/dbus_activation.rs @@ -5,14 +5,15 @@ use { crate::ApplicationExt, iced::Subscription, iced_futures::futures::{ - channel::mpsc::{Receiver, Sender}, SinkExt, + channel::mpsc::{Receiver, Sender}, }, std::{any::TypeId, collections::HashMap}, url::Url, zbus::{interface, proxy, zvariant::Value}, }; +#[cold] pub fn subscription() -> Subscription> { use iced_futures::futures::StreamExt; iced_futures::Subscription::run_with_id( @@ -105,10 +106,12 @@ pub struct DbusActivation(Option>); impl DbusActivation { #[must_use] + #[inline] pub fn new() -> Self { Self(None) } + #[inline] pub fn rx(&mut self) -> Receiver { let (tx, rx) = iced_futures::futures::channel::mpsc::channel(10); self.0 = Some(tx); @@ -139,6 +142,7 @@ pub trait DbusActivationInterface { #[interface(name = "org.freedesktop.DbusActivation")] impl DbusActivation { + #[cold] async fn activate(&mut self, platform_data: HashMap<&str, Value<'_>>) { if let Some(tx) = &mut self.0 { let _ = tx @@ -159,6 +163,7 @@ impl DbusActivation { } } + #[cold] async fn open(&mut self, uris: Vec<&str>, platform_data: HashMap<&str, Value<'_>>) { if let Some(tx) = &mut self.0 { let _ = tx @@ -181,6 +186,7 @@ impl DbusActivation { } } + #[cold] async fn activate_action( &mut self, action_name: &str, diff --git a/src/desktop.rs b/src/desktop.rs index 3f5f5755..ee35887c 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -240,6 +240,7 @@ impl DesktopEntryData { } #[cfg(not(windows))] +#[cold] pub async fn spawn_desktop_exec(exec: S, env_vars: I, app_id: Option<&str>) where S: AsRef, diff --git a/src/font.rs b/src/font.rs index c390cb67..e0eb4745 100644 --- a/src/font.rs +++ b/src/font.rs @@ -6,10 +6,12 @@ pub use iced::Font; use iced_core::font::Weight; +#[inline] pub fn default() -> Font { Font::from(crate::config::interface_font()) } +#[inline] pub fn light() -> Font { Font { weight: Weight::Light, @@ -17,6 +19,7 @@ pub fn light() -> Font { } } +#[inline] pub fn semibold() -> Font { Font { weight: Weight::Semibold, @@ -24,6 +27,7 @@ pub fn semibold() -> Font { } } +#[inline] pub fn bold() -> Font { Font { weight: Weight::Bold, @@ -31,6 +35,7 @@ pub fn bold() -> Font { } } +#[inline] pub fn mono() -> Font { Font::from(crate::config::monospace_font()) } diff --git a/src/icon_theme.rs b/src/icon_theme.rs index 277d2cff..69fe5841 100644 --- a/src/icon_theme.rs +++ b/src/icon_theme.rs @@ -13,12 +13,14 @@ pub(crate) static DEFAULT: Mutex> = Mutex::new(Cow::Borrowed(C /// The fallback icon theme to search if no icon theme was specified. #[must_use] #[allow(clippy::missing_panics_doc)] +#[inline] pub fn default() -> String { DEFAULT.lock().unwrap().to_string() } /// Set the fallback icon theme to search when loading system icons. #[allow(clippy::missing_panics_doc)] +#[cold] pub fn set_default(name: impl Into>) { *DEFAULT.lock().unwrap() = name.into(); } diff --git a/src/keyboard_nav.rs b/src/keyboard_nav.rs index 65211462..961a423b 100644 --- a/src/keyboard_nav.rs +++ b/src/keyboard_nav.rs @@ -3,7 +3,7 @@ //! Subscribe to common application keyboard shortcuts. -use iced::{event, keyboard, Event, Subscription}; +use iced::{Event, Subscription, event, keyboard}; use iced_core::keyboard::key::Named; use iced_futures::event::listen_raw; @@ -16,6 +16,7 @@ pub enum Action { Search, } +#[cold] pub fn subscription() -> Subscription { listen_raw(|event, status, _| { if event::Status::Ignored != status { diff --git a/src/malloc.rs b/src/malloc.rs index a56323fe..b99a66f4 100644 --- a/src/malloc.rs +++ b/src/malloc.rs @@ -11,6 +11,7 @@ unsafe extern "C" { fn mallopt(param: c_int, value: c_int) -> c_int; } +#[inline] pub fn trim(pad: usize) { unsafe { malloc_trim(pad); @@ -18,6 +19,7 @@ pub fn trim(pad: usize) { } /// Prevents glibc from hoarding memory via memory fragmentation. +#[inline] pub fn limit_mmap_threshold(threshold: i32) { unsafe { mallopt(M_MMAP_THRESHOLD, threshold as c_int); diff --git a/src/process.rs b/src/process.rs index b1a3ae03..1ad048dc 100644 --- a/src/process.rs +++ b/src/process.rs @@ -5,7 +5,7 @@ use smol::io::AsyncReadExt; use std::io; use std::os::fd::OwnedFd; -use std::process::{exit, Command, Stdio}; +use std::process::{Command, Stdio, exit}; #[cfg(feature = "tokio")] use tokio::io::AsyncReadExt; @@ -24,6 +24,7 @@ async fn read_from_pipe(read: OwnedFd) -> Option { } /// Performs a double fork with setsid to spawn and detach a command. +#[cold] pub async fn spawn(mut command: Command) -> Option { // NOTE: Windows platform is not supported command diff --git a/src/surface/mod.rs b/src/surface/mod.rs index c08108ee..3041fa54 100644 --- a/src/surface/mod.rs +++ b/src/surface/mod.rs @@ -50,6 +50,7 @@ pub enum Action { } impl std::fmt::Debug for Action { + #[cold] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::AppSubsurface(arg0, arg1) => f diff --git a/src/theme/mod.rs b/src/theme/mod.rs index f2e42036..e0b0a42f 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -53,36 +53,41 @@ pub(crate) static THEME: Mutex = Mutex::new(Theme { }); /// Currently-defined theme. +#[inline] #[allow(clippy::missing_panics_doc)] pub fn active() -> Theme { THEME.lock().unwrap().clone() } /// Currently-defined theme type. +#[inline] #[allow(clippy::missing_panics_doc)] pub fn active_type() -> ThemeType { THEME.lock().unwrap().theme_type.clone() } /// Preferred interface spacing parameters defined by the active theme. -#[inline(always)] +#[inline] pub fn spacing() -> Spacing { active().cosmic().spacing } /// Whether the active theme has a dark preference. +#[inline] #[must_use] pub fn is_dark() -> bool { active_type().is_dark() } /// Whether the active theme is high contrast. +#[inline] #[must_use] pub fn is_high_contrast() -> bool { active_type().is_high_contrast() } /// Watches for changes to the system's theme preference. +#[cold] pub fn subscription(is_dark: bool) -> Subscription { config_subscription::<_, crate::cosmic_theme::Theme>( ( @@ -173,6 +178,7 @@ pub enum ThemeType { impl ThemeType { /// Whether the theme has a dark preference. #[must_use] + #[inline] pub fn is_dark(&self) -> bool { match self { Self::Dark | Self::HighContrastDark => true, @@ -182,6 +188,7 @@ impl ThemeType { } /// Whether the theme has a high contrast. + #[inline] #[must_use] pub fn is_high_contrast(&self) -> bool { match self { @@ -191,6 +198,7 @@ impl ThemeType { } } + #[inline] /// Prefer dark or light theme. /// If `None`, the system preference is used. pub fn prefer_dark(&mut self, new_prefer_dark: Option) { @@ -208,6 +216,7 @@ pub struct Theme { } impl Theme { + #[inline] pub fn cosmic(&self) -> &cosmic_theme::Theme { match self.theme_type { ThemeType::Dark => &COSMIC_DARK, @@ -218,6 +227,7 @@ impl Theme { } } + #[inline] pub fn dark() -> Self { Self { theme_type: ThemeType::Dark, @@ -225,6 +235,7 @@ impl Theme { } } + #[inline] pub fn light() -> Self { Self { theme_type: ThemeType::Light, @@ -232,6 +243,7 @@ impl Theme { } } + #[inline] pub fn dark_hc() -> Self { Self { theme_type: ThemeType::HighContrastDark, @@ -239,6 +251,7 @@ impl Theme { } } + #[inline] pub fn light_hc() -> Self { Self { theme_type: ThemeType::HighContrastLight, @@ -246,6 +259,7 @@ impl Theme { } } + #[inline] pub fn custom(theme: Arc) -> Self { Self { theme_type: ThemeType::Custom(theme), @@ -253,6 +267,7 @@ impl Theme { } } + #[inline] pub fn system(theme: Arc) -> Self { Self { theme_type: ThemeType::System { @@ -263,6 +278,7 @@ impl Theme { } } + #[inline] /// get current container /// can be used in a component that is intended to be a child of a `CosmicContainer` pub fn current_container(&self) -> &cosmic_theme::Container { @@ -273,6 +289,7 @@ impl Theme { } } + #[inline] /// set the theme pub fn set_theme(&mut self, theme: ThemeType) { self.theme_type = theme; @@ -280,6 +297,7 @@ impl Theme { } impl LayeredTheme for Theme { + #[inline] fn set_layer(&mut self, layer: cosmic_theme::Layer) { self.layer = layer; } diff --git a/src/theme/portal.rs b/src/theme/portal.rs index e3dc7511..f0c88c01 100644 --- a/src/theme/portal.rs +++ b/src/theme/portal.rs @@ -1,6 +1,6 @@ -use ashpd::desktop::settings::{ColorScheme, Contrast}; use ashpd::desktop::Color; -use iced::futures::{self, select, FutureExt, SinkExt, StreamExt}; +use ashpd::desktop::settings::{ColorScheme, Contrast}; +use iced::futures::{self, FutureExt, SinkExt, StreamExt, select}; use iced_futures::stream; use tracing::error; @@ -11,6 +11,7 @@ pub enum Desktop { Contrast(Contrast), } +#[cold] pub fn desktop_settings() -> iced_futures::Subscription { iced_futures::Subscription::run_with_id( std::any::TypeId::of::(), diff --git a/src/widget/aspect_ratio.rs b/src/widget/aspect_ratio.rs index 39ac9c57..e66c14d0 100644 --- a/src/widget/aspect_ratio.rs +++ b/src/widget/aspect_ratio.rs @@ -1,7 +1,7 @@ //! A container which constraints itself to a specific aspect ratio. -use iced::widget::Container; use iced::Size; +use iced::widget::Container; use iced_core::event::{self, Event}; use iced_core::layout; use iced_core::mouse; @@ -76,6 +76,7 @@ where /// Sets the width of the [`self.`]. #[must_use] + #[inline] pub fn width(mut self, width: Length) -> Self { self.container = self.container.width(width); self @@ -83,6 +84,7 @@ where /// Sets the height of the [`Container`]. #[must_use] + #[inline] pub fn height(mut self, height: Length) -> Self { self.container = self.container.height(height); self @@ -90,6 +92,7 @@ where /// Sets the maximum width of the [`Container`]. #[must_use] + #[inline] pub fn max_width(mut self, max_width: f32) -> Self { self.container = self.container.max_width(max_width); self @@ -97,6 +100,7 @@ where /// Sets the maximum height of the [`Container`] in pixels. #[must_use] + #[inline] pub fn max_height(mut self, max_height: f32) -> Self { self.container = self.container.max_height(max_height); self @@ -104,6 +108,7 @@ where /// Sets the content alignment for the horizontal axis of the [`Container`]. #[must_use] + #[inline] pub fn align_x(mut self, alignment: Alignment) -> Self { self.container = self.container.align_x(alignment); self @@ -111,6 +116,7 @@ where /// Sets the content alignment for the vertical axis of the [`Container`]. #[must_use] + #[inline] pub fn align_y(mut self, alignment: Alignment) -> Self { self.container = self.container.align_y(alignment); self @@ -118,6 +124,7 @@ where /// Centers the contents in the horizontal axis of the [`Container`]. #[must_use] + #[inline] pub fn center_x(mut self, width: Length) -> Self { self.container = self.container.center_x(width); self @@ -125,6 +132,7 @@ where /// Centers the contents in the vertical axis of the [`Container`]. #[must_use] + #[inline] pub fn center_y(mut self, height: Length) -> Self { self.container = self.container.center_y(height); self @@ -132,6 +140,7 @@ where /// Centers the contents in the horizontal and vertical axis of the [`Container`]. #[must_use] + #[inline] pub fn center(mut self, length: Length) -> Self { self.container = self.container.center(length); self diff --git a/src/widget/autosize.rs b/src/widget/autosize.rs index 3f608351..172d505f 100644 --- a/src/widget/autosize.rs +++ b/src/widget/autosize.rs @@ -54,36 +54,43 @@ where } } + #[inline] pub fn limits(mut self, limits: layout::Limits) -> Self { self.limits = limits; self } + #[inline] pub fn auto_width(mut self, auto_width: bool) -> Self { self.auto_width = auto_width; self } + #[inline] pub fn auto_height(mut self, auto_height: bool) -> Self { self.auto_height = auto_height; self } + #[inline] pub fn max_width(mut self, v: f32) -> Self { self.limits = self.limits.max_width(v); self } + #[inline] pub fn max_height(mut self, v: f32) -> Self { self.limits = self.limits.max_height(v); self } + #[inline] pub fn min_width(mut self, v: f32) -> Self { self.limits = self.limits.min_width(v); self } + #[inline] pub fn min_height(mut self, v: f32) -> Self { self.limits = self.limits.min_height(v); self diff --git a/src/widget/button/icon.rs b/src/widget/button/icon.rs index 3b46d9d1..0bb3c84d 100644 --- a/src/widget/button/icon.rs +++ b/src/widget/button/icon.rs @@ -2,13 +2,13 @@ // SPDX-License-Identifier: MPL-2.0 use super::{Builder, ButtonClass}; +use crate::Element; use crate::widget::{ icon::{self, Handle}, tooltip, }; -use crate::Element; use apply::Apply; -use iced_core::{font::Weight, text::LineHeight, widget::Id, Alignment, Length, Padding}; +use iced_core::{Alignment, Length, Padding, font::Weight, text::LineHeight, widget::Id}; use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Icon>; @@ -114,11 +114,13 @@ impl Button<'_, Message> { self } + #[inline] pub fn selected(mut self, selected: bool) -> Self { self.variant.selected = selected; self } + #[inline] pub fn vertical(mut self, vertical: bool) -> Self { self.variant.vertical = vertical; self.class = ButtonClass::IconVertical; diff --git a/src/widget/button/image.rs b/src/widget/button/image.rs index 74e3b378..6a5c47b1 100644 --- a/src/widget/button/image.rs +++ b/src/widget/button/image.rs @@ -3,10 +3,10 @@ use super::Builder; use crate::{ - widget::{self, image::Handle}, Element, + widget::{self, image::Handle}, }; -use iced_core::{font::Weight, widget::Id, Length, Padding}; +use iced_core::{Length, Padding, font::Weight, widget::Id}; use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Image<'a, Handle, Message>>; @@ -28,6 +28,7 @@ pub struct Image<'a, Handle, Message> { } impl<'a, Message> Button<'a, Message> { + #[inline] pub fn new(variant: Image<'a, Handle, Message>) -> Self { Self { id: Id::unique(), @@ -47,16 +48,19 @@ impl<'a, Message> Button<'a, Message> { } } + #[inline] pub fn on_remove(mut self, message: Message) -> Self { self.variant.on_remove = Some(message); self } + #[inline] pub fn on_remove_maybe(mut self, message: Option) -> Self { self.variant.on_remove = message; self } + #[inline] pub fn selected(mut self, selected: bool) -> Self { self.variant.selected = selected; self diff --git a/src/widget/button/link.rs b/src/widget/button/link.rs index 77527ecd..dcfc6a36 100644 --- a/src/widget/button/link.rs +++ b/src/widget/button/link.rs @@ -55,6 +55,7 @@ impl<'a, Message> Button<'a, Message> { } } +#[inline(never)] pub fn icon() -> Handle { icon::from_svg_bytes(&include_bytes!("external-link.svg")[..]).symbolic(true) } diff --git a/src/widget/button/mod.rs b/src/widget/button/mod.rs index 9928628b..b654b274 100644 --- a/src/widget/button/mod.rs +++ b/src/widget/button/mod.rs @@ -45,7 +45,7 @@ use std::borrow::Cow; /// A button with a custom element for its content. pub fn custom<'a, Message>(content: impl Into>) -> Button<'a, Message> { - Button::new(content) + Button::new(content.into()) } /// An image button which may contain any widget as its content. @@ -53,7 +53,7 @@ pub fn custom_image_button<'a, Message>( content: impl Into>, on_remove: Option, ) -> Button<'a, Message> { - Button::new_image(content, on_remove) + Button::new_image(content.into(), on_remove) } /// A builder for constructing a custom [`Button`]. diff --git a/src/widget/button/text.rs b/src/widget/button/text.rs index 1b682393..da5f94f1 100644 --- a/src/widget/button/text.rs +++ b/src/widget/button/text.rs @@ -3,8 +3,7 @@ use super::{Builder, ButtonClass}; use crate::widget::{icon, row, tooltip}; -use crate::{ext::CollectionWidget, Element}; -use apply::Apply; +use crate::{Apply, Element}; use iced_core::{font::Weight, text::LineHeight, widget::Id, Alignment, Length, Padding}; use std::borrow::Cow; diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 3b6d5577..3a1df241 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -7,19 +7,19 @@ //! A [`Button`] has some local [`State`]. use iced_runtime::core::widget::Id; -use iced_runtime::{keyboard, task, Action, Task}; +use iced_runtime::{Action, Task, keyboard, task}; use iced_core::event::{self, Event}; use iced_core::renderer::{self, Quad, Renderer}; use iced_core::touch; -use iced_core::widget::tree::{self, Tree}; use iced_core::widget::Operation; -use iced_core::{layout, svg}; -use iced_core::{mouse, Border}; -use iced_core::{overlay, Shadow}; +use iced_core::widget::tree::{self, Tree}; use iced_core::{ Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget, }; +use iced_core::{Border, mouse}; +use iced_core::{Shadow, overlay}; +use iced_core::{layout, svg}; use iced_renderer::core::widget::operation; use crate::theme::THEME; @@ -118,24 +118,28 @@ impl<'a, Message> Button<'a, Message> { } /// Sets the [`Id`] of the [`Button`]. + #[inline] pub fn id(mut self, id: Id) -> Self { self.id = id; self } /// Sets the width of the [`Button`]. + #[inline] pub fn width(mut self, width: impl Into) -> Self { self.width = width.into(); self } /// Sets the height of the [`Button`]. + #[inline] pub fn height(mut self, height: impl Into) -> Self { self.height = height.into(); self } /// Sets the [`Padding`] of the [`Button`]. + #[inline] pub fn padding>(mut self, padding: P) -> Self { self.padding = padding.into(); self @@ -144,6 +148,7 @@ impl<'a, Message> Button<'a, Message> { /// Sets the message that will be produced when the [`Button`] is pressed and released. /// /// Unless `on_press` or `on_press_down` is called, the [`Button`] will be disabled. + #[inline] pub fn on_press(mut self, on_press: Message) -> Self { self.on_press = Some(on_press); self @@ -152,6 +157,7 @@ impl<'a, Message> Button<'a, Message> { /// Sets the message that will be produced when the [`Button`] is pressed, /// /// Unless `on_press` or `on_press_down` is called, the [`Button`] will be disabled. + #[inline] pub fn on_press_down(mut self, on_press: Message) -> Self { self.on_press_down = Some(on_press); self @@ -161,12 +167,14 @@ impl<'a, Message> Button<'a, Message> { /// if `Some`. /// /// If `None`, the [`Button`] will be disabled. + #[inline] pub fn on_press_maybe(mut self, on_press: Option) -> Self { self.on_press = on_press; self } /// Sets the the [`Button`] to enabled whether or not it has handlers for on press. + #[inline] pub fn force_enabled(mut self, enabled: bool) -> Self { self.force_enabled = enabled; self @@ -175,6 +183,7 @@ impl<'a, Message> Button<'a, Message> { /// Sets the widget to a selected state. /// /// Displays a selection indicator on image buttons. + #[inline] pub fn selected(mut self, selected: bool) -> Self { self.selected = selected; @@ -182,6 +191,7 @@ impl<'a, Message> Button<'a, Message> { } /// Sets the style variant of this [`Button`]. + #[inline] pub fn class(mut self, style: crate::theme::Button) -> Self { self.style = style; self @@ -579,8 +589,8 @@ impl<'a, Message: 'a + Clone> Widget p: mouse::Cursor, ) -> iced_accessibility::A11yTree { use iced_accessibility::{ - accesskit::{Action, DefaultActionVerb, NodeBuilder, NodeId, Rect, Role}, A11yNode, A11yTree, + accesskit::{Action, DefaultActionVerb, NodeBuilder, NodeId, Rect, Role}, }; // TODO why is state None sometimes? if matches!(state.state, iced_core::widget::tree::State::None) { @@ -668,26 +678,31 @@ pub struct State { impl State { /// Creates a new [`State`]. + #[inline] pub fn new() -> Self { Self::default() } /// Returns whether the [`Button`] is currently focused or not. + #[inline] pub fn is_focused(self) -> bool { self.is_focused } /// Returns whether the [`Button`] is currently hovered or not. + #[inline] pub fn is_hovered(self) -> bool { self.is_hovered } /// Focuses the [`Button`]. + #[inline] pub fn focus(&mut self) { self.is_focused = true; } /// Unfocuses the [`Button`]. + #[inline] pub fn unfocus(&mut self) { self.is_focused = false; } @@ -951,14 +966,17 @@ pub fn focus(id: Id) -> Task { } impl operation::Focusable for State { + #[inline] fn is_focused(&self) -> bool { Self::is_focused(*self) } + #[inline] fn focus(&mut self) { Self::focus(self); } + #[inline] fn unfocus(&mut self) { Self::unfocus(self); } diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index ea96360e..046a95df 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -6,7 +6,7 @@ use std::cmp; use crate::iced_core::{Alignment, Length, Padding}; -use crate::widget::{button, column, grid, icon, row, text, Grid}; +use crate::widget::{Grid, button, column, grid, icon, row, text}; use chrono::{Datelike, Days, Local, Months, NaiveDate, Weekday}; /// A widget that displays an interactive calendar. @@ -58,6 +58,7 @@ impl CalendarModel { } } + #[inline] pub fn new(selected: NaiveDate, visible: NaiveDate) -> Self { CalendarModel { selected, visible } } @@ -80,16 +81,19 @@ impl CalendarModel { self.visible = next_month_date; } + #[inline] pub fn set_prev_month(&mut self) { self.show_prev_month(); self.selected = self.visible; } + #[inline] pub fn set_next_month(&mut self) { self.show_next_month(); self.selected = self.visible; } + #[inline] pub fn set_selected_visible(&mut self, selected: NaiveDate) { self.selected = selected; self.visible = self.selected; @@ -225,6 +229,7 @@ fn padded_control<'a, Message>( .width(Length::Fill) } +#[inline] fn menu_control_padding() -> Padding { let guard = crate::theme::THEME.lock().unwrap(); let cosmic = guard.cosmic(); diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index c59ae407..f36578ee 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -3,17 +3,17 @@ use std::borrow::Cow; -use crate::widget::{button, column, container, icon, row, scrollable, text, LayerContainer}; +use crate::widget::{LayerContainer, button, column, container, icon, row, scrollable, text}; use crate::{Apply, Element, Renderer, Theme}; use super::overlay::Overlay; +use iced_core::Alignment; use iced_core::event::{self, Event}; use iced_core::widget::{Operation, Tree}; -use iced_core::Alignment; use iced_core::{ - layout, mouse, overlay as iced_overlay, renderer, Clipboard, Layout, Length, Rectangle, Shell, - Vector, Widget, + Clipboard, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, + overlay as iced_overlay, renderer, }; #[must_use] @@ -37,76 +37,97 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { where Drawer: Into>, { - let cosmic_theme::Spacing { - space_xxs, - space_s, - space_m, - space_l, - .. - } = crate::theme::active().cosmic().spacing; + #[inline(never)] + fn inner<'a, Message: Clone + 'static>( + title: Option>, + header_actions: Vec>, + header_opt: Option>, + footer_opt: Option>, + drawer: Element<'a, Message>, + on_close: Message, + max_width: f32, + ) -> Element<'a, Message> { + let cosmic_theme::Spacing { + space_xxs, + space_s, + space_m, + space_l, + .. + } = crate::theme::active().cosmic().spacing; - let horizontal_padding = if max_width < 392.0 { space_s } else { space_l }; + let horizontal_padding = if max_width < 392.0 { space_s } else { space_l }; - let header_row = row::with_capacity(3) - .width(Length::Fixed(480.0)) - .align_y(Alignment::Center) - .push( - row::with_children(header_actions) - .spacing(space_xxs) - .width(Length::FillPortion(1)), - ) - .push_maybe( - title.map(|title| text::heading(title).width(Length::FillPortion(1)).center()), - ) - .push( - button::text("Close") - .trailing_icon(icon::from_name("go-next-symbolic")) - .on_press(on_close) - .apply(container) - .width(Length::FillPortion(1)) - .align_x(Alignment::End), - ); - let header = column::with_capacity(2) - .width(Length::Fixed(480.0)) - .align_x(Alignment::Center) - .spacing(space_m) - .padding([space_m, horizontal_padding]) - .push(header_row) - .push_maybe(header_opt); - let footer = footer_opt.map(|element| { - container(element) + let header_row = row::with_capacity(3) .width(Length::Fixed(480.0)) .align_y(Alignment::Center) - .padding([space_xxs, horizontal_padding]) - }); - let pane = column::with_capacity(3) - .push(header) - .push( - scrollable(container(drawer.into()).padding([ - 0, - horizontal_padding, - if footer.is_some() { 0 } else { space_l }, - horizontal_padding, - ])) - .height(Length::Fill) - .width(Length::Shrink), - ) - .push_maybe(footer); + .push( + row::with_children(header_actions) + .spacing(space_xxs) + .width(Length::FillPortion(1)), + ) + .push_maybe( + title.map(|title| text::heading(title).width(Length::FillPortion(1)).center()), + ) + .push( + button::text("Close") + .trailing_icon(icon::from_name("go-next-symbolic")) + .on_press(on_close) + .apply(container) + .width(Length::FillPortion(1)) + .align_x(Alignment::End), + ); + let header = column::with_capacity(2) + .width(Length::Fixed(480.0)) + .align_x(Alignment::Center) + .spacing(space_m) + .padding([space_m, horizontal_padding]) + .push(header_row) + .push_maybe(header_opt); + let footer = footer_opt.map(|element| { + container(element) + .width(Length::Fixed(480.0)) + .align_y(Alignment::Center) + .padding([space_xxs, horizontal_padding]) + }); + let pane = column::with_capacity(3) + .push(header) + .push( + scrollable(container(drawer).padding([ + 0, + horizontal_padding, + if footer.is_some() { 0 } else { space_l }, + horizontal_padding, + ])) + .height(Length::Fill) + .width(Length::Shrink), + ) + .push_maybe(footer); - // XXX new limits do not exactly handle the max width well for containers - // XXX this is a hack to get around that - container( - LayerContainer::new(pane) - .layer(cosmic_theme::Layer::Primary) - .class(crate::style::Container::ContextDrawer) - .width(Length::Fill) - .height(Length::Fill) - .max_width(max_width), + // XXX new limits do not exactly handle the max width well for containers + // XXX this is a hack to get around that + container( + LayerContainer::new(pane) + .layer(cosmic_theme::Layer::Primary) + .class(crate::style::Container::ContextDrawer) + .width(Length::Fill) + .height(Length::Fill) + .max_width(max_width), + ) + .width(Length::Fill) + .height(Length::Fill) + .align_x(Alignment::End) + .into() + } + + inner( + title, + header_actions, + header_opt, + footer_opt, + drawer.into(), + on_close, + max_width, ) - .width(Length::Fill) - .height(Length::Fill) - .align_x(Alignment::End) - .into() } /// Creates an empty [`ContextDrawer`]. @@ -149,6 +170,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { } // Optionally assigns message to `on_close` event. + #[inline] pub fn on_close_maybe(mut self, message: Option) -> Self { self.on_close = message; self diff --git a/src/widget/flex_row/widget.rs b/src/widget/flex_row/widget.rs index e343757a..264201c1 100644 --- a/src/widget/flex_row/widget.rs +++ b/src/widget/flex_row/widget.rs @@ -6,8 +6,8 @@ use derive_setters::Setters; use iced_core::event::{self, Event}; use iced_core::widget::{Operation, Tree}; use iced_core::{ - layout, mouse, overlay, renderer, Clipboard, Layout, Length, Padding, Rectangle, Shell, Vector, - Widget, + Clipboard, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, layout, mouse, overlay, + renderer, }; /// Responsively generates rows and columns of widgets based on its dimensions. @@ -78,6 +78,7 @@ impl<'a, Message> FlexRow<'a, Message> { } /// Sets the space between each column and row. + #[inline] pub const fn spacing(mut self, spacing: u16) -> Self { self.column_spacing = spacing; self.row_spacing = spacing; diff --git a/src/widget/frames.rs b/src/widget/frames.rs index 7b6783cb..1c379ac1 100644 --- a/src/widget/frames.rs +++ b/src/widget/frames.rs @@ -10,17 +10,17 @@ use std::time::{Duration, Instant}; use ::image as image_rs; use iced_core::image::Renderer as ImageRenderer; use iced_core::mouse::Cursor; -use iced_core::widget::{tree, Tree}; +use iced_core::widget::{Tree, tree}; use iced_core::{ - event, layout, renderer, window, Clipboard, ContentFit, Element, Event, Layout, Length, - Rectangle, Shell, Size, Vector, Widget, + Clipboard, ContentFit, Element, Event, Layout, Length, Rectangle, Shell, Size, Vector, Widget, + event, layout, renderer, window, }; use iced_runtime::Command; use iced_widget::image::{self, Handle}; +use image_rs::AnimationDecoder; use image_rs::codecs::gif::GifDecoder; use image_rs::codecs::png::PngDecoder; use image_rs::codecs::webp::WebPDecoder; -use image_rs::AnimationDecoder; #[cfg(not(feature = "tokio"))] use iced_futures::futures::{AsyncRead, AsyncReadExt}; @@ -61,6 +61,7 @@ pub struct Frames { } impl fmt::Debug for Frames { + #[cold] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Frames").finish() } @@ -95,31 +96,36 @@ impl Frames { /// Load [`Frames`] from the supplied path pub fn load_from_path(path: impl AsRef) -> Command> { - #[cfg(feature = "tokio")] - use tokio::fs::File; - #[cfg(feature = "tokio")] - use tokio::io::BufReader; + #[inline(never)] + fn inner(path: &Path) -> Command> { + #[cfg(feature = "tokio")] + use tokio::fs::File; + #[cfg(feature = "tokio")] + use tokio::io::BufReader; - #[cfg(not(feature = "tokio"))] - use async_fs::File; - #[cfg(not(feature = "tokio"))] - use iced_futures::futures::io::BufReader; + #[cfg(not(feature = "tokio"))] + use async_fs::File; + #[cfg(not(feature = "tokio"))] + use iced_futures::futures::io::BufReader; - let path = path.as_ref().to_path_buf(); + let path = path.as_ref().to_path_buf(); - let f = async move { - let image_type = match &path.extension() { - Some(ext) if ext == &OsStr::new("gif") => ImageType::Gif, - Some(ext) if ext == &OsStr::new("apng") => ImageType::Apng, - Some(ext) if ext == &OsStr::new("webp") => ImageType::WebP, - _ => return Err(Error::Extension), + let f = async move { + let image_type = match &path.extension() { + Some(ext) if ext == &OsStr::new("gif") => ImageType::Gif, + Some(ext) if ext == &OsStr::new("apng") => ImageType::Apng, + Some(ext) if ext == &OsStr::new("webp") => ImageType::WebP, + _ => return Err(Error::Extension), + }; + let reader = BufReader::new(File::open(path).await?); + + Self::from_reader(reader, image_type).await }; - let reader = BufReader::new(File::open(path).await?); - Self::from_reader(reader, image_type).await - }; + Command::perform(f, std::convert::identity) + } - Command::perform(f, std::convert::identity) + inner(path.as_ref()) } /// Decode [`Frames`] from the supplied async reader diff --git a/src/widget/grid/widget.rs b/src/widget/grid/widget.rs index f09b3739..8e9efc78 100644 --- a/src/widget/grid/widget.rs +++ b/src/widget/grid/widget.rs @@ -105,6 +105,7 @@ impl<'a, Message> Grid<'a, Message> { self } + #[inline] pub fn insert_row(mut self) -> Self { self.row += 1; self.column = 1; diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 32bfa278..638bff5d 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -2,11 +2,11 @@ // SPDX-License-Identifier: MPL-2.0 use crate::cosmic_theme::{Density, Spacing}; -use crate::{theme, widget, Element}; +use crate::{Element, theme, widget}; use apply::Apply; use derive_setters::Setters; use iced::Length; -use iced_core::{widget::tree, Vector, Widget}; +use iced_core::{Vector, Widget, widget::tree}; use std::borrow::Cow; #[must_use] @@ -109,6 +109,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { /// Build the widget #[must_use] + #[inline] pub fn build(self) -> HeaderBarWidget<'a, Message> { HeaderBarWidget { header_bar_inner: self.view(), diff --git a/src/widget/icon/handle.rs b/src/widget/icon/handle.rs index 3a2db2c9..2d30427b 100644 --- a/src/widget/icon/handle.rs +++ b/src/widget/icon/handle.rs @@ -17,6 +17,7 @@ pub struct Handle { } impl Handle { + #[inline] pub fn icon(self) -> Icon { super::icon(self) } @@ -53,13 +54,17 @@ pub fn from_raster_bytes( + std::marker::Sync + 'static, ) -> Handle { - Handle { - symbolic: false, - data: match bytes.into() { - Cow::Owned(b) => Data::Image(image::Handle::from_bytes(b)), - Cow::Borrowed(b) => Data::Image(image::Handle::from_bytes(b)), - }, + fn inner(bytes: Cow<'static, [u8]>) -> Handle { + Handle { + symbolic: false, + data: match bytes { + Cow::Owned(b) => Data::Image(image::Handle::from_bytes(b)), + Cow::Borrowed(b) => Data::Image(image::Handle::from_bytes(b)), + }, + } } + + inner(bytes.into()) } /// Create an image handle from RGBA data, where you must define the width and height. @@ -71,13 +76,19 @@ pub fn from_raster_pixels( + std::marker::Send + std::marker::Sync, ) -> Handle { - Handle { - symbolic: false, - data: match pixels.into() { - Cow::Owned(pixels) => Data::Image(image::Handle::from_rgba(width, height, pixels)), - Cow::Borrowed(pixels) => Data::Image(image::Handle::from_rgba(width, height, pixels)), - }, + fn inner(width: u32, height: u32, pixels: Cow<'static, [u8]>) -> Handle { + Handle { + symbolic: false, + data: match pixels { + Cow::Owned(pixels) => Data::Image(image::Handle::from_rgba(width, height, pixels)), + Cow::Borrowed(pixels) => { + Data::Image(image::Handle::from_rgba(width, height, pixels)) + } + }, + } } + + inner(width, height, pixels.into()) } /// Create a SVG handle from memory. diff --git a/src/widget/icon/named.rs b/src/widget/icon/named.rs index 055b2e42..da5c4677 100644 --- a/src/widget/icon/named.rs +++ b/src/widget/icon/named.rs @@ -113,6 +113,7 @@ impl Named { None } + #[inline] pub fn handle(self) -> Handle { Handle { symbolic: self.symbolic, @@ -120,6 +121,7 @@ impl Named { } } + #[inline] pub fn icon(self) -> Icon { let size = self.size; @@ -133,18 +135,21 @@ impl Named { } impl From for Handle { + #[inline] fn from(builder: Named) -> Self { builder.handle() } } impl From for Icon { + #[inline] fn from(builder: Named) -> Self { builder.icon() } } impl From for crate::Element<'_, Message> { + #[inline] fn from(builder: Named) -> Self { builder.icon().into() } diff --git a/src/widget/layer_container.rs b/src/widget/layer_container.rs index f442bc51..74521b3d 100644 --- a/src/widget/layer_container.rs +++ b/src/widget/layer_container.rs @@ -69,6 +69,7 @@ where /// Sets the width of the [`self.`]. #[must_use] + #[inline] pub fn width(mut self, width: Length) -> Self { self.container = self.container.width(width); self @@ -76,6 +77,7 @@ where /// Sets the height of the [`LayerContainer`]. #[must_use] + #[inline] pub fn height(mut self, height: Length) -> Self { self.container = self.container.height(height); self @@ -83,6 +85,7 @@ where /// Sets the maximum width of the [`LayerContainer`]. #[must_use] + #[inline] pub fn max_width(mut self, max_width: f32) -> Self { self.container = self.container.max_width(max_width); self @@ -90,6 +93,7 @@ where /// Sets the maximum height of the [`LayerContainer`] in pixels. #[must_use] + #[inline] pub fn max_height(mut self, max_height: f32) -> Self { self.container = self.container.max_height(max_height); self @@ -97,6 +101,7 @@ where /// Sets the content alignment for the horizontal axis of the [`LayerContainer`]. #[must_use] + #[inline] pub fn align_x(mut self, alignment: Alignment) -> Self { self.container = self.container.align_x(alignment); self @@ -104,6 +109,7 @@ where /// Sets the content alignment for the vertical axis of the [`LayerContainer`]. #[must_use] + #[inline] pub fn align_y(mut self, alignment: Alignment) -> Self { self.container = self.container.align_y(alignment); self @@ -111,6 +117,7 @@ where /// Centers the contents in the horizontal axis of the [`LayerContainer`]. #[must_use] + #[inline] pub fn center_x(mut self, width: Length) -> Self { self.container = self.container.center_x(width); self @@ -118,6 +125,7 @@ where /// Centers the contents in the vertical axis of the [`LayerContainer`]. #[must_use] + #[inline] pub fn center_y(mut self, height: Length) -> Self { self.container = self.container.center_y(height); self @@ -125,6 +133,7 @@ where /// Centers the contents in the horizontal and vertical axis of the [`Container`]. #[must_use] + #[inline] pub fn center(mut self, length: Length) -> Self { self.container = self.container.center(length); self diff --git a/src/widget/list/column.rs b/src/widget/list/column.rs index 1a9b7348..d79d5206 100644 --- a/src/widget/list/column.rs +++ b/src/widget/list/column.rs @@ -5,11 +5,11 @@ use iced_core::Padding; use iced_widget::container::Catalog; use crate::{ - theme, + Apply, Element, theme, widget::{container, divider, vertical_space}, - Apply, Element, }; +#[inline] pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> { ListColumn::default() } @@ -42,48 +42,61 @@ impl Default for ListColumn<'_, Message> { } impl<'a, Message: 'static> ListColumn<'a, Message> { + #[inline] pub fn new() -> Self { Self::default() } #[allow(clippy::should_implement_trait)] - pub fn add(mut self, item: impl Into>) -> Self { - if !self.children.is_empty() { - self.children.push( - container(divider::horizontal::default()) - .padding([0, self.divider_padding]) - .into(), - ); + pub fn add(self, item: impl Into>) -> Self { + #[inline(never)] + fn inner<'a, Message: 'static>( + mut this: ListColumn<'a, Message>, + item: Element<'a, Message>, + ) -> ListColumn<'a, Message> { + if !this.children.is_empty() { + this.children.push( + container(divider::horizontal::default()) + .padding([0, this.divider_padding]) + .into(), + ); + } + + // Ensure a minimum height of 32. + let list_item = iced::widget::row![ + container(item).align_y(iced::Alignment::Center), + vertical_space().height(iced::Length::Fixed(32.)) + ] + .padding(this.list_item_padding) + .align_y(iced::Alignment::Center); + + this.children.push(list_item.into()); + this } - // Ensure a minimum height of 32. - let list_item = iced::widget::row![ - container(item).align_y(iced::Alignment::Center), - vertical_space().height(iced::Length::Fixed(32.)) - ] - .padding(self.list_item_padding) - .align_y(iced::Alignment::Center); - - self.children.push(list_item.into()); - self + inner(self, item.into()) } + #[inline] pub fn spacing(mut self, spacing: u16) -> Self { self.spacing = spacing; self } /// Sets the style variant of this [`Circular`]. + #[inline] pub fn style(mut self, style: ::Class<'a>) -> Self { self.style = style; self } + #[inline] pub fn padding(mut self, padding: impl Into) -> Self { self.padding = padding.into(); self } + #[inline] pub fn divider_padding(mut self, padding: u16) -> Self { self.divider_padding = padding; self diff --git a/src/widget/nav_bar.rs b/src/widget/nav_bar.rs index 5a32b4a3..fef3cbe4 100644 --- a/src/widget/nav_bar.rs +++ b/src/widget/nav_bar.rs @@ -7,13 +7,13 @@ use apply::Apply; use iced::{ - clipboard::{dnd::DndAction, mime::AllowedMimeTypes}, Background, Length, + clipboard::{dnd::DndAction, mime::AllowedMimeTypes}, }; use iced_core::{Border, Color, Shadow}; -use crate::widget::{container, menu, scrollable, segmented_button, Container, Icon}; -use crate::{theme, Theme}; +use crate::widget::{Container, Icon, container, menu, scrollable, segmented_button}; +use crate::{Theme, theme}; use super::dnd_destination::DragId; @@ -62,16 +62,19 @@ pub struct NavBar<'a, Message> { } impl<'a, Message: Clone + 'static> NavBar<'a, Message> { + #[inline] pub fn close_icon(mut self, close_icon: Icon) -> Self { self.segmented_button = self.segmented_button.close_icon(close_icon); self } + #[inline] pub fn context_menu(mut self, context_menu: Option>>) -> Self { self.segmented_button = self.segmented_button.context_menu(context_menu); self } + #[inline] pub fn drag_id(mut self, id: DragId) -> Self { self.segmented_button = self.segmented_button.drag_id(id); self @@ -79,6 +82,7 @@ impl<'a, Message: Clone + 'static> NavBar<'a, Message> { /// Pre-convert this widget into the [`Container`] widget that it becomes. #[must_use] + #[inline] pub fn into_container(self) -> Container<'a, Message, crate::Theme, crate::Renderer> { Container::from(self) } diff --git a/src/widget/nav_bar_toggle.rs b/src/widget/nav_bar_toggle.rs index 2a315683..dd4b788c 100644 --- a/src/widget/nav_bar_toggle.rs +++ b/src/widget/nav_bar_toggle.rs @@ -16,7 +16,7 @@ pub struct NavBarToggle { } #[must_use] -pub fn nav_bar_toggle() -> NavBarToggle { +pub const fn nav_bar_toggle() -> NavBarToggle { NavBarToggle { active: false, on_toggle: None, diff --git a/src/widget/rectangle_tracker/subscription.rs b/src/widget/rectangle_tracker/subscription.rs index e49946d5..541862cd 100644 --- a/src/widget/rectangle_tracker/subscription.rs +++ b/src/widget/rectangle_tracker/subscription.rs @@ -1,15 +1,17 @@ use iced::{ - futures::{ - channel::mpsc::{unbounded, UnboundedReceiver}, - stream, StreamExt, - }, Rectangle, + futures::{ + StreamExt, + channel::mpsc::{UnboundedReceiver, unbounded}, + stream, + }, }; use iced_futures::Subscription; use std::{collections::HashMap, fmt::Debug, hash::Hash}; use super::RectangleTracker; +#[cold] pub fn rectangle_tracker_subscription< I: 'static + Hash + Copy + Send + Sync + Debug, R: 'static + Hash + Copy + Send + Sync + Debug + Eq, diff --git a/src/widget/segmented_button/model/builder.rs b/src/widget/segmented_button/model/builder.rs index c7e3239e..d8070aa4 100644 --- a/src/widget/segmented_button/model/builder.rs +++ b/src/widget/segmented_button/model/builder.rs @@ -32,6 +32,7 @@ where } /// Consumes the builder and returns the model. + #[inline] pub fn build(self) -> Model { self.0 } @@ -43,6 +44,7 @@ where { /// Activates the newly-inserted item. #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + #[inline] pub fn activate(mut self) -> Self { self.model.0.activate(self.id); self @@ -50,6 +52,7 @@ where /// Defines that the close button should appear #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + #[inline] pub fn closable(mut self) -> Self { self.model.0.closable_set(self.id, true); self @@ -60,6 +63,7 @@ where /// The secondary map internally uses a `Vec`, so should only be used for data that /// is commonly associated. #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + #[inline] pub fn secondary(self, map: &mut SecondaryMap, data: Data) -> Self { map.insert(self.id, data); self @@ -69,6 +73,7 @@ where /// /// Sparse maps internally use a `HashMap`, for data that is sparsely associated. #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + #[inline] pub fn secondary_sparse( self, map: &mut SparseSecondaryMap, @@ -90,11 +95,13 @@ where /// .build() /// ``` #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + #[inline] pub fn data(mut self, data: Data) -> Self { self.model.0.data_set(self.id, data); self } + #[inline] pub fn divider_above(mut self) -> Self { self.model.0.divider_above_set(self.id, true); self @@ -115,6 +122,7 @@ where /// Define the position of the newly-inserted item. #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + #[inline] pub fn position(mut self, position: u16) -> Self { self.model.0.position_set(self.id, position); self @@ -122,6 +130,7 @@ where /// Swap the position with another item in the model. #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + #[inline] pub fn position_swap(mut self, other: Entity) -> Self { self.model.0.position_swap(self.id, other); self diff --git a/src/widget/segmented_button/model/entity.rs b/src/widget/segmented_button/model/entity.rs index 02a7d371..a3821244 100644 --- a/src/widget/segmented_button/model/entity.rs +++ b/src/widget/segmented_button/model/entity.rs @@ -25,6 +25,7 @@ where /// model.insert().text("Item A").activate(); /// ``` #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + #[inline] pub fn activate(self) -> Self { self.model.activate(self.id); self @@ -40,6 +41,7 @@ where /// model.insert().text("Item A").secondary(&mut secondary_data, String::new("custom data")); /// ``` #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + #[inline] pub fn secondary(self, map: &mut SecondaryMap, data: Data) -> Self { map.insert(self.id, data); self @@ -54,6 +56,7 @@ where /// model.insert().text("Item A").secondary(&mut secondary_data, String::new("custom data")); /// ``` #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + #[inline] pub fn secondary_sparse( self, map: &mut SparseSecondaryMap, @@ -65,6 +68,7 @@ where /// Shows a close button for this item. #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + #[inline] pub fn closable(self) -> Self { self.model.closable_set(self.id, true); self @@ -78,12 +82,14 @@ where /// model.insert().text("Item A").data(String::from("custom string")); /// ``` #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + #[inline] pub fn data(self, data: Data) -> Self { self.model.data_set(self.id, data); self } #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + #[inline] pub fn divider_above(self, divider_above: bool) -> Self { self.model.divider_above_set(self.id, divider_above); self @@ -95,6 +101,7 @@ where /// model.insert().text("Item A").icon(IconSource::from("icon-a")); /// ``` #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + #[inline] pub fn icon(self, icon: impl Into) -> Self { self.model.icon_set(self.id, icon.into()); self @@ -106,11 +113,13 @@ where /// let id = model.insert("Item A").id(); /// ``` #[must_use] - pub fn id(self) -> Entity { + #[inline] + pub const fn id(self) -> Entity { self.id } #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + #[inline] pub fn indent(self, indent: u16) -> Self { self.model.indent_set(self.id, indent); self @@ -118,6 +127,7 @@ where /// Define the position of the item. #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + #[inline] pub fn position(self, position: u16) -> Self { self.model.position_set(self.id, position); self @@ -125,6 +135,7 @@ where /// Swap the position with another item in the model. #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + #[inline] pub fn position_swap(self, other: Entity) -> Self { self.model.position_swap(self.id, other); self diff --git a/src/widget/segmented_button/model/mod.rs b/src/widget/segmented_button/model/mod.rs index c24fe85f..83a1702d 100644 --- a/src/widget/segmented_button/model/mod.rs +++ b/src/widget/segmented_button/model/mod.rs @@ -89,11 +89,13 @@ where /// ```ignore /// model.activate(id); /// ``` + #[inline] pub fn activate(&mut self, id: Entity) { Selectable::activate(self, id); } /// Activates the item at the given position, returning true if it was activated. + #[inline] pub fn activate_position(&mut self, position: u16) -> bool { if let Some(entity) = self.entity_at(position) { self.activate(entity); @@ -113,6 +115,7 @@ where /// .build(); /// ``` #[must_use] + #[inline] pub fn builder() -> ModelBuilder { ModelBuilder::default() } @@ -126,6 +129,7 @@ where /// ```ignore /// model.clear(); /// ``` + #[inline] pub fn clear(&mut self) { for entity in self.order.clone() { self.remove(entity); @@ -133,6 +137,7 @@ where } /// Shows or hides the item's close button. + #[inline] pub fn closable_set(&mut self, id: Entity, closable: bool) { if let Some(settings) = self.items.get_mut(id) { settings.closable = closable; @@ -146,6 +151,7 @@ where /// println!("ID is still valid"); /// } /// ``` + #[inline] pub fn contains_item(&self, id: Entity) -> bool { self.items.contains_key(id) } @@ -203,6 +209,7 @@ where .and_then(|storage| storage.remove(id)); } + #[inline] pub fn divider_above(&self, id: Entity) -> Option { self.divider_aboves.get(id).copied() } @@ -215,6 +222,7 @@ where self.divider_aboves.insert(id, divider_above) } + #[inline] pub fn divider_above_remove(&mut self, id: Entity) -> Option { self.divider_aboves.remove(id) } @@ -224,6 +232,7 @@ where /// ```ignore /// model.enable(id, true); /// ``` + #[inline] pub fn enable(&mut self, id: Entity, enable: bool) { if let Some(e) = self.items.get_mut(id) { e.enabled = enable; @@ -232,6 +241,7 @@ where /// Get the item that is located at a given position. #[must_use] + #[inline] pub fn entity_at(&mut self, position: u16) -> Option { self.order.get(position as usize).copied() } @@ -243,6 +253,7 @@ where /// println!("has icon: {:?}", icon); /// } /// ``` + #[inline] pub fn icon(&self, id: Entity) -> Option<&Icon> { self.icons.get(id) } @@ -254,6 +265,7 @@ where /// println!("previously had icon: {:?}", old_icon); /// } /// ``` + #[inline] pub fn icon_set(&mut self, id: Entity, icon: Icon) -> Option { if !self.contains_item(id) { return None; @@ -268,6 +280,7 @@ where /// if let Some(old_icon) = model.icon_remove(id) { /// println!("previously had icon: {:?}", old_icon); /// } + #[inline] pub fn icon_remove(&mut self, id: Entity) -> Option { self.icons.remove(id) } @@ -278,6 +291,7 @@ where /// let id = model.insert().text("Item A").icon("custom-icon").id(); /// ``` #[must_use] + #[inline] pub fn insert(&mut self) -> EntityMut { let id = self.items.insert(Settings::default()); self.order.push_back(id); @@ -286,14 +300,16 @@ where /// Check if the given ID is the active ID. #[must_use] + #[inline] pub fn is_active(&self, id: Entity) -> bool { ::is_active(self, id) } /// Whether the item should contain a close button. #[must_use] + #[inline] pub fn is_closable(&self, id: Entity) -> bool { - self.items.get(id).map_or(false, |e| e.closable) + self.items.get(id).map(|e| e.closable).unwrap_or_default() } /// Check if the item is enabled. @@ -306,11 +322,13 @@ where /// } /// ``` #[must_use] + #[inline] pub fn is_enabled(&self, id: Entity) -> bool { - self.items.get(id).map_or(false, |e| e.enabled) + self.items.get(id).map(|e| e.enabled).unwrap_or_default() } /// Get number of items in the model. + #[inline] pub fn len(&self) -> usize { self.order.len() } @@ -320,10 +338,12 @@ where self.order.iter().copied() } + #[inline] pub fn indent(&self, id: Entity) -> Option { self.indents.get(id).copied() } + #[inline] pub fn indent_set(&mut self, id: Entity, indent: u16) -> Option { if !self.contains_item(id) { return None; @@ -332,6 +352,7 @@ where self.indents.insert(id, indent) } + #[inline] pub fn indent_remove(&mut self, id: Entity) -> Option { self.indents.remove(id) } @@ -343,6 +364,7 @@ where /// println!("found item at {}", position); /// } #[must_use] + #[inline] pub fn position(&self, id: Entity) -> Option { #[allow(clippy::cast_possible_truncation)] self.order.iter().position(|k| *k == id).map(|v| v as u16) @@ -356,9 +378,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); @@ -415,6 +435,7 @@ where /// println!("{:?} has text {text}", id); /// } /// ``` + #[inline] pub fn text(&self, id: Entity) -> Option<&str> { self.text.get(id).map(Cow::as_ref) } @@ -439,6 +460,7 @@ where /// if let Some(old_text) = model.text_remove(id) { /// println!("{:?} had text {}", id, old_text); /// } + #[inline] pub fn text_remove(&mut self, id: Entity) -> Option> { self.text.remove(id) } diff --git a/src/widget/segmented_button/model/selection.rs b/src/widget/segmented_button/model/selection.rs index 1366c18c..c0927652 100644 --- a/src/widget/segmented_button/model/selection.rs +++ b/src/widget/segmented_button/model/selection.rs @@ -39,6 +39,7 @@ impl Selectable for Model { } } + #[inline] fn is_active(&self, id: Entity) -> bool { self.selection.active == id } @@ -47,23 +48,27 @@ impl Selectable for Model { impl Model { /// Get an immutable reference to the data associated with the active item. #[must_use] + #[inline] pub fn active_data(&self) -> Option<&Data> { self.data(self.active()) } /// Get a mutable reference to the data associated with the active item. #[must_use] + #[inline] pub fn active_data_mut(&mut self) -> Option<&mut Data> { self.data_mut(self.active()) } /// Deactivates the active item. + #[inline] pub fn deactivate(&mut self) { Selectable::deactivate(self, Entity::default()); } /// The ID of the active item. #[must_use] + #[inline] pub fn active(&self) -> Entity { self.selection.active } @@ -86,10 +91,12 @@ impl Selectable for Model { } } + #[inline] fn deactivate(&mut self, id: Entity) { self.selection.active.remove(&id); } + #[inline] fn is_active(&self, id: Entity) -> bool { self.selection.active.contains(&id) } @@ -97,11 +104,13 @@ impl Selectable for Model { impl Model { /// Deactivates the item in the model. + #[inline] pub fn deactivate(&mut self, id: Entity) { Selectable::deactivate(self, id); } /// The IDs of the active items. + #[inline] pub fn active(&self) -> impl Iterator + '_ { self.selection.active.iter().copied() } diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 9bdc9ad0..2a05d44a 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -20,7 +20,7 @@ use iced::{ event, keyboard, mouse, touch, }; use iced_core::mouse::ScrollDelta; -use iced_core::text::{LineHeight, Paragraph, Renderer as TextRenderer, Shaping, Wrapping}; +use iced_core::text::{LineHeight, Renderer as TextRenderer, Shaping, Wrapping}; use iced_core::widget::{self, operation, tree}; use iced_core::{Border, Gradient, Point, Renderer as IcedRenderer, Shadow, Text}; use iced_core::{Clipboard, Layout, Shell, Widget, layout, renderer, widget::Tree}; @@ -158,6 +158,7 @@ where Model: Selectable, SelectionMode: Default, { + #[inline] pub fn new(model: &'a Model) -> Self { Self { model, @@ -1726,6 +1727,7 @@ impl Id { /// /// This function produces a different [`Id`] every time it is called. #[must_use] + #[inline] pub fn unique() -> Self { Self(widget::Id::unique()) } diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index 36cc555f..0a5406f7 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -19,11 +19,19 @@ pub fn item<'a, Message: 'static>( title: impl Into> + 'a, widget: impl Into> + 'a, ) -> Row<'a, Message> { - item_row(vec![ - text(title).wrapping(Wrapping::Word).into(), - horizontal_space().into(), - widget.into(), - ]) + #[inline(never)] + fn inner<'a, Message: 'static>( + title: Cow<'a, str>, + widget: Element<'a, Message>, + ) -> Row<'a, Message> { + item_row(vec![ + text(title).wrapping(Wrapping::Word).into(), + horizontal_space().into(), + widget, + ]) + } + + inner(title.into(), widget.into()) } /// A settings item aligned in a row @@ -41,13 +49,21 @@ pub fn flex_item<'a, Message: 'static>( title: impl Into> + 'a, widget: impl Into> + 'a, ) -> FlexRow<'a, Message> { - flex_item_row(vec![ - text(title) - .wrapping(Wrapping::Word) - .width(Length::Fill) - .into(), - container(widget).into(), - ]) + #[inline(never)] + fn inner<'a, Message: 'static>( + title: Cow<'a, str>, + widget: Element<'a, Message>, + ) -> FlexRow<'a, Message> { + flex_item_row(vec![ + text(title) + .wrapping(Wrapping::Word) + .width(Length::Fill) + .into(), + container(widget).into(), + ]) + } + + inner(title.into(), widget.into()) } /// A settings item aligned in a flex row @@ -88,15 +104,16 @@ pub struct Item<'a, Message> { impl<'a, Message: 'static> Item<'a, Message> { /// Assigns a control to the item. pub fn control(self, widget: impl Into>) -> Row<'a, Message> { - item_row(self.control_(widget)) + item_row(self.control_(widget.into())) } /// Assigns a control which flexes. pub fn flex_control(self, widget: impl Into>) -> FlexRow<'a, Message> { - flex_item_row(self.control_(widget)) + flex_item_row(self.control_(widget.into())) } - fn control_(self, widget: impl Into>) -> Vec> { + #[inline(never)] + fn control_(self, widget: Element<'a, Message>) -> Vec> { let mut contents = Vec::with_capacity(4); if let Some(icon) = self.icon { diff --git a/src/widget/text.rs b/src/widget/text.rs index b542f2e0..37e85b80 100644 --- a/src/widget/text.rs +++ b/src/widget/text.rs @@ -26,72 +26,117 @@ pub enum Typography { /// [`Text`] widget with the Title 1 typography preset. pub fn title1<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text.into()) - .size(35.0) - .line_height(LineHeight::Absolute(52.0.into())) - .font(crate::font::semibold()) + #[inline(never)] + fn inner(text: Cow) -> Text { + Text::new(text) + .size(35.0) + .line_height(LineHeight::Absolute(52.0.into())) + .font(crate::font::semibold()) + } + + inner(text.into()) } /// [`Text`] widget with the Title 2 typography preset. pub fn title2<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text.into()) - .size(29.0) - .line_height(LineHeight::Absolute(43.0.into())) - .font(crate::font::semibold()) + #[inline(never)] + fn inner(text: Cow) -> Text { + Text::new(text) + .size(29.0) + .line_height(LineHeight::Absolute(43.0.into())) + .font(crate::font::semibold()) + } + + inner(text.into()) } /// [`Text`] widget with the Title 3 typography preset. pub fn title3<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text.into()) - .size(24.0) - .line_height(LineHeight::Absolute(36.0.into())) - .font(crate::font::bold()) + #[inline(never)] + fn inner(text: Cow) -> Text { + Text::new(text) + .size(24.0) + .line_height(LineHeight::Absolute(36.0.into())) + .font(crate::font::bold()) + } + + inner(text.into()) } /// [`Text`] widget with the Title 4 typography preset. pub fn title4<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text.into()) - .size(20.0) - .line_height(LineHeight::Absolute(30.0.into())) - .font(crate::font::bold()) + #[inline(never)] + fn inner(text: Cow) -> Text { + Text::new(text) + .size(20.0) + .line_height(LineHeight::Absolute(30.0.into())) + .font(crate::font::bold()) + } + + inner(text.into()) } /// [`Text`] widget with the Heading typography preset. pub fn heading<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text.into()) - .size(14.0) - .line_height(LineHeight::Absolute(iced::Pixels(21.0))) - .font(crate::font::bold()) + #[inline(never)] + fn inner(text: Cow) -> Text { + Text::new(text) + .size(14.0) + .line_height(LineHeight::Absolute(iced::Pixels(21.0))) + .font(crate::font::bold()) + } + + inner(text.into()) } /// [`Text`] widget with the Caption Heading typography preset. pub fn caption_heading<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text.into()) - .size(12.0) - .line_height(LineHeight::Absolute(iced::Pixels(17.0))) - .font(crate::font::semibold()) + #[inline(never)] + fn inner(text: Cow) -> Text { + Text::new(text) + .size(12.0) + .line_height(LineHeight::Absolute(iced::Pixels(17.0))) + .font(crate::font::semibold()) + } + + inner(text.into()) } /// [`Text`] widget with the Body typography preset. pub fn body<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text.into()) - .size(14.0) - .line_height(LineHeight::Absolute(21.0.into())) - .font(crate::font::default()) + #[inline(never)] + fn inner(text: Cow) -> Text { + Text::new(text) + .size(14.0) + .line_height(LineHeight::Absolute(21.0.into())) + .font(crate::font::default()) + } + + inner(text.into()) } /// [`Text`] widget with the Caption typography preset. pub fn caption<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text.into()) - .size(12.0) - .line_height(LineHeight::Absolute(17.0.into())) - .font(crate::font::default()) + #[inline(never)] + fn inner(text: Cow) -> Text { + Text::new(text) + .size(12.0) + .line_height(LineHeight::Absolute(17.0.into())) + .font(crate::font::default()) + } + + inner(text.into()) } /// [`Text`] widget with the Monotext typography preset. pub fn monotext<'a>(text: impl Into> + 'a) -> Text<'a, crate::Theme, Renderer> { - Text::new(text.into()) - .size(14.0) - .line_height(LineHeight::Absolute(20.0.into())) - .font(crate::font::mono()) + #[inline(never)] + fn inner(text: Cow) -> Text { + Text::new(text) + .size(14.0) + .line_height(LineHeight::Absolute(20.0.into())) + .font(crate::font::mono()) + } + + inner(text.into()) } diff --git a/src/widget/toaster/mod.rs b/src/widget/toaster/mod.rs index 1acbce0c..efd93a9d 100644 --- a/src/widget/toaster/mod.rs +++ b/src/widget/toaster/mod.rs @@ -6,16 +6,14 @@ use std::collections::VecDeque; use std::rc::Rc; -use crate::widget::container; use crate::widget::Column; +use crate::widget::container; use iced::Task; use iced_core::Element; -use slotmap::new_key_type; use slotmap::SlotMap; +use slotmap::new_key_type; use widget::Toaster; -use crate::ext::CollectionWidget; - use super::column; use super::{button, icon, row, text}; @@ -106,6 +104,7 @@ pub struct Action { } impl std::fmt::Debug for Action { + #[cold] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Action") .field("description", &self.description) From 0aa518984ec6bb29c368b879c20b971f04fbc0c7 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 21 Mar 2025 13:33:07 +0100 Subject: [PATCH 154/556] chore: format for 2024 edition --- src/app/multi_window.rs | 3 +- src/app/settings.rs | 4 +- src/applet/mod.rs | 10 ++-- src/applet/token/subscription.rs | 2 +- src/applet/token/wayland_handler.rs | 2 +- src/dialog/file_chooser/open.rs | 4 +- src/dialog/file_chooser/save.rs | 4 +- src/lib.rs | 4 +- src/surface/action.rs | 29 +++++---- src/theme/style/dropdown.rs | 2 +- src/theme/style/iced.rs | 2 +- src/theme/style/segmented_button.rs | 6 +- src/widget/about.rs | 2 +- src/widget/button/link.rs | 4 +- src/widget/button/mod.rs | 14 ++--- src/widget/button/style.rs | 2 +- src/widget/button/text.rs | 2 +- src/widget/color_picker/mod.rs | 12 ++-- src/widget/context_drawer/overlay.rs | 4 +- src/widget/context_menu.rs | 4 +- src/widget/dialog.rs | 2 +- src/widget/dnd_destination.rs | 10 ++-- src/widget/dnd_source.rs | 34 +++++------ src/widget/dropdown/menu/appearance.rs | 2 +- src/widget/dropdown/menu/mod.rs | 6 +- src/widget/dropdown/multi/menu.rs | 4 +- src/widget/dropdown/multi/mod.rs | 2 +- src/widget/dropdown/multi/widget.rs | 2 +- src/widget/dropdown/widget.rs | 22 +++---- src/widget/grid/widget.rs | 4 +- src/widget/icon/handle.rs | 14 ++--- src/widget/icon/mod.rs | 2 +- src/widget/list/mod.rs | 2 +- src/widget/menu.rs | 4 +- src/widget/menu/flex.rs | 3 +- src/widget/menu/menu_bar.rs | 5 +- src/widget/menu/menu_inner.rs | 3 +- src/widget/menu/menu_tree.rs | 4 +- src/widget/mod.rs | 82 +++++++++++++------------- src/widget/nav_bar_toggle.rs | 2 +- src/widget/popover.rs | 2 +- src/widget/rectangle_tracker/mod.rs | 2 +- src/widget/responsive_container.rs | 2 +- src/widget/responsive_menu_bar.rs | 2 +- src/widget/segmented_button/mod.rs | 6 +- src/widget/segmented_button/style.rs | 2 +- src/widget/settings/item.rs | 7 +-- src/widget/settings/mod.rs | 6 +- src/widget/settings/section.rs | 2 +- src/widget/spin_button.rs | 3 +- src/widget/text_input/style.rs | 2 +- src/widget/toaster/widget.rs | 6 +- src/widget/toggler.rs | 2 +- src/widget/warning.rs | 2 +- src/widget/wayland/tooltip/mod.rs | 2 +- src/widget/wayland/tooltip/widget.rs | 18 +++--- src/widget/wrapper.rs | 4 +- 57 files changed, 196 insertions(+), 199 deletions(-) diff --git a/src/app/multi_window.rs b/src/app/multi_window.rs index 368f2f03..65ac61f7 100644 --- a/src/app/multi_window.rs +++ b/src/app/multi_window.rs @@ -7,10 +7,9 @@ use iced::application; use iced::window; use iced::{ - self, + self, Program, program::{self, with_style, with_subscription, with_theme, with_title}, runtime::{Appearance, DefaultStyle}, - Program, }; use iced::{Element, Result, Settings, Subscription, Task}; diff --git a/src/app/settings.rs b/src/app/settings.rs index a5782c99..926181e1 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -3,9 +3,9 @@ //! Configure a new COSMIC application. -use crate::{font, Theme}; -use iced_core::layout::Limits; +use crate::{Theme, font}; use iced_core::Font; +use iced_core::layout::Limits; /// Configure a new COSMIC application. #[allow(clippy::struct_excessive_bools)] diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 25af302a..a89f4043 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -2,22 +2,22 @@ pub mod token; use crate::{ + Application, Element, Renderer, app::iced_settings, cctk::sctk, iced::{ - self, + self, Color, Length, Limits, Rectangle, alignment::{Horizontal, Vertical}, widget::Container, - window, Color, Length, Limits, Rectangle, + window, }, iced_widget, - theme::{self, system_dark, system_light, Button, THEME}, + theme::{self, Button, THEME, system_dark, system_light}, widget::{ self, - autosize::{self, autosize, Autosize}, + autosize::{self, Autosize, autosize}, layer_container, }, - Application, Element, Renderer, }; pub use cosmic_panel_config; use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize}; diff --git a/src/applet/token/subscription.rs b/src/applet/token/subscription.rs index e3cc6198..706c0301 100644 --- a/src/applet/token/subscription.rs +++ b/src/applet/token/subscription.rs @@ -2,8 +2,8 @@ use crate::iced; use crate::iced_futures::futures; use cctk::sctk::reexports::calloop; use futures::{ - channel::mpsc::{unbounded, UnboundedReceiver}, SinkExt, StreamExt, + channel::mpsc::{UnboundedReceiver, unbounded}, }; use iced::Subscription; use iced_futures::stream; diff --git a/src/applet/token/wayland_handler.rs b/src/applet/token/wayland_handler.rs index cb795c8a..ee8f9b4e 100644 --- a/src/applet/token/wayland_handler.rs +++ b/src/applet/token/wayland_handler.rs @@ -21,7 +21,7 @@ use sctk::{ activation::{ActivationHandler, ActivationState}, registry::{ProvidesRegistryState, RegistryState}, }; -use wayland_client::{globals::registry_queue_init, Connection, QueueHandle}; +use wayland_client::{Connection, QueueHandle, globals::registry_queue_init}; struct AppData { exit: bool, diff --git a/src/dialog/file_chooser/open.rs b/src/dialog/file_chooser/open.rs index 80e5ffbe..f24afda9 100644 --- a/src/dialog/file_chooser/open.rs +++ b/src/dialog/file_chooser/open.rs @@ -7,10 +7,10 @@ //! example in our repository. #[cfg(feature = "xdg-portal")] -pub use portal::{file, files, folder, folders, FileResponse, MultiFileResponse}; +pub use portal::{FileResponse, MultiFileResponse, file, files, folder, folders}; #[cfg(feature = "rfd")] -pub use rust_fd::{file, files, folder, folders, FileResponse, MultiFileResponse}; +pub use rust_fd::{FileResponse, MultiFileResponse, file, files, folder, folders}; use super::Error; use std::path::PathBuf; diff --git a/src/dialog/file_chooser/save.rs b/src/dialog/file_chooser/save.rs index 63c07340..cfb1382b 100644 --- a/src/dialog/file_chooser/save.rs +++ b/src/dialog/file_chooser/save.rs @@ -7,10 +7,10 @@ //! example in our repository. #[cfg(feature = "xdg-portal")] -pub use portal::{file, Response}; +pub use portal::{Response, file}; #[cfg(feature = "rfd")] -pub use rust_fd::{file, Response}; +pub use rust_fd::{Response, file}; use super::Error; use std::path::PathBuf; diff --git a/src/lib.rs b/src/lib.rs index 0515911d..119d7af5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,9 +6,9 @@ /// Recommended default imports. pub mod prelude { - pub use crate::ext::*; #[cfg(feature = "winit")] pub use crate::ApplicationExt; + pub use crate::ext::*; pub use crate::{Also, Apply, Element, Renderer, Task, Theme}; } @@ -107,7 +107,7 @@ pub mod task; pub mod theme; #[doc(inline)] -pub use theme::{style, Theme}; +pub use theme::{Theme, style}; pub mod widget; type Plain = iced_core::text::paragraph::Plain<::Paragraph>; diff --git a/src/surface/action.rs b/src/surface/action.rs index af7cc2de..e27815eb 100644 --- a/src/surface/action.rs +++ b/src/surface/action.rs @@ -24,9 +24,9 @@ pub fn destroy_subsurface(id: iced_core::window::Id) -> Action { #[must_use] pub fn app_popup( settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings - + Send - + Sync - + 'static, + + Send + + Sync + + 'static, view: Option< Box< dyn for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action> @@ -58,9 +58,9 @@ pub fn app_popup( #[must_use] pub fn simple_subsurface( settings: impl Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings - + Send - + Sync - + 'static, + + Send + + Sync + + 'static, view: Option< Box crate::Element<'static, crate::Action> + Send + Sync + 'static>, >, @@ -87,9 +87,9 @@ pub fn simple_subsurface( #[must_use] pub fn simple_popup( settings: impl Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings - + Send - + Sync - + 'static, + + Send + + Sync + + 'static, view: Option< impl Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, >, @@ -117,10 +117,13 @@ pub fn simple_popup( #[cfg(all(feature = "wayland", feature = "winit"))] #[must_use] pub fn subsurface( - settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings - + Send - + Sync - + 'static, + settings: impl Fn( + &mut App, + ) + -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + + Send + + Sync + + 'static, // XXX Boxed trait object is required for less cumbersome type inference, but we box it anyways. view: Option< Box< diff --git a/src/theme/style/dropdown.rs b/src/theme/style/dropdown.rs index f62ab984..02c69c2a 100644 --- a/src/theme/style/dropdown.rs +++ b/src/theme/style/dropdown.rs @@ -1,8 +1,8 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -use crate::widget::dropdown; use crate::Theme; +use crate::widget::dropdown; use iced::{Background, Color}; impl dropdown::menu::StyleSheet for Theme { diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 2e5d8c13..b1118480 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -3,7 +3,7 @@ //! Contains stylesheet implementations for widgets native to iced. -use crate::theme::{CosmicComponent, Theme, TRANSPARENT_COMPONENT}; +use crate::theme::{CosmicComponent, TRANSPARENT_COMPONENT, Theme}; use cosmic_theme::composite::over; use iced::{ overlay::menu, diff --git a/src/theme/style/segmented_button.rs b/src/theme/style/segmented_button.rs index 8c2dbdaa..70e2c937 100644 --- a/src/theme/style/segmented_button.rs +++ b/src/theme/style/segmented_button.rs @@ -6,7 +6,7 @@ use crate::widget::segmented_button::{Appearance, ItemAppearance, StyleSheet}; use crate::{theme::Theme, widget::segmented_button::ItemStatusAppearance}; use cosmic_theme::{Component, Container}; -use iced_core::{border::Radius, Background}; +use iced_core::{Background, border::Radius}; #[derive(Default)] pub enum SegmentedButton { @@ -165,7 +165,7 @@ impl StyleSheet for Theme { mod horizontal { use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance}; use cosmic_theme::Component; - use iced_core::{border::Radius, Background}; + use iced_core::{Background, border::Radius}; pub fn selection_active( cosmic: &cosmic_theme::Theme, @@ -252,7 +252,7 @@ pub fn hover( mod vertical { use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance}; use cosmic_theme::Component; - use iced_core::{border::Radius, Background}; + use iced_core::{Background, border::Radius}; pub fn selection_active( cosmic: &cosmic_theme::Theme, diff --git a/src/widget/about.rs b/src/widget/about.rs index acd13ac9..7e23da25 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -1,8 +1,8 @@ use { crate::{ + Element, iced::{Alignment, Length}, widget::{self, horizontal_space}, - Element, }, license::License, }; diff --git a/src/widget/button/link.rs b/src/widget/button/link.rs index dcfc6a36..b86ef1a3 100644 --- a/src/widget/button/link.rs +++ b/src/widget/button/link.rs @@ -5,12 +5,12 @@ use super::Builder; use super::ButtonClass; +use crate::Element; use crate::prelude::*; use crate::widget::icon::{self, Handle}; use crate::widget::{button, row, tooltip}; -use crate::Element; use iced_core::text::LineHeight; -use iced_core::{font::Weight, widget::Id, Alignment, Length, Padding}; +use iced_core::{Alignment, Length, Padding, font::Weight, widget::Id}; use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Hyperlink>; diff --git a/src/widget/button/mod.rs b/src/widget/button/mod.rs index b654b274..721ee8b7 100644 --- a/src/widget/button/mod.rs +++ b/src/widget/button/mod.rs @@ -8,21 +8,21 @@ pub use crate::theme::Button as ButtonClass; pub mod link; use derive_setters::Setters; #[doc(inline)] -pub use link::link; -#[doc(inline)] pub use link::Button as LinkButton; +#[doc(inline)] +pub use link::link; mod icon; #[doc(inline)] -pub use icon::icon; -#[doc(inline)] pub use icon::Button as IconButton; +#[doc(inline)] +pub use icon::icon; mod image; #[doc(inline)] -pub use image::image; -#[doc(inline)] pub use image::Button as ImageButton; +#[doc(inline)] +pub use image::image; mod style; #[doc(inline)] @@ -36,7 +36,7 @@ pub use text::{destructive, standard, suggested, text}; mod widget; #[doc(inline)] -pub use widget::{draw, focus, layout, mouse_interaction, Button}; +pub use widget::{Button, draw, focus, layout, mouse_interaction}; use iced_core::font::Weight; use iced_core::widget::Id; diff --git a/src/widget/button/style.rs b/src/widget/button/style.rs index 36100114..21afa08b 100644 --- a/src/widget/button/style.rs +++ b/src/widget/button/style.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 //! Change the apperance of a button. -use iced_core::{border::Radius, Background, Color, Vector}; +use iced_core::{Background, Color, Vector, border::Radius}; use crate::theme::THEME; diff --git a/src/widget/button/text.rs b/src/widget/button/text.rs index da5f94f1..e5dea9f3 100644 --- a/src/widget/button/text.rs +++ b/src/widget/button/text.rs @@ -4,7 +4,7 @@ use super::{Builder, ButtonClass}; use crate::widget::{icon, row, tooltip}; use crate::{Apply, Element}; -use iced_core::{font::Weight, text::LineHeight, widget::Id, Alignment, Length, Padding}; +use iced_core::{Alignment, Length, Padding, font::Weight, text::LineHeight, widget::Id}; use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Text>; diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 65961114..1762330d 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -9,30 +9,30 @@ use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{Duration, Instant}; +use crate::Element; use crate::theme::iced::Slider; use crate::theme::{Button, THEME}; use crate::widget::{button::Catalog, container, segmented_button::Entity, slider}; -use crate::Element; use derive_setters::Setters; use iced::Task; use iced_core::event::{self, Event}; use iced_core::gradient::{ColorStop, Linear}; use iced_core::renderer::Quad; -use iced_core::widget::{tree, Tree}; +use iced_core::widget::{Tree, tree}; use iced_core::{ - layout, mouse, renderer, Background, Border, Clipboard, Color, Layout, Length, Radians, - Rectangle, Renderer, Shadow, Shell, Size, Vector, Widget, + Background, Border, Clipboard, Color, Layout, Length, Radians, Rectangle, Renderer, Shadow, + Shell, Size, Vector, Widget, layout, mouse, renderer, }; use iced_widget::slider::HandleShape; -use iced_widget::{canvas, column, horizontal_space, row, scrollable, vertical_space, Row}; +use iced_widget::{Row, canvas, column, horizontal_space, row, scrollable, vertical_space}; use lazy_static::lazy_static; use palette::{FromColor, RgbHue}; use super::divider::horizontal; use super::icon::{self, from_name}; use super::segmented_button::{self, SingleSelect}; -use super::{button, segmented_control, text, text_input, tooltip, Icon}; +use super::{Icon, button, segmented_control, text, text_input, tooltip}; #[doc(inline)] pub use ColorPickerModel as Model; diff --git a/src/widget/context_drawer/overlay.rs b/src/widget/context_drawer/overlay.rs index c4b779ac..d9cc88ab 100644 --- a/src/widget/context_drawer/overlay.rs +++ b/src/widget/context_drawer/overlay.rs @@ -5,9 +5,9 @@ use crate::Element; use iced::advanced::layout::{self, Layout}; use iced::advanced::widget::{self, Operation}; -use iced::advanced::{overlay, renderer}; use iced::advanced::{Clipboard, Shell}; -use iced::{event, mouse, Event, Point, Rectangle, Size}; +use iced::advanced::{overlay, renderer}; +use iced::{Event, Point, Rectangle, Size, event, mouse}; use iced_core::Renderer; pub(super) struct Overlay<'a, 'b, Message> { diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 10f3ef3e..87122029 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -9,8 +9,8 @@ use crate::widget::menu::{ use derive_setters::Setters; use iced::touch::Finger; use iced::{Event, Vector}; -use iced_core::widget::{tree, Tree, Widget}; -use iced_core::{event, mouse, touch, Length, Point, Size}; +use iced_core::widget::{Tree, Widget, tree}; +use iced_core::{Length, Point, Size, event, mouse, touch}; use std::collections::HashSet; /// A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation. diff --git a/src/widget/dialog.rs b/src/widget/dialog.rs index 3c8f181e..34ac9bd1 100644 --- a/src/widget/dialog.rs +++ b/src/widget/dialog.rs @@ -1,4 +1,4 @@ -use crate::{iced::Length, style, theme, widget, Element}; +use crate::{Element, iced::Length, style, theme, widget}; use std::borrow::Cow; pub fn dialog<'a, Message>() -> Dialog<'a, Message> { diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index 31fa0305..89c7d44a 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -6,22 +6,22 @@ use std::{ use iced::Vector; use crate::{ + Element, iced::{ + Event, Length, Rectangle, clipboard::{ dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent}, mime::AllowedMimeTypes, }, event, id::Internal, - mouse, overlay, Event, Length, Rectangle, + mouse, overlay, }, iced_core::{ - self, layout, - widget::{tree, Tree}, - Clipboard, Shell, + self, Clipboard, Shell, layout, + widget::{Tree, tree}, }, widget::{Id, Widget}, - Element, }; pub fn dnd_destination<'a, Message: 'static>( diff --git a/src/widget/dnd_source.rs b/src/widget/dnd_source.rs index 4936ca10..f21f9670 100644 --- a/src/widget/dnd_source.rs +++ b/src/widget/dnd_source.rs @@ -3,17 +3,17 @@ use std::any::Any; use iced_core::window; use crate::{ + Element, iced::{ + Event, Length, Point, Rectangle, Vector, clipboard::dnd::{DndAction, DndEvent, SourceEvent}, - event, mouse, overlay, Event, Length, Point, Rectangle, Vector, + event, mouse, overlay, }, iced_core::{ - self, layout, renderer, - widget::{tree, Tree}, - Clipboard, Shell, + self, Clipboard, Shell, layout, renderer, + widget::{Tree, tree}, }, - widget::{container, Id, Widget}, - Element, + widget::{Id, Widget, container}, }; pub fn dnd_source< @@ -40,10 +40,10 @@ pub struct DndSource<'a, Message, D> { } impl< - 'a, - Message: Clone + 'static, - D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, - > DndSource<'a, Message, D> + 'a, + Message: Clone + 'static, + D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, +> DndSource<'a, Message, D> { pub fn new(child: impl Into>) -> Self { Self { @@ -152,10 +152,8 @@ impl< } } -impl< - Message: Clone + 'static, - D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, - > Widget for DndSource<'_, Message, D> +impl + Widget for DndSource<'_, Message, D> { fn children(&self) -> Vec { vec![Tree::new(&self.container)] @@ -400,10 +398,10 @@ impl< } impl< - 'a, - Message: Clone + 'static, - D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, - > From> for Element<'a, Message> + 'a, + Message: Clone + 'static, + D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, +> From> for Element<'a, Message> { fn from(e: DndSource<'a, Message, D>) -> Element<'a, Message> { Element::new(e) diff --git a/src/widget/dropdown/menu/appearance.rs b/src/widget/dropdown/menu/appearance.rs index 64c524c2..d1bed21c 100644 --- a/src/widget/dropdown/menu/appearance.rs +++ b/src/widget/dropdown/menu/appearance.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MPL-2.0 AND MIT //! Change the appearance of menus. -use iced_core::{border::Radius, Background, Color}; +use iced_core::{Background, Color, border::Radius}; /// The appearance of a menu. #[derive(Debug, Clone, Copy)] diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index 681a4c37..0026283c 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -9,14 +9,14 @@ use std::sync::{Arc, Mutex}; pub use appearance::{Appearance, StyleSheet}; use crate::surface; -use crate::widget::{icon, Container, RcWrapper}; +use crate::widget::{Container, RcWrapper, icon}; use iced_core::event::{self, Event}; use iced_core::layout::{self, Layout}; use iced_core::text::{self, Text}; use iced_core::widget::Tree; use iced_core::{ - alignment, mouse, overlay, renderer, svg, touch, Border, Clipboard, Element, Length, Padding, - Pixels, Point, Rectangle, Renderer, Shadow, Shell, Size, Vector, Widget, + Border, Clipboard, Element, Length, Padding, Pixels, Point, Rectangle, Renderer, Shadow, Shell, + Size, Vector, Widget, alignment, mouse, overlay, renderer, svg, touch, }; use iced_widget::scrollable::Scrollable; diff --git a/src/widget/dropdown/multi/menu.rs b/src/widget/dropdown/multi/menu.rs index 3d37d928..da103f8a 100644 --- a/src/widget/dropdown/multi/menu.rs +++ b/src/widget/dropdown/multi/menu.rs @@ -7,8 +7,8 @@ use iced_core::layout::{self, Layout}; use iced_core::text::{self, Text}; use iced_core::widget::Tree; use iced_core::{ - alignment, mouse, overlay, renderer, svg, touch, Border, Clipboard, Element, Length, Padding, - Pixels, Point, Rectangle, Renderer, Shadow, Shell, Size, Vector, Widget, + Border, Clipboard, Element, Length, Padding, Pixels, Point, Rectangle, Renderer, Shadow, Shell, + Size, Vector, Widget, alignment, mouse, overlay, renderer, svg, touch, }; use iced_widget::scrollable::Scrollable; diff --git a/src/widget/dropdown/multi/mod.rs b/src/widget/dropdown/multi/mod.rs index b4963490..543001c9 100644 --- a/src/widget/dropdown/multi/mod.rs +++ b/src/widget/dropdown/multi/mod.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MPL-2.0 AND MIT mod model; -pub use model::{list, model, List, Model}; +pub use model::{List, Model, list, model}; pub mod menu; pub use menu::Menu; diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index 14f89d72..1b0637bb 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -8,10 +8,10 @@ use derive_setters::Setters; use iced_core::event::{self, Event}; use iced_core::text::{self, Paragraph, Text}; use iced_core::widget::tree::{self, Tree}; -use iced_core::{alignment, keyboard, layout, mouse, overlay, renderer, svg, touch, Shadow}; use iced_core::{ Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, }; +use iced_core::{Shadow, alignment, keyboard, layout, mouse, overlay, renderer, svg, touch}; use iced_widget::pick_list; use std::ffi::OsStr; diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index f6cecea7..826060d0 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -4,16 +4,16 @@ use super::menu::{self, Menu}; use crate::widget::icon::{self, Handle}; -use crate::{surface, Element}; +use crate::{Element, surface}; use derive_setters::Setters; use iced::window; use iced_core::event::{self, Event}; use iced_core::text::{self, Paragraph, Text}; use iced_core::widget::tree::{self, Tree}; -use iced_core::{alignment, keyboard, layout, mouse, overlay, renderer, svg, touch, Shadow}; use iced_core::{ Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, }; +use iced_core::{Shadow, alignment, keyboard, layout, mouse, overlay, renderer, svg, touch}; use iced_widget::pick_list::{self, Catalog}; use std::borrow::Cow; use std::ffi::OsStr; @@ -149,10 +149,10 @@ where } impl< - S: AsRef + Send + Sync + Clone + 'static, - Message: 'static + Clone, - AppMessage: 'static + Clone, - > Widget for Dropdown<'_, S, Message, AppMessage> + S: AsRef + Send + Sync + Clone + 'static, + Message: 'static + Clone, + AppMessage: 'static + Clone, +> Widget for Dropdown<'_, S, Message, AppMessage> where [S]: std::borrow::ToOwned, { @@ -344,11 +344,11 @@ where } impl< - 'a, - S: AsRef + Send + Sync + Clone + 'static, - Message: 'static + std::clone::Clone, - AppMessage: 'static + std::clone::Clone, - > From> for crate::Element<'a, Message> + 'a, + S: AsRef + Send + Sync + Clone + 'static, + Message: 'static + std::clone::Clone, + AppMessage: 'static + std::clone::Clone, +> From> for crate::Element<'a, Message> where [S]: std::borrow::ToOwned, { diff --git a/src/widget/grid/widget.rs b/src/widget/grid/widget.rs index 8e9efc78..0aca7943 100644 --- a/src/widget/grid/widget.rs +++ b/src/widget/grid/widget.rs @@ -6,8 +6,8 @@ use derive_setters::Setters; use iced_core::event::{self, Event}; use iced_core::widget::{Operation, Tree}; use iced_core::{ - layout, mouse, overlay, renderer, Alignment, Clipboard, Layout, Length, Padding, Rectangle, - Shell, Vector, Widget, + Alignment, Clipboard, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, layout, mouse, + overlay, renderer, }; /// Responsively generates rows and columns of widgets based on its dimmensions. diff --git a/src/widget/icon/handle.rs b/src/widget/icon/handle.rs index 2d30427b..1fa2d85f 100644 --- a/src/widget/icon/handle.rs +++ b/src/widget/icon/handle.rs @@ -49,10 +49,10 @@ pub fn from_path(path: PathBuf) -> Handle { /// Create an image handle from memory. pub fn from_raster_bytes( bytes: impl Into> - + std::convert::AsRef<[u8]> - + std::marker::Send - + std::marker::Sync - + 'static, + + std::convert::AsRef<[u8]> + + std::marker::Send + + std::marker::Sync + + 'static, ) -> Handle { fn inner(bytes: Cow<'static, [u8]>) -> Handle { Handle { @@ -72,9 +72,9 @@ pub fn from_raster_pixels( width: u32, height: u32, pixels: impl Into> - + std::convert::AsRef<[u8]> - + std::marker::Send - + std::marker::Sync, + + std::convert::AsRef<[u8]> + + std::marker::Send + + std::marker::Sync, ) -> Handle { fn inner(width: u32, height: u32, pixels: Cow<'static, [u8]>) -> Handle { Handle { diff --git a/src/widget/icon/mod.rs b/src/widget/icon/mod.rs index 45d5451d..5a90d35b 100644 --- a/src/widget/icon/mod.rs +++ b/src/widget/icon/mod.rs @@ -10,7 +10,7 @@ use std::sync::Arc; pub use named::{IconFallback, Named}; mod handle; -pub use handle::{from_path, from_raster_bytes, from_raster_pixels, from_svg_bytes, Data, Handle}; +pub use handle::{Data, Handle, from_path, from_raster_bytes, from_raster_pixels, from_svg_bytes}; use crate::Element; use derive_setters::Setters; diff --git a/src/widget/list/mod.rs b/src/widget/list/mod.rs index 7ec5d107..c6e2051c 100644 --- a/src/widget/list/mod.rs +++ b/src/widget/list/mod.rs @@ -3,4 +3,4 @@ pub mod column; -pub use self::column::{list_column, ListColumn}; +pub use self::column::{ListColumn, list_column}; diff --git a/src/widget/menu.rs b/src/widget/menu.rs index f5c9c461..2b54bf6e 100644 --- a/src/widget/menu.rs +++ b/src/widget/menu.rs @@ -64,12 +64,12 @@ pub use key_bind::KeyBind; mod menu_bar; pub(crate) use menu_bar::MenuBarState; -pub use menu_bar::{menu_bar as bar, MenuBar}; +pub use menu_bar::{MenuBar, menu_bar as bar}; mod menu_inner; mod menu_tree; pub use menu_tree::{ - menu_button, menu_items as items, menu_root as root, MenuItem as Item, MenuTree as Tree, + MenuItem as Item, MenuTree as Tree, menu_button, menu_items as items, menu_root as root, }; pub use crate::style::menu_bar::{Appearance, StyleSheet}; diff --git a/src/widget/menu/flex.rs b/src/widget/menu/flex.rs index e7930574..c093e802 100644 --- a/src/widget/menu/flex.rs +++ b/src/widget/menu/flex.rs @@ -2,8 +2,9 @@ use iced_core::widget::Tree; use iced_widget::core::{ + Alignment, Element, Padding, Point, Size, layout::{Limits, Node}, - renderer, Alignment, Element, Padding, Point, Size, + renderer, }; /// The main axis of a flex layout. diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 283e5922..2584e762 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -12,12 +12,11 @@ use crate::style::menu_bar::StyleSheet; use iced::{Point, Vector}; use iced_core::Border; use iced_widget::core::{ - event, + Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, event, layout::{Limits, Node}, mouse::{self, Cursor}, overlay, renderer, touch, - widget::{tree, Tree}, - Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, + widget::{Tree, tree}, }; /// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing. diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 1cd60dec..f8c0471a 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -6,12 +6,11 @@ use crate::style::menu_bar::StyleSheet; use iced_core::{Border, Shadow}; use iced_widget::core::{ - event, + Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, event, layout::{Limits, Node}, mouse::{self, Cursor}, overlay, renderer, touch, widget::Tree, - Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, }; /// The condition of when to close a menu diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index 86ed0dfb..d3ff8f1d 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -6,12 +6,12 @@ use std::borrow::Cow; use std::collections::HashMap; use std::rc::Rc; -use iced_widget::core::{renderer, Element}; +use iced_widget::core::{Element, renderer}; use crate::iced_core::{Alignment, Length}; use crate::widget::menu::action::MenuAction; use crate::widget::menu::key_bind::KeyBind; -use crate::widget::{icon, Button}; +use crate::widget::{Button, icon}; use crate::{theme, widget}; /// Nested menu is essentially a tree of items, a menu is a collection of items diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 75a3191c..276435f8 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -48,46 +48,46 @@ // Re-exports from Iced #[doc(inline)] -pub use iced::widget::{canvas, Canvas}; +pub use iced::widget::{Canvas, canvas}; #[doc(inline)] -pub use iced::widget::{checkbox, Checkbox}; +pub use iced::widget::{Checkbox, checkbox}; #[doc(inline)] -pub use iced::widget::{combo_box, ComboBox}; +pub use iced::widget::{ComboBox, combo_box}; #[doc(inline)] -pub use iced::widget::{container, Container}; +pub use iced::widget::{Container, container}; #[doc(inline)] -pub use iced::widget::{horizontal_space, vertical_space, Space}; +pub use iced::widget::{Space, horizontal_space, vertical_space}; #[doc(inline)] -pub use iced::widget::{image, Image}; +pub use iced::widget::{Image, image}; #[doc(inline)] -pub use iced::widget::{lazy, Lazy}; +pub use iced::widget::{Lazy, lazy}; #[doc(inline)] -pub use iced::widget::{mouse_area, MouseArea}; +pub use iced::widget::{MouseArea, mouse_area}; #[doc(inline)] -pub use iced::widget::{pane_grid, PaneGrid}; +pub use iced::widget::{PaneGrid, pane_grid}; #[doc(inline)] -pub use iced::widget::{progress_bar, ProgressBar}; +pub use iced::widget::{ProgressBar, progress_bar}; #[doc(inline)] -pub use iced::widget::{responsive, Responsive}; +pub use iced::widget::{Responsive, responsive}; #[doc(inline)] -pub use iced::widget::{slider, vertical_slider, Slider, VerticalSlider}; +pub use iced::widget::{Slider, VerticalSlider, slider, vertical_slider}; #[doc(inline)] -pub use iced::widget::{svg, Svg}; +pub use iced::widget::{Svg, svg}; #[doc(inline)] -pub use iced::widget::{text_editor, TextEditor}; +pub use iced::widget::{TextEditor, text_editor}; #[doc(inline)] pub use iced_core::widget::{Id, Operation, Widget}; @@ -113,7 +113,7 @@ pub(crate) mod common; pub mod calendar; #[doc(inline)] -pub use calendar::{calendar, Calendar}; +pub use calendar::{Calendar, calendar}; pub mod card; #[doc(inline)] @@ -129,10 +129,10 @@ pub use iced::widget::qr_code; pub mod context_drawer; #[doc(inline)] -pub use context_drawer::{context_drawer, ContextDrawer}; +pub use context_drawer::{ContextDrawer, context_drawer}; #[doc(inline)] -pub use column::{column, Column}; +pub use column::{Column, column}; pub mod column { //! A container which aligns its children in a column. @@ -159,21 +159,21 @@ pub mod column { pub mod layer_container; #[doc(inline)] -pub use layer_container::{layer_container, LayerContainer}; +pub use layer_container::{LayerContainer, layer_container}; pub mod context_menu; #[doc(inline)] -pub use context_menu::{context_menu, ContextMenu}; +pub use context_menu::{ContextMenu, context_menu}; pub mod dialog; #[doc(inline)] -pub use dialog::{dialog, Dialog}; +pub use dialog::{Dialog, dialog}; /// An element to distinguish a boundary between two elements. pub mod divider { /// Horizontal variant of a divider. pub mod horizontal { - use iced::widget::{horizontal_rule, Rule}; + use iced::widget::{Rule, horizontal_rule}; /// Horizontal divider with default thickness #[must_use] @@ -196,7 +196,7 @@ pub mod divider { /// Vertical variant of a divider. pub mod vertical { - use iced::widget::{vertical_rule, Rule}; + use iced::widget::{Rule, vertical_rule}; /// Vertical divider with default thickness #[must_use] @@ -220,35 +220,35 @@ pub mod divider { pub mod dnd_destination; #[doc(inline)] -pub use dnd_destination::{dnd_destination, DndDestination}; +pub use dnd_destination::{DndDestination, dnd_destination}; pub mod dnd_source; #[doc(inline)] -pub use dnd_source::{dnd_source, DndSource}; +pub use dnd_source::{DndSource, dnd_source}; pub mod dropdown; #[doc(inline)] -pub use dropdown::{dropdown, Dropdown}; +pub use dropdown::{Dropdown, dropdown}; pub mod flex_row; #[doc(inline)] -pub use flex_row::{flex_row, FlexRow}; +pub use flex_row::{FlexRow, flex_row}; pub mod grid; #[doc(inline)] -pub use grid::{grid, Grid}; +pub use grid::{Grid, grid}; mod header_bar; #[doc(inline)] -pub use header_bar::{header_bar, HeaderBar}; +pub use header_bar::{HeaderBar, header_bar}; pub mod icon; #[doc(inline)] -pub use icon::{icon, Icon}; +pub use icon::{Icon, icon}; pub mod id_container; #[doc(inline)] -pub use id_container::{id_container, IdContainer}; +pub use id_container::{IdContainer, id_container}; #[cfg(feature = "animated-image")] pub mod frames; @@ -257,7 +257,7 @@ pub use taffy::JustifyContent; pub mod list; #[doc(inline)] -pub use list::{list_column, ListColumn}; +pub use list::{ListColumn, list_column}; pub mod menu; @@ -267,22 +267,22 @@ pub use nav_bar::{nav_bar, nav_bar_dnd}; pub mod nav_bar_toggle; #[doc(inline)] -pub use nav_bar_toggle::{nav_bar_toggle, NavBarToggle}; +pub use nav_bar_toggle::{NavBarToggle, nav_bar_toggle}; pub mod popover; #[doc(inline)] -pub use popover::{popover, Popover}; +pub use popover::{Popover, popover}; pub mod radio; #[doc(inline)] -pub use radio::{radio, Radio}; +pub use radio::{Radio, radio}; pub mod rectangle_tracker; #[doc(inline)] -pub use rectangle_tracker::{rectangle_tracking_container, RectangleTracker}; +pub use rectangle_tracker::{RectangleTracker, rectangle_tracking_container}; #[doc(inline)] -pub use row::{row, Row}; +pub use row::{Row, row}; pub mod row { //! A container which aligns its children in a row. @@ -319,30 +319,30 @@ pub mod settings; pub mod spin_button; #[doc(inline)] -pub use spin_button::{spin_button, vertical as vertical_spin_button, SpinButton}; +pub use spin_button::{SpinButton, spin_button, vertical as vertical_spin_button}; pub mod tab_bar; pub mod text; #[doc(inline)] -pub use text::{text, Text}; +pub use text::{Text, text}; pub mod text_input; #[doc(inline)] pub use text_input::{ - editable_input, inline_input, search_input, secure_input, text_input, TextInput, + TextInput, editable_input, inline_input, search_input, secure_input, text_input, }; pub mod toaster; #[doc(inline)] -pub use toaster::{toaster, Toast, ToastId, Toasts}; +pub use toaster::{Toast, ToastId, Toasts, toaster}; mod toggler; #[doc(inline)] pub use toggler::toggler; #[doc(inline)] -pub use tooltip::{tooltip, Tooltip}; +pub use tooltip::{Tooltip, tooltip}; #[cfg(all(feature = "wayland", feature = "winit"))] pub mod wayland; diff --git a/src/widget/nav_bar_toggle.rs b/src/widget/nav_bar_toggle.rs index dd4b788c..23495e3b 100644 --- a/src/widget/nav_bar_toggle.rs +++ b/src/widget/nav_bar_toggle.rs @@ -3,7 +3,7 @@ //! A button for toggling the navigation side panel. -use crate::{widget, Element}; +use crate::{Element, widget}; use derive_setters::Setters; #[derive(Setters)] diff --git a/src/widget/popover.rs b/src/widget/popover.rs index 12a64d06..30d5828e 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -9,7 +9,7 @@ use iced_core::mouse; use iced_core::overlay; use iced_core::renderer; use iced_core::touch; -use iced_core::widget::{tree, Operation, Tree}; +use iced_core::widget::{Operation, Tree, tree}; use iced_core::{ Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, }; diff --git a/src/widget/rectangle_tracker/mod.rs b/src/widget/rectangle_tracker/mod.rs index 3e7753e9..632578ff 100644 --- a/src/widget/rectangle_tracker/mod.rs +++ b/src/widget/rectangle_tracker/mod.rs @@ -1,8 +1,8 @@ mod subscription; +use iced::Vector; use iced::futures::channel::mpsc::UnboundedSender; use iced::widget::Container; -use iced::Vector; pub use subscription::*; use iced_core::event::{self, Event}; diff --git a/src/widget/responsive_container.rs b/src/widget/responsive_container.rs index 2f15a889..92bedef1 100644 --- a/src/widget/responsive_container.rs +++ b/src/widget/responsive_container.rs @@ -6,7 +6,7 @@ use iced_core::layout; use iced_core::mouse; use iced_core::overlay; use iced_core::renderer; -use iced_core::widget::{tree, Id, Tree}; +use iced_core::widget::{Id, Tree, tree}; use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; pub(crate) fn responsive_container<'a, Message: 'static, Theme, E>( diff --git a/src/widget/responsive_menu_bar.rs b/src/widget/responsive_menu_bar.rs index 38857100..41f9eae8 100644 --- a/src/widget/responsive_menu_bar.rs +++ b/src/widget/responsive_menu_bar.rs @@ -3,8 +3,8 @@ use std::collections::HashMap; use apply::Apply; use crate::{ - widget::{button, icon, responsive_container}, Core, Element, + widget::{button, icon, responsive_container}, }; use super::menu; diff --git a/src/widget/segmented_button/mod.rs b/src/widget/segmented_button/mod.rs index 34b6ec74..e609d70b 100644 --- a/src/widget/segmented_button/mod.rs +++ b/src/widget/segmented_button/mod.rs @@ -79,14 +79,14 @@ mod style; mod vertical; mod widget; -pub use self::horizontal::{horizontal, HorizontalSegmentedButton}; +pub use self::horizontal::{HorizontalSegmentedButton, horizontal}; pub use self::model::{ BuilderEntity, Entity, EntityMut, Model, ModelBuilder, MultiSelect, MultiSelectEntityMut, MultiSelectModel, Selectable, SingleSelect, SingleSelectEntityMut, SingleSelectModel, }; pub use self::style::{Appearance, ItemAppearance, ItemStatusAppearance, StyleSheet}; -pub use self::vertical::{vertical, VerticalSegmentedButton}; -pub use self::widget::{focus, Id, SegmentedButton, SegmentedVariant}; +pub use self::vertical::{VerticalSegmentedButton, vertical}; +pub use self::widget::{Id, SegmentedButton, SegmentedVariant, focus}; /// Associates extra data with an external secondary map. /// diff --git a/src/widget/segmented_button/style.rs b/src/widget/segmented_button/style.rs index 30fa3363..102b3686 100644 --- a/src/widget/segmented_button/style.rs +++ b/src/widget/segmented_button/style.rs @@ -1,7 +1,7 @@ // Copyright 2022 System76 // SPDX-License-Identifier: MPL-2.0 -use iced_core::{border::Radius, Background, Color}; +use iced_core::{Background, Color, border::Radius}; /// Appearance of the segmented button. #[derive(Default, Clone, Copy)] diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index 0a5406f7..6aa4e761 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -4,12 +4,11 @@ use std::borrow::Cow; use crate::{ - theme, - widget::{column, container, flex_row, horizontal_space, row, text, FlexRow, Row}, - Element, + Element, theme, + widget::{FlexRow, Row, column, container, flex_row, horizontal_space, row, text}, }; use derive_setters::Setters; -use iced_core::{text::Wrapping, Length}; +use iced_core::{Length, text::Wrapping}; use taffy::AlignContent; /// A settings item aligned in a row diff --git a/src/widget/settings/mod.rs b/src/widget/settings/mod.rs index 805e58c8..984dd081 100644 --- a/src/widget/settings/mod.rs +++ b/src/widget/settings/mod.rs @@ -5,10 +5,10 @@ pub mod item; pub mod section; pub use self::item::{flex_item, flex_item_row, item, item_row}; -pub use self::section::{section, Section}; +pub use self::section::{Section, section}; -use crate::widget::{column, Column}; -use crate::{theme, Element}; +use crate::widget::{Column, column}; +use crate::{Element, theme}; /// A column with a predefined style for creating a settings panel #[must_use] diff --git a/src/widget/settings/section.rs b/src/widget/settings/section.rs index 71b9a92e..bc885005 100644 --- a/src/widget/settings/section.rs +++ b/src/widget/settings/section.rs @@ -1,8 +1,8 @@ // Copyright 2022 System76 // SPDX-License-Identifier: MPL-2.0 -use crate::widget::{column, text, ListColumn}; use crate::Element; +use crate::widget::{ListColumn, column, text}; use std::borrow::Cow; /// A section within a settings view column. diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index 8739342d..1a2f3e0f 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -4,9 +4,8 @@ //! A control for incremental adjustments of a value. use crate::{ - theme, + Element, theme, widget::{button, column, container, icon, row, text}, - Element, }; use apply::Apply; use iced::{Alignment, Length}; diff --git a/src/widget/text_input/style.rs b/src/widget/text_input/style.rs index aa25c86e..8af5e63e 100644 --- a/src/widget/text_input/style.rs +++ b/src/widget/text_input/style.rs @@ -4,7 +4,7 @@ //! Change the appearance of a text input. -use iced_core::{border::Radius, Background, Color}; +use iced_core::{Background, Color, border::Radius}; /// The appearance of a text input. #[derive(Debug, Clone, Copy)] diff --git a/src/widget/toaster/widget.rs b/src/widget/toaster/widget.rs index bde5c890..f6324e15 100644 --- a/src/widget/toaster/widget.rs +++ b/src/widget/toaster/widget.rs @@ -4,15 +4,15 @@ use iced::{Limits, Size}; use iced_core::layout::Node; +use iced_core::Element; +use iced_core::Overlay; use iced_core::event::{self, Event}; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; use iced_core::renderer::{self}; -use iced_core::widget::tree::Tree; use iced_core::widget::Operation; -use iced_core::Element; -use iced_core::Overlay; +use iced_core::widget::tree::Tree; use iced_core::{Clipboard, Layout, Length, Point, Rectangle, Shell, Vector, Widget}; pub struct Toaster<'a, Message, Theme, Renderer> { diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index 47656a01..65179d99 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -1,7 +1,7 @@ // Copyright 2022 System76 // SPDX-License-Identifier: MPL-2.0 -use iced::{widget, Length}; +use iced::{Length, widget}; use iced_core::text; pub fn toggler<'a, Message, Theme: iced_widget::toggler::Catalog, Renderer>( diff --git a/src/widget/warning.rs b/src/widget/warning.rs index f05a08f9..942ffb8b 100644 --- a/src/widget/warning.rs +++ b/src/widget/warning.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 use super::icon; -use crate::{theme, widget, Element, Renderer, Theme}; +use crate::{Element, Renderer, Theme, theme, widget}; use apply::Apply; use iced::{Alignment, Background, Color, Length}; use iced_core::{Border, Shadow}; diff --git a/src/widget/wayland/tooltip/mod.rs b/src/widget/wayland/tooltip/mod.rs index 79a2fda9..947d1e83 100644 --- a/src/widget/wayland/tooltip/mod.rs +++ b/src/widget/wayland/tooltip/mod.rs @@ -5,7 +5,7 @@ pub mod widget; // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -use iced_core::{border::Radius, Background, Color, Vector}; +use iced_core::{Background, Color, Vector, border::Radius}; use crate::theme::THEME; diff --git a/src/widget/wayland/tooltip/widget.rs b/src/widget/wayland/tooltip/widget.rs index 35dd14de..5194d5c7 100644 --- a/src/widget/wayland/tooltip/widget.rs +++ b/src/widget/wayland/tooltip/widget.rs @@ -16,14 +16,14 @@ use iced_runtime::core::widget::Id; use iced_core::event::{self, Event}; use iced_core::renderer; use iced_core::touch; -use iced_core::widget::tree::{self, Tree}; use iced_core::widget::Operation; -use iced_core::{layout, svg}; -use iced_core::{mouse, Border}; -use iced_core::{overlay, Shadow}; +use iced_core::widget::tree::{self, Tree}; use iced_core::{ Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget, }; +use iced_core::{Border, mouse}; +use iced_core::{Shadow, overlay}; +use iced_core::{layout, svg}; pub use super::{Catalog, Style}; @@ -75,14 +75,14 @@ impl<'a, Message, TopLevelMessage> Tooltip<'a, Message, TopLevelMessage> { content: impl Into>, settings: Option< impl Fn(Rectangle) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings - + Send - + Sync - + 'static, - >, - view: impl Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, + >, + view: impl Fn() -> crate::Element<'static, crate::Action> + + Send + + Sync + + 'static, on_leave: Message, on_surface_action: impl Fn(crate::surface::Action) -> Message + 'static, ) -> Self { diff --git a/src/widget/wrapper.rs b/src/widget/wrapper.rs index 219254c7..0579c4b2 100644 --- a/src/widget/wrapper.rs +++ b/src/widget/wrapper.rs @@ -5,8 +5,8 @@ use std::{ }; use crate::Element; -use iced::{event, Length, Rectangle, Size}; -use iced_core::{id::Id, widget, widget::tree, Widget}; +use iced::{Length, Rectangle, Size, event}; +use iced_core::{Widget, id::Id, widget, widget::tree}; #[derive(Debug)] pub struct RcWrapper { From cb682be3c887bcbf591309d350bac4d4710e104b Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Fri, 21 Mar 2025 12:23:46 -0400 Subject: [PATCH 155/556] fix(applet): fixes for recent updates --- src/applet/mod.rs | 4 ++-- src/widget/dropdown/widget.rs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/applet/mod.rs b/src/applet/mod.rs index a89f4043..cf2b41b4 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -108,7 +108,7 @@ impl Context { #[must_use] pub fn suggested_size(&self, is_symbolic: bool) -> (u16, u16) { match &self.size { - Size::PanelSize(ref size) => { + Size::PanelSize(size) => { let s = size.get_applet_icon_size(is_symbolic) as u16; (s, s) } @@ -142,7 +142,7 @@ impl Context { #[must_use] pub fn suggested_padding(&self, is_symbolic: bool) -> u16 { match &self.size { - Size::PanelSize(ref size) => size.get_applet_padding(is_symbolic), + Size::PanelSize(size) => size.get_applet_padding(is_symbolic), Size::Hardcoded(_) => 8, } } diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 826060d0..e2df1a55 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -536,7 +536,6 @@ pub fn update< let on_close = surface::action::destroy_popup(id); let on_surface_action_clone = on_surface_action.clone(); let translation = layout.virtual_offset(); - dbg!(translation); let get_popup_action = surface::action::simple_popup::< AppMessage, Box< From ae5bb40d6e5945d54c892cc5bcfdf9480252d9f1 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 24 Mar 2025 03:36:49 +0100 Subject: [PATCH 156/556] fix(text_input): conflicts with keyboard focus navigation --- src/widget/text_input/input.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 52fde469..14938572 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -647,14 +647,10 @@ where }; } - if !state - .is_focused - .as_ref() - .map(|f| f.updated_at == LAST_FOCUS_UPDATE.with(|f| f.get())) - .unwrap_or_default() - { - state.unfocus(); - // state.is_read_only = true; + if let Some(f) = state.is_focused.as_ref() { + if f.updated_at != LAST_FOCUS_UPDATE.with(|f| f.get()) { + state.unfocus(); + } } self.is_read_only = state.is_read_only; @@ -1787,7 +1783,6 @@ pub fn update<'a, Message: Clone + 'static>( // Or to connect the text input to a button. shell.publish(on_tab.clone()); } else { - state.unfocus(); state.is_read_only = true; if let Some(on_unfocus) = on_unfocus { From d011be6feb65c44c9a2f188219c20039c73e2414 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 24 Mar 2025 04:27:41 +0100 Subject: [PATCH 157/556] fix(button): unfocus on click to prevent multiple focused buttons --- src/widget/button/widget.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 3a1df241..bbf5e821 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -724,12 +724,14 @@ pub fn update<'a, Message: Clone>( match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { + // Unfocus the button on clicks in case another widget was clicked. + let state = state(); + state.unfocus(); + if on_press.is_some() || on_press_down.is_some() { let bounds = layout.bounds(); if cursor.is_over(bounds) { - let state = state(); - state.is_pressed = true; if let Some(on_press_down) = on_press_down { From c955c8400f275f9306b8c6a3e0e0d7b0d7408c6b Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 24 Mar 2025 04:36:53 +0100 Subject: [PATCH 158/556] fix(segmented_button): fix widget focus not being applied on click --- src/widget/segmented_button/widget.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 2a05d44a..e0c3a2a8 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -936,6 +936,8 @@ where if can_activate { shell.publish(on_activate(key)); + state.focused = true; + state.focused_item = Item::Tab(key); state.pressed_item = None; return event::Status::Captured; } @@ -1022,6 +1024,8 @@ where if let Some(key) = activate_key { shell.publish(on_activate(key)); + state.focused = true; + state.focused_item = Item::Tab(key); return event::Status::Captured; } } From 2753941aad9bd2a6e2909a09b2350df50765daa9 Mon Sep 17 00:00:00 2001 From: Adam Cosner <160804448+Adam-Cosner@users.noreply.github.com> Date: Mon, 24 Mar 2025 16:48:20 +0000 Subject: [PATCH 159/556] feat(widget): add table widget --- examples/table-view/Cargo.toml | 15 ++ examples/table-view/src/main.rs | 272 ++++++++++++++++++++ justfile | 2 +- src/widget/mod.rs | 4 + src/widget/table/mod.rs | 47 ++++ src/widget/table/model/category.rs | 19 ++ src/widget/table/model/entity.rs | 127 ++++++++++ src/widget/table/model/mod.rs | 365 +++++++++++++++++++++++++++ src/widget/table/model/selection.rs | 115 +++++++++ src/widget/table/widget/compact.rs | 257 +++++++++++++++++++ src/widget/table/widget/mod.rs | 2 + src/widget/table/widget/standard.rs | 375 ++++++++++++++++++++++++++++ 12 files changed, 1599 insertions(+), 1 deletion(-) create mode 100644 examples/table-view/Cargo.toml create mode 100644 examples/table-view/src/main.rs create mode 100644 src/widget/table/mod.rs create mode 100644 src/widget/table/model/category.rs create mode 100644 src/widget/table/model/entity.rs create mode 100644 src/widget/table/model/mod.rs create mode 100644 src/widget/table/model/selection.rs create mode 100644 src/widget/table/widget/compact.rs create mode 100644 src/widget/table/widget/mod.rs create mode 100644 src/widget/table/widget/standard.rs diff --git a/examples/table-view/Cargo.toml b/examples/table-view/Cargo.toml new file mode 100644 index 00000000..ba3bd88e --- /dev/null +++ b/examples/table-view/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "table-view" +version = "0.1.0" +edition = "2021" + +[dependencies] +tracing = "0.1.37" +tracing-subscriber = "0.3.17" +tracing-log = "0.2.0" +chrono = "*" + +[dependencies.libcosmic] +features = ["debug", "multi-window", "wayland", "winit", "desktop", "tokio"] +path = "../.." +default-features = false diff --git a/examples/table-view/src/main.rs b/examples/table-view/src/main.rs new file mode 100644 index 00000000..8b2d4f62 --- /dev/null +++ b/examples/table-view/src/main.rs @@ -0,0 +1,272 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +//! Table API example + +use std::collections::HashMap; + +use chrono::Datelike; +use cosmic::app::{Core, Settings, Task}; +use cosmic::iced_core::Size; +use cosmic::prelude::*; +use cosmic::widget::table; +use cosmic::widget::{self, nav_bar}; +use cosmic::{executor, iced}; + +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash)] +pub enum Category { + #[default] + Name, + Date, + Size, +} + +impl std::fmt::Display for Category { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Name => "Name", + Self::Date => "Date", + Self::Size => "Size", + }) + } +} + +impl table::ItemCategory for Category { + fn width(&self) -> iced::Length { + match self { + Self::Name => iced::Length::Fill, + Self::Date => iced::Length::Fixed(200.0), + Self::Size => iced::Length::Fixed(150.0), + } + } +} + +struct Item { + name: String, + date: chrono::DateTime, + size: u64, +} + +impl Default for Item { + fn default() -> Self { + Self { + name: Default::default(), + date: Default::default(), + size: Default::default(), + } + } +} + +impl table::ItemInterface for Item { + fn get_icon(&self, category: Category) -> Option { + if category == Category::Name { + Some(cosmic::widget::icon::from_name("application-x-executable-symbolic").icon()) + } else { + None + } + } + + fn get_text(&self, category: Category) -> std::borrow::Cow<'static, str> { + match category { + Category::Name => self.name.clone().into(), + Category::Date => self.date.format("%Y/%m/%d").to_string().into(), + Category::Size => format!("{} items", self.size).into(), + } + } + + fn compare(&self, other: &Self, category: Category) -> std::cmp::Ordering { + match category { + Category::Name => self.name.to_lowercase().cmp(&other.name.to_lowercase()), + Category::Date => self.date.cmp(&other.date), + Category::Size => self.size.cmp(&other.size), + } + } +} + +/// Runs application with these settings +#[rustfmt::skip] +fn main() -> Result<(), Box> { + tracing_subscriber::fmt::init(); + let _ = tracing_log::LogTracer::init(); + + let settings = Settings::default() + .size(Size::new(1024., 768.)); + + cosmic::app::run::(settings, ())?; + + Ok(()) +} + +/// Messages that are used specifically by our [`App`]. +#[derive(Clone, Debug)] +pub enum Message { + ItemSelect(table::Entity), + CategorySelect(Category), + PrintMsg(String), + NoOp, +} + +/// The [`App`] stores application-specific state. +pub struct App { + core: Core, + table_model: table::SingleSelectModel, +} + +/// Implement [`cosmic::Application`] to integrate with COSMIC. +impl cosmic::Application for App { + /// Default async executor to use with the app. + type Executor = executor::Default; + + /// Argument received [`cosmic::Application::new`]. + type Flags = (); + + /// Message type specific to our [`App`]. + type Message = Message; + + /// The unique application ID to supply to the window manager. + const APP_ID: &'static str = "org.cosmic.AppDemoTable"; + + fn core(&self) -> &Core { + &self.core + } + + fn core_mut(&mut self) -> &mut Core { + &mut self.core + } + + /// Creates the application, and optionally emits task on initialize. + fn init(core: Core, _: Self::Flags) -> (Self, Task) { + let mut nav_model = nav_bar::Model::default(); + + nav_model.activate_position(0); + + let mut table_model = + table::Model::new(vec![Category::Name, Category::Date, Category::Size]); + + let _ = table_model.insert(Item { + name: "Foo".into(), + date: chrono::DateTime::default() + .with_day(1) + .unwrap() + .with_month(1) + .unwrap() + .with_year(1970) + .unwrap(), + size: 2, + }); + let _ = table_model.insert(Item { + name: "Bar".into(), + date: chrono::DateTime::default() + .with_day(2) + .unwrap() + .with_month(1) + .unwrap() + .with_year(1970) + .unwrap(), + size: 4, + }); + let _ = table_model.insert(Item { + name: "Baz".into(), + date: chrono::DateTime::default() + .with_day(3) + .unwrap() + .with_month(1) + .unwrap() + .with_year(1970) + .unwrap(), + size: 12, + }); + + let app = App { core, table_model }; + + let command = Task::none(); + + (app, command) + } + + /// Handle application events here. + fn update(&mut self, message: Self::Message) -> Task { + match message { + Message::ItemSelect(entity) => self.table_model.activate(entity), + Message::CategorySelect(category) => { + let mut ascending = true; + if let Some(old_sort) = self.table_model.get_sort() { + if old_sort.0 == category { + ascending = !old_sort.1; + } + } + self.table_model.sort(category, ascending) + } + Message::PrintMsg(string) => tracing_log::log::info!("{}", string), + Message::NoOp => {} + } + Task::none() + } + + /// Creates a view after each update. + fn view(&self) -> Element { + cosmic::widget::responsive(|size| { + if size.width < 600.0 { + widget::compact_table(&self.table_model) + .on_item_left_click(Message::ItemSelect) + .item_context(|item| { + Some(widget::menu::items( + &HashMap::new(), + vec![widget::menu::Item::Button( + format!("Action on {}", item.name), + None, + Action::None, + )], + )) + }) + .apply(Element::from) + } else { + widget::table(&self.table_model) + .on_item_left_click(Message::ItemSelect) + .on_category_left_click(Message::CategorySelect) + .item_context(|item| { + Some(widget::menu::items( + &HashMap::new(), + vec![widget::menu::Item::Button( + format!("Action on {}", item.name), + None, + Action::None, + )], + )) + }) + .category_context(|category| { + Some(widget::menu::items( + &HashMap::new(), + vec![ + widget::menu::Item::Button( + format!("Action on {} category", category.to_string()), + None, + Action::None, + ), + widget::menu::Item::Button( + format!("Other action on {} category", category.to_string()), + None, + Action::None, + ), + ], + )) + }) + .apply(Element::from) + } + }) + .into() + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Action { + None, +} + +impl widget::menu::Action for Action { + type Message = Message; + + fn message(&self) -> Self::Message { + Message::NoOp + } +} diff --git a/justfile b/justfile index 0280da7c..4653434e 100644 --- a/justfile +++ b/justfile @@ -1,4 +1,4 @@ -examples := 'applet application calendar config context-menu cosmic image-button menu multi-window nav-context open-dialog' +examples := 'applet application calendar config context-menu cosmic image-button menu multi-window nav-context open-dialog table-view' clippy_args := '-W clippy::all -W clippy::pedantic' # Check for errors and linter warnings diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 276435f8..ac8cf7f8 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -323,6 +323,10 @@ pub use spin_button::{SpinButton, spin_button, vertical as vertical_spin_button} pub mod tab_bar; +pub mod table; +#[doc(inline)] +pub use table::{compact_table, table}; + pub mod text; #[doc(inline)] pub use text::{Text, text}; diff --git a/src/widget/table/mod.rs b/src/widget/table/mod.rs new file mode 100644 index 00000000..c39a393d --- /dev/null +++ b/src/widget/table/mod.rs @@ -0,0 +1,47 @@ +//! A widget allowing the user to display tables of information with optional sorting by category +//! + +pub mod model; +pub use model::{ + category::ItemCategory, + category::ItemInterface, + selection::{MultiSelect, SingleSelect}, + Entity, Model, +}; +pub mod widget; +pub use widget::compact::CompactTableView; +pub use widget::standard::TableView; + +pub type SingleSelectTableView<'a, Item, Category, Message> = + TableView<'a, SingleSelect, Item, Category, Message>; +pub type SingleSelectModel = Model; + +pub type MultiSelectTableView<'a, Item, Category, Message> = + TableView<'a, MultiSelect, Item, Category, Message>; +pub type MultiSelectModel = Model; + +pub fn table<'a, SelectionMode, Item, Category, Message>( + model: &'a Model, +) -> TableView<'a, SelectionMode, Item, Category, Message> +where + Message: Clone, + SelectionMode: Default, + Category: ItemCategory, + Item: ItemInterface, + Model: model::selection::Selectable, +{ + TableView::new(model) +} + +pub fn compact_table<'a, SelectionMode, Item, Category, Message>( + model: &'a Model, +) -> CompactTableView<'a, SelectionMode, Item, Category, Message> +where + Message: Clone, + SelectionMode: Default, + Category: ItemCategory, + Item: ItemInterface, + Model: model::selection::Selectable, +{ + CompactTableView::new(model) +} diff --git a/src/widget/table/model/category.rs b/src/widget/table/model/category.rs new file mode 100644 index 00000000..e9bb7477 --- /dev/null +++ b/src/widget/table/model/category.rs @@ -0,0 +1,19 @@ +use std::borrow::Cow; + +use crate::widget::Icon; + +/// Implementation of std::fmt::Display allows user to customize the header +/// Ideally, this is implemented on an enum. +pub trait ItemCategory: + Default + std::fmt::Display + Clone + Copy + PartialEq + Eq + std::hash::Hash +{ + /// Function that gets the width of the data + fn width(&self) -> iced::Length; +} + +pub trait ItemInterface { + fn get_icon(&self, category: Category) -> Option; + fn get_text(&self, category: Category) -> Cow<'static, str>; + + fn compare(&self, other: &Self, category: Category) -> std::cmp::Ordering; +} diff --git a/src/widget/table/model/entity.rs b/src/widget/table/model/entity.rs new file mode 100644 index 00000000..44dd79a5 --- /dev/null +++ b/src/widget/table/model/entity.rs @@ -0,0 +1,127 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use slotmap::{SecondaryMap, SparseSecondaryMap}; + +use super::{ + category::{ItemCategory, ItemInterface}, + Entity, Model, Selectable, +}; + +/// A newly-inserted item which may have additional actions applied to it. +pub struct EntityMut< + 'a, + SelectionMode: Default, + Item: ItemInterface, + Category: ItemCategory, +> { + pub(super) id: Entity, + pub(super) model: &'a mut Model, +} + +impl<'a, SelectionMode: Default, Item: ItemInterface, Category: ItemCategory> + EntityMut<'a, SelectionMode, Item, Category> +where + Model: Selectable, +{ + /// Activates the newly-inserted item. + /// + /// ```ignore + /// model.insert().text("Item A").activate(); + /// ``` + #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + pub fn activate(self) -> Self { + self.model.activate(self.id); + self + } + + /// Associates extra data with an external secondary map. + /// + /// The secondary map internally uses a `Vec`, so should only be used for data that + /// is commonly associated. + /// + /// ```ignore + /// let mut secondary_data = segmented_button::SecondaryMap::default(); + /// model.insert().text("Item A").secondary(&mut secondary_data, String::new("custom data")); + /// ``` + #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + pub fn secondary(self, map: &mut SecondaryMap, data: Data) -> Self { + map.insert(self.id, data); + self + } + + /// Associates extra data with an external sparse secondary map. + /// + /// Sparse maps internally use a `HashMap`, for data that is sparsely associated. + /// + /// ```ignore + /// let mut secondary_data = segmented_button::SparseSecondaryMap::default(); + /// model.insert().text("Item A").secondary(&mut secondary_data, String::new("custom data")); + /// ``` + #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + pub fn secondary_sparse( + self, + map: &mut SparseSecondaryMap, + data: Data, + ) -> Self { + map.insert(self.id, data); + self + } + + /// Associates data with the item. + /// + /// There may only be one data component per Rust type. + /// + /// ```ignore + /// model.insert().text("Item A").data(String::from("custom string")); + /// ``` + #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + pub fn data(self, data: Data) -> Self { + self.model.data_set(self.id, data); + self + } + + /// Returns the ID of the item that was inserted. + /// + /// ```ignore + /// let id = model.insert("Item A").id(); + /// ``` + #[must_use] + pub fn id(self) -> Entity { + self.id + } + + #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + pub fn indent(self, indent: u16) -> Self { + self.model.indent_set(self.id, indent); + self + } + + /// Define the position of the item. + #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + pub fn position(self, position: u16) -> Self { + self.model.position_set(self.id, position); + self + } + + /// Swap the position with another item in the model. + #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + pub fn position_swap(self, other: Entity) -> Self { + self.model.position_swap(self.id, other); + self + } + + /// Defines the text for the item. + #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + pub fn item(self, item: Item) -> Self { + self.model.item_set(self.id, item); + self + } + + /// Calls a function with the ID without consuming the wrapper. + #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] + pub fn with_id(self, func: impl FnOnce(Entity)) -> Self { + func(self.id); + self + } +} diff --git a/src/widget/table/model/mod.rs b/src/widget/table/model/mod.rs new file mode 100644 index 00000000..3af94c57 --- /dev/null +++ b/src/widget/table/model/mod.rs @@ -0,0 +1,365 @@ +pub mod category; +pub mod entity; +pub mod selection; + +use std::{ + any::{Any, TypeId}, + collections::{HashMap, VecDeque}, +}; + +use category::{ItemCategory, ItemInterface}; +use entity::EntityMut; +use selection::Selectable; +use slotmap::{SecondaryMap, SlotMap}; + +slotmap::new_key_type! { + /// Unique key type for items in the table + pub struct Entity; +} + +/// The portion of the model used only by the application. +#[derive(Debug, Default)] +pub(super) struct Storage(HashMap>>); + +pub struct Model, Category: ItemCategory> +where + Category: ItemCategory, +{ + pub(super) categories: Vec, + + /// Stores the items + pub(super) items: SlotMap, + + /// Whether the item is selected or not + pub(super) active: SecondaryMap, + + /// Optional indents for the table items + pub(super) indents: SecondaryMap, + + /// Order which the items will be displayed. + pub(super) order: VecDeque, + + /// Stores the current selection(s) + pub(super) selection: SelectionMode, + + /// What category to sort by and whether it's ascending or not + pub(super) sort: Option<(Category, bool)>, + + /// Application-managed data associated with each item + pub(super) storage: Storage, +} + +impl, Category: ItemCategory> + Model +where + Self: Selectable, +{ + pub fn new(categories: Vec) -> Self { + Self { + categories, + items: SlotMap::default(), + active: SecondaryMap::default(), + indents: SecondaryMap::default(), + order: VecDeque::new(), + selection: SelectionMode::default(), + sort: None, + storage: Storage::default(), + } + } + + pub fn categories(&mut self, cats: Vec) { + self.categories = cats; + } + + /// Activates the item in the model. + /// + /// ```ignore + /// model.activate(id); + /// ``` + pub fn activate(&mut self, id: Entity) { + Selectable::activate(self, id); + } + + /// Activates the item at the given position, returning true if it was activated. + pub fn activate_position(&mut self, position: u16) -> bool { + if let Some(entity) = self.entity_at(position) { + self.activate(entity); + return true; + } + + false + } + + /// Removes all items from the model. + /// + /// Any IDs held elsewhere by the application will no longer be usable with the map. + /// The generation is incremented on removal, so the stale IDs will return `None` for + /// any attempt to get values from the map. + /// + /// ```ignore + /// model.clear(); + /// ``` + pub fn clear(&mut self) { + for entity in self.order.clone() { + self.remove(entity); + } + } + + /// Check if an item exists in the map. + /// + /// ```ignore + /// if model.contains_item(id) { + /// println!("ID is still valid"); + /// } + /// ``` + pub fn contains_item(&self, id: Entity) -> bool { + self.items.contains_key(id) + } + + /// Get an immutable reference to data associated with an item. + /// + /// ```ignore + /// if let Some(data) = model.data::(id) { + /// println!("found string on {:?}: {}", id, data); + /// } + /// ``` + pub fn item(&self, id: Entity) -> Option<&Item> { + self.items.get(id) + } + + /// Get a mutable reference to data associated with an item. + pub fn item_mut(&mut self, id: Entity) -> Option<&mut Item> { + self.items.get_mut(id) + } + + /// Associates data with the item. + /// + /// There may only be one data component per Rust type. + /// + /// ```ignore + /// model.data_set::(id, String::from("custom string")); + /// ``` + pub fn item_set(&mut self, id: Entity, data: Item) { + if let Some(item) = self.items.get_mut(id) { + *item = data; + } + } + + /// Get an immutable reference to data associated with an item. + /// + /// ```ignore + /// if let Some(data) = model.data::(id) { + /// println!("found string on {:?}: {}", id, data); + /// } + /// ``` + pub fn data(&self, id: Entity) -> Option<&Data> { + self.storage + .0 + .get(&TypeId::of::()) + .and_then(|storage| storage.get(id)) + .and_then(|data| data.downcast_ref()) + } + + /// Get a mutable reference to data associated with an item. + pub fn data_mut(&mut self, id: Entity) -> Option<&mut Data> { + self.storage + .0 + .get_mut(&TypeId::of::()) + .and_then(|storage| storage.get_mut(id)) + .and_then(|data| data.downcast_mut()) + } + + /// Associates data with the item. + /// + /// There may only be one data component per Rust type. + /// + /// ```ignore + /// model.data_set::(id, String::from("custom string")); + /// ``` + pub fn data_set(&mut self, id: Entity, data: Data) { + if self.contains_item(id) { + self.storage + .0 + .entry(TypeId::of::()) + .or_default() + .insert(id, Box::new(data)); + } + } + + /// Removes a specific data type from the item. + /// + /// ```ignore + /// model.data.remove::(id); + /// ``` + pub fn data_remove(&mut self, id: Entity) { + self.storage + .0 + .get_mut(&TypeId::of::()) + .and_then(|storage| storage.remove(id)); + } + + /// Enable or disable an item. + /// + /// ```ignore + /// model.enable(id, true); + /// ``` + pub fn enable(&mut self, id: Entity, enable: bool) { + if let Some(e) = self.active.get_mut(id) { + *e = enable; + } + } + + /// Get the item that is located at a given position. + #[must_use] + pub fn entity_at(&mut self, position: u16) -> Option { + self.order.get(position as usize).copied() + } + + /// Inserts a new item in the model. + /// + /// ```ignore + /// let id = model.insert().text("Item A").icon("custom-icon").id(); + /// ``` + #[must_use] + pub fn insert(&mut self, item: Item) -> EntityMut { + let id = self.items.insert(item); + self.order.push_back(id); + EntityMut { model: self, id } + } + + /// Check if the given ID is the active ID. + #[must_use] + pub fn is_active(&self, id: Entity) -> bool { + ::is_active(self, id) + } + + /// Check if the item is enabled. + /// + /// ```ignore + /// if model.is_enabled(id) { + /// if let Some(text) = model.text(id) { + /// println!("{text} is enabled"); + /// } + /// } + /// ``` + #[must_use] + pub fn is_enabled(&self, id: Entity) -> bool { + self.active.get(id).map_or(false, |e| *e) + } + + /// Iterates across items in the model in the order that they are displayed. + pub fn iter(&self) -> impl Iterator + '_ { + self.order.iter().copied() + } + + pub fn indent(&self, id: Entity) -> Option { + self.indents.get(id).copied() + } + + pub fn indent_set(&mut self, id: Entity, indent: u16) -> Option { + if !self.contains_item(id) { + return None; + } + + self.indents.insert(id, indent) + } + + pub fn indent_remove(&mut self, id: Entity) -> Option { + self.indents.remove(id) + } + + /// The position of the item in the model. + /// + /// ```ignore + /// if let Some(position) = model.position(id) { + /// println!("found item at {}", position); + /// } + #[must_use] + pub fn position(&self, id: Entity) -> Option { + #[allow(clippy::cast_possible_truncation)] + self.order.iter().position(|k| *k == id).map(|v| v as u16) + } + + /// Change the position of an item in the model. + /// + /// ```ignore + /// if let Some(new_position) = model.position_set(id, 0) { + /// println!("placed item at {}", new_position); + /// } + /// ``` + pub fn position_set(&mut self, id: Entity, position: u16) -> Option { + let Some(index) = self.position(id) else { + return None; + }; + + self.order.remove(index as usize); + + let position = self.order.len().min(position as usize); + + self.order.insert(position, id); + Some(position) + } + + /// Swap the position of two items in the model. + /// + /// Returns false if the swap cannot be performed. + /// + /// ```ignore + /// if model.position_swap(first_id, second_id) { + /// println!("positions swapped"); + /// } + /// ``` + pub fn position_swap(&mut self, first: Entity, second: Entity) -> bool { + let Some(first_index) = self.position(first) else { + return false; + }; + + let Some(second_index) = self.position(second) else { + return false; + }; + + self.order.swap(first_index as usize, second_index as usize); + true + } + + /// Removes an item from the model. + /// + /// The generation of the slot for the ID will be incremented, so this ID will no + /// longer be usable with the map. Subsequent attempts to get values from the map + /// with this ID will return `None` and failed to assign values. + pub fn remove(&mut self, id: Entity) { + self.items.remove(id); + self.deactivate(id); + + for storage in self.storage.0.values_mut() { + storage.remove(id); + } + + if let Some(index) = self.position(id) { + self.order.remove(index as usize); + } + } + + /// Get the sort data + pub fn get_sort(&self) -> Option<(Category, bool)> { + self.sort + } + + /// Sorts items in the model, this should be called before it is drawn after all items have been added for the view + pub fn sort(&mut self, category: Category, ascending: bool) { + self.sort = Some((category, ascending)); + let mut order: Vec = self.order.iter().cloned().collect(); + order.sort_by(|entity_a, entity_b| { + if ascending { + self.item(*entity_a) + .unwrap() + .compare(self.item(*entity_b).unwrap(), category) + } else { + self.item(*entity_b) + .unwrap() + .compare(self.item(*entity_a).unwrap(), category) + } + }); + self.order = order.into(); + } +} diff --git a/src/widget/table/model/selection.rs b/src/widget/table/model/selection.rs new file mode 100644 index 00000000..24b7b67d --- /dev/null +++ b/src/widget/table/model/selection.rs @@ -0,0 +1,115 @@ +// Copyright 2022 System76 +// SPDX-License-Identifier: MPL-2.0 + +//! Describes logic specific to the single-select and multi-select modes of a model. + +use super::{ + category::{ItemCategory, ItemInterface}, + Entity, Model, +}; +use std::collections::HashSet; + +/// Describes a type that has selectable items. +pub trait Selectable { + /// Activate an item. + fn activate(&mut self, id: Entity); + + /// Deactivate an item. + fn deactivate(&mut self, id: Entity); + + /// Checks if the item is active. + fn is_active(&self, id: Entity) -> bool; +} + +/// [`Model`] Ensures that only one key may be selected. +#[derive(Debug, Default)] +pub struct SingleSelect { + pub active: Entity, +} + +impl, Category: ItemCategory> Selectable + for Model +{ + fn activate(&mut self, id: Entity) { + if !self.items.contains_key(id) { + return; + } + + self.selection.active = id; + } + + fn deactivate(&mut self, id: Entity) { + if id == self.selection.active { + self.selection.active = Entity::default(); + } + } + + fn is_active(&self, id: Entity) -> bool { + self.selection.active == id + } +} + +impl, Category: ItemCategory> Model { + /// Get an immutable reference to the data associated with the active item. + #[must_use] + pub fn active_data(&self) -> Option<&Data> { + self.data(self.active()) + } + + /// Get a mutable reference to the data associated with the active item. + #[must_use] + pub fn active_data_mut(&mut self) -> Option<&mut Data> { + self.data_mut(self.active()) + } + + /// Deactivates the active item. + pub fn deactivate(&mut self) { + Selectable::deactivate(self, Entity::default()); + } + + /// The ID of the active item. + #[must_use] + pub fn active(&self) -> Entity { + self.selection.active + } +} + +/// [`Model`] permits multiple keys to be active at a time. +#[derive(Debug, Default)] +pub struct MultiSelect { + pub active: HashSet, +} + +impl, Category: ItemCategory> Selectable + for Model +{ + fn activate(&mut self, id: Entity) { + if !self.items.contains_key(id) { + return; + } + + if !self.selection.active.insert(id) { + self.selection.active.remove(&id); + } + } + + fn deactivate(&mut self, id: Entity) { + self.selection.active.remove(&id); + } + + fn is_active(&self, id: Entity) -> bool { + self.selection.active.contains(&id) + } +} + +impl, Category: ItemCategory> Model { + /// Deactivates the item in the model. + pub fn deactivate(&mut self, id: Entity) { + Selectable::deactivate(self, id); + } + + /// The IDs of the active items. + pub fn active(&self) -> impl Iterator + '_ { + self.selection.active.iter().copied() + } +} diff --git a/src/widget/table/widget/compact.rs b/src/widget/table/widget/compact.rs new file mode 100644 index 00000000..0264be71 --- /dev/null +++ b/src/widget/table/widget/compact.rs @@ -0,0 +1,257 @@ +use derive_setters::Setters; + +use crate::widget::table::model::{ + category::{ItemCategory, ItemInterface}, + selection::Selectable, + Entity, Model, +}; +use crate::{ + theme, + widget::{self, container, menu}, + Apply, Element, +}; +use iced::{Alignment, Border, Padding}; + +#[derive(Setters)] +#[must_use] +pub struct CompactTableView<'a, SelectionMode, Item, Category, Message> +where + Category: ItemCategory, + Item: ItemInterface, + Model: Selectable, + SelectionMode: Default, + Message: Clone + 'static, +{ + pub(super) model: &'a Model, + + #[setters(into)] + pub(super) element_padding: Padding, + + #[setters(into)] + pub(super) item_padding: Padding, + pub(super) item_spacing: u16, + pub(super) icon_size: u16, + + #[setters(into)] + pub(super) divider_padding: Padding, + + // === Item Interaction === + #[setters(skip)] + pub(super) on_item_mb_left: Option Message + 'static>>, + #[setters(skip)] + pub(super) on_item_mb_double: Option Message + 'static>>, + #[setters(skip)] + pub(super) on_item_mb_mid: Option Message + 'static>>, + #[setters(skip)] + pub(super) on_item_mb_right: Option Message + 'static>>, + #[setters(skip)] + pub(super) item_context_builder: Box Option>>>, +} + +impl<'a, SelectionMode, Item, Category, Message> + From> for Element<'a, Message> +where + Category: ItemCategory, + Item: ItemInterface, + Model: Selectable, + SelectionMode: Default, + Message: Clone + 'static, +{ + fn from(val: CompactTableView<'a, SelectionMode, Item, Category, Message>) -> Self { + let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing; + val.model + .iter() + .map(|entity| { + let item = val.model.item(entity).unwrap(); + let selected = val.model.is_active(entity); + let context_menu = (val.item_context_builder)(&item); + + widget::column() + .spacing(val.item_spacing) + .push( + widget::divider::horizontal::default() + .apply(container) + .padding(val.divider_padding), + ) + .push( + widget::row() + .spacing(space_xxxs) + .align_y(Alignment::Center) + .push_maybe( + item.get_icon(Category::default()) + .map(|icon| icon.size(val.icon_size)), + ) + .push( + widget::column() + .push(widget::text::body(item.get_text(Category::default()))) + .push({ + let mut elements = val + .model + .categories + .iter() + .skip_while(|cat| **cat != Category::default()) + .map(|category| { + vec![ + widget::text::caption(item.get_text(*category)) + .apply(Element::from), + widget::text::caption("-").apply(Element::from), + ] + }) + .flatten() + .collect::>>(); + elements.pop(); + elements + .apply(widget::row::with_children) + .spacing(space_xxxs) + .wrap() + }), + ) + .apply(container) + .padding(val.item_padding) + .width(iced::Length::Fill) + .class(theme::Container::custom(move |theme| { + widget::container::Style { + icon_color: if selected { + Some(theme.cosmic().on_accent_color().into()) + } else { + None + }, + text_color: if selected { + Some(theme.cosmic().on_accent_color().into()) + } else { + None + }, + background: if selected { + Some(iced::Background::Color( + theme.cosmic().accent_color().into(), + )) + } else { + None + }, + border: Border { + radius: theme.cosmic().radius_xs().into(), + ..Default::default() + }, + shadow: Default::default(), + } + })) + .apply(widget::mouse_area) + // Left click + .apply(|mouse_area| { + if let Some(ref on_item_mb) = val.on_item_mb_left { + mouse_area.on_press((on_item_mb)(entity)) + } else { + mouse_area + } + }) + // Double click + .apply(|mouse_area| { + if let Some(ref on_item_mb) = val.on_item_mb_left { + mouse_area.on_double_click((on_item_mb)(entity)) + } else { + mouse_area + } + }) + // Middle click + .apply(|mouse_area| { + if let Some(ref on_item_mb) = val.on_item_mb_mid { + mouse_area.on_middle_press((on_item_mb)(entity)) + } else { + mouse_area + } + }) + // Right click + .apply(|mouse_area| { + if let Some(ref on_item_mb) = val.on_item_mb_right { + mouse_area.on_right_press((on_item_mb)(entity)) + } else { + mouse_area + } + }) + .apply(|ma| widget::context_menu(ma, context_menu)), + ) + .apply(Element::from) + }) + .collect::>>() + .apply(widget::column::with_children) + .spacing(val.item_spacing) + .padding(val.element_padding) + .apply(Element::from) + } +} + +impl<'a, SelectionMode, Item, Category, Message> + CompactTableView<'a, SelectionMode, Item, Category, Message> +where + SelectionMode: Default, + Model: Selectable, + Category: ItemCategory, + Item: ItemInterface, + Message: Clone + 'static, +{ + pub fn new(model: &'a Model) -> Self { + let cosmic_theme::Spacing { + space_xxxs, + space_xxs, + .. + } = theme::active().cosmic().spacing; + + Self { + model, + element_padding: Padding::from(0), + + divider_padding: Padding::from(0).left(space_xxxs).right(space_xxxs), + + item_padding: Padding::from(space_xxs).into(), + item_spacing: 0, + icon_size: 48, + + on_item_mb_left: None, + on_item_mb_double: None, + on_item_mb_mid: None, + on_item_mb_right: None, + item_context_builder: Box::new(|_| None), + } + } + + pub fn on_item_left_click(mut self, on_click: F) -> Self + where + F: Fn(Entity) -> Message + 'static, + { + self.on_item_mb_left = Some(Box::new(on_click)); + self + } + + pub fn on_item_double_click(mut self, on_click: F) -> Self + where + F: Fn(Entity) -> Message + 'static, + { + self.on_item_mb_double = Some(Box::new(on_click)); + self + } + + pub fn on_item_middle_click(mut self, on_click: F) -> Self + where + F: Fn(Entity) -> Message + 'static, + { + self.on_item_mb_mid = Some(Box::new(on_click)); + self + } + + pub fn on_item_right_click(mut self, on_click: F) -> Self + where + F: Fn(Entity) -> Message + 'static, + { + self.on_item_mb_right = Some(Box::new(on_click)); + self + } + + pub fn item_context(mut self, context_menu_builder: F) -> Self + where + F: Fn(&Item) -> Option>> + 'static, + Message: 'static, + { + self.item_context_builder = Box::new(context_menu_builder); + self + } +} diff --git a/src/widget/table/widget/mod.rs b/src/widget/table/widget/mod.rs new file mode 100644 index 00000000..0396796e --- /dev/null +++ b/src/widget/table/widget/mod.rs @@ -0,0 +1,2 @@ +pub mod compact; +pub mod standard; diff --git a/src/widget/table/widget/standard.rs b/src/widget/table/widget/standard.rs new file mode 100644 index 00000000..35762eea --- /dev/null +++ b/src/widget/table/widget/standard.rs @@ -0,0 +1,375 @@ +use derive_setters::Setters; + +use crate::widget::table::model::{ + category::{ItemCategory, ItemInterface}, + selection::Selectable, + Entity, Model, +}; +use crate::{ + theme, + widget::{self, container, divider, menu}, + Apply, Element, +}; +use iced::{Alignment, Border, Length, Padding}; + +// THIS IS A PLACEHOLDER UNTIL A MORE SOPHISTICATED WIDGET CAN BE DEVELOPED + +#[derive(Setters)] +#[must_use] +pub struct TableView<'a, SelectionMode, Item, Category, Message> +where + Category: ItemCategory, + Item: ItemInterface, + Model: Selectable, + SelectionMode: Default, + Message: Clone + 'static, +{ + pub(super) model: &'a Model, + + #[setters(into)] + pub(super) element_padding: Padding, + #[setters(into)] + pub(super) width: Length, + #[setters(into)] + pub(super) height: Length, + + #[setters(into)] + pub(super) item_padding: Padding, + pub(super) item_spacing: u16, + pub(super) icon_spacing: u16, + pub(super) icon_size: u16, + + #[setters(into)] + pub(super) divider_padding: Padding, + + // === Item Interaction === + #[setters(skip)] + pub(super) on_item_mb_left: Option Message + 'static>>, + #[setters(skip)] + pub(super) on_item_mb_double: Option Message + 'static>>, + #[setters(skip)] + pub(super) on_item_mb_mid: Option Message + 'static>>, + #[setters(skip)] + pub(super) on_item_mb_right: Option Message + 'static>>, + #[setters(skip)] + pub(super) item_context_builder: Box Option>>>, + // Item DND + + // === Category Interaction === + #[setters(skip)] + pub(super) on_category_mb_left: Option Message + 'static>>, + #[setters(skip)] + pub(super) on_category_mb_double: Option Message + 'static>>, + #[setters(skip)] + pub(super) on_category_mb_mid: Option Message + 'static>>, + #[setters(skip)] + pub(super) on_category_mb_right: Option Message + 'static>>, + #[setters(skip)] + pub(super) category_context_builder: + Box Option>>>, +} + +impl<'a, SelectionMode, Item, Category, Message> + From> for Element<'a, Message> +where + Category: ItemCategory, + Item: ItemInterface, + Model: Selectable, + SelectionMode: Default, + Message: Clone + 'static, +{ + fn from(val: TableView<'a, SelectionMode, Item, Category, Message>) -> Self { + // Header row + let header_row = val + .model + .categories + .iter() + .cloned() + .map(|category| { + let cat_context_tree = (val.category_context_builder)(category); + + let mut sort_state = 0; + + if let Some(sort) = val.model.sort { + if sort.0 == category { + if sort.1 { + sort_state = 1; + } else { + sort_state = 2; + } + } + }; + + // Build the category header + widget::row() + .spacing(val.icon_spacing) + .push(widget::text::heading(category.to_string())) + .push_maybe(match sort_state { + 1 => Some(widget::icon::from_name("pan-up-symbolic").icon()), + 2 => Some(widget::icon::from_name("pan-down-symbolic").icon()), + _ => None, + }) + .apply(container) + .padding( + Padding::default() + .left(val.item_padding.left) + .right(val.item_padding.right), + ) + .width(category.width()) + .apply(widget::mouse_area) + .apply(|mouse_area| { + if let Some(ref on_category_select) = val.on_category_mb_left { + mouse_area.on_press((on_category_select)(category)) + } else { + mouse_area + } + }) + .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 + let items_full = if val.model.items.is_empty() { + vec![divider::horizontal::default() + .apply(container) + .padding(val.divider_padding) + .apply(Element::from)] + } else { + val.model + .iter() + .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); + + vec![ + divider::horizontal::default() + .apply(container) + .padding(val.divider_padding) + .apply(Element::from), + categories + .iter() + .map(|category| { + widget::row() + .spacing(val.icon_spacing) + .push_maybe( + item.get_icon(*category) + .map(|icon| icon.size(val.icon_size)), + ) + .push(widget::text::body(item.get_text(*category))) + .align_y(Alignment::Center) + .apply(container) + .width(category.width()) + .align_y(Alignment::Center) + .apply(Element::from) + }) + .collect::>>() + .apply(widget::row::with_children) + .apply(container) + .padding(val.item_padding) + .class(theme::Container::custom(move |theme| { + widget::container::Style { + icon_color: if selected { + Some(theme.cosmic().on_accent_color().into()) + } else { + None + }, + text_color: if selected { + Some(theme.cosmic().on_accent_color().into()) + } else { + None + }, + background: if selected { + Some(iced::Background::Color( + theme.cosmic().accent_color().into(), + )) + } else { + None + }, + border: Border { + radius: theme.cosmic().radius_xs().into(), + ..Default::default() + }, + shadow: Default::default(), + } + })) + .apply(widget::mouse_area) + // Left click + .apply(|mouse_area| { + if let Some(ref on_item_mb) = val.on_item_mb_left { + mouse_area.on_press((on_item_mb)(entity)) + } else { + mouse_area + } + }) + // Double click + .apply(|mouse_area| { + if let Some(ref on_item_mb) = val.on_item_mb_left { + mouse_area.on_double_click((on_item_mb)(entity)) + } else { + mouse_area + } + }) + // Middle click + .apply(|mouse_area| { + if let Some(ref on_item_mb) = val.on_item_mb_mid { + mouse_area.on_middle_press((on_item_mb)(entity)) + } else { + mouse_area + } + }) + // Right click + .apply(|mouse_area| { + if let Some(ref on_item_mb) = val.on_item_mb_right { + mouse_area.on_right_press((on_item_mb)(entity)) + } else { + mouse_area + } + }) + .apply(|mouse_area| widget::context_menu(mouse_area, item_context)) + .apply(Element::from), + ] + }) + .flatten() + .collect::>>() + }; + vec![vec![header_row], items_full] + .into_iter() + .flatten() + .collect::>>() + .apply(widget::column::with_children) + .width(val.width) + .height(val.height) + .spacing(val.item_spacing) + .padding(val.element_padding) + .apply(Element::from) + } +} + +impl<'a, SelectionMode, Item, Category, Message> + TableView<'a, SelectionMode, Item, Category, Message> +where + SelectionMode: Default, + Model: Selectable, + Category: ItemCategory, + Item: ItemInterface, + Message: Clone + 'static, +{ + pub fn new(model: &'a Model) -> Self { + let cosmic_theme::Spacing { + space_xxxs, + space_xxs, + .. + } = theme::active().cosmic().spacing; + + Self { + model, + + element_padding: Padding::from(0), + width: Length::Fill, + height: Length::Shrink, + + item_padding: Padding::from(space_xxs).into(), + item_spacing: 0, + icon_spacing: space_xxxs, + icon_size: 24, + + divider_padding: Padding::from(0).left(space_xxxs).right(space_xxxs), + + on_item_mb_left: None, + on_item_mb_double: None, + on_item_mb_mid: None, + on_item_mb_right: None, + item_context_builder: Box::new(|_| None), + + on_category_mb_left: None, + on_category_mb_double: None, + on_category_mb_mid: None, + on_category_mb_right: None, + category_context_builder: Box::new(|_| None), + } + } + + pub fn on_item_left_click(mut self, on_click: F) -> Self + where + F: Fn(Entity) -> Message + 'static, + { + self.on_item_mb_left = Some(Box::new(on_click)); + self + } + + pub fn on_item_double_click(mut self, on_click: F) -> Self + where + F: Fn(Entity) -> Message + 'static, + { + self.on_item_mb_double = Some(Box::new(on_click)); + self + } + + pub fn on_item_middle_click(mut self, on_click: F) -> Self + where + F: Fn(Entity) -> Message + 'static, + { + self.on_item_mb_mid = Some(Box::new(on_click)); + self + } + + pub fn on_item_right_click(mut self, on_click: F) -> Self + where + F: Fn(Entity) -> Message + 'static, + { + self.on_item_mb_right = Some(Box::new(on_click)); + self + } + + pub fn item_context(mut self, context_menu_builder: F) -> Self + where + F: Fn(&Item) -> Option>> + 'static, + Message: 'static, + { + self.item_context_builder = Box::new(context_menu_builder); + self + } + + pub fn on_category_left_click(mut self, on_select: F) -> Self + where + F: Fn(Category) -> Message + 'static, + { + self.on_category_mb_left = Some(Box::new(on_select)); + self + } + pub fn on_category_double_click(mut self, on_select: F) -> Self + where + F: Fn(Category) -> Message + 'static, + { + self.on_category_mb_double = Some(Box::new(on_select)); + self + } + pub fn on_category_middle_click(mut self, on_select: F) -> Self + where + F: Fn(Category) -> Message + 'static, + { + self.on_category_mb_mid = Some(Box::new(on_select)); + self + } + + pub fn on_category_right_click(mut self, on_select: F) -> Self + where + F: Fn(Category) -> Message + 'static, + { + self.on_category_mb_right = Some(Box::new(on_select)); + self + } + + pub fn category_context(mut self, context_menu_builder: F) -> Self + where + F: Fn(Category) -> Option>> + 'static, + Message: 'static, + { + self.category_context_builder = Box::new(context_menu_builder); + self + } +} From 61c0fc75438b25b094bd1d9f756cd7d43ce82261 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 26 Mar 2025 16:13:32 +0100 Subject: [PATCH 160/556] fix(popover): modal popover handling broken --- src/widget/popover.rs | 95 +++++++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 39 deletions(-) diff --git a/src/widget/popover.rs b/src/widget/popover.rs index 30d5828e..6c6f6652 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -1,7 +1,7 @@ // Copyright 2022 System76 // SPDX-License-Identifier: MPL-2.0 -//! A widget showing a popup in an overlay positioned relative to another widget. +//! A container which displays an overlay when a popup widget is attached. use iced_core::event::{self, Event}; use iced_core::layout; @@ -9,7 +9,7 @@ use iced_core::mouse; use iced_core::overlay; use iced_core::renderer; use iced_core::touch; -use iced_core::widget::{Operation, Tree, tree}; +use iced_core::widget::{Operation, Tree}; use iced_core::{ Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, }; @@ -30,6 +30,7 @@ pub enum Position { Point(Point), } +/// A container which displays overlays when a popup widget is assigned. #[must_use] pub struct Popover<'a, Message, Renderer> { content: Element<'a, Message, crate::Theme, Renderer>, @@ -50,29 +51,31 @@ impl<'a, Message, Renderer> Popover<'a, Message, Renderer> { } } - /// A modal popup interrupts user inputs and demands action. + /// A modal popup intercepts user inputs while a popup is active. + #[inline] pub fn modal(mut self, modal: bool) -> Self { self.modal = modal; self } /// Emitted when the popup is closed. + #[inline] pub fn on_close(mut self, on_close: Message) -> Self { self.on_close = Some(on_close); self } + #[inline] pub fn popup(mut self, popup: impl Into>) -> Self { self.popup = Some(popup.into()); self } + #[inline] pub fn position(mut self, position: Position) -> Self { self.position = position; self } - - // TODO More options for positioning similar to GdkPopup, xdg_popup } impl Widget @@ -80,14 +83,6 @@ impl Widget where Renderer: iced_core::Renderer, { - fn tag(&self) -> tree::Tag { - tree::Tag::of::() - } - - fn state(&self) -> tree::State { - tree::State::new(State { is_open: true }) - } - fn children(&self) -> Vec { if let Some(popup) = &self.popup { vec![Tree::new(&self.content), Tree::new(popup)] @@ -114,7 +109,7 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let tree = &mut tree.children[0]; + let tree = content_tree_mut(tree); self.content.as_widget().layout(tree, renderer, limits) } @@ -127,7 +122,7 @@ where ) { self.content .as_widget() - .operate(&mut tree.children[0], layout, renderer, operation); + .operate(content_tree_mut(tree), layout, renderer, operation); } fn on_event( @@ -141,26 +136,25 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { - if !self.modal - && matches!( - event, - Event::Mouse(mouse::Event::ButtonPressed(_)) - | Event::Touch(touch::Event::FingerPressed { .. }) - ) - { - let state = tree.state.downcast_mut::(); - let was_open = state.is_open; - state.is_open = cursor_position.is_over(layout.bounds()); - - if let Some(on_close) = self.on_close.clone() { - if was_open && !state.is_open { + if self.popup.is_some() { + if self.modal { + if matches!(event, Event::Mouse(_) | Event::Touch(_)) { + return event::Status::Captured; + } + } else if let Some(on_close) = self.on_close.clone() { + if matches!( + event, + Event::Mouse(mouse::Event::ButtonPressed(_)) + | Event::Touch(touch::Event::FingerPressed { .. }) + ) && !cursor_position.is_over(layout.bounds()) + { shell.publish(on_close); } } } self.content.as_widget_mut().on_event( - &mut tree.children[0], + content_tree_mut(tree), event, layout, cursor_position, @@ -179,8 +173,11 @@ where viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { + if self.modal && self.popup.is_some() && cursor_position.is_over(layout.bounds()) { + return mouse::Interaction::None; + } self.content.as_widget().mouse_interaction( - &tree.children[0], + content_tree(tree), layout, cursor_position, viewport, @@ -199,7 +196,7 @@ where viewport: &Rectangle, ) { self.content.as_widget().draw( - &tree.children[0], + content_tree(tree), renderer, theme, renderer_style, @@ -216,10 +213,6 @@ where renderer: &Renderer, mut translation: Vector, ) -> Option> { - if !tree.state.downcast_mut::().is_open { - return None; - } - if let Some(popup) = &mut self.popup { let bounds = layout.bounds(); @@ -248,10 +241,11 @@ where content: popup, position: self.position, pos: Point::new(translation.x, translation.y), + modal: self.modal, }))) } else { self.content.as_widget_mut().overlay( - &mut tree.children[0], + content_tree_mut(tree), layout, renderer, translation, @@ -267,7 +261,7 @@ where dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { self.content.as_widget().drag_destinations( - &tree.children[0], + content_tree(tree), layout, renderer, dnd_rectangles, @@ -282,8 +276,9 @@ where state: &Tree, p: mouse::Cursor, ) -> iced_accessibility::A11yTree { - let c_state = &state.children[0]; - self.content.as_widget().a11y_nodes(layout, c_state, p) + self.content + .as_widget() + .a11y_nodes(layout, content_tree(state), p) } } @@ -303,6 +298,7 @@ pub struct Overlay<'a, 'b, Message, Renderer> { content: &'a mut Element<'b, Message, crate::Theme, Renderer>, position: Position, pos: Point, + modal: bool, } impl overlay::Overlay @@ -370,6 +366,13 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { + if self.modal + && matches!(event, Event::Mouse(_) | Event::Touch(_)) + && !cursor_position.is_over(layout.bounds()) + { + return event::Status::Captured; + } + self.content.as_widget_mut().on_event( self.tree, event, @@ -389,6 +392,10 @@ where viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { + if self.modal && !cursor_position.is_over(layout.bounds()) { + return mouse::Interaction::None; + } + self.content.as_widget().mouse_interaction( self.tree, layout, @@ -434,3 +441,13 @@ where struct State { is_open: bool, } + +/// The first child in [`Popover::children`] is always the wrapped content. +fn content_tree(tree: &Tree) -> &Tree { + &tree.children[0] +} + +/// The first child in [`Popover::children`] is always the wrapped content. +fn content_tree_mut(tree: &mut Tree) -> &mut Tree { + &mut tree.children[0] +} From 8bce57ed83462be8f9963c0cebbe93c39bea4131 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 26 Mar 2025 16:32:33 +0100 Subject: [PATCH 161/556] chore(table): format code --- src/widget/table/mod.rs | 2 +- src/widget/table/model/entity.rs | 2 +- src/widget/table/model/selection.rs | 2 +- src/widget/table/widget/compact.rs | 5 ++--- src/widget/table/widget/standard.rs | 15 ++++++++------- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/widget/table/mod.rs b/src/widget/table/mod.rs index c39a393d..7063dc8e 100644 --- a/src/widget/table/mod.rs +++ b/src/widget/table/mod.rs @@ -3,10 +3,10 @@ pub mod model; pub use model::{ + Entity, Model, category::ItemCategory, category::ItemInterface, selection::{MultiSelect, SingleSelect}, - Entity, Model, }; pub mod widget; pub use widget::compact::CompactTableView; diff --git a/src/widget/table/model/entity.rs b/src/widget/table/model/entity.rs index 44dd79a5..51c60609 100644 --- a/src/widget/table/model/entity.rs +++ b/src/widget/table/model/entity.rs @@ -4,8 +4,8 @@ use slotmap::{SecondaryMap, SparseSecondaryMap}; use super::{ - category::{ItemCategory, ItemInterface}, Entity, Model, Selectable, + category::{ItemCategory, ItemInterface}, }; /// A newly-inserted item which may have additional actions applied to it. diff --git a/src/widget/table/model/selection.rs b/src/widget/table/model/selection.rs index 24b7b67d..20a07248 100644 --- a/src/widget/table/model/selection.rs +++ b/src/widget/table/model/selection.rs @@ -4,8 +4,8 @@ //! Describes logic specific to the single-select and multi-select modes of a model. use super::{ - category::{ItemCategory, ItemInterface}, Entity, Model, + category::{ItemCategory, ItemInterface}, }; use std::collections::HashSet; diff --git a/src/widget/table/widget/compact.rs b/src/widget/table/widget/compact.rs index 0264be71..cc3bff1c 100644 --- a/src/widget/table/widget/compact.rs +++ b/src/widget/table/widget/compact.rs @@ -1,14 +1,13 @@ use derive_setters::Setters; use crate::widget::table::model::{ + Entity, Model, category::{ItemCategory, ItemInterface}, selection::Selectable, - Entity, Model, }; use crate::{ - theme, + Apply, Element, theme, widget::{self, container, menu}, - Apply, Element, }; use iced::{Alignment, Border, Padding}; diff --git a/src/widget/table/widget/standard.rs b/src/widget/table/widget/standard.rs index 35762eea..620d33fd 100644 --- a/src/widget/table/widget/standard.rs +++ b/src/widget/table/widget/standard.rs @@ -1,14 +1,13 @@ use derive_setters::Setters; use crate::widget::table::model::{ + Entity, Model, category::{ItemCategory, ItemInterface}, selection::Selectable, - Entity, Model, }; use crate::{ - theme, + Apply, Element, theme, widget::{self, container, divider, menu}, - Apply, Element, }; use iced::{Alignment, Border, Length, Padding}; @@ -132,10 +131,12 @@ where .apply(Element::from); // Build the items let items_full = if val.model.items.is_empty() { - vec![divider::horizontal::default() - .apply(container) - .padding(val.divider_padding) - .apply(Element::from)] + vec![ + divider::horizontal::default() + .apply(container) + .padding(val.divider_padding) + .apply(Element::from), + ] } else { val.model .iter() From c0b0b817e83680379a77110ab9885d4b6b31e1c9 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 26 Mar 2025 16:34:22 +0100 Subject: [PATCH 162/556] fix(text_input): send on_unfocus message where they were missing --- src/widget/text_input/input.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 14938572..47841c06 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -650,6 +650,7 @@ where if let Some(f) = state.is_focused.as_ref() { if f.updated_at != LAST_FOCUS_UPDATE.with(|f| f.get()) { state.unfocus(); + state.emit_unfocus = true; } } @@ -876,6 +877,16 @@ where } } + let state = tree.state.downcast_mut::(); + + if let Some(on_unfocus) = self.on_unfocus.as_ref() { + if state.emit_unfocus { + state.emit_unfocus = false; + eprintln!("unfocus"); + shell.publish(on_unfocus.clone()); + } + } + let dnd_id = self.dnd_id(); let id = Widget::id(self); update( @@ -1532,6 +1543,10 @@ pub fn update<'a, Message: Clone + 'static>( return event::Status::Captured; } else { state.unfocus(); + + if let Some(on_unfocus) = on_unfocus { + shell.publish(on_unfocus.clone()); + } } } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) @@ -2486,6 +2501,7 @@ pub struct State { pub dirty: bool, pub is_secure: bool, pub is_read_only: bool, + pub emit_unfocus: bool, select_on_focus: bool, is_focused: Option, dragging_state: Option, @@ -2560,6 +2576,7 @@ impl State { label: crate::Plain::default(), helper_text: crate::Plain::default(), is_read_only, + emit_unfocus: false, is_focused: None, select_on_focus: false, dragging_state: None, From 0eb86850ce3672bfce3ebaa9c34210ab23261a66 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 27 Mar 2025 16:11:27 +0100 Subject: [PATCH 163/556] fix(text_input): support numpad keys --- src/widget/text_input/input.rs | 253 ++++++++++++++++++--------------- 1 file changed, 135 insertions(+), 118 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 47841c06..405e876f 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -1303,6 +1303,7 @@ pub fn layout( layout::Node::with_children(limits.resolve(width, size.height, size), nodes) } +// TODO: Merge into widget method since iced has done the same. /// Processes an [`Event`] and updates the [`State`] of a [`TextInput`] /// accordingly. #[allow(clippy::too_many_arguments)] @@ -1579,7 +1580,12 @@ pub fn update<'a, Message: Clone + 'static>( return event::Status::Captured; } } - Event::Keyboard(keyboard::Event::KeyPressed { key, text, .. }) => { + Event::Keyboard(keyboard::Event::KeyPressed { + key, + text, + physical_key, + .. + }) => { let state = state(); if let Some(focus) = &mut state.is_focused { @@ -1591,7 +1597,134 @@ pub fn update<'a, Message: Clone + 'static>( focus.updated_at = Instant::now(); LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at)); - match key { + // Check if Ctrl+A/C/V/X was pressed. + if state.keyboard_modifiers.command() { + match key.as_ref() { + keyboard::Key::Character("c") => { + if !is_secure { + if let Some((start, end)) = state.cursor.selection(value) { + clipboard.write( + iced_core::clipboard::Kind::Standard, + value.select(start, end).to_string(), + ); + } + } + } + // XXX if we want to allow cutting of secure text, we need to + // update the cache and decide which value to cut + keyboard::Key::Character("x") => { + if !is_secure { + if let Some((start, end)) = state.cursor.selection(value) { + clipboard.write( + iced_core::clipboard::Kind::Standard, + value.select(start, end).to_string(), + ); + } + + let mut editor = Editor::new(value, &mut state.cursor); + editor.delete(); + let content = editor.contents(); + state.tracked_value = Value::new(&content); + if let Some(on_input) = on_input { + let message = (on_input)(content); + shell.publish(message); + } + } + } + keyboard::Key::Character("v") => { + let content = if let Some(content) = state.is_pasting.take() { + content + } else { + let content: String = clipboard + .read(iced_core::clipboard::Kind::Standard) + .unwrap_or_default() + .chars() + .filter(|c| !c.is_control()) + .collect(); + + Value::new(&content) + }; + + let mut editor = Editor::new(unsecured_value, &mut state.cursor); + + editor.paste(content.clone()); + + 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 = if let Some(paste) = &on_paste { + (paste)(contents) + } else { + (on_input)(contents) + }; + + shell.publish(message); + } + + state.is_pasting = Some(content); + + let value = if is_secure { + unsecured_value.secure() + } else { + unsecured_value + }; + + update_cache(state, &value); + return event::Status::Captured; + } + + keyboard::Key::Character("a") => { + state.cursor.select_all(value); + return event::Status::Captured; + } + + _ => {} + } + } + + if let Some(text) = text { + let Some(on_input) = on_input else { + return event::Status::Ignored; + }; + + state.is_pasting = None; + + if !state.keyboard_modifiers.command() && !modifiers.control() { + let c = text.chars().next().filter(|c| !c.is_control()); + + let Some(c) = c else { + return event::Status::Captured; + }; + + let mut editor = Editor::new(unsecured_value, &mut state.cursor); + + editor.insert(c); + + let contents = editor.contents(); + let unsecured_value = Value::new(&contents); + state.tracked_value = unsecured_value.clone(); + + let message = (on_input)(contents); + shell.publish(message); + + focus.updated_at = Instant::now(); + LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at)); + + let value = if is_secure { + unsecured_value.secure() + } else { + unsecured_value + }; + + update_cache(state, &value); + + return event::Status::Captured; + } + } + + match key.as_ref() { keyboard::Key::Named(keyboard::key::Named::Enter) => { if let Some(on_submit) = on_submit { shell.publish((on_submit)(unsecured_value.to_string())); @@ -1698,90 +1831,6 @@ pub fn update<'a, Message: Clone + 'static>( state.cursor.move_to(value.len()); } } - keyboard::Key::Character(ref c) - if "c" == c && state.keyboard_modifiers.command() => - { - if !is_secure { - if let Some((start, end)) = state.cursor.selection(value) { - clipboard.write( - iced_core::clipboard::Kind::Standard, - value.select(start, end).to_string(), - ); - } - } - } - // XXX if we want to allow cutting of secure text, we need to - // update the cache and decide which value to cut - keyboard::Key::Character(c) - if "x" == c && state.keyboard_modifiers.command() => - { - if !is_secure { - if let Some((start, end)) = state.cursor.selection(value) { - clipboard.write( - iced_core::clipboard::Kind::Standard, - value.select(start, end).to_string(), - ); - } - - let mut editor = Editor::new(value, &mut state.cursor); - editor.delete(); - let content = editor.contents(); - state.tracked_value = Value::new(&content); - if let Some(on_input) = on_input { - let message = (on_input)(content); - shell.publish(message); - } - } - } - keyboard::Key::Character(c) - if "v" == c && state.keyboard_modifiers.command() => - { - let content = if let Some(content) = state.is_pasting.take() { - content - } else { - let content: String = clipboard - .read(iced_core::clipboard::Kind::Standard) - .unwrap_or_default() - .chars() - .filter(|c| !c.is_control()) - .collect(); - - Value::new(&content) - }; - - let mut editor = Editor::new(unsecured_value, &mut state.cursor); - - editor.paste(content.clone()); - - 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 = if let Some(paste) = &on_paste { - (paste)(contents) - } else { - (on_input)(contents) - }; - - shell.publish(message); - } - - state.is_pasting = Some(content); - - let value = if is_secure { - unsecured_value.secure() - } else { - unsecured_value - }; - - update_cache(state, &value); - } - keyboard::Key::Character(c) - if "a" == c && state.keyboard_modifiers.command() => - { - state.cursor.select_all(value); - } keyboard::Key::Named(keyboard::key::Named::Escape) => { state.unfocus(); state.is_read_only = true; @@ -1813,38 +1862,6 @@ pub fn update<'a, Message: Clone + 'static>( ) => { return event::Status::Ignored; } - keyboard::Key::Character(_) - | keyboard::Key::Named(keyboard::key::Named::Space) => { - if state.is_pasting.is_none() - && !state.keyboard_modifiers.command() - && !modifiers.control() - { - let mut editor = Editor::new(unsecured_value, &mut state.cursor); - - let character = - text.unwrap_or_default().chars().next().unwrap_or_default(); - if !character.is_control() { - editor.insert(character); - } - 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); - } - - focus.updated_at = Instant::now(); - LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at)); - - let value = if is_secure { - unsecured_value.secure() - } else { - unsecured_value - }; - update_cache(state, &value); - } - } _ => {} } From f16bc4a7640a8895add431899b50eb4ec731b570 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 27 Mar 2025 16:38:28 +0100 Subject: [PATCH 164/556] fix(text_input): Backspace key ignored --- src/widget/text_input/input.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 405e876f..26665bb0 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -1684,7 +1684,8 @@ pub fn update<'a, Message: Clone + 'static>( } } - if let Some(text) = text { + // Capture keyboard inputs that should be submitted. + if let Some(c) = text.and_then(|t| t.chars().next().filter(|c| !c.is_control())) { let Some(on_input) = on_input else { return event::Status::Ignored; }; @@ -1692,12 +1693,6 @@ pub fn update<'a, Message: Clone + 'static>( state.is_pasting = None; if !state.keyboard_modifiers.command() && !modifiers.control() { - let c = text.chars().next().filter(|c| !c.is_control()); - - let Some(c) = c else { - return event::Status::Captured; - }; - let mut editor = Editor::new(unsecured_value, &mut state.cursor); editor.insert(c); From e6326a28d71cb579077ac131630c4b3b7272ba0b Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Mon, 31 Mar 2025 08:50:47 -0600 Subject: [PATCH 165/556] Update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 55f6d5c1..0053fd17 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 55f6d5c10e0328a04ce584e454de1b831bebe011 +Subproject commit 0053fd177af075a986aff948386f1169050045b8 From 227f7a76b11a0b7caebae632603b33d0a89a42e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Tue, 1 Apr 2025 21:02:12 +0200 Subject: [PATCH 166/556] fix(about): match section spacing to designs Also makes the buttons use the `Button::Link` class. --- src/widget/about.rs | 95 +++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 55 deletions(-) diff --git a/src/widget/about.rs b/src/widget/about.rs index 7e23da25..bdb14fc0 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -15,7 +15,7 @@ pub struct About { name: Option, /// The application's icon name. icon: Option, - /// The application’s version. + /// The application's version. version: Option, /// Name of the application's author. author: Option, @@ -45,76 +45,61 @@ pub struct About { links: Vec<(String, String)>, } +fn add_contributors(contributors: Vec<(&str, &str)>) -> Vec<(String, String)> { + contributors + .into_iter() + .map(|(name, email)| (name.to_string(), format!("mailto:{email}"))) + .collect() +} + impl<'a> About { /// Artists who contributed to the application. pub fn artists(mut self, artists: impl Into>) -> Self { - let artists: Vec<(&'a str, &'a str)> = artists.into(); - self.artists = artists - .into_iter() - .map(|(k, v)| (k.to_string(), format!("mailto:{v}"))) - .collect(); + self.artists = add_contributors(artists.into()); self } /// Designers who contributed to the application. pub fn designers(mut self, designers: impl Into>) -> Self { - let designers: Vec<(&'a str, &'a str)> = designers.into(); - self.designers = designers - .into_iter() - .map(|(k, v)| (k.to_string(), format!("mailto:{v}"))) - .collect(); + self.designers = add_contributors(designers.into()); self } /// Developers who contributed to the application. pub fn developers(mut self, developers: impl Into>) -> Self { - let developers: Vec<(&'a str, &'a str)> = developers.into(); - self.developers = developers - .into_iter() - .map(|(k, v)| (k.to_string(), format!("mailto:{v}"))) - .collect(); + self.developers = add_contributors(developers.into()); self } /// Documenters who contributed to the application. pub fn documenters(mut self, documenters: impl Into>) -> Self { - let documenters: Vec<(&'a str, &'a str)> = documenters.into(); - self.documenters = documenters - .into_iter() - .map(|(k, v)| (k.to_string(), format!("mailto:{v}"))) - .collect(); + self.documenters = add_contributors(documenters.into()); self } /// Translators who contributed to the application. pub fn translators(mut self, translators: impl Into>) -> Self { - let translators: Vec<(&'a str, &'a str)> = translators.into(); - self.translators = translators - .into_iter() - .map(|(k, v)| (k.to_string(), format!("mailto:{v}"))) - .collect(); + self.translators = add_contributors(translators.into()); self } /// Links associated with the application. - pub fn links>(mut self, links: impl Into>) -> Self { - let links: Vec<(T, &'a str)> = links.into(); + pub fn links, V: Into>( + mut self, + links: impl IntoIterator, + ) -> Self { self.links = links .into_iter() - .map(|(k, v)| (k.into(), v.to_string())) + .map(|(name, url)| (name.into(), url.into())) .collect(); self } fn license_url(&self) -> Option { - let license: &dyn License = match self.license.as_ref() { - Some(license) => license.parse().ok()?, - None => return None, - }; - - self.license - .as_ref() - .map(|_| format!("https://spdx.org/licenses/{}.html", license.id())) + self.license.as_ref().and_then(|license_str| { + let license: &dyn License = license_str.parse().ok()?; + Some(format!("https://spdx.org/licenses/{}.html", license.id())) + }) } } @@ -124,14 +109,12 @@ pub fn about<'a, Message: Clone + 'static>( on_url_press: impl Fn(String) -> Message, ) -> Element<'a, Message> { let cosmic_theme::Spacing { - space_xxs, - space_xs, - .. - } = crate::theme::active().cosmic().spacing; + space_xxs, space_m, .. + } = crate::theme::spacing(); let section = |list: &'a Vec<(String, String)>, title: &'a str| { (!list.is_empty()).then_some({ - let developers: Vec> = + let items: Vec> = list.iter() .map(|(name, url)| { widget::button::custom( @@ -141,16 +124,15 @@ pub fn about<'a, Message: Clone + 'static>( .push_maybe((!url.is_empty()).then_some( crate::widget::icon::from_name("link-symbolic").icon(), )) - .padding(space_xxs) .align_y(Alignment::Center), ) - .class(crate::theme::Button::Text) + .class(crate::theme::Button::Link) .on_press(on_url_press(url.clone())) .width(Length::Fill) .into() }) .collect(); - widget::settings::section().title(title).extend(developers) + widget::settings::section().title(title).extend(items) }) }; @@ -159,15 +141,14 @@ pub fn about<'a, Message: Clone + 'static>( .icon .as_ref() .map(|icon| crate::desktop::IconSource::Name(icon.clone()).as_cosmic_icon()); - + let author = about.author.as_ref().map(widget::text::body); + let version = about.version.as_ref().map(widget::button::standard); let links_section = section(&about.links, "Links"); let developers_section = section(&about.developers, "Developers"); let designers_section = section(&about.designers, "Designers"); let artists_section = section(&about.artists, "Artists"); let translators_section = section(&about.translators, "Translators"); let documenters_section = section(&about.documenters, "Documenters"); - let author = about.author.as_ref().map(widget::text); - let version = about.version.as_ref().map(widget::button::standard); let license = about.license.as_ref().map(|license| { let url = about.license_url(); widget::settings::section().title("License").add( @@ -179,10 +160,9 @@ pub fn about<'a, Message: Clone + 'static>( url.is_some() .then_some(crate::widget::icon::from_name("link-symbolic").icon()), ) - .padding(space_xxs) .align_y(Alignment::Center), ) - .class(crate::theme::Button::Text) + .class(crate::theme::Button::Link) .on_press(on_url_press(url.unwrap_or_default())) .width(Length::Fill), ) @@ -191,10 +171,15 @@ pub fn about<'a, Message: Clone + 'static>( let comments = about.comments.as_ref().map(widget::text::body); widget::column() - .push_maybe(application_icon) - .push_maybe(application_name) - .push_maybe(author) - .push_maybe(version) + .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(links_section) .push_maybe(developers_section) @@ -205,7 +190,7 @@ pub fn about<'a, Message: Clone + 'static>( .push_maybe(comments) .push_maybe(copyright) .align_x(Alignment::Center) - .spacing(space_xs) + .spacing(space_m) .width(Length::Fill) .into() } From 148656948179bb53362e77e6e8057255f2f9d8c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Tue, 1 Apr 2025 21:13:52 +0200 Subject: [PATCH 167/556] chore: use `theme::spacing()` where applicable --- examples/spin-button/src/main.rs | 2 +- src/widget/context_drawer/widget.rs | 2 +- src/widget/header_bar.rs | 4 ++-- src/widget/list/column.rs | 2 +- src/widget/menu/menu_tree.rs | 2 +- src/widget/mod.rs | 4 ++-- src/widget/segmented_control.rs | 10 ++++------ src/widget/settings/item.rs | 4 ++-- src/widget/settings/mod.rs | 2 +- src/widget/tab_bar.rs | 10 ++++------ src/widget/table/widget/compact.rs | 4 ++-- src/widget/table/widget/standard.rs | 2 +- 12 files changed, 22 insertions(+), 26 deletions(-) diff --git a/examples/spin-button/src/main.rs b/examples/spin-button/src/main.rs index 602f3f4a..310c5107 100644 --- a/examples/spin-button/src/main.rs +++ b/examples/spin-button/src/main.rs @@ -131,7 +131,7 @@ impl Application for SpinButtonExamplApp { } fn view(&self) -> Element { - let space_xs = cosmic::theme::active().cosmic().spacing.space_xs; + let space_xs = cosmic::theme::spacing().space_xs; let vert_spinner_row = iced::widget::row![ spin_button::vertical(&self.i8_str, self.i8_num, 1, -5, 5, Message::UpdateI8), diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index f36578ee..fd752ec6 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -53,7 +53,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { space_m, space_l, .. - } = crate::theme::active().cosmic().spacing; + } = crate::theme::spacing(); let horizontal_padding = if max_width < 392.0 { space_s } else { space_l }; diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 638bff5d..80a2cbd8 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -292,7 +292,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { space_xxxs, space_xxs, .. - } = theme::active().cosmic().spacing; + } = theme::spacing(); // Take ownership of the regions to be packed. let start = std::mem::take(&mut self.start); @@ -434,7 +434,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .take() .map(|m| icon!("window-close-symbolic", 16, m)), ) - .spacing(theme::active().cosmic().space_xxs()) + .spacing(theme::spacing().space_xxs) .apply(widget::container) .center_y(Length::Fill) .into() diff --git a/src/widget/list/column.rs b/src/widget/list/column.rs index d79d5206..a3dedd96 100644 --- a/src/widget/list/column.rs +++ b/src/widget/list/column.rs @@ -28,7 +28,7 @@ impl Default for ListColumn<'_, Message> { fn default() -> Self { let cosmic_theme::Spacing { space_xxs, space_m, .. - } = theme::active().cosmic().spacing; + } = theme::spacing(); Self { spacing: 0, diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index d3ff8f1d..921b4dba 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -240,7 +240,7 @@ where .enumerate() .flat_map(|(i, item)| { let mut trees = vec![]; - let spacing = crate::theme::active().cosmic().spacing; + let spacing = crate::theme::spacing(); match item { MenuItem::Button(label, icon, action) => { diff --git a/src/widget/mod.rs b/src/widget/mod.rs index ac8cf7f8..70fc175f 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -18,7 +18,7 @@ //! //! const REPOSITORY: &str = "https://github.com/pop-os/libcosmic"; //! -//! let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing; +//! let cosmic_theme::Spacing { space_xxs, .. } = theme::spacing(); //! //! let link = widget::button::link(REPOSITORY) //! .on_press(Message::LaunchUrl(REPOSITORY)) @@ -364,7 +364,7 @@ pub mod tooltip { tooltip: impl Into>, position: Position, ) -> Tooltip<'a, Message> { - let xxs = crate::theme::active().cosmic().space_xxs(); + let xxs = crate::theme::spacing().space_xxs; Tooltip::new(content, tooltip, position) .class(crate::theme::Container::Tooltip) diff --git a/src/widget/segmented_control.rs b/src/widget/segmented_control.rs index 6466f8b3..9dbcfc51 100644 --- a/src/widget/segmented_control.rs +++ b/src/widget/segmented_control.rs @@ -20,9 +20,8 @@ pub fn horizontal( where Model: Selectable, { - let theme = crate::theme::active(); - let space_s = theme.cosmic().space_s(); - let space_xxs = theme.cosmic().space_xxs(); + let space_s = crate::theme::spacing().space_s; + let space_xxs = crate::theme::spacing().space_xxs; segmented_button::horizontal(model) .button_alignment(iced::Alignment::Center) @@ -46,9 +45,8 @@ where Model: Selectable, SelectionMode: Default, { - let theme = crate::theme::active(); - let space_s = theme.cosmic().space_s(); - let space_xxs = theme.cosmic().space_xxs(); + let space_s = crate::theme::spacing().space_s; + let space_xxs = crate::theme::spacing().space_xxs; segmented_button::vertical(model) .button_alignment(iced::Alignment::Center) diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index 6aa4e761..a8c38a0d 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -38,7 +38,7 @@ pub fn item<'a, Message: 'static>( #[allow(clippy::module_name_repetitions)] pub fn item_row(children: Vec>) -> Row { row::with_children(children) - .spacing(theme::active().cosmic().space_xs()) + .spacing(theme::spacing().space_xs) .align_y(iced::Alignment::Center) } @@ -69,7 +69,7 @@ pub fn flex_item<'a, Message: 'static>( #[allow(clippy::module_name_repetitions)] pub fn flex_item_row(children: Vec>) -> FlexRow { flex_row(children) - .spacing(theme::active().cosmic().space_xs()) + .spacing(theme::spacing().space_xs) .min_item_width(200.0) .justify_items(iced::Alignment::Center) .justify_content(AlignContent::SpaceBetween) diff --git a/src/widget/settings/mod.rs b/src/widget/settings/mod.rs index 984dd081..597d9bdd 100644 --- a/src/widget/settings/mod.rs +++ b/src/widget/settings/mod.rs @@ -13,5 +13,5 @@ use crate::{Element, theme}; /// A column with a predefined style for creating a settings panel #[must_use] pub fn view_column(children: Vec>) -> Column { - column::with_children(children).spacing(theme::active().cosmic().space_m()) + column::with_children(children).spacing(theme::spacing().space_m) } diff --git a/src/widget/tab_bar.rs b/src/widget/tab_bar.rs index 0f17be96..b3def5ca 100644 --- a/src/widget/tab_bar.rs +++ b/src/widget/tab_bar.rs @@ -20,9 +20,8 @@ pub fn horizontal( where Model: Selectable, { - let theme = crate::theme::active(); - let space_s = theme.cosmic().space_s(); - let space_xs = theme.cosmic().space_xs(); + let space_s = crate::theme::spacing().space_s; + let space_xs = crate::theme::spacing().space_xs; segmented_button::horizontal(model) .minimum_button_width(76) @@ -44,9 +43,8 @@ where Model: Selectable, SelectionMode: Default, { - let theme = crate::theme::active(); - let space_s = theme.cosmic().space_s(); - let space_xs = theme.cosmic().space_xs(); + let space_s = crate::theme::spacing().space_s; + let space_xs = crate::theme::spacing().space_xs; SegmentedButton::new(model) .minimum_button_width(76) diff --git a/src/widget/table/widget/compact.rs b/src/widget/table/widget/compact.rs index cc3bff1c..43a32de2 100644 --- a/src/widget/table/widget/compact.rs +++ b/src/widget/table/widget/compact.rs @@ -57,7 +57,7 @@ where Message: Clone + 'static, { fn from(val: CompactTableView<'a, SelectionMode, Item, Category, Message>) -> Self { - let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing; + let cosmic_theme::Spacing { space_xxxs, .. } = theme::spacing(); val.model .iter() .map(|entity| { @@ -193,7 +193,7 @@ where space_xxxs, space_xxs, .. - } = theme::active().cosmic().spacing; + } = theme::spacing(); Self { model, diff --git a/src/widget/table/widget/standard.rs b/src/widget/table/widget/standard.rs index 620d33fd..01d0ea56 100644 --- a/src/widget/table/widget/standard.rs +++ b/src/widget/table/widget/standard.rs @@ -264,7 +264,7 @@ where space_xxxs, space_xxs, .. - } = theme::active().cosmic().spacing; + } = theme::spacing(); Self { model, From 9b9600a5d6f7a96b8293a205cabda05c0c1f1a76 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 2 Apr 2025 17:30:38 +0200 Subject: [PATCH 168/556] fix(desktop): matching the wrong desktop enrties and not getting icons --- Cargo.toml | 8 +- src/desktop.rs | 206 ++++++++++++++++++----------------------------- src/theme/mod.rs | 7 +- 3 files changed, 86 insertions(+), 135 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 41c1d7e7..1ebfa221 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,6 +100,7 @@ apply = "0.3.0" ashpd = { version = "0.9.2", default-features = false, optional = true } async-fs = { version = "2.1", optional = true } async-std = { version = "1.13", optional = true } +auto_enums = "0.8.7" cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "178eb0b", optional = true } chrono = "0.4.40" cosmic-config = { path = "cosmic-config" } @@ -131,8 +132,11 @@ url = "2.5.4" zbus = { version = "4.4.0", default-features = false, optional = true } [target.'cfg(unix)'.dependencies] -freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } -freedesktop-desktop-entry = { version = "0.7.9", optional = true } +freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons", branch = "icon-fixes" } +# freedesktop-icons = { package = "cosmic-freedesktop-icons", path = "../../pop/freedesktop-icons" } +freedesktop-desktop-entry = { git = "https://github.com/pop-os/freedesktop-desktop-entry", branch = "appid-match", optional = true } +# freedesktop-desktop-entry = { version = "0.7.9", optional = true } +# freedesktop-desktop-entry = { path = "../../pop/freedesktop-desktop-entry", optional = true } shlex = { version = "1.3.0", optional = true } [dependencies.cosmic-theme] diff --git a/src/desktop.rs b/src/desktop.rs index ee35887c..1fef2e7d 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -1,47 +1,32 @@ #[cfg(not(windows))] -pub use freedesktop_desktop_entry::DesktopEntry; +pub use freedesktop_desktop_entry as fde; #[cfg(not(windows))] pub use mime::Mime; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; #[cfg(not(windows))] use std::{borrow::Cow, ffi::OsStr}; -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum IconSource { - Name(String), - Path(PathBuf), +pub trait IconSourceExt { + fn as_cosmic_icon(&self) -> crate::widget::icon::Icon; } -impl IconSource { - pub fn from_unknown(icon: &str) -> Self { - let icon_path = Path::new(icon); - if icon_path.is_absolute() && icon_path.exists() { - Self::Path(icon_path.into()) - } else { - Self::Name(icon.into()) - } - } - - pub fn as_cosmic_icon(&self) -> crate::widget::icon::Icon { +impl IconSourceExt for fde::IconSource { + fn as_cosmic_icon(&self) -> crate::widget::icon::Icon { match self { - Self::Name(name) => crate::widget::icon::from_name(name.as_str()) + fde::IconSource::Name(name) => crate::widget::icon::from_name(name.as_str()) .size(128) .fallback(Some(crate::widget::icon::IconFallback::Names(vec![ "application-default".into(), "application-x-executable".into(), ]))) .into(), - Self::Path(path) => crate::widget::icon(crate::widget::icon::from_path(path.clone())), + fde::IconSource::Path(path) => { + crate::widget::icon(crate::widget::icon::from_path(path.clone())) + } } } } -impl Default for IconSource { - fn default() -> Self { - Self::Name("application-default".to_string()) - } -} - #[cfg(not(windows))] #[derive(Debug, Clone, PartialEq)] pub struct DesktopAction { @@ -56,7 +41,7 @@ pub struct DesktopEntryData { pub name: String, pub wm_class: Option, pub exec: Option, - pub icon: IconSource, + pub icon: fde::IconSource, pub path: Option, pub categories: Vec, pub desktop_actions: Vec, @@ -66,127 +51,94 @@ pub struct DesktopEntryData { #[cfg(not(windows))] pub fn load_applications<'a>( - locale: impl Into>, + locales: &'a [String], include_no_display: bool, -) -> Vec { - load_applications_filtered(locale, |de| include_no_display || !de.no_display()) +) -> impl Iterator + 'a { + fde::Iter::new(fde::default_paths()) + .filter_map(move |p| fde::DesktopEntry::from_path(p, Some(locales)).ok()) + .filter(move |de| include_no_display || !de.no_display()) + .map(move |de| DesktopEntryData::from_desktop_entry(locales, de)) } +// Create an iterator which filters desktop entries by app IDs. #[cfg(not(windows))] -pub fn app_id_or_fallback_matches(app_id: &str, entry: &DesktopEntryData) -> bool { - let lowercase_wm_class = entry.wm_class.as_ref().map(|s| s.to_lowercase()); - - app_id == entry.id - || Some(app_id.to_lowercase()) == lowercase_wm_class - || app_id.to_lowercase() == entry.name.to_lowercase() -} - -#[cfg(not(windows))] -pub fn load_applications_for_app_ids<'a, 'b>( - locale: impl Into>, - app_ids: impl Iterator, +#[auto_enums::auto_enum(Iterator)] +pub fn load_applications_for_app_ids<'a>( + iter: impl Iterator + 'a, + locales: &'a [String], + app_ids: Vec<&'a str>, fill_missing_ones: bool, include_no_display: bool, -) -> Vec { - let mut app_ids = app_ids.collect::>(); - let mut applications = load_applications_filtered(locale, |de| { - if !include_no_display && de.no_display() { - return false; - } - // If appid matches, or startup_wm_class matches... - if let Some(i) = app_ids.iter().position(|id| { - id == &de.appid - || id - .to_lowercase() - .eq(&de.startup_wm_class().unwrap_or_default().to_lowercase()) - }) { - app_ids.remove(i); - true - // Fallback: If the name matches... - } else if let Some(i) = app_ids.iter().position(|id| { - de.name::<&str>(&[]) - .map(|n| n.to_lowercase() == id.to_lowercase()) - .unwrap_or_default() - }) { - app_ids.remove(i); - true - } else { - false - } - }); - if fill_missing_ones { - applications.extend(app_ids.into_iter().map(|app_id| DesktopEntryData { - id: app_id.to_string(), - name: app_id.to_string(), - icon: IconSource::default(), - ..Default::default() - })); - } - applications -} +) -> impl Iterator + 'a { + let app_ids = std::rc::Rc::new(std::cell::RefCell::new(app_ids)); + let app_ids_ = app_ids.clone(); -#[cfg(not(windows))] -pub fn load_applications_filtered<'a, F: FnMut(&DesktopEntry) -> bool>( - locale: impl Into>, - mut filter: F, -) -> Vec { - let locale = locale.into(); - let locale_arr: Option> = locale.map(|l| vec![l]); - freedesktop_desktop_entry::Iter::new(freedesktop_desktop_entry::default_paths()) - .filter_map(|path| { - DesktopEntry::from_path(&path, locale_arr.as_deref()) - .ok() - .and_then(|de| { - if !filter(&de) { - return None; - } + let applications = iter + .filter(move |de| { + if !include_no_display && de.no_display() { + return false; + } - Some(DesktopEntryData::from_desktop_entry( - locale, - path.clone(), - de, - )) + // Search by ID first + app_ids + .borrow() + .iter() + .position(|id| de.matches_id(fde::unicase::Ascii::new(*id))) + // Then fall back to search by name + .or_else(|| { + app_ids + .borrow() + .iter() + .position(|id| de.matches_name(fde::unicase::Ascii::new(*id))) }) + // Remove the app ID if found + .map(|i| { + app_ids.borrow_mut().remove(i); + true + }) + .unwrap_or_default() }) - .collect() + .map(move |de| DesktopEntryData::from_desktop_entry(locales, de)); + + if fill_missing_ones { + applications.chain( + std::iter::once_with(move || { + std::mem::take(&mut *app_ids_.borrow_mut()) + .into_iter() + .map(|app_id| DesktopEntryData { + id: app_id.to_string(), + name: app_id.to_string(), + icon: fde::IconSource::default(), + ..Default::default() + }) + }) + .flatten(), + ) + } else { + applications + } } #[cfg(not(windows))] -pub fn load_desktop_file<'a>( - locale: impl Into>, - path: impl AsRef, -) -> Option { - let path = path.as_ref(); - let locale = locale.into(); - let locale_arr: Option> = locale.clone().map(|l| vec![l]); - - DesktopEntry::from_path(&path, locale_arr.as_deref()) +pub fn load_desktop_file<'a>(locales: &'a [String], path: PathBuf) -> Option { + fde::DesktopEntry::from_path(path, Some(locales)) .ok() - .map(|de| DesktopEntryData::from_desktop_entry(locale, PathBuf::from(path), de)) + .map(|de| DesktopEntryData::from_desktop_entry(locales, de)) } #[cfg(not(windows))] impl DesktopEntryData { - fn from_desktop_entry<'a>( - locale: impl Into>, - path: impl Into>, - de: DesktopEntry, + pub fn from_desktop_entry<'a>( + locales: &'a [String], + de: fde::DesktopEntry, ) -> DesktopEntryData { - let locale = locale.into(); - let locale_arr: Option> = locale.map(|l| vec![l]); let name = de - .name(locale_arr.as_deref().unwrap_or_default()) + .name(locales) .unwrap_or(Cow::Borrowed(&de.appid)) .to_string(); // check if absolute path exists and otherwise treat it as a name - let icon = de.icon().unwrap_or(&de.appid); - let icon_path = Path::new(icon); - let icon = if icon_path.is_absolute() && icon_path.exists() { - IconSource::Path(icon_path.into()) - } else { - IconSource::Name(icon.into()) - }; + let icon = fde::IconSource::from_unknown(de.icon().unwrap_or(&de.appid)); DesktopEntryData { id: de.appid.to_string(), @@ -194,7 +146,6 @@ impl DesktopEntryData { exec: de.exec().map(ToString::to_string), name, icon, - path: path.into(), categories: de .categories() .unwrap_or_default() @@ -207,11 +158,7 @@ impl DesktopEntryData { actions .into_iter() .filter_map(|action| { - let name = de.action_entry_localized( - action, - "Name", - locale_arr.as_deref().unwrap_or_default(), - ); + let name = de.action_entry_localized(action, "Name", locales); let exec = de.action_entry(action, "Exec"); if let (Some(name), Some(exec)) = (name, exec) { Some(DesktopAction { @@ -235,6 +182,7 @@ impl DesktopEntryData { }) .unwrap_or_default(), prefers_dgpu: de.prefers_non_default_gpu(), + path: Some(de.path), } } } diff --git a/src/theme/mod.rs b/src/theme/mod.rs index e0b0a42f..7fbb5bad 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -6,18 +6,17 @@ #[cfg(feature = "xdg-portal")] pub mod portal; pub mod style; -use cosmic_theme::Spacing; -use cosmic_theme::ThemeMode; -pub use style::*; use cosmic_config::CosmicConfigEntry; use cosmic_config::config_subscription; use cosmic_theme::Component; use cosmic_theme::LayeredTheme; +use cosmic_theme::Spacing; +use cosmic_theme::ThemeMode; use iced_futures::Subscription; use iced_runtime::{Appearance, DefaultStyle}; - use std::sync::{Arc, Mutex}; +pub use style::*; #[cfg(feature = "dbus-config")] use cosmic_config::dbus; From f7d22446c1766b6d2e60b7e0333717f96ca347e9 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 4 Apr 2025 02:37:35 +0200 Subject: [PATCH 169/556] fix: update imports for fde and fd-icons --- Cargo.toml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1ebfa221..357e6474 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -132,11 +132,8 @@ url = "2.5.4" zbus = { version = "4.4.0", default-features = false, optional = true } [target.'cfg(unix)'.dependencies] -freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons", branch = "icon-fixes" } -# freedesktop-icons = { package = "cosmic-freedesktop-icons", path = "../../pop/freedesktop-icons" } -freedesktop-desktop-entry = { git = "https://github.com/pop-os/freedesktop-desktop-entry", branch = "appid-match", optional = true } -# freedesktop-desktop-entry = { version = "0.7.9", optional = true } -# freedesktop-desktop-entry = { path = "../../pop/freedesktop-desktop-entry", optional = true } +freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } +freedesktop-desktop-entry = { version = "0.7.10", optional = true } shlex = { version = "1.3.0", optional = true } [dependencies.cosmic-theme] From 15091632304c35cbf32f9d62a3515e9db85bc69e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Fri, 4 Apr 2025 15:43:32 +0200 Subject: [PATCH 170/556] fix(app): match padding to designs Also makes the maximize button change to restore when the window is maximized. --- iced | 2 +- res/icons/window-restore-symbolic.svg | 4 ++ src/app/mod.rs | 57 ++++++++++++++++++--------- src/widget/header_bar.rs | 44 +++++++++++++++------ src/widget/scrollable.rs | 3 ++ 5 files changed, 80 insertions(+), 30 deletions(-) create mode 100644 res/icons/window-restore-symbolic.svg diff --git a/iced b/iced index 0053fd17..696aa926 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 0053fd177af075a986aff948386f1169050045b8 +Subproject commit 696aa92626e22f09268d4254a8be5e60a46fcea0 diff --git a/res/icons/window-restore-symbolic.svg b/res/icons/window-restore-symbolic.svg new file mode 100644 index 00000000..bcb506f5 --- /dev/null +++ b/res/icons/window-restore-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/app/mod.rs b/src/app/mod.rs index 2f4192b6..d73d024c 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -541,23 +541,30 @@ impl ApplicationExt for App { fn view_main(&self) -> Element> { let core = self.core(); let is_condensed = core.is_condensed(); - // TODO: More granularity might be needed for different resize border - // and window border handling of maximized and tiled windows + // TODO: More granularity might be needed for different window border + // handling of maximized and tiled windows let sharp_corners = core.window.sharp_corners; let content_container = core.window.content_container; + let show_context = core.window.show_context; + let context_is_overlay = core.window.context_is_overlay; let nav_bar_active = core.nav_bar_active(); let focused = core .focused_window() .is_some_and(|i| Some(i) == self.core().main_window_id()); - let main_content_padding = if content_container { - if nav_bar_active { - [0, 8, 8, 0] - } else { - [0, 8, 8, 8] - } - } else { + let border_padding = if sharp_corners { 8 } else { 7 }; + + let main_content_padding = if !content_container { [0, 0, 0, 0] + } else { + let right_padding = if show_context && !context_is_overlay { + 0 + } else { + border_padding + }; + let left_padding = if nav_bar_active { 0 } else { border_padding }; + + [0, right_padding, 0, left_padding] }; let content_row = crate::widget::row::with_children({ @@ -568,7 +575,16 @@ impl ApplicationExt for App { .nav_bar() .map(|nav| id_container(nav, iced_core::id::Id::new("COSMIC_nav_bar"))) { - widgets.push(container(nav).padding([0, 8, 8, 8]).into()); + widgets.push( + container(nav) + .padding([ + 0, + if is_condensed { border_padding } else { 8 }, + border_padding, + border_padding, + ]) + .into(), + ); true } else { false @@ -579,7 +595,7 @@ impl ApplicationExt for App { //TODO: reduce duplication let context_width = core.context_width(has_nav); - if core.window.context_is_overlay && core.window.show_context { + if context_is_overlay && show_context { if let Some(context) = self.context_drawer() { widgets.push( crate::widget::context_drawer( @@ -600,7 +616,7 @@ impl ApplicationExt for App { }) .apply(container) .padding(if content_container { - [0, 8, 8, 0] + [0, border_padding, border_padding, border_padding] } else { [0, 0, 0, 0] }) @@ -648,7 +664,7 @@ impl ApplicationExt for App { }) .apply(container) .padding(if content_container { - [0, 8, 8, 0] + [0, border_padding, border_padding, border_padding] } else { [0, 0, 0, 0] }) @@ -665,11 +681,15 @@ impl ApplicationExt for App { }); let content_col = crate::widget::column::with_capacity(2) .push(content_row) - .push_maybe( - self.footer() - .map(|footer| container(footer.map(crate::Action::App)).padding([0, 8, 8, 8])), - ); - let content: Element<_> = if core.window.content_container { + .push_maybe(self.footer().map(|footer| { + container(footer.map(crate::Action::App)).padding([ + 0, + border_padding, + border_padding, + border_padding, + ]) + })); + let content: Element<_> = if content_container { content_col .apply(container) .width(iced::Length::Fill) @@ -692,6 +712,7 @@ impl ApplicationExt for App { Some({ let mut header = crate::widget::header_bar() .focused(focused) + .maximized(sharp_corners) .title(&core.window.header_title) .on_drag(crate::Action::Cosmic(Action::Drag)) .on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu)) diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 80a2cbd8..dcd8aa0f 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -23,6 +23,7 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { end: Vec::new(), density: None, focused: false, + maximized: false, on_double_click: None, } } @@ -76,6 +77,9 @@ pub struct HeaderBar<'a, Message> { /// Focused state of the window focused: bool, + + /// Maximized state of the window + maximized: bool, } impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { @@ -302,10 +306,22 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { // Also packs the window controls at the very end. end.push(self.window_controls()); - let height = match self.density.unwrap_or_else(crate::config::header_size) { - Density::Compact => 40.0, - Density::Spacious => 48.0, - Density::Standard => 48.0, + // Center content depending on window border + let padding = match self.density.unwrap_or_else(crate::config::header_size) { + Density::Compact => { + if self.maximized { + [4, 8, 4, 8] + } else { + [3, 7, 4, 7] + } + } + _ => { + if self.maximized { + [8, 8, 8, 8] + } else { + [7, 7, 8, 7] + } + } }; let portion = ((start.len().max(end.len()) as f32 / center.len().max(1) as f32).round() as u16) @@ -349,8 +365,8 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { }), ) .align_y(iced::Alignment::Center) - .height(Length::Fixed(height)) - .padding([0, 8]) + .height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32)) + .padding(padding) .spacing(8) .apply(widget::container) .class(crate::theme::Container::HeaderBar { @@ -424,11 +440,17 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .take() .map(|m: Message| icon!("window-minimize-symbolic", 16, m)), ) - .push_maybe( - self.on_maximize - .take() - .map(|m| icon!("window-maximize-symbolic", 16, m)), - ) + .push_maybe(self.on_maximize.take().map(|m| { + icon!( + if self.maximized { + "window-restore-symbolic" + } else { + "window-maximize-symbolic" + }, + 16, + m + ) + })) .push_maybe( self.on_close .take() diff --git a/src/widget/scrollable.rs b/src/widget/scrollable.rs index 71f63aad..81ed3533 100644 --- a/src/widget/scrollable.rs +++ b/src/widget/scrollable.rs @@ -8,4 +8,7 @@ pub fn scrollable<'a, Message>( element: impl Into>, ) -> widget::Scrollable<'a, Message, crate::Theme, Renderer> { widget::scrollable(element) + .scroller_width(8.0) + .scrollbar_width(8.0) + .scrollbar_padding(8.0) } From dd1b16a353b4023126eb3650302a8bec781d6aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Fri, 4 Apr 2025 17:18:13 +0200 Subject: [PATCH 171/556] feat(scrollable): add helper for horizontal scrollables Adds a helper function to get horizontal scrollables with COSMIC styling. --- src/app/mod.rs | 19 +++--------------- src/widget/mod.rs | 5 ++--- src/widget/scrollable.rs | 14 ------------- src/widget/scrollable/mod.rs | 6 ++++++ src/widget/scrollable/scrollable.rs | 31 +++++++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 33 deletions(-) delete mode 100644 src/widget/scrollable.rs create mode 100644 src/widget/scrollable/mod.rs create mode 100644 src/widget/scrollable/scrollable.rs diff --git a/src/app/mod.rs b/src/app/mod.rs index d73d024c..18f6b399 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -546,7 +546,6 @@ impl ApplicationExt for App { let sharp_corners = core.window.sharp_corners; let content_container = core.window.content_container; let show_context = core.window.show_context; - let context_is_overlay = core.window.context_is_overlay; let nav_bar_active = core.nav_bar_active(); let focused = core .focused_window() @@ -557,11 +556,7 @@ impl ApplicationExt for App { let main_content_padding = if !content_container { [0, 0, 0, 0] } else { - let right_padding = if show_context && !context_is_overlay { - 0 - } else { - border_padding - }; + let right_padding = if show_context { 0 } else { border_padding }; let left_padding = if nav_bar_active { 0 } else { border_padding }; [0, right_padding, 0, left_padding] @@ -595,7 +590,7 @@ impl ApplicationExt for App { //TODO: reduce duplication let context_width = core.context_width(has_nav); - if context_is_overlay && show_context { + if core.window.context_is_overlay && show_context { if let Some(context) = self.context_drawer() { widgets.push( crate::widget::context_drawer( @@ -615,17 +610,11 @@ impl ApplicationExt for App { )) }) .apply(container) - .padding(if content_container { - [0, border_padding, border_padding, border_padding] - } else { - [0, 0, 0, 0] - }) + .padding([0, border_padding, 0, 0]) .apply(Element::from) .map(crate::Action::App), ); } else { - //TODO: container and padding are temporary, until - //the `resize_border` is moved to not cover window content widgets.push( container(main_content.map(crate::Action::App)) .padding(main_content_padding) @@ -634,8 +623,6 @@ impl ApplicationExt for App { } } else { //TODO: hide content when out of space - //TODO: container and padding are temporary, until - //the `resize_border` is moved to not cover window content widgets.push( container(main_content.map(crate::Action::App)) .padding(main_content_padding) diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 70fc175f..746292d3 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -308,10 +308,9 @@ pub mod row { } } -mod scrollable; +pub mod scrollable; #[doc(inline)] -pub use scrollable::*; - +pub use scrollable::scrollable; pub mod segmented_button; pub mod segmented_control; diff --git a/src/widget/scrollable.rs b/src/widget/scrollable.rs deleted file mode 100644 index 81ed3533..00000000 --- a/src/widget/scrollable.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2022 System76 -// SPDX-License-Identifier: MPL-2.0 - -use crate::{Element, Renderer}; -use iced::widget; - -pub fn scrollable<'a, Message>( - element: impl Into>, -) -> widget::Scrollable<'a, Message, crate::Theme, Renderer> { - widget::scrollable(element) - .scroller_width(8.0) - .scrollbar_width(8.0) - .scrollbar_padding(8.0) -} diff --git a/src/widget/scrollable/mod.rs b/src/widget/scrollable/mod.rs new file mode 100644 index 00000000..2485edf4 --- /dev/null +++ b/src/widget/scrollable/mod.rs @@ -0,0 +1,6 @@ +// Copyright 2022 System76 +// SPDX-License-Identifier: MPL-2.0 + +mod scrollable; + +pub use scrollable::{horizontal, scrollable, vertical}; diff --git a/src/widget/scrollable/scrollable.rs b/src/widget/scrollable/scrollable.rs new file mode 100644 index 00000000..a3fa4edd --- /dev/null +++ b/src/widget/scrollable/scrollable.rs @@ -0,0 +1,31 @@ +// Copyright 2022 System76 +// SPDX-License-Identifier: MPL-2.0 + +use crate::{Element, Renderer}; +use iced::widget; + +pub fn scrollable<'a, Message>( + element: impl Into>, +) -> widget::Scrollable<'a, Message, crate::Theme, Renderer> { + vertical(element) +} + +pub fn vertical<'a, Message>( + element: impl Into>, +) -> widget::Scrollable<'a, Message, crate::Theme, Renderer> { + widget::scrollable(element) + .scroller_width(8.0) + .scrollbar_width(8.0) + .scrollbar_padding(8.0) +} + +pub fn horizontal<'a, Message>( + element: impl Into>, +) -> widget::Scrollable<'a, Message, crate::Theme, Renderer> { + widget::scrollable(element) + .direction(widget::scrollable::Direction::Horizontal( + widget::scrollable::Scrollbar::new(), + )) + .scroller_width(8.0) + .scrollbar_width(8.0) +} From 3df0e5c2fed208e2819315b4fde42975052b6ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Sat, 5 Apr 2025 14:55:11 +0200 Subject: [PATCH 172/556] fix(app): remove context padding without content_container --- src/app/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 18f6b399..4cb144ad 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -610,7 +610,7 @@ impl ApplicationExt for App { )) }) .apply(container) - .padding([0, border_padding, 0, 0]) + .padding([0, if content_container { border_padding } else { 0 }, 0, 0]) .apply(Element::from) .map(crate::Action::App), ); From 0ddde755ee922edcff1a3b11cc00753cb29fe3b0 Mon Sep 17 00:00:00 2001 From: Dryadxon <81884588+Dryadxon@users.noreply.github.com> Date: Mon, 7 Apr 2025 17:43:40 +0200 Subject: [PATCH 173/556] fix(about): use fde::IconSource following commit 9b9600a5d6f7a96b8293a205cabda05c0c1f1a76 --- src/widget/about.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/widget/about.rs b/src/widget/about.rs index bdb14fc0..47a9baa1 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -1,6 +1,7 @@ use { crate::{ Element, + desktop::{IconSourceExt, fde}, iced::{Alignment, Length}, widget::{self, horizontal_space}, }, @@ -140,7 +141,7 @@ pub fn about<'a, Message: Clone + 'static>( let application_icon = about .icon .as_ref() - .map(|icon| crate::desktop::IconSource::Name(icon.clone()).as_cosmic_icon()); + .map(|icon| fde::IconSource::Name(icon.clone()).as_cosmic_icon()); let author = about.author.as_ref().map(widget::text::body); let version = about.version.as_ref().map(widget::button::standard); let links_section = section(&about.links, "Links"); From 164f8ce1553df972b21e00292069d54515784eed Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 11 Apr 2025 08:26:24 -0400 Subject: [PATCH 174/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 696aa926..1b462682 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 696aa92626e22f09268d4254a8be5e60a46fcea0 +Subproject commit 1b462682c047852c303c8c4caefc48caeec900a5 From 924a9ff371309da87b7414e8eaae0693d02481c2 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 10 Apr 2025 16:36:45 -0400 Subject: [PATCH 175/556] fix(input): don't ignore typing for managed inputs --- src/widget/text_input/input.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 26665bb0..3e0c9831 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -1686,7 +1686,7 @@ pub fn update<'a, Message: Clone + 'static>( // Capture keyboard inputs that should be submitted. if let Some(c) = text.and_then(|t| t.chars().next().filter(|c| !c.is_control())) { - let Some(on_input) = on_input else { + if state.is_read_only || (!manage_value && on_input.is_none()) { return event::Status::Ignored; }; @@ -1701,8 +1701,10 @@ pub fn update<'a, Message: Clone + 'static>( let unsecured_value = Value::new(&contents); state.tracked_value = unsecured_value.clone(); - let message = (on_input)(contents); - shell.publish(message); + if let Some(on_input) = on_input { + let message = (on_input)(contents); + shell.publish(message); + } focus.updated_at = Instant::now(); LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at)); From 67df54f38390eda8180b55b55b0fe9825894a62a Mon Sep 17 00:00:00 2001 From: Johann Tuffe Date: Sat, 12 Apr 2025 00:19:43 +0800 Subject: [PATCH 176/556] perf(table): sort optimizations --- src/widget/table/model/mod.rs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/widget/table/model/mod.rs b/src/widget/table/model/mod.rs index 3af94c57..f664e438 100644 --- a/src/widget/table/model/mod.rs +++ b/src/widget/table/model/mod.rs @@ -347,19 +347,20 @@ where /// Sorts items in the model, this should be called before it is drawn after all items have been added for the view pub fn sort(&mut self, category: Category, ascending: bool) { - self.sort = Some((category, ascending)); - let mut order: Vec = self.order.iter().cloned().collect(); - order.sort_by(|entity_a, entity_b| { - if ascending { - self.item(*entity_a) - .unwrap() - .compare(self.item(*entity_b).unwrap(), category) - } else { - self.item(*entity_b) - .unwrap() - .compare(self.item(*entity_a).unwrap(), category) + match self.sort { + Some((cat, asc)) if cat == category && asc == ascending => return, + Some((cat, _)) if cat == category => self.order.make_contiguous().reverse(), + _ => { + self.order.make_contiguous().sort_by(|entity_a, entity_b| { + let cmp = self + .items + .get(*entity_a) + .unwrap() + .compare(self.items.get(*entity_b).unwrap(), category); + if ascending { cmp } else { cmp.reverse() } + }); } - }); - self.order = order.into(); + } + self.sort = Some((category, ascending)); } } From c2a7d63060cb7596d5de558c482f482b8a97e6c1 Mon Sep 17 00:00:00 2001 From: Vadim Khitrin Date: Fri, 11 Apr 2025 23:09:53 +0300 Subject: [PATCH 177/556] fix: Calculate Icons For Header Bar Individually Based on the existing non Linux macro, we are required to calculate icons individually. --- src/widget/header_bar.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index dcd8aa0f..369685f5 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -441,15 +441,11 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .map(|m: Message| icon!("window-minimize-symbolic", 16, m)), ) .push_maybe(self.on_maximize.take().map(|m| { - icon!( - if self.maximized { - "window-restore-symbolic" - } else { - "window-maximize-symbolic" - }, - 16, - m - ) + if self.maximized { + icon!("window-restore-symbolic", 16, m) + } else { + icon!("window-maximize-symbolic", 16, m) + } })) .push_maybe( self.on_close From cc3ca6ed145b5eddff0e93b298cdf39218872bd2 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 11 Apr 2025 16:45:50 -0400 Subject: [PATCH 178/556] refactor: applet tooltips may be for an overflow window --- examples/applet/src/window.rs | 1 + src/applet/mod.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/applet/src/window.rs b/examples/applet/src/window.rs index 15085d85..3fa177f0 100644 --- a/examples/applet/src/window.rs +++ b/examples/applet/src/window.rs @@ -135,6 +135,7 @@ impl cosmic::Application for Window { "test", self.popup.is_some(), |a| Message::Surface(a), + None )) } diff --git a/src/applet/mod.rs b/src/applet/mod.rs index cf2b41b4..8f8cbfc0 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -225,6 +225,7 @@ impl Context { tooltip: impl Into>, has_popup: bool, on_surface_action: impl Fn(crate::surface::Action) -> Message + 'static, + parent_id: Option, ) -> crate::widget::wayland::tooltip::widget::Tooltip<'a, Message, Message> { let window_id = *TOOLTIP_WINDOW_ID; let subsurface_id = TOOLTIP_ID.clone(); @@ -243,7 +244,7 @@ impl Context { }; SctkPopupSettings { - parent: window::Id::RESERVED, + parent: parent_id.unwrap_or(window::Id::RESERVED), id: window_id, grab: false, input_zone: Some(Rectangle::new( From 99d2478d98c29eaa872571be51e701856e94b6d2 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 11 Apr 2025 16:48:04 -0400 Subject: [PATCH 179/556] cargo fmt --- examples/applet/src/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/applet/src/window.rs b/examples/applet/src/window.rs index 3fa177f0..4a822cea 100644 --- a/examples/applet/src/window.rs +++ b/examples/applet/src/window.rs @@ -135,7 +135,7 @@ impl cosmic::Application for Window { "test", self.popup.is_some(), |a| Message::Surface(a), - None + None, )) } From bbcd874d9c01a7336d5e64ddac8c1c8e1c6d1a6f Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 15 Apr 2025 09:41:01 -0400 Subject: [PATCH 180/556] refactor: responsive headers should allow some options --- examples/application/src/main.rs | 40 +++---- src/app/cosmic.rs | 9 +- src/widget/mod.rs | 2 +- src/widget/responsive_menu_bar.rs | 177 +++++++++++++++++++----------- 4 files changed, 142 insertions(+), 86 deletions(-) diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index 50329298..92c2c242 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -168,8 +168,10 @@ impl cosmic::Application for App { Message::ToggleHide => { self.hidden = !self.hidden; } - Message::Surface(_) => { - // unimplemented!() + Message::Surface(a) => { + return cosmic::task::message(cosmic::Action::Cosmic( + cosmic::app::Action::Surface(a), + )); } Message::Hi => { dbg!("hi"); @@ -280,49 +282,49 @@ impl cosmic::Application for App { } #[cfg(feature = "wayland")] { - vec![cosmic::widget::responsive_menu_bar( + vec![cosmic::widget::responsive_menu_bar().into_element( self.core(), &self.keybinds, MENU_ID.clone(), Message::Surface, vec![ ( - "hiiiiiiiiiiiiiiiiiii 1".into(), - vec![menu::Item::Button("hi 1".into(), None, Action::Hi)], + "hiiiiiiiiiiiiiiiiiii 1", + vec![menu::Item::Button("hi 1", None, Action::Hi)], ), ( "hiiiiiiiiiiiiiiiiiii 2".into(), vec![ - menu::Item::Button("hi 2".into(), None, Action::Hi), - menu::Item::Button("hi 22".into(), None, Action::Hi), + menu::Item::Button("hi 2", None, Action::Hi), + menu::Item::Button("hi 22", None, Action::Hi), ], ), ( "hiiiiiiiiiiiiiiiiiii 3".into(), vec![ - menu::Item::Button("hi 3".into(), None, Action::Hi), - menu::Item::Button("hi 33".into(), None, Action::Hi), - menu::Item::Button("hi 333".into(), None, Action::Hi), + menu::Item::Button("hi 3", None, Action::Hi), + menu::Item::Button("hi 33", None, Action::Hi), + menu::Item::Button("hi 333", None, Action::Hi), ], ), ( "hiiiiiiiiiiiiiiiiiii 4".into(), vec![ - menu::Item::Button("hi 4".into(), None, Action::Hi), - menu::Item::Button("hi 44".into(), None, Action::Hi), - menu::Item::Button("hi 444".into(), None, Action::Hi), + menu::Item::Button("hi 4", None, Action::Hi), + menu::Item::Button("hi 44", None, Action::Hi), + menu::Item::Button("hi 444", None, Action::Hi), menu::Item::Folder( "nest 4".into(), vec![ - menu::Item::Button("hi 4".into(), None, Action::Hi), - menu::Item::Button("hi 44".into(), None, Action::Hi), - menu::Item::Button("hi 444".into(), None, Action::Hi), + menu::Item::Button("hi 4", None, Action::Hi), + menu::Item::Button("hi 44", None, Action::Hi), + menu::Item::Button("hi 444", None, Action::Hi), menu::Item::Folder( "nest 2 4".into(), vec![ - menu::Item::Button("hi 4".into(), None, Action::Hi), - menu::Item::Button("hi 44".into(), None, Action::Hi), - menu::Item::Button("hi 444".into(), None, Action::Hi), + menu::Item::Button("hi 4", None, Action::Hi), + menu::Item::Button("hi 44", None, Action::Hi), + menu::Item::Button("hi 444", None, Action::Hi), ], ), ], diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index ec129d33..40748473 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -61,8 +61,9 @@ where &mut self, _surface_message: crate::surface::Action, ) -> iced::Task> { - #[cfg(feature = "wayland")] + #[cfg(feature = "surface-message")] match _surface_message { + #[cfg(feature = "wayland")] crate::surface::Action::AppSubsurface(settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() @@ -91,6 +92,7 @@ where iced_winit::commands::subsurface::get_subsurface(settings(&mut self.app)) } } + #[cfg(feature = "wayland")] crate::surface::Action::Subsurface(settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() @@ -117,6 +119,7 @@ where iced_winit::commands::subsurface::get_subsurface(settings()) } } + #[cfg(feature = "wayland")] crate::surface::Action::AppPopup(settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() @@ -162,6 +165,7 @@ where core.menu_bars.insert(menu_bar, (limits, size)); iced::Task::none() } + #[cfg(feature = "wayland")] crate::surface::Action::Popup(settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() @@ -192,9 +196,10 @@ where crate::surface::Action::Task(f) => { f().map(|sm| crate::Action::Cosmic(Action::Surface(sm))) } + _ => iced::Task::none(), } - #[cfg(not(feature = "wayland"))] + #[cfg(not(feature = "surface-message"))] iced::Task::none() } diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 746292d3..f212906a 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -103,7 +103,7 @@ pub(crate) mod responsive_container; mod responsive_menu_bar; #[cfg(feature = "surface-message")] #[doc(inline)] -pub use responsive_menu_bar::responsive_menu_bar; +pub use responsive_menu_bar::{ResponsiveMenuBar, responsive_menu_bar}; pub mod button; #[doc(inline)] diff --git a/src/widget/responsive_menu_bar.rs b/src/widget/responsive_menu_bar.rs index 41f9eae8..c2ee1308 100644 --- a/src/widget/responsive_menu_bar.rs +++ b/src/widget/responsive_menu_bar.rs @@ -7,72 +7,121 @@ use crate::{ widget::{button, icon, responsive_container}, }; -use super::menu; +use super::menu::{self, ItemHeight, ItemWidth}; -/// # Panics -/// -/// Will panic if the menu bar collapses without tracking the size -pub fn responsive_menu_bar<'a, Message: Clone + 'static, A: menu::Action>( - core: &Core, - key_binds: &HashMap, - id: crate::widget::Id, - action_message: impl Fn(crate::surface::Action) -> Message + 'static, - trees: Vec<( - std::borrow::Cow<'static, str>, - Vec>>, - )>, -) -> Element<'a, Message> { - use crate::widget::id_container; +pub fn responsive_menu_bar() -> ResponsiveMenuBar { + ResponsiveMenuBar::default() +} - let menu_bar_size = core.menu_bars.get(&id); +pub struct ResponsiveMenuBar { + item_width: ItemWidth, + item_height: ItemHeight, + spacing: f32, +} - #[allow(clippy::if_not_else)] - if !menu_bar_size.is_some_and(|(limits, size)| { - let max_size = limits.max(); - max_size.width < size.width - }) { - responsive_container::responsive_container( - id_container( - menu::bar( - trees - .into_iter() - .map(|mt| { - menu::Tree::<_>::with_children( - menu::root(mt.0), - menu::items(key_binds, mt.1), - ) - }) - .collect(), - ), - crate::widget::Id::new(format!("menu_bar_expanded_{id}")), - ), - id, - action_message, - ) - .apply(Element::from) - } else { - responsive_container::responsive_container( - id_container( - menu::bar(vec![menu::Tree::<_>::with_children( - Element::from( - button::icon(icon::from_name("open-menu-symbolic")) - .padding([4, 12]) - .class(crate::theme::Button::MenuRoot), - ), - menu::items( - key_binds, - trees - .into_iter() - .map(|mt| menu::Item::Folder(mt.0, mt.1)) - .collect(), - ), - )]), - crate::widget::Id::new(format!("menu_bar_collapsed_{id}")), - ), - id, - action_message, - ) - .size(menu_bar_size.unwrap().1) - .apply(Element::from) +impl Default for ResponsiveMenuBar { + fn default() -> ResponsiveMenuBar { + ResponsiveMenuBar { + item_width: ItemWidth::Uniform(150), + item_height: ItemHeight::Uniform(30), + spacing: 0., + } + } +} + +impl ResponsiveMenuBar { + /// Set the item width + pub fn item_width(mut self, item_width: ItemWidth) -> Self { + self.item_width = item_width; + self + } + + /// Set the item height + pub fn item_height(mut self, item_height: ItemHeight) -> Self { + self.item_height = item_height; + self + } + + /// Set the spacing + pub fn spacing(mut self, spacing: f32) -> Self { + self.spacing = spacing; + self + } + + /// # Panics + /// + /// Will panic if the menu bar collapses without tracking the size + pub fn into_element< + 'a, + Message: Clone + 'static, + A: menu::Action, + S: Into> + 'static, + >( + self, + core: &Core, + key_binds: &HashMap, + id: crate::widget::Id, + action_message: impl Fn(crate::surface::Action) -> Message + 'static, + trees: Vec<(S, Vec>)>, + ) -> Element<'a, Message> { + use crate::widget::id_container; + + let menu_bar_size = core.menu_bars.get(&id); + + #[allow(clippy::if_not_else)] + if !menu_bar_size.is_some_and(|(limits, size)| { + let max_size = limits.max(); + max_size.width < size.width + }) { + responsive_container::responsive_container( + id_container( + menu::bar( + trees + .into_iter() + .map(|mt| { + menu::Tree::<_>::with_children( + menu::root(mt.0), + menu::items(key_binds, mt.1.into()), + ) + }) + .collect(), + ) + .item_width(self.item_width) + .item_height(self.item_height) + .spacing(self.spacing), + crate::widget::Id::new(format!("menu_bar_expanded_{id}")), + ), + id, + action_message, + ) + .apply(Element::from) + } else { + responsive_container::responsive_container( + id_container( + menu::bar(vec![menu::Tree::<_>::with_children( + Element::from( + button::icon(icon::from_name("open-menu-symbolic")) + .padding([4, 12]) + .class(crate::theme::Button::MenuRoot), + ), + menu::items( + key_binds, + trees + .into_iter() + .map(|mt| menu::Item::Folder(mt.0, mt.1.into())) + .collect(), + ), + )]) + .item_height(self.item_height) + .item_width(self.item_width) + .spacing(self.spacing), + crate::widget::Id::new(format!("menu_bar_collapsed_{id}")), + ), + id, + action_message, + ) + .size(menu_bar_size.unwrap().1) + .apply(Element::from) + } } } From 1abd6d75786697b48e1d5922717febd0ebeb48ac Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 15 Apr 2025 17:58:20 -0400 Subject: [PATCH 181/556] refactor(responsive menu): define a custom width for the collapsed menu This can be much smaller than a regular menu to save space --- src/widget/responsive_menu_bar.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/widget/responsive_menu_bar.rs b/src/widget/responsive_menu_bar.rs index c2ee1308..65c5d3eb 100644 --- a/src/widget/responsive_menu_bar.rs +++ b/src/widget/responsive_menu_bar.rs @@ -14,6 +14,7 @@ pub fn responsive_menu_bar() -> ResponsiveMenuBar { } pub struct ResponsiveMenuBar { + collapsed_item_width: ItemWidth, item_width: ItemWidth, item_height: ItemHeight, spacing: f32, @@ -22,6 +23,7 @@ pub struct ResponsiveMenuBar { impl Default for ResponsiveMenuBar { fn default() -> ResponsiveMenuBar { ResponsiveMenuBar { + collapsed_item_width: ItemWidth::Static(84), item_width: ItemWidth::Uniform(150), item_height: ItemHeight::Uniform(30), spacing: 0., @@ -110,10 +112,17 @@ impl ResponsiveMenuBar { .into_iter() .map(|mt| menu::Item::Folder(mt.0, mt.1.into())) .collect(), - ), + ) + .into_iter() + .map(|t| { + t.width(match self.item_width { + ItemWidth::Uniform(w) | ItemWidth::Static(w) => w, + }) + }) + .collect(), )]) .item_height(self.item_height) - .item_width(self.item_width) + .item_width(self.collapsed_item_width) .spacing(self.spacing), crate::widget::Id::new(format!("menu_bar_collapsed_{id}")), ), From 7aadfe6ba627236173405144c4deb4bdcff8208c Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 21 Apr 2025 18:12:41 +0200 Subject: [PATCH 182/556] improv(context-drawer): import definitions and add map method --- src/app/context_drawer.rs | 18 ++++++++++++++++++ src/app/mod.rs | 2 +- src/widget/context_drawer/widget.rs | 17 ++++++++++++++++- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/app/context_drawer.rs b/src/app/context_drawer.rs index bb681242..5d15d7b4 100644 --- a/src/app/context_drawer.rs +++ b/src/app/context_drawer.rs @@ -62,4 +62,22 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { self.footer = Some(footer.into()); self } + + pub fn map( + mut self, + on_message: fn(Message) -> Out, + ) -> ContextDrawer<'a, Out> { + ContextDrawer { + title: self.title, + content: self.content.map(on_message), + header: self.header.map(|el| el.map(on_message)), + footer: self.footer.map(|el| el.map(on_message)), + on_close: on_message(self.on_close), + header_actions: self + .header_actions + .into_iter() + .map(|el| el.map(on_message)) + .collect(), + } + } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 4cb144ad..31355059 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -10,6 +10,7 @@ mod action; pub use action::Action; use cosmic_config::CosmicConfigEntry; pub mod context_drawer; +pub use context_drawer::{ContextDrawer, context_drawer}; pub mod cosmic; #[cfg(all(feature = "winit", feature = "multi-window"))] pub(crate) mod multi_window; @@ -22,7 +23,6 @@ use crate::prelude::*; use crate::theme::THEME; use crate::widget::{container, horizontal_space, id_container, menu, nav_bar, popover}; use apply::Apply; -use context_drawer::ContextDrawer; use iced::window; use iced::{Length, Subscription}; pub use settings::Settings; diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index fd752ec6..7f493dac 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -164,12 +164,27 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { } /// Sets the [`Id`] of the [`ContextDrawer`]. + #[inline] pub fn id(mut self, id: iced_core::widget::Id) -> Self { self.id = Some(id); self } - // Optionally assigns message to `on_close` event. + /// Map the message type of the context drawer to another + #[inline] + pub fn map( + mut self, + on_message: fn(Message) -> Out, + ) -> ContextDrawer<'a, Out> { + ContextDrawer { + id: self.id, + content: self.content.map(on_message), + drawer: self.drawer.map(on_message), + on_close: self.on_close.map(on_message), + } + } + + /// Optionally assigns message to `on_close` event. #[inline] pub fn on_close_maybe(mut self, message: Option) -> Self { self.on_close = message; From 9b9d373e89aaf48e9d77e1621fe0de991d573764 Mon Sep 17 00:00:00 2001 From: Horu <73709188+HigherOrderLogic@users.noreply.github.com> Date: Tue, 22 Apr 2025 22:37:58 +1000 Subject: [PATCH 183/556] chore(task): add none function --- src/task.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/task.rs b/src/task.rs index 35dcf4de..f155706e 100644 --- a/src/task.rs +++ b/src/task.rs @@ -31,3 +31,7 @@ pub fn stream + 'static, Y: 'static>( ) -> iced::Task { iced::Task::stream(stream.map(Into::into)) } + +pub fn none() -> iced::Task { + iced::Task::none() +} From e5802b535bf9d6599b0ac90a9499e80c60c0284b Mon Sep 17 00:00:00 2001 From: Juniper <67175453+Ultrasquid9@users.noreply.github.com> Date: Thu, 24 Apr 2025 11:26:33 -0400 Subject: [PATCH 184/556] chore: add highlighter feature for iced --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 357e6474..667013e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,6 +87,7 @@ winit_wgpu = ["winit", "wgpu"] xdg-portal = ["ashpd"] qr_code = ["iced/qr_code"] markdown = ["iced/markdown"] +highlighter = ["iced/highlighter"] async-std = [ "dep:async-std", "ashpd/async-std", From 6763abec4169cbdf7ce63e3fabfd320462543d09 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 30 Apr 2025 17:13:59 +0200 Subject: [PATCH 185/556] chore(cargo): update fde to 0.7.11 --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 667013e8..d1a1720b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -134,7 +134,7 @@ zbus = { version = "4.4.0", default-features = false, optional = true } [target.'cfg(unix)'.dependencies] freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } -freedesktop-desktop-entry = { version = "0.7.10", optional = true } +freedesktop-desktop-entry = { version = "0.7.11", optional = true } shlex = { version = "1.3.0", optional = true } [dependencies.cosmic-theme] @@ -215,6 +215,6 @@ libcosmic = { path = "./" } # FIXME update winit deps where necessary to use this # [patch.crates-io] -[patch."https://github.com/pop-os/winit.git"] +# [patch."https://github.com/pop-os/winit.git"] # winit = { git = "https://github.com/rust-windowing/winit.git", rev = "241b7a80bba96c91fa3901729cd5dec66abb9be4" } # winit = { path = "../../winit" } From 76c7100ef454c4b92e19a7f531e701694b24451b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 29 Apr 2025 13:53:16 -0400 Subject: [PATCH 186/556] refactor: add only_show_in filter for loaded apps --- src/desktop.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/desktop.rs b/src/desktop.rs index 1fef2e7d..c9c4c491 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -53,10 +53,18 @@ pub struct DesktopEntryData { pub fn load_applications<'a>( locales: &'a [String], include_no_display: bool, + only_show_in: Option<&'a str>, ) -> impl Iterator + 'a { fde::Iter::new(fde::default_paths()) .filter_map(move |p| fde::DesktopEntry::from_path(p, Some(locales)).ok()) - .filter(move |de| include_no_display || !de.no_display()) + .filter(move |de| { + (include_no_display || !de.no_display()) + && !only_show_in.zip(de.only_show_in()).is_some_and( + |(xdg_current_desktop, only_show_in)| { + !only_show_in.contains(&xdg_current_desktop) + }, + ) + }) .map(move |de| DesktopEntryData::from_desktop_entry(locales, de)) } @@ -69,6 +77,7 @@ pub fn load_applications_for_app_ids<'a>( app_ids: Vec<&'a str>, fill_missing_ones: bool, include_no_display: bool, + only_show_in: Option<&'a str>, ) -> impl Iterator + 'a { let app_ids = std::rc::Rc::new(std::cell::RefCell::new(app_ids)); let app_ids_ = app_ids.clone(); @@ -78,6 +87,11 @@ pub fn load_applications_for_app_ids<'a>( if !include_no_display && de.no_display() { return false; } + if only_show_in.zip(de.only_show_in()).is_some_and( + |(xdg_current_desktop, only_show_in)| !only_show_in.contains(&xdg_current_desktop), + ) { + return false; + } // Search by ID first app_ids From 264b9d83678e2125b51f9dc5de964d6a804d633e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 30 Apr 2025 12:28:05 -0400 Subject: [PATCH 187/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 1b462682..d4585d59 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 1b462682c047852c303c8c4caefc48caeec900a5 +Subproject commit d4585d59df8c411c47a3443782414c4b82e11ef9 From 8fa4b1cae2620a1caffc4daec26736f1e4a6330b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 30 Apr 2025 15:32:41 -0400 Subject: [PATCH 188/556] fix: card style --- src/theme/style/iced.rs | 22 ---------------------- src/widget/card/style.rs | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index b1118480..b284e4e3 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -1406,28 +1406,6 @@ impl text_input::Catalog for Theme { } } -// TODO card -// impl crate::widget::card::style::StyleSheet for Theme { -// fn default(&self) -> crate::widget::card::style::Style { -// let cosmic = self.cosmic(); - -// match self.layer { -// cosmic_theme::Layer::Background => crate::widget::card::style::Style { -// card_1: Background::Color(cosmic.background.component.hover.into()), -// card_2: Background::Color(cosmic.background.component.pressed.into()), -// }, -// cosmic_theme::Layer::Primary => crate::widget::card::style::Style { -// card_1: Background::Color(cosmic.primary.component.hover.into()), -// card_2: Background::Color(cosmic.primary.component.pressed.into()), -// }, -// cosmic_theme::Layer::Secondary => crate::widget::card::style::Style { -// card_1: Background::Color(cosmic.secondary.component.hover.into()), -// card_2: Background::Color(cosmic.secondary.component.pressed.into()), -// }, -// } -// } -// } - #[derive(Default)] pub enum TextEditor<'a> { #[default] diff --git a/src/widget/card/style.rs b/src/widget/card/style.rs index d73ef8fc..0e63e846 100644 --- a/src/widget/card/style.rs +++ b/src/widget/card/style.rs @@ -24,3 +24,24 @@ pub trait Catalog { /// The default [`Appearance`] of the cards. fn default(&self) -> Style; } + +impl crate::widget::card::style::Catalog for crate::Theme { + fn default(&self) -> crate::widget::card::style::Style { + let cosmic = self.cosmic(); + + match self.layer { + cosmic_theme::Layer::Background => crate::widget::card::style::Style { + card_1: Background::Color(cosmic.background.component.hover.into()), + card_2: Background::Color(cosmic.background.component.pressed.into()), + }, + cosmic_theme::Layer::Primary => crate::widget::card::style::Style { + card_1: Background::Color(cosmic.primary.component.hover.into()), + card_2: Background::Color(cosmic.primary.component.pressed.into()), + }, + cosmic_theme::Layer::Secondary => crate::widget::card::style::Style { + card_1: Background::Color(cosmic.secondary.component.hover.into()), + card_2: Background::Color(cosmic.secondary.component.pressed.into()), + }, + } + } +} From e838616813470aa6a108ede07ab8e6050b68a5a6 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 30 Apr 2025 16:32:05 -0600 Subject: [PATCH 189/556] Update iced with support for infinite list --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index d4585d59..3766ae5c 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit d4585d59df8c411c47a3443782414c4b82e11ef9 +Subproject commit 3766ae5c9e8327ebcb5f3110694810b6bc263049 From 7151638f519941d8439ad0ee05723c39ed561941 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 1 May 2025 18:08:56 -0400 Subject: [PATCH 190/556] fix(theme mode subscription): avoid checking the keys because this interferes with the first value from the subscription the check should be redundant, because we also later check whether the value has changed or not anyway --- src/app/cosmic.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 40748473..940030d8 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -602,9 +602,6 @@ impl Cosmic { }; } Action::SystemThemeModeChange(keys, mode) => { - if !keys.contains(&"is_dark") { - return iced::Task::none(); - } if match THEME.lock().unwrap().theme_type { ThemeType::System { theme: _, From c8c650c0410258bb8cd5321bb93845979c7260a4 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 6 May 2025 13:31:42 -0400 Subject: [PATCH 191/556] fix: text input RTL panic RTL text will want to alight to the right, and will try to use all of an unbounded width in the input. It also tends to be offset in a negative direction. --- src/widget/text_input/input.rs | 67 ++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 3e0c9831..86baa31c 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -699,8 +699,7 @@ where let size = self.size.unwrap_or_else(|| renderer.default_size().0); - let bounds = limits.max(); - + let bounds = limits.resolve(Length::Shrink, Length::Fill, Size::INFINITY); let value_paragraph = &mut state.value; let v = self.value.to_string(); value_paragraph.update(Text { @@ -716,7 +715,7 @@ where vertical_alignment: alignment::Vertical::Center, line_height: text::LineHeight::default(), shaping: text::Shaping::Advanced, - wrapping: text::Wrapping::default(), + wrapping: text::Wrapping::None, }); let Size { width, height } = @@ -760,6 +759,7 @@ where font, iced::Pixels(size), line_height, + limits, ); } res @@ -1141,7 +1141,7 @@ pub fn layout( vertical_alignment: alignment::Vertical::Center, line_height, shaping: text::Shaping::Advanced, - wrapping: text::Wrapping::default(), + wrapping: text::Wrapping::None, }); let label_size = label_paragraph.min_bounds(); @@ -1194,9 +1194,7 @@ pub fn layout( let text_limits = limits .width(width) .height(line_height.to_absolute(text_size.into())); - - let text_bounds = text_limits.resolve(width, Length::Shrink, Size::INFINITY); - + let text_bounds = text_limits.resolve(Length::Shrink, Length::Shrink, Size::INFINITY); let text_node = layout::Node::new( text_bounds - Size::new(leading_icon_width + trailing_icon_width, 0.0), ) @@ -1250,7 +1248,7 @@ pub fn layout( .width(width) .height(text_input_height + padding.vertical()) .shrink(padding); - let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITY); + let text_bounds = limits.resolve(Length::Shrink, Length::Shrink, Size::INFINITY); let text = layout::Node::new(text_bounds).move_to(Point::new(padding.left, padding.top)); @@ -1280,7 +1278,7 @@ pub fn layout( vertical_alignment: alignment::Vertical::Center, line_height: helper_text_line_height, shaping: text::Shaping::Advanced, - wrapping: text::Wrapping::default(), + wrapping: text::Wrapping::None, }); let helper_text_size = helper_text_paragraph.min_bounds(); let helper_text_node = layout::Node::new(helper_text_size).translate(helper_pos); @@ -1339,7 +1337,15 @@ pub fn update<'a, Message: Clone + 'static>( manage_value: bool, ) -> event::Status { let update_cache = |state, value| { - replace_paragraph(state, layout, value, font, iced::Pixels(size), line_height); + replace_paragraph( + state, + layout, + value, + font, + iced::Pixels(size), + line_height, + &Limits::NONE.max_width(text_layout.bounds().width), + ); }; let mut secured_value = if is_secure { @@ -2208,7 +2214,7 @@ pub fn draw<'a, Message>( vertical_alignment: alignment::Vertical::Top, line_height, shaping: text::Shaping::Advanced, - wrapping: text::Wrapping::default(), + wrapping: text::Wrapping::None, }, label_layout.bounds().position(), appearance.label_color, @@ -2243,6 +2249,8 @@ pub fn draw<'a, Message>( let text = value.to_string(); let font = font.unwrap_or_else(|| renderer.default_font()); let size = size.unwrap_or_else(|| renderer.default_size().0); + let text_width = state.value.min_width(); + let actual_width = text_width.max(text_bounds.width); let radius_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0.into(); #[cfg(feature = "wayland")] @@ -2264,7 +2272,6 @@ pub fn draw<'a, Message>( || ((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS) % 2 == 0; - if is_cursor_visible { if dnd_icon { (None, 0.0) @@ -2273,7 +2280,12 @@ pub fn draw<'a, Message>( Some(( renderer::Quad { bounds: Rectangle { - x: text_bounds.x + text_value_width - offset, + x: text_bounds.x + text_value_width - offset + + if text_value_width < 0. { + actual_width + } else { + 0. + }, y: text_bounds.y, width: 1.0, height: text_bounds.height, @@ -2310,7 +2322,6 @@ pub fn draw<'a, Message>( measure_cursor_and_scroll_offset(value_paragraph.raw(), text_bounds, right); let width = right_position - left_position; - if dnd_icon { (None, 0.0) } else { @@ -2318,7 +2329,13 @@ pub fn draw<'a, Message>( Some(( renderer::Quad { bounds: Rectangle { - x: text_bounds.x + left_position, + x: text_bounds.x + + left_position + + if left_position < 0. || right_position < 0. { + actual_width + } else { + 0. + }, y: text_bounds.y, width, height: text_bounds.height, @@ -2349,8 +2366,6 @@ pub fn draw<'a, Message>( (None, 0.0) }; - let text_width = state.value.min_width(); - let render = |renderer: &mut crate::Renderer| { if let Some((cursor, color)) = cursor { renderer.fill_quad(cursor, color); @@ -2361,7 +2376,7 @@ pub fn draw<'a, Message>( let bounds = Rectangle { x: text_bounds.x - offset, y: text_bounds.center_y(), - width: f32::INFINITY, + width: actual_width, ..text_bounds }; let color = if text.is_empty() { @@ -2384,7 +2399,7 @@ pub fn draw<'a, Message>( vertical_alignment: alignment::Vertical::Center, line_height: text::LineHeight::default(), shaping: text::Shaping::Advanced, - wrapping: text::Wrapping::default(), + wrapping: text::Wrapping::None, }, bounds.position(), color, @@ -2432,7 +2447,7 @@ pub fn draw<'a, Message>( vertical_alignment: alignment::Vertical::Top, line_height: helper_line_height, shaping: text::Shaping::Advanced, - wrapping: text::Wrapping::default(), + wrapping: text::Wrapping::None, }, helper_text_layout.bounds().position(), text_color, @@ -2769,20 +2784,26 @@ fn replace_paragraph( font: ::Font, text_size: Pixels, line_height: text::LineHeight, + limits: &layout::Limits, ) { let mut children_layout = layout.children(); - let text_bounds = children_layout.next().unwrap().bounds(); + let text_bounds = children_layout.next().unwrap(); + let bounds = limits.resolve( + Length::Shrink, + Length::Fill, + Size::new(0., text_bounds.bounds().height), + ); state.value = crate::Plain::new(Text { font, line_height, content: &value.to_string(), - bounds: Size::new(f32::INFINITY, text_bounds.height), + bounds, size: text_size, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, shaping: text::Shaping::Advanced, - wrapping: text::Wrapping::default(), + wrapping: text::Wrapping::None, }); } From c42b8ff5a5a463ece6710b976fe97afd65b87c1e Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 15 May 2025 14:15:52 -0600 Subject: [PATCH 192/556] Update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 3766ae5c..1a25f501 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 3766ae5c9e8327ebcb5f3110694810b6bc263049 +Subproject commit 1a25f501d02f545f0989cf13e2bd4cac5d8d520f From d4a87bd394c8341bddcee2e274d3a695da74cf68 Mon Sep 17 00:00:00 2001 From: Mia McMahill <84749759+electricbrass@users.noreply.github.com> Date: Fri, 16 May 2025 10:00:35 -0500 Subject: [PATCH 193/556] fix: embed spin button and warning icons on non-linux systems --- res/icons/list-add-symbolic.svg | 3 ++ res/icons/list-remove-symbolic.svg | 3 ++ src/widget/spin_button.rs | 59 ++++++++++++------------------ src/widget/warning.rs | 10 +++++ 4 files changed, 40 insertions(+), 35 deletions(-) create mode 100644 res/icons/list-add-symbolic.svg create mode 100644 res/icons/list-remove-symbolic.svg diff --git a/res/icons/list-add-symbolic.svg b/res/icons/list-add-symbolic.svg new file mode 100644 index 00000000..59b2fb03 --- /dev/null +++ b/res/icons/list-add-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/icons/list-remove-symbolic.svg b/res/icons/list-remove-symbolic.svg new file mode 100644 index 00000000..5b9ded7c --- /dev/null +++ b/res/icons/list-remove-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index 1a2f3e0f..9a2c59d7 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -149,29 +149,33 @@ where } } } +macro_rules! make_button { + ($spin_button:expr, $icon:expr, $operation:expr) => {{ + #[cfg(target_os = "linux")] + let button = icon::from_name($icon); + + #[cfg(not(target_os = "linux"))] + let button = icon::from_svg_bytes( + include_bytes!(concat!["../../res/icons/", $icon ,".svg"]) + ).symbolic(true); + + button.apply(button::icon) + .on_press(($spin_button.on_press)($operation( + $spin_button.value, + $spin_button.step, + $spin_button.min, + $spin_button.max, + ))) + }}; +} fn horizontal_variant(spin_button: SpinButton<'_, T, Message>) -> Element<'_, Message> where Message: Clone + 'static, T: Copy + Sub + Add + PartialOrd, { - let decrement_button = icon::from_name("list-remove-symbolic") - .apply(button::icon) - .on_press((spin_button.on_press)(decrement::( - spin_button.value, - spin_button.step, - spin_button.min, - spin_button.max, - ))); - - let increment_button = icon::from_name("list-add-symbolic") - .apply(button::icon) - .on_press((spin_button.on_press)(increment::( - spin_button.value, - spin_button.step, - spin_button.min, - spin_button.max, - ))); + let decrement_button = make_button!(spin_button, "list-remove-symbolic", decrement); + let increment_button = make_button!(spin_button, "list-add-symbolic", increment); let label = text::title4(spin_button.label) .apply(container) @@ -193,23 +197,8 @@ where Message: Clone + 'static, T: Copy + Sub + Add + PartialOrd, { - let decrement_button = icon::from_name("list-remove-symbolic") - .apply(button::icon) - .on_press((spin_button.on_press)(decrement::( - spin_button.value, - spin_button.step, - spin_button.min, - spin_button.max, - ))); - - let increment_button = icon::from_name("list-add-symbolic") - .apply(button::icon) - .on_press((spin_button.on_press)(increment::( - spin_button.value, - spin_button.step, - spin_button.min, - spin_button.max, - ))); + let decrement_button = make_button!(spin_button, "list-remove-symbolic", decrement); + let increment_button = make_button!(spin_button, "list-add-symbolic", increment); let label = text::title4(spin_button.label) .apply(container) @@ -263,4 +252,4 @@ mod tests { fn decrement() { assert_eq!(super::decrement(0i32, 10, 15, 35), 15); } -} +} \ No newline at end of file diff --git a/src/widget/warning.rs b/src/widget/warning.rs index 942ffb8b..92fa7178 100644 --- a/src/widget/warning.rs +++ b/src/widget/warning.rs @@ -33,11 +33,21 @@ impl<'a, Message: 'static + Clone> Warning<'a, Message> { pub fn into_widget(self) -> widget::Container<'a, Message, crate::Theme, Renderer> { let label = widget::container(crate::widget::text(self.message)).width(Length::Fill); + #[cfg(target_os = "linux")] let close_button = icon::from_name("window-close-symbolic") .size(16) .apply(widget::button::icon) .on_press_maybe(self.on_close); + #[cfg(not(target_os = "linux"))] + let close_button = icon::from_svg_bytes( + include_bytes!("../../res/icons/window-close-symbolic.svg") + ) + .symbolic(true) + .apply(widget::button::icon) + .icon_size(16) + .on_press_maybe(self.on_close); + widget::row::with_capacity(2) .push(label) .push(close_button) From b7aed0e4d6a5caf66c70bc2d44e00070f4d87d4e Mon Sep 17 00:00:00 2001 From: UchiWerfer <87275644+UchiWerfer@users.noreply.github.com> Date: Fri, 16 May 2025 17:02:16 +0200 Subject: [PATCH 194/556] feat(calendar): add first day of week parameter --- src/widget/calendar.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index 046a95df..07d0d0b2 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -15,12 +15,14 @@ pub fn calendar( on_select: impl Fn(NaiveDate) -> M + 'static, on_prev: impl Fn() -> M + 'static, on_next: impl Fn() -> M + 'static, + first_day_of_week: Weekday ) -> Calendar { Calendar { model, on_select: Box::new(on_select), on_prev: Box::new(on_prev), on_next: Box::new(on_next), + first_day_of_week } } @@ -105,6 +107,7 @@ pub struct Calendar<'a, M> { on_select: Box M>, on_prev: Box M>, on_next: Box M>, + first_day_of_week: Weekday } impl<'a, Message> From> for crate::Element<'a, Message> @@ -130,7 +133,7 @@ where let mut calendar_grid: Grid<'_, Message> = grid().padding([0, 12].into()).width(Length::Fill); - let mut first_day_of_week = Weekday::Sun; // TODO: Configurable + let mut first_day_of_week = this.first_day_of_week; for _ in 0..7 { calendar_grid = calendar_grid.push( text(first_day_of_week.to_string()) From 2a1af3a24fe697bc3aab8bda86913d89ec1d4bd4 Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Mon, 19 May 2025 13:06:09 -0400 Subject: [PATCH 195/556] chore: update iced --- iced | 2 +- src/widget/calendar.rs | 6 +++--- src/widget/spin_button.rs | 15 ++++++++------- src/widget/warning.rs | 13 ++++++------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/iced b/iced index 1a25f501..70d104a2 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 1a25f501d02f545f0989cf13e2bd4cac5d8d520f +Subproject commit 70d104a28a87f06eb46d76268b6fa18c407ee2c2 diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index 07d0d0b2..826c46f9 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -15,14 +15,14 @@ pub fn calendar( on_select: impl Fn(NaiveDate) -> M + 'static, on_prev: impl Fn() -> M + 'static, on_next: impl Fn() -> M + 'static, - first_day_of_week: Weekday + first_day_of_week: Weekday, ) -> Calendar { Calendar { model, on_select: Box::new(on_select), on_prev: Box::new(on_prev), on_next: Box::new(on_next), - first_day_of_week + first_day_of_week, } } @@ -107,7 +107,7 @@ pub struct Calendar<'a, M> { on_select: Box M>, on_prev: Box M>, on_next: Box M>, - first_day_of_week: Weekday + first_day_of_week: Weekday, } impl<'a, Message> From> for crate::Element<'a, Message> diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index 9a2c59d7..0dba4109 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -153,13 +153,14 @@ macro_rules! make_button { ($spin_button:expr, $icon:expr, $operation:expr) => {{ #[cfg(target_os = "linux")] let button = icon::from_name($icon); - + #[cfg(not(target_os = "linux"))] - let button = icon::from_svg_bytes( - include_bytes!(concat!["../../res/icons/", $icon ,".svg"]) - ).symbolic(true); - - button.apply(button::icon) + let button = + icon::from_svg_bytes(include_bytes!(concat!["../../res/icons/", $icon, ".svg"])) + .symbolic(true); + + button + .apply(button::icon) .on_press(($spin_button.on_press)($operation( $spin_button.value, $spin_button.step, @@ -252,4 +253,4 @@ mod tests { fn decrement() { assert_eq!(super::decrement(0i32, 10, 15, 35), 15); } -} \ No newline at end of file +} diff --git a/src/widget/warning.rs b/src/widget/warning.rs index 92fa7178..3e3a1ad4 100644 --- a/src/widget/warning.rs +++ b/src/widget/warning.rs @@ -40,13 +40,12 @@ impl<'a, Message: 'static + Clone> Warning<'a, Message> { .on_press_maybe(self.on_close); #[cfg(not(target_os = "linux"))] - let close_button = icon::from_svg_bytes( - include_bytes!("../../res/icons/window-close-symbolic.svg") - ) - .symbolic(true) - .apply(widget::button::icon) - .icon_size(16) - .on_press_maybe(self.on_close); + let close_button = + icon::from_svg_bytes(include_bytes!("../../res/icons/window-close-symbolic.svg")) + .symbolic(true) + .apply(widget::button::icon) + .icon_size(16) + .on_press_maybe(self.on_close); widget::row::with_capacity(2) .push(label) From 147fc5a2a46e3b6d742617510a6245a222074bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Sun, 18 May 2025 18:32:25 +0200 Subject: [PATCH 196/556] improv(header_bar): reduce chance of end elements being pushed out --- src/widget/header_bar.rs | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 369685f5..1d19f095 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -326,7 +326,6 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { let portion = ((start.len().max(end.len()) as f32 / center.len().max(1) as f32).round() as u16) .max(1); - let center_empty = center.is_empty() && self.title.is_empty(); // Creates the headerbar widget. let mut widget = widget::row::with_capacity(3) // If elements exist in the start region, append them here. @@ -340,17 +339,19 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { ) // If elements exist in the center region, use them here. // This will otherwise use the title as a widget if a title was defined. - .push(if !center.is_empty() { - widget::row::with_children(center) - .spacing(space_xxxs) - .align_y(iced::Alignment::Center) - .apply(widget::container) - .center_x(Length::Fill) - .into() - } else if self.title.is_empty() { - widget::horizontal_space().into() + .push_maybe(if !center.is_empty() { + Some( + widget::row::with_children(center) + .spacing(space_xxxs) + .align_y(iced::Alignment::Center) + .apply(widget::container) + .center_x(Length::Fill) + .into(), + ) + } else if !self.title.is_empty() { + Some(self.title_widget()) } else { - self.title_widget() + None }) .push( widget::row::with_children(end) @@ -358,11 +359,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .align_y(iced::Alignment::Center) .apply(widget::container) .align_x(iced::Alignment::End) - .width(if center_empty { - Length::Fill - } else { - Length::FillPortion(portion) - }), + .width(Length::FillPortion(portion)), ) .align_y(iced::Alignment::Center) .height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32)) From a46483f161ee4bfc13e14288fbf07757bc469766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Sun, 18 May 2025 19:24:27 +0200 Subject: [PATCH 197/556] fix(header_bar): add `is_ssd` field --- src/widget/header_bar.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 1d19f095..fdc9d962 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, + is_ssd: false, on_double_click: None, } } @@ -80,6 +81,9 @@ pub struct HeaderBar<'a, Message> { /// Maximized state of the window maximized: bool, + + /// HeaderBar used for server-side decorations + is_ssd: bool, } impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { @@ -363,7 +367,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { ) .align_y(iced::Alignment::Center) .height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32)) - .padding(padding) + .padding(if self.is_ssd { [0, 8, 0, 8] } else { padding }) .spacing(8) .apply(widget::container) .class(crate::theme::Container::HeaderBar { From ce56237ab9ea2aa7777656a32c9c28e2a38fc6c7 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 20 May 2025 22:28:57 -0400 Subject: [PATCH 198/556] fix(dnd): leave event handlers should expect None as the drag Id --- src/widget/dnd_destination.rs | 15 ++++++++++++--- src/widget/segmented_button/widget.rs | 5 ++--- src/widget/text_input/input.rs | 4 +++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index 89c7d44a..121648ab 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -241,6 +241,11 @@ impl<'a, Message: 'static> DndDestination<'a, Message> { Internal::Set(_) => panic!("Invalid Id assigned to dnd destination."), })) } + + pub fn id(mut self, id: Id) -> Self { + self.id = id; + self + } } impl Widget @@ -354,8 +359,12 @@ impl Widget } return event::Status::Captured; } - Event::Dnd(DndEvent::Offer(id, OfferEvent::Leave)) if id == Some(my_id) => { - state.on_leave(self.on_leave.as_ref().map(std::convert::AsRef::as_ref)); + Event::Dnd(DndEvent::Offer(id, OfferEvent::Leave)) => { + if let Some(msg) = + state.on_leave(self.on_leave.as_ref().map(std::convert::AsRef::as_ref)) + { + shell.publish(msg); + } if self.forward_drag_as_cursor { let drag_cursor = mouse::Cursor::Unavailable; @@ -403,7 +412,7 @@ impl Widget } return event::Status::Captured; } - Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if id == Some(my_id) => { + Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) => { if let Some(msg) = state.on_leave(self.on_leave.as_ref().map(std::convert::AsRef::as_ref)) { diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index e0c3a2a8..3cb64e2d 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -733,9 +733,8 @@ where entity, ); } - DndEvent::Offer(id, OfferEvent::Leave | OfferEvent::LeaveDestination) - if Some(my_id) == *id => - { + DndEvent::Offer(id, OfferEvent::LeaveDestination) if Some(my_id) != *id => {} + DndEvent::Offer(id, OfferEvent::Leave | OfferEvent::LeaveDestination) => { if let Some(Some(entity)) = entity { if let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() { shell.publish(on_dnd_leave(entity)); diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 86baa31c..eed1bed1 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -1995,10 +1995,12 @@ pub fn update<'a, Message: Clone + 'static>( return event::Status::Ignored; } #[cfg(feature = "wayland")] + Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if Some(dnd_id) != id => {} + #[cfg(feature = "wayland")] Event::Dnd(DndEvent::Offer( rectangle, OfferEvent::Leave | OfferEvent::LeaveDestination, - )) if rectangle == Some(dnd_id) => { + )) => { cold(); let state = state(); // ASHLEY TODO we should be able to reset but for now we don't if we are handling a From 25322e0263eaeff6e6594e54f97ad45840f6c98e Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 22 May 2025 15:02:23 -0700 Subject: [PATCH 199/556] feat!: update zbus from 4.x to 5.x --- Cargo.toml | 4 ++-- cosmic-config/Cargo.toml | 2 +- src/dbus_activation.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d1a1720b..4b19d103 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ desktop-systemd-scope = ["desktop", "dep:zbus"] # Enables keycode serialization serde-keycode = ["iced_core/serde"] # Prevents multiple separate process instances. -single-instance = ["dep:zbus", "ron"] +single-instance = ["zbus/blocking-api", "ron"] # smol async runtime smol = ["dep:smol", "iced/smol", "zbus?/async-io", "rfd?/async-std"] tokio = [ @@ -130,7 +130,7 @@ tokio = { version = "1.44.1", optional = true } tracing = "0.1.41" unicode-segmentation = "1.12" url = "2.5.4" -zbus = { version = "4.4.0", default-features = false, optional = true } +zbus = { version = "5.7.1", default-features = false, optional = true } [target.'cfg(unix)'.dependencies] freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } diff --git a/cosmic-config/Cargo.toml b/cosmic-config/Cargo.toml index 3fa7eb9d..a79237c8 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -11,7 +11,7 @@ subscription = ["iced_futures"] [dependencies] cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } -zbus = { version = "4.4.0", default-features = false, optional = true } +zbus = { version = "5.7.1", default-features = false, optional = true } atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" } calloop = { version = "0.14.2", optional = true } notify = "8.0.0" diff --git a/src/dbus_activation.rs b/src/dbus_activation.rs index 4fffa422..9880a7d0 100644 --- a/src/dbus_activation.rs +++ b/src/dbus_activation.rs @@ -21,7 +21,7 @@ pub fn subscription() -> Subscription Date: Thu, 22 May 2025 16:48:57 -0700 Subject: [PATCH 200/556] chore: update rfd and ashpd --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4b19d103..e9934e45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,7 +98,7 @@ async-std = [ [dependencies] apply = "0.3.0" -ashpd = { version = "0.9.2", default-features = false, optional = true } +ashpd = { version = "0.11.0", default-features = false, optional = true } async-fs = { version = "2.1", optional = true } async-std = { version = "1.13", optional = true } auto_enums = "0.8.7" @@ -118,7 +118,7 @@ libc = { version = "0.2.171", optional = true } license = { version = "3.6.0", optional = true } mime = { version = "0.3.17", optional = true } palette = "0.7.6" -rfd = { version = "0.14.1", default-features = false, features = [ +rfd = { version = "0.15.3", default-features = false, features = [ "xdg-portal", ], optional = true } rustix = { version = "1.0", features = ["pipe", "process"], optional = true } From 1fce5df160f595d1b1e5a8e2bb2a24775419f82d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 23 May 2025 12:40:10 -0400 Subject: [PATCH 201/556] refactor: add optional parameter for layout offset and bounds for button handlers Buttons are often used for toggling popups, so something allowing more straightforward positioning is important. --- examples/applet/src/window.rs | 100 ++++++++++++++++++-------------- src/applet/mod.rs | 6 +- src/widget/button/mod.rs | 6 +- src/widget/button/widget.rs | 102 ++++++++++++++++++++++++++++----- src/widget/calendar.rs | 2 +- src/widget/color_picker/mod.rs | 8 ++- src/widget/menu/menu_tree.rs | 11 +++- 7 files changed, 165 insertions(+), 70 deletions(-) diff --git a/examples/applet/src/window.rs b/examples/applet/src/window.rs index 4a822cea..66b2040a 100644 --- a/examples/applet/src/window.rs +++ b/examples/applet/src/window.rs @@ -1,7 +1,7 @@ use cosmic::app::{Core, Task}; use cosmic::iced::window::Id; -use cosmic::iced::Length; +use cosmic::iced::{Length, Rectangle}; use cosmic::iced_runtime::core::window; use cosmic::surface::action::{app_popup, destroy_popup}; use cosmic::widget::{dropdown::popup_dropdown, list_column, settings, toggler}; @@ -85,50 +85,62 @@ impl cosmic::Application for Window { } fn view(&self) -> Element { - let btn = self.core.applet.icon_button("display-symbolic").on_press( - if let Some(id) = self.popup { - Message::Surface(destroy_popup(id)) - } else { - Message::Surface(app_popup::( - |state: &mut Window| { - let new_id = Id::unique(); - state.popup = Some(new_id); - let popup_settings = state.core.applet.get_popup_settings( - state.core.main_window_id().unwrap(), - new_id, - None, - None, - None, - ); + let have_popup = self.popup.clone(); + let btn = self + .core + .applet + .icon_button("display-symbolic") + .on_press_with_rectangle(move |offset, bounds| { + if let Some(id) = have_popup { + Message::Surface(destroy_popup(id)) + } else { + Message::Surface(app_popup::( + move |state: &mut Window| { + let new_id = Id::unique(); + state.popup = Some(new_id); + let mut popup_settings = state.core.applet.get_popup_settings( + state.core.main_window_id().unwrap(), + new_id, + None, + None, + None, + ); - popup_settings - }, - Some(Box::new(move |state: &Window| { - let content_list = list_column() - .padding(5) - .spacing(0) - .add(settings::item( - "Example row", - cosmic::widget::container( - toggler(state.example_row) - .on_toggle(|value| Message::ToggleExampleRow(value)), - ) - .height(Length::Fixed(50.)), - )) - .add(popup_dropdown( - &["1", "asdf", "hello", "test"], - state.selected, - Message::Selected, - state.popup.unwrap_or(Id::NONE), - Message::Surface, - |m| m, - )); - Element::from(state.core.applet.popup_container(content_list)) - .map(cosmic::Action::App) - })), - )) - }, - ); + popup_settings.positioner.anchor_rect = Rectangle { + x: (bounds.x - offset.x) as i32, + y: (bounds.y - offset.y) as i32, + width: bounds.width as i32, + height: bounds.height as i32, + }; + + popup_settings + }, + Some(Box::new(move |state: &Window| { + let content_list = list_column() + .padding(5) + .spacing(0) + .add(settings::item( + "Example row", + cosmic::widget::container( + toggler(state.example_row) + .on_toggle(|value| Message::ToggleExampleRow(value)), + ) + .height(Length::Fixed(50.)), + )) + .add(popup_dropdown( + &["1", "asdf", "hello", "test"], + state.selected, + Message::Selected, + state.popup.unwrap_or(Id::NONE), + Message::Surface, + |m| m, + )); + Element::from(state.core.applet.popup_container(content_list)) + .map(cosmic::Action::App) + })), + )) + } + }); Element::from(self.core.applet.applet_tooltip::( btn, diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 8f8cbfc0..02a8c004 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -177,7 +177,7 @@ impl Context { matches!(self.anchor, PanelAnchor::Top | PanelAnchor::Bottom) } - pub fn icon_button_from_handle<'a, Message: 'static>( + pub fn icon_button_from_handle<'a, Message: Clone + 'static>( &self, icon: widget::icon::Handle, ) -> crate::widget::Button<'a, Message> { @@ -206,7 +206,7 @@ impl Context { .class(Button::AppletIcon) } - pub fn icon_button<'a, Message: 'static>( + pub fn icon_button<'a, Message: Clone + 'static>( &self, icon_name: &'a str, ) -> crate::widget::Button<'a, Message> { @@ -503,7 +503,7 @@ pub fn style() -> iced_runtime::Appearance { } } -pub fn menu_button<'a, Message>( +pub fn menu_button<'a, Message: Clone + 'a>( content: impl Into>, ) -> crate::widget::Button<'a, Message> { crate::widget::button::custom(content) diff --git a/src/widget/button/mod.rs b/src/widget/button/mod.rs index 721ee8b7..d9a4df94 100644 --- a/src/widget/button/mod.rs +++ b/src/widget/button/mod.rs @@ -44,12 +44,14 @@ use iced_core::{Length, Padding}; use std::borrow::Cow; /// A button with a custom element for its content. -pub fn custom<'a, Message>(content: impl Into>) -> Button<'a, Message> { +pub fn custom<'a, Message: Clone + 'a>( + content: impl Into>, +) -> Button<'a, Message> { Button::new(content.into()) } /// An image button which may contain any widget as its content. -pub fn custom_image_button<'a, Message>( +pub fn custom_image_button<'a, Message: Clone + 'a>( content: impl Into>, on_remove: Option, ) -> Button<'a, Message> { diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index bbf5e821..5a1da458 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -47,8 +47,8 @@ pub struct Button<'a, Message> { #[cfg(feature = "a11y")] label: Option>, content: crate::Element<'a, Message>, - on_press: Option, - on_press_down: Option, + on_press: Option Message + 'a>>, + on_press_down: Option Message + 'a>>, width: Length, height: Length, padding: Padding, @@ -58,7 +58,7 @@ pub struct Button<'a, Message> { force_enabled: bool, } -impl<'a, Message> Button<'a, Message> { +impl<'a, Message: Clone + 'a> Button<'a, Message> { /// Creates a new [`Button`] with the given content. pub(super) fn new(content: impl Into>) -> Self { Self { @@ -150,7 +150,19 @@ impl<'a, Message> Button<'a, Message> { /// Unless `on_press` or `on_press_down` is called, the [`Button`] will be disabled. #[inline] pub fn on_press(mut self, on_press: Message) -> Self { - self.on_press = Some(on_press); + self.on_press = Some(Box::new(move |_, _| on_press.clone())); + self + } + + /// Sets the message that will be produced when the [`Button`] is pressed and released. + /// + /// Unless `on_press` or `on_press_down` is called, the [`Button`] will be disabled. + #[inline] + pub fn on_press_with_rectangle( + mut self, + on_press: impl Fn(Vector, Rectangle) -> Message + 'a, + ) -> Self { + self.on_press = Some(Box::new(on_press)); self } @@ -159,7 +171,19 @@ impl<'a, Message> Button<'a, Message> { /// Unless `on_press` or `on_press_down` is called, the [`Button`] will be disabled. #[inline] pub fn on_press_down(mut self, on_press: Message) -> Self { - self.on_press_down = Some(on_press); + self.on_press_down = Some(Box::new(move |_, _| on_press.clone())); + self + } + + /// Sets the message that will be produced when the [`Button`] is pressed, + /// + /// Unless `on_press` or `on_press_down` is called, the [`Button`] will be disabled. + #[inline] + pub fn on_press_down_with_rectange( + mut self, + on_press: impl Fn(Vector, Rectangle) -> Message + 'a, + ) -> Self { + self.on_press_down = Some(Box::new(on_press)); self } @@ -169,7 +193,49 @@ impl<'a, Message> Button<'a, Message> { /// If `None`, the [`Button`] will be disabled. #[inline] pub fn on_press_maybe(mut self, on_press: Option) -> Self { - self.on_press = on_press; + if let Some(m) = on_press { + self.on_press(m) + } else { + self.on_press = None; + self + } + } + + /// Sets the message that will be produced when the [`Button`] is pressed and released. + /// + /// Unless `on_press` or `on_press_down` is called, the [`Button`] will be disabled. + #[inline] + pub fn on_press_maybe_with_rectangle( + mut self, + on_press: impl Fn(Vector, Rectangle) -> Message + 'a, + ) -> Self { + self.on_press = Some(Box::new(on_press)); + self + } + + /// Sets the message that will be produced when the [`Button`] is pressed, + /// if `Some`. + /// + /// If `None`, the [`Button`] will be disabled. + #[inline] + pub fn on_press_down_maybe(mut self, on_press: Option) -> Self { + if let Some(m) = on_press { + self.on_press(m) + } else { + self.on_press_down = None; + self + } + } + + /// Sets the message that will be produced when the [`Button`] is pressed and released. + /// + /// Unless `on_press` or `on_press_down` is called, the [`Button`] will be disabled. + #[inline] + pub fn on_press_down_maybe_with_rectangle( + mut self, + on_press: impl Fn(Vector, Rectangle) -> Message + 'a, + ) -> Self { + self.on_press_down = Some(Box::new(on_press)); self } @@ -350,8 +416,8 @@ impl<'a, Message: 'a + Clone> Widget layout, cursor, shell, - &self.on_press, - &self.on_press_down, + self.on_press.as_deref(), + self.on_press_down.as_deref(), || tree.state.downcast_mut::(), ) } @@ -710,15 +776,15 @@ impl State { /// Processes the given [`Event`] and updates the [`State`] of a [`Button`] /// accordingly. -#[allow(clippy::needless_pass_by_value)] +#[allow(clippy::needless_pass_by_value, clippy::too_many_arguments)] pub fn update<'a, Message: Clone>( _id: Id, event: Event, layout: Layout<'_>, cursor: mouse::Cursor, shell: &mut Shell<'_, Message>, - on_press: &Option, - on_press_down: &Option, + on_press: Option<&dyn Fn(Vector, Rectangle) -> Message>, + on_press_down: Option<&dyn Fn(Vector, Rectangle) -> Message>, state: impl FnOnce() -> &'a mut State, ) -> event::Status { match event { @@ -735,7 +801,8 @@ pub fn update<'a, Message: Clone>( state.is_pressed = true; if let Some(on_press_down) = on_press_down { - shell.publish(on_press_down.clone()); + let msg = (on_press_down)(layout.virtual_offset(), layout.bounds()); + shell.publish(msg); } return event::Status::Captured; @@ -753,7 +820,8 @@ pub fn update<'a, Message: Clone>( let bounds = layout.bounds(); if cursor.is_over(bounds) { - shell.publish(on_press); + let msg = (on_press)(layout.virtual_offset(), layout.bounds()); + shell.publish(msg); } return event::Status::Captured; @@ -771,7 +839,9 @@ pub fn update<'a, Message: Clone>( .then(|| on_press.clone()) { state.is_pressed = false; - shell.publish(on_press); + let msg = (on_press)(layout.virtual_offset(), layout.bounds()); + + shell.publish(msg); } return event::Status::Captured; } @@ -780,7 +850,9 @@ pub fn update<'a, Message: Clone>( let state = state(); if state.is_focused && key == keyboard::Key::Named(keyboard::key::Named::Enter) { state.is_pressed = true; - shell.publish(on_press); + let msg = (on_press)(layout.virtual_offset(), layout.bounds()); + + shell.publish(msg); return event::Status::Captured; } } diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index 826c46f9..83b1dcfd 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -191,7 +191,7 @@ where } } -fn date_button( +fn date_button( date: NaiveDate, is_currently_viewed_month: bool, is_currently_selected_day: bool, diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 1762330d..7d088515 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -122,7 +122,11 @@ impl ColorPickerModel { /// Get a color picker button that displays the applied color /// - pub fn picker_button<'a, Message: 'static, T: Fn(ColorPickerUpdate) -> Message>( + pub fn picker_button< + 'a, + Message: 'static + std::clone::Clone, + T: Fn(ColorPickerUpdate) -> Message, + >( &self, f: T, icon_portion: Option, @@ -888,7 +892,7 @@ fn color_to_string(c: palette::Hsv, is_hex: bool) -> String { #[allow(clippy::too_many_lines)] /// A button for selecting a color from a color picker. -pub fn color_button<'a, Message: 'static>( +pub fn color_button<'a, Message: Clone + 'static>( on_press: Option, color: Option, icon_portion: Length, diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index 921b4dba..1f3fd4ab 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -142,9 +142,12 @@ where } } -pub fn menu_button<'a, Message: 'a>( +pub fn menu_button<'a, Message>( children: Vec>, -) -> crate::widget::Button<'a, Message> { +) -> crate::widget::Button<'a, Message> +where + Message: std::clone::Clone + 'a, +{ widget::button::custom( widget::Row::with_children(children) .align_y(Alignment::Center) @@ -195,6 +198,7 @@ pub fn menu_root<'a, Message, Renderer: renderer::Renderer>( ) -> Button<'a, Message> where Element<'a, Message, crate::Theme, Renderer>: From>, + Message: std::clone::Clone + 'a, { widget::button::custom(widget::text(label)) .padding([4, 12]) @@ -215,7 +219,7 @@ pub fn menu_items< 'a, A: MenuAction, L: Into> + 'static, - Message: 'a, + Message, Renderer: renderer::Renderer + 'a, >( key_binds: &HashMap, @@ -223,6 +227,7 @@ pub fn menu_items< ) -> Vec> where Element<'a, Message, crate::Theme, Renderer>: From>, + Message: 'a + Clone, { fn find_key(action: &A, key_binds: &HashMap) -> String { for (key_bind, key_action) in key_binds { From a55ed23ba8d8f8f72599daf0824074986057bdff Mon Sep 17 00:00:00 2001 From: Soso <51865119+sgued@users.noreply.github.com> Date: Mon, 26 May 2025 22:53:35 +0200 Subject: [PATCH 202/556] improv!(dropdown): accept `impl Into` (#881) --- src/widget/dropdown/mod.rs | 13 ++++++++----- src/widget/dropdown/widget.rs | 16 ++++++++-------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/widget/dropdown/mod.rs b/src/widget/dropdown/mod.rs index 3ebaffaf..bcb37af8 100644 --- a/src/widget/dropdown/mod.rs +++ b/src/widget/dropdown/mod.rs @@ -4,6 +4,8 @@ //! Displays a list of options in a popover menu on select. +use std::borrow::Cow; + pub mod menu; use iced_core::window; pub use menu::Menu; @@ -17,14 +19,15 @@ use crate::surface; /// Displays a list of options in a popover menu on select. pub fn dropdown< + 'a, S: AsRef + std::clone::Clone + Send + Sync + 'static, Message: 'static + Clone, >( - selections: &[S], + selections: impl Into>, selected: Option, on_selected: impl Fn(usize) -> Message + Send + Sync + 'static, -) -> Dropdown<'_, S, Message, Message> { - Dropdown::new(selections, selected, on_selected) +) -> Dropdown<'a, S, Message, Message> { + Dropdown::new(selections.into(), selected, on_selected) } /// Displays a list of options in a popover menu on select. @@ -35,7 +38,7 @@ pub fn popup_dropdown< Message: 'static + Clone, AppMessage: 'static + Clone, >( - selections: &'a [S], + selections: impl Into>, selected: Option, on_selected: impl Fn(usize) -> Message + Send + Sync + 'static, _parent_id: window::Id, @@ -43,7 +46,7 @@ pub fn popup_dropdown< _map_action: impl Fn(Message) -> AppMessage + Send + Sync + 'static, ) -> Dropdown<'a, S, Message, AppMessage> { let dropdown: Dropdown<'_, S, Message, AppMessage> = - Dropdown::new(selections, selected, on_selected); + Dropdown::new(selections.into(), selected, on_selected); #[cfg(all(feature = "winit", feature = "wayland"))] let dropdown = dropdown.with_popup(_parent_id, _on_surface_action, _map_action); diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index e2df1a55..76ee7b05 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -34,9 +34,9 @@ where #[setters(skip)] on_selected: Arc Message + Send + Sync>, #[setters(skip)] - selections: &'a [S], + selections: Cow<'a, [S]>, #[setters] - icons: &'a [icon::Handle], + icons: Cow<'a, [icon::Handle]>, #[setters(skip)] selected: Option, #[setters(into)] @@ -73,14 +73,14 @@ where /// Creates a new [`Dropdown`] with the given list of selections, the current /// selected value, and the message to produce when an option is selected. pub fn new( - selections: &'a [S], + selections: Cow<'a, [S]>, selected: Option, on_selected: impl Fn(usize) -> Message + 'static + Send + Sync, ) -> Self { Self { on_selected: Arc::new(on_selected), selections, - icons: &[], + icons: Cow::Borrowed(&[]), selected, width: Length::Shrink, gap: Self::DEFAULT_GAP, @@ -246,12 +246,12 @@ where self.positioner.clone(), self.on_selected.clone(), self.selected, - self.selections, + &self.selections, || tree.state.downcast_mut::(), self.window_id, self.on_surface_action.clone(), self.action_map.clone(), - self.icons, + &self.icons, self.gap, self.padding, self.text_size, @@ -322,8 +322,8 @@ where self.text_size.unwrap_or(14.0), self.text_line_height, self.font, - self.selections, - self.icons, + &self.selections, + &self.icons, self.selected, self.on_selected.as_ref(), translation, From b73532f62f0a97b1611f2c35f54d0a805f845480 Mon Sep 17 00:00:00 2001 From: Ryan Brue Date: Fri, 23 May 2025 20:28:32 -0500 Subject: [PATCH 203/556] Handle Custom panel size in libcosmic The logic here is set up to mimic the current text sizes for the hardcoded panel sizes. Signed-off-by: Ryan Brue --- src/applet/mod.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 02a8c004..2efbfae2 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -431,6 +431,17 @@ impl Context { Size::PanelSize(PanelSize::M) => crate::widget::text::title4, Size::PanelSize(PanelSize::S) => crate::widget::text::body, Size::PanelSize(PanelSize::XS) => crate::widget::text::body, + Size::PanelSize(PanelSize::Custom(s)) => { + if s >= 80 { + crate::widget::text::title2 + } else if s >= 64 { + crate::widget::text::title3 + } else if s >= 48 { + crate::widget::text::title4 + } else { + crate::widget::text::body + } + } Size::Hardcoded(_) => crate::widget::text, }; t(msg).font(crate::font::default()) From 6fb4a4a43e6267d9b4d936a4ca725f1f96111dc5 Mon Sep 17 00:00:00 2001 From: Ryan Brue Date: Wed, 28 May 2025 13:59:07 -0500 Subject: [PATCH 204/556] Use size thresholds for panel sizing in libcosmic Signed-off-by: Ryan Brue --- src/applet/mod.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 2efbfae2..c44bc483 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -426,23 +426,24 @@ impl Context { pub fn text<'a>(&self, msg: impl Into>) -> crate::widget::Text<'a, crate::Theme> { let msg = msg.into(); let t = match self.size { - Size::PanelSize(PanelSize::XL) => crate::widget::text::title2, - Size::PanelSize(PanelSize::L) => crate::widget::text::title3, - Size::PanelSize(PanelSize::M) => crate::widget::text::title4, - Size::PanelSize(PanelSize::S) => crate::widget::text::body, - Size::PanelSize(PanelSize::XS) => crate::widget::text::body, - Size::PanelSize(PanelSize::Custom(s)) => { - if s >= 80 { - crate::widget::text::title2 - } else if s >= 64 { - crate::widget::text::title3 - } else if s >= 48 { - crate::widget::text::title4 - } else { + Size::Hardcoded(_) => crate::widget::text, + Size::PanelSize(ref s) => { + let size = s.get_applet_icon_size_with_padding(false); + + let size_threshold_small = PanelSize::S.get_applet_icon_size_with_padding(false); + let size_threshold_medium = PanelSize::M.get_applet_icon_size_with_padding(false); + let size_threshold_large = PanelSize::L.get_applet_icon_size_with_padding(false); + + if size <= size_threshold_small { crate::widget::text::body + } else if size <= size_threshold_medium { + crate::widget::text::title4 + } else if size <= size_threshold_large { + crate::widget::text::title3 + } else { + crate::widget::text::title2 } } - Size::Hardcoded(_) => crate::widget::text, }; t(msg).font(crate::font::default()) } From 944c6761f73f097552042912780104783f9ca62e Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Thu, 29 May 2025 00:40:06 -0400 Subject: [PATCH 205/556] fix(windows): Mingw doesn't support trim Closes: #872 --- src/app/cosmic.rs | 6 +++--- src/app/mod.rs | 2 +- src/applet/mod.rs | 2 +- src/lib.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 940030d8..055ff947 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -215,7 +215,7 @@ where crate::Action::DbusActivation(message) => self.app.dbus_activation(message), }; - #[cfg(target_env = "gnu")] + #[cfg(all(target_env = "gnu", not(target_os = "windows")))] crate::malloc::trim(0); message @@ -397,7 +397,7 @@ where self.app.view().map(crate::Action::App) }; - #[cfg(target_env = "gnu")] + #[cfg(all(target_env = "gnu", not(target_os = "windows")))] crate::malloc::trim(0); view @@ -407,7 +407,7 @@ where pub fn view(&self) -> Element> { let view = self.app.view_main(); - #[cfg(target_env = "gnu")] + #[cfg(all(target_env = "gnu", not(target_os = "windows")))] crate::malloc::trim(0); view diff --git a/src/app/mod.rs b/src/app/mod.rs index 31355059..eec9741c 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -96,7 +96,7 @@ pub(crate) fn iced_settings( /// /// Returns error on application failure. pub fn run(settings: Settings, flags: App::Flags) -> iced::Result { - #[cfg(target_env = "gnu")] + #[cfg(all(target_env = "gnu", not(target_os = "windows")))] if let Some(threshold) = settings.default_mmap_threshold { crate::malloc::limit_mmap_threshold(threshold); } diff --git a/src/applet/mod.rs b/src/applet/mod.rs index c44bc483..e0ab993c 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -460,7 +460,7 @@ pub fn run(flags: App::Flags) -> iced::Result { let mut settings = helper.window_settings(); settings.resizable = None; - #[cfg(target_env = "gnu")] + #[cfg(all(target_env = "gnu", not(target_os = "windows")))] if let Some(threshold) = settings.default_mmap_threshold { crate::malloc::limit_mmap_threshold(threshold); } diff --git a/src/lib.rs b/src/lib.rs index 119d7af5..e8aeeedd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,7 +90,7 @@ pub use iced_wgpu; pub mod icon_theme; pub mod keyboard_nav; -#[cfg(target_env = "gnu")] +#[cfg(all(target_env = "gnu", not(target_os = "windows")))] pub(crate) mod malloc; #[cfg(all(feature = "process", not(windows)))] From 5b77f37fdeee777e73eec1b7d259ed66739e5510 Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Sun, 1 Jun 2025 23:25:09 +0000 Subject: [PATCH 206/556] feat: allow dialog resize --- src/widget/dialog.rs | 62 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/src/widget/dialog.rs b/src/widget/dialog.rs index 34ac9bd1..50bf4f1e 100644 --- a/src/widget/dialog.rs +++ b/src/widget/dialog.rs @@ -1,4 +1,8 @@ -use crate::{Element, iced::Length, style, theme, widget}; +use crate::{ + Element, + iced::{Length, Pixels}, + style, theme, widget, +}; use std::borrow::Cow; pub fn dialog<'a, Message>() -> Dialog<'a, Message> { @@ -13,6 +17,10 @@ pub struct Dialog<'a, Message> { primary_action: Option>, secondary_action: Option>, tertiary_action: Option>, + width: Option, + height: Option, + max_width: Option, + max_height: Option, } impl Default for Dialog<'_, Message> { @@ -31,6 +39,10 @@ impl<'a, Message> Dialog<'a, Message> { primary_action: None, secondary_action: None, tertiary_action: None, + width: None, + height: None, + max_width: None, + max_height: None, } } @@ -68,6 +80,26 @@ impl<'a, Message> Dialog<'a, Message> { self.tertiary_action = Some(button.into()); self } + + pub fn width(mut self, width: impl Into) -> Self { + self.width = Some(width.into()); + self + } + + pub fn height(mut self, height: impl Into) -> Self { + self.height = Some(height.into()); + self + } + + pub fn max_height(mut self, max_height: impl Into) -> Self { + self.max_height = Some(max_height.into()); + self + } + + pub fn max_width(mut self, max_width: impl Into) -> Self { + self.max_width = Some(max_width.into()); + self + } } impl<'a, Message: Clone + 'static> From> for Element<'a, Message> { @@ -123,14 +155,26 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes button_row = button_row.push(button); } - Element::from( - widget::container( - widget::column::with_children(vec![content_row.into(), button_row.into()]) - .spacing(space_l), - ) - .class(style::Container::Dialog) - .padding(space_m) - .width(Length::Fixed(570.0)), + let mut container = widget::container( + widget::column::with_children(vec![content_row.into(), button_row.into()]) + .spacing(space_l), ) + .class(style::Container::Dialog) + .padding(space_m) + .width(dialog.width.unwrap_or(Length::Fixed(570.0))); + + if let Some(height) = dialog.height { + container = container.height(height); + } + + if let Some(max_width) = dialog.max_width { + container = container.max_width(max_width); + } + + if let Some(max_height) = dialog.max_height { + container = container.max_height(max_height); + } + + Element::from(container) } } From 92ec78ba29fa32d38287323001c4d6f736680950 Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Tue, 10 Jun 2025 12:22:07 -0400 Subject: [PATCH 207/556] feat: menu bar popups --- examples/application/Cargo.toml | 1 + examples/application/src/main.rs | 195 ++- examples/calendar/src/main.rs | 1 + examples/context-menu/Cargo.toml | 10 +- examples/context-menu/src/main.rs | 2 +- examples/menu/src/main.rs | 3 +- examples/table-view/src/main.rs | 4 +- iced | 2 +- src/surface/action.rs | 2 +- src/theme/style/menu_bar.rs | 2 +- src/widget/button/widget.rs | 1 - src/widget/context_menu.rs | 60 +- src/widget/dropdown/widget.rs | 10 +- src/widget/menu/flex.rs | 171 ++- src/widget/menu/menu_bar.rs | 465 +++++-- src/widget/menu/menu_inner.rs | 1631 ++++++++++++++++--------- src/widget/menu/menu_tree.rs | 116 +- src/widget/nav_bar.rs | 2 +- src/widget/responsive_menu_bar.rs | 24 +- src/widget/segmented_button/widget.rs | 43 +- src/widget/table/mod.rs | 12 +- src/widget/table/widget/compact.rs | 6 +- src/widget/table/widget/standard.rs | 13 +- src/widget/wrapper.rs | 13 + 24 files changed, 1861 insertions(+), 928 deletions(-) diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index 3c5ce8e2..e5ae2f30 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -25,4 +25,5 @@ features = [ "wgpu", "single-instance", "multi-window", + "surface-message", ] diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index 92c2c242..c70a9d30 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -46,13 +46,19 @@ impl Page { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Action { Hi, + Hi2, + Hi3, } impl MenuAction for Action { type Message = Message; fn message(&self) -> Message { - Message::Hi + match self { + Action::Hi => Message::Hi, + Action::Hi2 => Message::Hi2, + Action::Hi3 => Message::Hi3, + } } } @@ -86,6 +92,8 @@ pub enum Message { ToggleHide, Surface(cosmic::surface::Action), Hi, + Hi2, + Hi3, } /// The [`App`] stores application-specific state. @@ -176,6 +184,12 @@ impl cosmic::Application for App { Message::Hi => { dbg!("hi"); } + Message::Hi2 => { + dbg!("hi 2"); + } + Message::Hi3 => { + dbg!("hi 3"); + } } Task::none() } @@ -221,119 +235,80 @@ impl cosmic::Application for App { } fn header_start(&self) -> Vec> { - use cosmic::widget::menu::Tree; - #[cfg(not(feature = "wayland"))] - { - vec![cosmic::widget::menu::bar(vec![ - Tree::with_children( - menu::root("hiiiiiiiiiiiiiiiiiii 1"), - menu::items( - &self.keybinds, - vec![menu::Item::Button("hi", None, Action::Hi)], - ), + vec![cosmic::widget::responsive_menu_bar().into_element( + self.core(), + &self.keybinds, + MENU_ID.clone(), + Message::Surface, + vec![ + ( + "hi 1".into(), + vec![ + menu::Item::Button("hi 12", None, Action::Hi), + menu::Item::Button("hi 13", None, Action::Hi2), + ], ), - Tree::with_children( - menu::root("hiiiiiiiiiiiiiiiiii 2"), - menu::items( - &self.keybinds, - vec![menu::Item::Button("hi 2", None, Action::Hi)], - ), + ( + "hi 2".into(), + vec![ + menu::Item::Button("hi 21", None, Action::Hi), + menu::Item::Button("hi 22", None, Action::Hi2), + menu::Item::Folder( + "nest 3 2 >".into(), + vec![ + menu::Item::Button("21", None, Action::Hi), + menu::Item::Button("242", None, Action::Hi2), + menu::Item::Button("2443", None, Action::Hi3), + menu::Item::Folder( + "nest 4 2 >".into(), + vec![ + menu::Item::Button("243", None, Action::Hi2), + menu::Item::Button("2444", None, Action::Hi), + ], + ), + ], + ), + ], ), - Tree::with_children( - menu::root("hiiiiiiiiiiiiiiiiiiiii 3"), - menu::items( - &self.keybinds, - vec![ - menu::Item::Button("hi 3", None, Action::Hi), - menu::Item::Button("hi 3 #2", None, Action::Hi), - ], - ), + ( + "hi 3".into(), + vec![ + menu::Item::Button("hi 31", None, Action::Hi), + menu::Item::Button("hi 332", None, Action::Hi2), + menu::Item::Button("hi 3333", None, Action::Hi3), + menu::Item::Button("hi 33334", None, Action::Hi3), + menu::Item::Button("hi 333335", None, Action::Hi3), + menu::Item::Button("hi 3333336", None, Action::Hi3), + ], ), - Tree::with_children( - menu::root("hi 3"), - menu::items( - &self.keybinds, - vec![ - menu::Item::Button("hi 3", None, Action::Hi), - menu::Item::Button("hi 3 #2", None, Action::Hi), - menu::Item::Button("hi 3 #3", None, Action::Hi), - ], - ), + ( + "hiiiiiiiiiiiiiiiiiii 4".into(), + vec![ + menu::Item::Button("hi 4", None, Action::Hi), + menu::Item::Button("hi 44", None, Action::Hi2), + menu::Item::Button("hi 444", None, Action::Hi3), + menu::Item::Folder( + "nest 4 >".into(), + vec![ + menu::Item::Button("hi 41", None, Action::Hi), + menu::Item::Button("hi 442", None, Action::Hi2), + menu::Item::Folder( + "nest 3 4 >".into(), + vec![ + menu::Item::Button("hi 443", None, Action::Hi2), + menu::Item::Button("hi 4444", None, Action::Hi), + menu::Item::Button("hi 44444", None, Action::Hi3), + menu::Item::Button("hi 444445", None, Action::Hi3), + menu::Item::Button("hi 4444446", None, Action::Hi3), + menu::Item::Button("hi 44444447", None, Action::Hi3), + ], + ), + ], + ), + ], ), - Tree::with_children( - menu::root("hi 4"), - menu::items( - &self.keybinds, - vec![ - menu::Item::Folder( - "hi 41 extra root", - vec![menu::Item::Button("hi 3", None, Action::Hi)], - ), - menu::Item::Button("hi 42", None, Action::Hi), - menu::Item::Button("hi 43", None, Action::Hi), - menu::Item::Button("hi 44", None, Action::Hi), - menu::Item::Button("hi 45", None, Action::Hi), - menu::Item::Button("hi 46", None, Action::Hi), - ], - ), - ), - ]) - .into()] - } - #[cfg(feature = "wayland")] - { - vec![cosmic::widget::responsive_menu_bar().into_element( - self.core(), - &self.keybinds, - MENU_ID.clone(), - Message::Surface, - vec![ - ( - "hiiiiiiiiiiiiiiiiiii 1", - vec![menu::Item::Button("hi 1", None, Action::Hi)], - ), - ( - "hiiiiiiiiiiiiiiiiiii 2".into(), - vec![ - menu::Item::Button("hi 2", None, Action::Hi), - menu::Item::Button("hi 22", None, Action::Hi), - ], - ), - ( - "hiiiiiiiiiiiiiiiiiii 3".into(), - vec![ - menu::Item::Button("hi 3", None, Action::Hi), - menu::Item::Button("hi 33", None, Action::Hi), - menu::Item::Button("hi 333", None, Action::Hi), - ], - ), - ( - "hiiiiiiiiiiiiiiiiiii 4".into(), - vec![ - menu::Item::Button("hi 4", None, Action::Hi), - menu::Item::Button("hi 44", None, Action::Hi), - menu::Item::Button("hi 444", None, Action::Hi), - menu::Item::Folder( - "nest 4".into(), - vec![ - menu::Item::Button("hi 4", None, Action::Hi), - menu::Item::Button("hi 44", None, Action::Hi), - menu::Item::Button("hi 444", None, Action::Hi), - menu::Item::Folder( - "nest 2 4".into(), - vec![ - menu::Item::Button("hi 4", None, Action::Hi), - menu::Item::Button("hi 44", None, Action::Hi), - menu::Item::Button("hi 444", None, Action::Hi), - ], - ), - ], - ), - ], - ), - ], - )] - } + ], + )] } } diff --git a/examples/calendar/src/main.rs b/examples/calendar/src/main.rs index c73c4da7..47549a70 100644 --- a/examples/calendar/src/main.rs +++ b/examples/calendar/src/main.rs @@ -92,6 +92,7 @@ impl cosmic::Application for App { |date| Message::DateSelected(date), || Message::PrevMonth, || Message::NextMonth, + chrono::Weekday::Sun, ); content = content.push(calendar); diff --git a/examples/context-menu/Cargo.toml b/examples/context-menu/Cargo.toml index 5b9ad020..9a24a1c8 100644 --- a/examples/context-menu/Cargo.toml +++ b/examples/context-menu/Cargo.toml @@ -11,4 +11,12 @@ tracing-log = "0.2.0" [dependencies.libcosmic] path = "../../" default-features = false -features = ["debug", "winit", "tokio", "xdg-portal", "multi-window"] +features = [ + "debug", + "winit", + "tokio", + "xdg-portal", + "multi-window", + "surface-message", + "wayland", +] diff --git a/examples/context-menu/src/main.rs b/examples/context-menu/src/main.rs index 840cf865..4a307840 100644 --- a/examples/context-menu/src/main.rs +++ b/examples/context-menu/src/main.rs @@ -93,7 +93,7 @@ impl cosmic::Application for App { /// Creates a view after each update. fn view(&self) -> Element { let widget = cosmic::widget::context_menu( - cosmic::widget::button::text(&self.button_label).on_press(Message::Clicked), + cosmic::widget::button::text(self.button_label.to_string()).on_press(Message::Clicked), self.context_menu(), ); diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs index 5b65732e..7037a62c 100644 --- a/examples/menu/src/main.rs +++ b/examples/menu/src/main.rs @@ -15,6 +15,7 @@ use cosmic::widget::menu::action::MenuAction; use cosmic::widget::menu::key_bind::KeyBind; use cosmic::widget::menu::key_bind::Modifier; use cosmic::widget::menu::{self, ItemHeight, ItemWidth}; +use cosmic::widget::RcElementWrapper; use cosmic::{executor, Element}; /// Runs application with these settings @@ -155,7 +156,7 @@ impl cosmic::Application for App { pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap) -> Element<'a, Message> { menu::bar(vec![menu::Tree::with_children( - menu::root("File"), + RcElementWrapper::new(Element::from(menu::root("File"))), menu::items( key_binds, vec![ diff --git a/examples/table-view/src/main.rs b/examples/table-view/src/main.rs index 8b2d4f62..6bd773bc 100644 --- a/examples/table-view/src/main.rs +++ b/examples/table-view/src/main.rs @@ -209,11 +209,11 @@ impl cosmic::Application for App { if size.width < 600.0 { widget::compact_table(&self.table_model) .on_item_left_click(Message::ItemSelect) - .item_context(|item| { + .item_context(move |item| { Some(widget::menu::items( &HashMap::new(), vec![widget::menu::Item::Button( - format!("Action on {}", item.name), + format!("Action on {}", item.name.to_string()), None, Action::None, )], diff --git a/iced b/iced index 70d104a2..717bc5db 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 70d104a28a87f06eb46d76268b6fa18c407ee2c2 +Subproject commit 717bc5dbfbc8f78e367e08e76a9572ee0ebc1f32 diff --git a/src/surface/action.rs b/src/surface/action.rs index e27815eb..fdf2680e 100644 --- a/src/surface/action.rs +++ b/src/surface/action.rs @@ -85,7 +85,7 @@ pub fn simple_subsurface( /// Used to create a popup message from within a widget. #[cfg(all(feature = "wayland", feature = "winit"))] #[must_use] -pub fn simple_popup( +pub fn simple_popup( settings: impl Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync diff --git a/src/theme/style/menu_bar.rs b/src/theme/style/menu_bar.rs index 7f99a1a5..ed0e657a 100644 --- a/src/theme/style/menu_bar.rs +++ b/src/theme/style/menu_bar.rs @@ -42,7 +42,7 @@ pub enum MenuBarStyle { #[default] Default, /// A [`Theme`] that uses a `Custom` palette. - Custom(Arc>), + Custom(Arc + Send + Sync>), } impl From Appearance> for MenuBarStyle { diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 5a1da458..aa8f0c32 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -460,7 +460,6 @@ impl<'a, Message: 'a + Clone> Widget if !self.selected && matches!(self.style, crate::theme::Button::HeaderBar) { headerbar_alpha = Some(0.8); } - theme.hovered(state.is_focused, self.selected, &self.style) } } else { diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 87122029..6769dff2 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -4,26 +4,26 @@ //! A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation. use crate::widget::menu::{ - self, CloseCondition, ItemHeight, ItemWidth, MenuBarState, PathHighlight, + self, CloseCondition, ItemHeight, ItemWidth, MenuBarState, PathHighlight, menu_roots_diff, }; use derive_setters::Setters; use iced::touch::Finger; -use iced::{Event, Vector}; +use iced::{Event, Vector, window}; use iced_core::widget::{Tree, Widget, tree}; use iced_core::{Length, Point, Size, event, mouse, touch}; use std::collections::HashSet; /// A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation. -pub fn context_menu<'a, Message: 'a>( - content: impl Into> + 'a, +pub fn context_menu( + content: impl Into> + 'static, // on_context: Message, - context_menu: Option>>, -) -> ContextMenu<'a, Message> { + context_menu: Option>>, +) -> ContextMenu<'static, Message> { let mut this = ContextMenu { content: content.into(), context_menu: context_menu.map(|menus| { vec![menu::Tree::with_children( - crate::widget::row::<'static, Message>(), + crate::Element::from(crate::widget::row::<'static, Message>()), menus, )] }), @@ -43,10 +43,12 @@ pub struct ContextMenu<'a, Message> { #[setters(skip)] content: crate::Element<'a, Message>, #[setters(skip)] - context_menu: Option>>, + context_menu: Option>>, } -impl Widget for ContextMenu<'_, Message> { +impl Widget + for ContextMenu<'_, Message> +{ fn tag(&self) -> tree::Tag { tree::Tag::of::() } @@ -56,6 +58,7 @@ impl Widget for ContextM tree::State::new(LocalState { context_cursor: Point::default(), fingers_pressed: Default::default(), + menu_bar_state: Default::default(), }) } @@ -67,7 +70,6 @@ impl Widget for ContextM // Assign the context menu's elements as this widget's children. if let Some(ref context_menu) = self.context_menu { let mut tree = Tree::empty(); - tree.state = tree::State::new(MenuBarState::default()); tree.children = context_menu .iter() .map(|root| { @@ -75,7 +77,7 @@ impl Widget for ContextM let flat = root .flattern() .iter() - .map(|mt| Tree::new(mt.item.as_widget())) + .map(|mt| Tree::new(mt.item.clone())) .collect(); tree.children = flat; tree @@ -90,6 +92,10 @@ impl Widget for ContextM fn diff(&mut self, tree: &mut Tree) { tree.children[0].diff(self.content.as_widget_mut()); + let state = tree.state.downcast_mut::(); + state.menu_bar_state.inner.with_data_mut(|inner| { + menu_roots_diff(self.context_menu.as_mut().unwrap(), &mut inner.tree); + }); // if let Some(ref mut context_menus) = self.context_menu { // for (menu, tree) in context_menus @@ -183,10 +189,12 @@ impl Widget for ContextM && (right_button_released(&event) || (touch_lifted(&event) && fingers_pressed == 2)) { state.context_cursor = cursor.position().unwrap_or_default(); + let state = tree.state.downcast_mut::(); - let menu_state = tree.children[1].state.downcast_mut::(); - menu_state.open = true; - menu_state.view_cursor = cursor; + state.menu_bar_state.inner.with_data_mut(|state| { + state.open = true; + state.view_cursor = cursor; + }); return event::Status::Captured; } @@ -213,22 +221,19 @@ impl Widget for ContextM ) -> Option> { let state = tree.state.downcast_ref::(); - let Some(context_menu) = self.context_menu.as_mut() else { - return None; - }; + let context_menu = self.context_menu.as_mut()?; - if !tree.children[1].state.downcast_ref::().open { + if !state.menu_bar_state.inner.with_data(|state| state.open) { return None; } let mut bounds = layout.bounds(); bounds.x = state.context_cursor.x; bounds.y = state.context_cursor.y; - Some( crate::widget::menu::Menu { - tree: &mut tree.children[1], - menu_roots: context_menu, + tree: state.menu_bar_state.clone(), + menu_roots: std::borrow::Cow::Owned(context_menu.clone()), bounds_expand: 16, menu_overlays_parent: true, close_condition: CloseCondition { @@ -243,8 +248,12 @@ impl Widget for ContextM cross_offset: 0, root_bounds_list: vec![bounds], path_highlight: Some(PathHighlight::MenuActive), - style: &crate::theme::menu_bar::MenuBarStyle::Default, + style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default), position: Point::new(translation.x, translation.y), + is_overlay: true, + window_id: window::Id::NONE, + depth: 0, + on_surface_action: None, } .overlay(), ) @@ -263,8 +272,10 @@ impl Widget for ContextM } } -impl<'a, Message: Clone + 'a> From> for crate::Element<'a, Message> { - fn from(widget: ContextMenu<'a, Message>) -> Self { +impl<'a, Message: Clone + 'static> From> + for crate::Element<'static, Message> +{ + fn from(widget: ContextMenu<'static, Message>) -> Self { Self::new(widget) } } @@ -283,4 +294,5 @@ fn touch_lifted(event: &Event) -> bool { pub struct LocalState { context_cursor: Point, fingers_pressed: HashSet, + menu_bar_state: MenuBarState, } diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 76ee7b05..d196215d 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -536,15 +536,7 @@ pub fn update< let on_close = surface::action::destroy_popup(id); let on_surface_action_clone = on_surface_action.clone(); let translation = layout.virtual_offset(); - let get_popup_action = surface::action::simple_popup::< - AppMessage, - Box< - dyn Fn() -> Element<'static, crate::Action> - + Send - + Sync - + 'static, - >, - >( + let get_popup_action = surface::action::simple_popup::( move || { SctkPopupSettings { parent, diff --git a/src/widget/menu/flex.rs b/src/widget/menu/flex.rs index c093e802..5eaf3d94 100644 --- a/src/widget/menu/flex.rs +++ b/src/widget/menu/flex.rs @@ -1,12 +1,14 @@ // From iced_aw, license MIT -use iced_core::widget::Tree; +use iced_core::{Widget, widget::Tree}; use iced_widget::core::{ Alignment, Element, Padding, Point, Size, layout::{Limits, Node}, renderer, }; +use crate::widget::RcElementWrapper; + /// The main axis of a flex layout. #[derive(Debug)] pub enum Axis { @@ -217,3 +219,170 @@ where Node::with_children(size.expand(padding), nodes) } + +/// Computes the flex layout with the given axis and limits, applying spacing, +/// padding and alignment to the items as needed. +/// +/// It returns a new layout [`Node`]. +pub fn resolve_wrapper<'a, Message>( + axis: &Axis, + renderer: &crate::Renderer, + limits: &Limits, + padding: Padding, + spacing: f32, + align_items: Alignment, + items: &[&RcElementWrapper], + tree: &mut [&mut Tree], +) -> Node { + let limits = limits.shrink(padding); + let total_spacing = spacing * items.len().saturating_sub(1) as f32; + let max_cross = axis.cross(limits.max()); + + let mut fill_sum = 0; + let mut cross = axis.cross(limits.min()).max(axis.cross(Size::INFINITY)); + let mut available = axis.main(limits.max()) - total_spacing; + + let mut nodes: Vec = Vec::with_capacity(items.len()); + nodes.resize(items.len(), Node::default()); + + if align_items == Alignment::Center { + let mut fill_cross = axis.cross(limits.min()); + + for (child, tree) in items.iter().zip(tree.iter_mut()) { + let c_size = child.size(); + let cross_fill_factor = match axis { + Axis::Horizontal => c_size.height, + Axis::Vertical => c_size.width, + } + .fill_factor(); + + if cross_fill_factor == 0 { + let (max_width, max_height) = axis.pack(available, max_cross); + + let child_limits = Limits::new(Size::ZERO, Size::new(max_width, max_height)); + + let layout = child.layout(tree, renderer, &child_limits); + let size = layout.size(); + + fill_cross = fill_cross.max(axis.cross(size)); + } + } + + cross = fill_cross; + } + + for (i, (child, tree)) in items.iter().zip(tree.iter_mut()).enumerate() { + let c_size = child.size(); + let fill_factor = match axis { + Axis::Horizontal => c_size.width, + Axis::Vertical => c_size.height, + } + .fill_factor(); + + if fill_factor == 0 { + let (min_width, min_height) = if align_items == Alignment::Center { + axis.pack(0.0, cross) + } else { + axis.pack(0.0, 0.0) + }; + + let (max_width, max_height) = if align_items == Alignment::Center { + axis.pack(available, cross) + } else { + axis.pack(available, max_cross) + }; + + let child_limits = Limits::new( + Size::new(min_width, min_height), + Size::new(max_width, max_height), + ); + + let layout = child.layout(tree, renderer, &child_limits); + let size = layout.size(); + + available -= axis.main(size); + + if align_items != Alignment::Center { + cross = cross.max(axis.cross(size)); + } + + nodes[i] = layout; + } else { + fill_sum += fill_factor; + } + } + + let remaining = available.max(0.0); + + for (i, (child, tree)) in items.iter().zip(tree.iter_mut()).enumerate() { + let c_size = child.size(); + let fill_factor = match axis { + Axis::Horizontal => c_size.width, + Axis::Vertical => c_size.height, + } + .fill_factor(); + + if fill_factor != 0 { + let max_main = remaining * f32::from(fill_factor) / f32::from(fill_sum); + let min_main = if max_main.is_infinite() { + 0.0 + } else { + max_main + }; + + let (min_width, min_height) = if align_items == Alignment::Center { + axis.pack(min_main, cross) + } else { + axis.pack(min_main, axis.cross(limits.min())) + }; + + let (max_width, max_height) = if align_items == Alignment::Center { + axis.pack(max_main, cross) + } else { + axis.pack(max_main, max_cross) + }; + + let child_limits = Limits::new( + Size::new(min_width, min_height), + Size::new(max_width, max_height), + ); + + let layout = child.layout(tree, renderer, &child_limits); + + if align_items != Alignment::Center { + cross = cross.max(axis.cross(layout.size())); + } + + nodes[i] = layout; + } + } + + let pad = axis.pack(padding.left, padding.top); + let mut main = pad.0; + + for (i, node) in nodes.iter_mut().enumerate() { + if i > 0 { + main += spacing; + } + + let (x, y) = axis.pack(main, pad.1); + + let node_ = node.clone().move_to(Point::new(x, y)); + + let node_ = match axis { + Axis::Horizontal => node_.align(Alignment::Start, align_items, Size::new(0.0, cross)), + Axis::Vertical => node_.align(align_items, Alignment::Start, Size::new(cross, 0.0)), + }; + + let size = node_.bounds().size(); + + *node = node_; + + main += axis.main(size); + } + + let (width, height) = axis.pack(main - pad.0, cross); + let size = limits.resolve(width, height, Size::new(width, height)); + + Node::with_children(size.expand(padding), nodes) +} diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 2584e762..eddc4f3a 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -1,73 +1,98 @@ // From iced_aw, license MIT //! A widget that handles menu trees +use std::{collections::HashMap, sync::Arc}; + use super::{ menu_inner::{ CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight, }, menu_tree::MenuTree, }; -use crate::style::menu_bar::StyleSheet; +use crate::{ + Renderer, + style::menu_bar::StyleSheet, + widget::{ + RcWrapper, + dropdown::menu::{self, State}, + menu::menu_inner::init_root_menu, + }, +}; -use iced::{Point, Vector}; +use iced::{Point, Shadow, Vector, window}; use iced_core::Border; use iced_widget::core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, event, layout::{Limits, Node}, mouse::{self, Cursor}, - overlay, renderer, touch, + overlay, + renderer::{self, Renderer as IcedRenderer}, + touch, widget::{Tree, tree}, }; /// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing. -pub fn menu_bar( - menu_roots: Vec>, -) -> MenuBar { +pub fn menu_bar(menu_roots: Vec>) -> MenuBar +where + Message: Clone + 'static, +{ MenuBar::new(menu_roots) } +#[derive(Clone, Default)] pub(crate) struct MenuBarState { + pub(crate) inner: RcWrapper, +} + +pub(crate) struct MenuBarStateInner { + pub(crate) tree: Tree, + pub(crate) popup_id: HashMap, pub(crate) pressed: bool, + pub(crate) bar_pressed: bool, pub(crate) view_cursor: Cursor, pub(crate) open: bool, - pub(crate) active_root: Option, + pub(crate) active_root: Vec, pub(crate) horizontal_direction: Direction, pub(crate) vertical_direction: Direction, + /// List of all menu states pub(crate) menu_states: Vec, } -impl MenuBarState { - pub(super) fn get_trimmed_indices(&self) -> impl Iterator + '_ { +impl MenuBarStateInner { + /// get the list of indices hovered for the menu + pub(super) fn get_trimmed_indices(&self, index: usize) -> impl Iterator + '_ { self.menu_states .iter() + .skip(index) .take_while(|ms| ms.index.is_some()) .map(|ms| ms.index.expect("No indices were found in the menu state.")) } pub(super) fn reset(&mut self) { self.open = false; - self.active_root = None; + self.active_root = Vec::new(); self.menu_states.clear(); } } -impl Default for MenuBarState { +impl Default for MenuBarStateInner { fn default() -> Self { Self { + tree: Tree::empty(), pressed: false, view_cursor: Cursor::Available([-0.5, -0.5].into()), open: false, - active_root: None, + active_root: Vec::new(), horizontal_direction: Direction::Positive, vertical_direction: Direction::Positive, menu_states: Vec::new(), + popup_id: HashMap::new(), + bar_pressed: false, } } } -pub(crate) fn menu_roots_children( - menu_roots: &Vec>, -) -> Vec +pub(crate) fn menu_roots_children(menu_roots: &Vec>) -> Vec where - Renderer: renderer::Renderer, + Message: Clone + 'static, { /* menu bar @@ -85,7 +110,7 @@ where let flat = root .flattern() .iter() - .map(|mt| Tree::new(mt.item.as_widget())) + .map(|mt| Tree::new(mt.item.clone())) .collect(); tree.children = flat; tree @@ -94,11 +119,9 @@ where } #[allow(invalid_reference_casting)] -pub(crate) fn menu_roots_diff( - menu_roots: &mut Vec>, - tree: &mut Tree, -) where - Renderer: renderer::Renderer, +pub(crate) fn menu_roots_diff(menu_roots: &mut Vec>, tree: &mut Tree) +where + Message: Clone + 'static, { if tree.children.len() > menu_roots.len() { tree.children.truncate(menu_roots.len()); @@ -112,7 +135,7 @@ pub(crate) fn menu_roots_diff( .flattern() .iter() .map(|mt| { - let widget = mt.item.as_widget(); + let widget = &mt.item; let widget_ptr = widget as *const dyn Widget; let widget_ptr_mut = widget_ptr as *mut dyn Widget; @@ -130,7 +153,7 @@ pub(crate) fn menu_roots_diff( let flat = root .flattern() .iter() - .map(|mt| Tree::new(mt.item.as_widget())) + .map(|mt| Tree::new(mt.item.clone())) .collect(); tree.children = flat; tree @@ -139,12 +162,18 @@ pub(crate) fn menu_roots_diff( } } +pub fn get_mut_or_default(vec: &mut Vec, index: usize) -> &mut T { + if index < vec.len() { + &mut vec[index] + } else { + vec.resize_with(index + 1, T::default); + &mut vec[index] + } +} + /// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing. #[allow(missing_debug_implementations)] -pub struct MenuBar<'a, Message, Renderer = crate::Renderer> -where - Renderer: renderer::Renderer, -{ +pub struct MenuBar { width: Length, height: Length, spacing: f32, @@ -156,17 +185,22 @@ where item_width: ItemWidth, item_height: ItemHeight, path_highlight: Option, - menu_roots: Vec>, + menu_roots: Vec>, style: ::Style, + window_id: window::Id, + #[cfg(all(feature = "wayland", feature = "winit"))] + positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, + pub(crate) on_surface_action: + Option Message + Send + Sync + 'static>>, } -impl<'a, Message, Renderer> MenuBar<'a, Message, Renderer> +impl MenuBar where - Renderer: renderer::Renderer, + Message: Clone + 'static, { /// Creates a new [`MenuBar`] with the given menu roots #[must_use] - pub fn new(menu_roots: Vec>) -> Self { + pub fn new(menu_roots: Vec>) -> Self { let mut menu_roots = menu_roots; menu_roots.iter_mut().for_each(MenuTree::set_index); @@ -188,6 +222,10 @@ where path_highlight: Some(PathHighlight::MenuActive), menu_roots, style: ::Style::default(), + window_id: window::Id::NONE, + #[cfg(all(feature = "wayland", feature = "winit"))] + positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), + on_surface_action: None, } } @@ -278,17 +316,196 @@ where self.width = width; self } + + #[cfg(all(feature = "wayland", feature = "winit"))] + pub fn with_positioner( + mut self, + positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, + ) -> Self { + self.positioner = positioner; + self + } + + #[must_use] + pub fn window_id(mut self, id: window::Id) -> Self { + self.window_id = id; + self + } + + #[must_use] + pub fn window_id_maybe(mut self, id: Option) -> Self { + if let Some(id) = id { + self.window_id = id; + } + self + } + + #[must_use] + pub fn on_surface_action( + mut self, + handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static, + ) -> Self { + self.on_surface_action = Some(Arc::new(handler)); + self + } + + #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + #[allow(clippy::too_many_lines)] + fn create_popup( + &mut self, + layout: Layout<'_>, + view_cursor: Cursor, + renderer: &Renderer, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + my_state: &mut MenuBarState, + ) { + if self.window_id != window::Id::NONE && self.on_surface_action.is_some() { + use crate::surface::action::destroy_popup; + use iced_runtime::platform_specific::wayland::popup::{ + SctkPopupSettings, SctkPositioner, + }; + + 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()); + + // if position is not on menu bar button skip. + let hovered_root = layout + .children() + .position(|lo| view_cursor.is_over(lo.bounds())); + + if old_active_root + .zip(hovered_root) + .is_some_and(|r| r.0 == r.1) + { + return; + } + let (id, root_list) = my_state.inner.with_data_mut(|state| { + if let Some(id) = state.popup_id.get(&self.window_id).copied() { + // close existing popups + state.menu_states.clear(); + state.active_root.clear(); + shell.publish(surface_action(destroy_popup(id))); + state.view_cursor = view_cursor; + (id, layout.children().map(|lo| lo.bounds()).collect()) + } else { + ( + window::Id::unique(), + layout.children().map(|lo| lo.bounds()).collect(), + ) + } + }); + + let mut popup_menu: Menu<'static, _> = Menu { + tree: my_state.clone(), + menu_roots: std::borrow::Cow::Owned(self.menu_roots.clone()), + bounds_expand: self.bounds_expand, + menu_overlays_parent: false, + close_condition: self.close_condition, + item_width: self.item_width, + item_height: self.item_height, + bar_bounds: layout.bounds(), + main_offset: self.main_offset, + cross_offset: self.cross_offset, + root_bounds_list: root_list, + path_highlight: self.path_highlight, + style: std::borrow::Cow::Owned(self.style.clone()), + position: Point::new(0., 0.), + is_overlay: false, + window_id: id, + depth: 0, + on_surface_action: self.on_surface_action.clone(), + }; + + init_root_menu( + &mut popup_menu, + renderer, + shell, + view_cursor.position().unwrap(), + viewport.size(), + Vector::new(0., 0.), + layout.bounds(), + self.main_offset as f32, + ); + let (anchor_rect, gravity) = my_state.inner.with_data_mut(|state| { + state.popup_id.insert(self.window_id, id); + (state + .menu_states + .iter() + .find(|s| s.index.is_none()) + .map(|s| s.menu_bounds.parent_bounds) + .map_or_else( + || { + let bounds = layout.bounds(); + Rectangle { + x: bounds.x as i32, + y: bounds.y as i32, + width: bounds.width as i32, + height: bounds.height as i32, + } + }, + |r| Rectangle { + x: r.x as i32, + y: r.y as i32, + width: r.width as i32, + height: r.height as i32, + }, + ), match (state.horizontal_direction, state.vertical_direction) { + (Direction::Positive, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight, + (Direction::Positive, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight, + (Direction::Negative, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft, + (Direction::Negative, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft, + }) + }); + + let menu_node = popup_menu.layout(renderer, Limits::NONE.min_width(1.).min_height(1.)); + let popup_size = menu_node.size(); + let positioner = SctkPositioner { + size: Some(( + popup_size.width.ceil() as u32 + 2, + popup_size.height.ceil() as u32 + 2, + )), + anchor_rect, + anchor: + cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::BottomLeft, + gravity, + reactive: true, + ..Default::default() + }; + let parent = self.window_id; + shell.publish((surface_action)(crate::surface::action::simple_popup( + move || SctkPopupSettings { + parent, + id, + positioner: positioner.clone(), + parent_size: None, + grab: true, + close_with_children: false, + input_zone: None, + }, + Some(move || { + Element::from(crate::widget::container(popup_menu.clone()).center(Length::Fill)) + .map(crate::action::app) + }), + ))); + } + } } -impl Widget for MenuBar<'_, Message, Renderer> +impl Widget for MenuBar where - Renderer: renderer::Renderer, + Message: Clone + 'static, { fn size(&self) -> iced_core::Size { iced_core::Size::new(self.width, self.height) } fn diff(&mut self, tree: &mut Tree) { - menu_roots_diff(&mut self.menu_roots, tree); + let state = tree.state.downcast_mut::(); + state + .inner + .with_data_mut(|inner| menu_roots_diff(&mut self.menu_roots, &mut inner.tree)); } fn tag(&self) -> tree::Tag { @@ -318,7 +535,7 @@ where .iter_mut() .map(|t| &mut t.children[0]) .collect::>(); - flex::resolve( + flex::resolve_wrapper( &flex::Axis::Horizontal, renderer, &limits, @@ -330,6 +547,7 @@ where ) } + #[allow(clippy::too_many_lines)] fn on_event( &mut self, tree: &mut Tree, @@ -357,19 +575,70 @@ where viewport, ); - let state = tree.state.downcast_mut::(); + let my_state = tree.state.downcast_mut::(); + + // XXX this should reset the state if there are no other copies of the state, which implies no dropdown menus open. + let reset = self.window_id != window::Id::NONE + && my_state + .inner + .with_data(|d| !d.open && !d.active_root.is_empty()); + + let open = my_state.inner.with_data_mut(|state| { + if reset { + if let Some(popup_id) = state.popup_id.get(&self.window_id).copied() { + if let Some(handler) = self.on_surface_action.as_ref() { + shell.publish((handler)(crate::surface::Action::DestroyPopup(popup_id))); + state.reset(); + } + } + } + state.open + }); match event { Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. } | FingerLost { .. }) => { - if state.menu_states.is_empty() && view_cursor.is_over(layout.bounds()) { - state.view_cursor = view_cursor; - state.open = true; - // #[cfg(feature = "wayland")] - // TODO emit Message to open menu + let create_popup = my_state.inner.with_data_mut(|state| { + let mut create_popup = false; + if state.menu_states.is_empty() && view_cursor.is_over(layout.bounds()) { + state.view_cursor = view_cursor; + state.open = true; + create_popup = true; + } else if let Some(_id) = state.popup_id.remove(&self.window_id) { + state.menu_states.clear(); + state.active_root.clear(); + state.open = false; + #[cfg(all( + feature = "wayland", + feature = "winit", + feature = "surface-message" + ))] + { + let surface_action = self.on_surface_action.as_ref().unwrap(); + + shell.publish(surface_action(crate::surface::action::destroy_popup( + _id, + ))); + } + state.view_cursor = view_cursor; + } + create_popup + }); + + if !create_popup { + return event::Status::Ignored; } + #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state); + } + Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered) + if open && view_cursor.is_over(layout.bounds()) => + { + #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state); } _ => (), } + root_status } @@ -385,49 +654,51 @@ where ) { let state = tree.state.downcast_ref::(); let cursor_pos = view_cursor.position().unwrap_or_default(); - let position = if state.open && (cursor_pos.x < 0.0 || cursor_pos.y < 0.0) { - state.view_cursor - } else { - view_cursor - }; + state.inner.with_data_mut(|state| { + let position = if state.open && (cursor_pos.x < 0.0 || cursor_pos.y < 0.0) { + state.view_cursor + } else { + view_cursor + }; - // draw path highlight - if self.path_highlight.is_some() { - let styling = theme.appearance(&self.style); - if let Some(active) = state.active_root { - let active_bounds = layout - .children() - .nth(active) - .expect("Active child not found in menu?") - .bounds(); - let path_quad = renderer::Quad { - bounds: active_bounds, - border: Border { - radius: styling.bar_border_radius.into(), - ..Default::default() - }, - shadow: Default::default(), - }; + // draw path highlight + if self.path_highlight.is_some() { + let styling = theme.appearance(&self.style); + if let Some(active) = state.active_root.first() { + let active_bounds = layout + .children() + .nth(*active) + .expect("Active child not found in menu?") + .bounds(); + let path_quad = renderer::Quad { + bounds: active_bounds, + border: Border { + radius: styling.bar_border_radius.into(), + ..Default::default() + }, + shadow: Shadow::default(), + }; - renderer.fill_quad(path_quad, styling.path); + renderer.fill_quad(path_quad, styling.path); + } } - } - self.menu_roots - .iter() - .zip(&tree.children) - .zip(layout.children()) - .for_each(|((root, t), lo)| { - root.item.as_widget().draw( - &t.children[root.index], - renderer, - theme, - style, - lo, - position, - viewport, - ); - }); + self.menu_roots + .iter() + .zip(&tree.children) + .zip(layout.children()) + .for_each(|((root, t), lo)| { + root.item.draw( + &t.children[root.index], + renderer, + theme, + style, + lo, + position, + viewport, + ); + }); + }); } fn overlay<'b>( @@ -437,18 +708,18 @@ where _renderer: &Renderer, translation: Vector, ) -> Option> { - // #[cfg(feature = "wayland")] - // return None; + #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + return None; let state = tree.state.downcast_ref::(); - if !state.open { + if state.inner.with_data(|state| !state.open) { return None; } Some( Menu { - tree, - menu_roots: &mut self.menu_roots, + tree: state.clone(), + menu_roots: std::borrow::Cow::Owned(self.menu_roots.clone()), bounds_expand: self.bounds_expand, menu_overlays_parent: false, close_condition: self.close_condition, @@ -459,27 +730,30 @@ where cross_offset: self.cross_offset, root_bounds_list: layout.children().map(|lo| lo.bounds()).collect(), path_highlight: self.path_highlight, - style: &self.style, + style: std::borrow::Cow::Borrowed(&self.style), position: Point::new(translation.x, translation.y), + is_overlay: true, + window_id: window::Id::NONE, + depth: 0, + on_surface_action: self.on_surface_action.clone(), } .overlay(), ) } } -impl<'a, Message, Renderer> From> - for Element<'a, Message, crate::Theme, Renderer> + +impl From> for Element<'_, Message, crate::Theme, Renderer> where - Message: 'a, - Renderer: 'a + renderer::Renderer, + Message: Clone + 'static, { - fn from(value: MenuBar<'a, Message, Renderer>) -> Self { + fn from(value: MenuBar) -> Self { Self::new(value) } } #[allow(unused_results, clippy::too_many_arguments)] -fn process_root_events( - menu_roots: &mut [MenuTree<'_, Message, Renderer>], +fn process_root_events( + menu_roots: &mut [MenuTree], view_cursor: Cursor, tree: &mut Tree, event: &event::Event, @@ -490,7 +764,6 @@ fn process_root_events( viewport: &Rectangle, ) -> event::Status where - Renderer: renderer::Renderer, { menu_roots .iter_mut() @@ -498,7 +771,7 @@ where .zip(layout.children()) .map(|((root, t), lo)| { // assert!(t.tag == tree::Tag::stateless()); - root.item.as_widget_mut().on_event( + root.item.on_event( &mut t.children[root.index], event.clone(), lo, diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index f8c0471a..8ebca090 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -1,10 +1,13 @@ // From iced_aw, license MIT //! Menu tree overlay +use std::{borrow::Cow, sync::Arc}; + use super::{menu_bar::MenuBarState, menu_tree::MenuTree}; use crate::style::menu_bar::StyleSheet; -use iced_core::{Border, Shadow}; +use iced::window; +use iced_core::{Border, Renderer as IcedRenderer, Shadow, Widget}; use iced_widget::core::{ Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, event, layout::{Limits, Node}, @@ -227,20 +230,21 @@ pub(super) struct MenuSlice { pub(super) upper_bound_rel: f32, } +#[derive(Debug, Clone)] /// Menu bounds in overlay space -struct MenuBounds { +pub struct MenuBounds { child_positions: Vec, child_sizes: Vec, children_bounds: Rectangle, - parent_bounds: Rectangle, + pub parent_bounds: Rectangle, check_bounds: Rectangle, offset_bounds: Rectangle, } impl MenuBounds { #[allow(clippy::too_many_arguments)] - fn new( - menu_tree: &MenuTree<'_, Message, Renderer>, - renderer: &Renderer, + fn new( + menu_tree: &MenuTree, + renderer: &crate::Renderer, item_width: ItemWidth, item_height: ItemHeight, viewport_size: Size, @@ -249,10 +253,8 @@ impl MenuBounds { bounds_expand: u16, parent_bounds: Rectangle, tree: &mut [Tree], - ) -> Self - where - Renderer: renderer::Renderer, - { + is_overlay: bool, + ) -> Self { let (children_size, child_positions, child_sizes) = get_children_layout(menu_tree, renderer, item_width, item_height, tree); @@ -262,7 +264,11 @@ impl MenuBounds { // overlay space children position let (children_position, offset_position) = { let (cp, op) = aod.resolve(view_parent_bounds, children_size, viewport_size); - (cp - overlay_offset, op - overlay_offset) + if is_overlay { + (cp - overlay_offset, op - overlay_offset) + } else { + (Point::ORIGIN, op - overlay_offset) + } }; // calc offset bounds @@ -288,23 +294,22 @@ impl MenuBounds { } } +#[derive(Clone)] pub(crate) struct MenuState { + /// The index of the active menu item pub(super) index: Option, scroll_offset: f32, - menu_bounds: MenuBounds, + pub menu_bounds: MenuBounds, } impl MenuState { - pub(super) fn layout( + pub(super) fn layout( &self, overlay_offset: Vector, slice: MenuSlice, - renderer: &Renderer, - menu_tree: &MenuTree<'_, Message, Renderer>, + renderer: &crate::Renderer, + menu_tree: &[MenuTree], tree: &mut [Tree], - ) -> Node - where - Renderer: renderer::Renderer, - { + ) -> Node { let MenuSlice { start_index, end_index, @@ -312,18 +317,14 @@ impl MenuState { upper_bound_rel, } = slice; - assert_eq!( - menu_tree.children.len(), - self.menu_bounds.child_positions.len() - ); + debug_assert_eq!(menu_tree.len(), self.menu_bounds.child_positions.len()); // viewport space children bounds let children_bounds = self.menu_bounds.children_bounds + overlay_offset; - let child_nodes = self.menu_bounds.child_positions[start_index..=end_index] .iter() .zip(self.menu_bounds.child_sizes[start_index..=end_index].iter()) - .zip(menu_tree.children[start_index..=end_index].iter()) + .zip(menu_tree[start_index..=end_index].iter()) .map(|((cp, size), mt)| { let mut position = *cp; let mut size = *size; @@ -336,10 +337,9 @@ impl MenuState { size.height = upper_bound_rel - position; } - let limits = Limits::new(Size::ZERO, size); + let limits = Limits::new(size, size); mt.item - .as_widget() .layout(&mut tree[mt.index], renderer, &limits) .move_to(Point::new(0.0, position + self.scroll_offset)) }) @@ -348,30 +348,28 @@ impl MenuState { Node::with_children(children_bounds.size(), child_nodes).move_to(children_bounds.position()) } - fn layout_single( + fn layout_single( &self, overlay_offset: Vector, index: usize, - renderer: &Renderer, - menu_tree: &MenuTree<'_, Message, Renderer>, + renderer: &crate::Renderer, + menu_tree: &MenuTree, tree: &mut Tree, - ) -> Node - where - Renderer: renderer::Renderer, - { + ) -> Node { // viewport space children bounds let children_bounds = self.menu_bounds.children_bounds + overlay_offset; let position = self.menu_bounds.child_positions[index]; let limits = Limits::new(Size::ZERO, self.menu_bounds.child_sizes[index]); let parent_offset = children_bounds.position() - Point::ORIGIN; - let node = menu_tree.item.as_widget().layout(tree, renderer, &limits); + let node = menu_tree.item.layout(tree, renderer, &limits); node.clone().move_to(Point::new( parent_offset.x, parent_offset.y + position + self.scroll_offset, )) } + /// returns a slice of the menu items that are inside the viewport pub(super) fn slice( &self, viewport_size: Size, @@ -426,12 +424,11 @@ impl MenuState { } } -pub(crate) struct Menu<'a, 'b, Message, Renderer> -where - Renderer: renderer::Renderer, -{ - pub(crate) tree: &'b mut Tree, - pub(crate) menu_roots: &'b mut Vec>, +#[derive(Clone)] +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) bounds_expand: u16, /// Allows menu overlay items to overlap the parent pub(crate) menu_overlays_parent: bool, @@ -443,72 +440,113 @@ where pub(crate) cross_offset: i32, pub(crate) root_bounds_list: Vec, pub(crate) path_highlight: Option, - pub(crate) style: &'b ::Style, + pub(crate) style: Cow<'b, ::Style>, pub(crate) position: Point, + pub(crate) is_overlay: bool, + /// window id for this popup + pub(crate) window_id: window::Id, + pub(crate) depth: usize, + pub(crate) on_surface_action: + Option Message + Send + Sync + 'static>>, } -impl<'b, Message, Renderer> Menu<'_, 'b, Message, Renderer> -where - Renderer: renderer::Renderer, -{ - pub(crate) fn overlay(self) -> overlay::Element<'b, Message, crate::Theme, Renderer> { +impl<'b, Message: Clone + 'static> Menu<'b, Message> { + pub(crate) fn overlay(self) -> overlay::Element<'b, Message, crate::Theme, crate::Renderer> { overlay::Element::new(Box::new(self)) } -} -impl overlay::Overlay - for Menu<'_, '_, Message, Renderer> -where - Renderer: renderer::Renderer, -{ - fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node { - // layout children - let position = self.position; - let state = self.tree.state.downcast_mut::(); - let overlay_offset = Point::ORIGIN - position; - let tree_children = &mut self.tree.children; - let children = state - .active_root - .map(|active_root| { - let root = &self.menu_roots[active_root]; - let active_tree = &mut tree_children[active_root]; - state.menu_states.iter().enumerate().fold( - (root, Vec::new()), - |(menu_root, mut nodes), (_i, ms)| { - let slice = ms.slice(bounds, overlay_offset, self.item_height); - let _start_index = slice.start_index; - let _end_index = slice.end_index; - let children_node = ms.layout( - overlay_offset, - slice, - renderer, - menu_root, - &mut active_tree.children, - ); - nodes.push(children_node); - // only the last menu can have a None active index - ( - ms.index - .map_or(menu_root, |active| &menu_root.children[active]), - nodes, - ) - }, - ) - }) - .map(|(_, l)| l) - .unwrap_or_default(); - // overlay space viewport rectangle - Node::with_children(bounds, children).translate(Point::ORIGIN - position) + pub(crate) fn layout(&self, renderer: &crate::Renderer, limits: Limits) -> Node { + // layout children; + let position = self.position; + let mut intrinsic_size = Size::ZERO; + + let empty = Vec::new(); + self.tree.inner.with_data_mut(|data| { + if data.active_root.len() < self.depth + 1 || data.menu_states.len() < self.depth + 1 { + return Node::new(limits.min()); + } + + let overlay_offset = Point::ORIGIN - position; + let tree_children: &mut Vec = &mut data.tree.children; + + let children = (if self.is_overlay { 0 } else { self.depth }..=if self.is_overlay { + data.active_root.len() - 1 + } else { + self.depth + }) + .map(|active_root| { + if self.menu_roots.is_empty() { + return (&empty, vec![]); + } + let (active_tree, roots) = + data.active_root[..=active_root].iter().skip(1).fold( + ( + &mut tree_children[data.active_root[0]].children, + &self.menu_roots[data.active_root[0]].children, + ), + |(tree, mt), next_active_root| (tree, &mt[*next_active_root].children), + ); + + data.menu_states[if self.is_overlay { 0 } else { self.depth } + ..=if self.is_overlay { + data.active_root.len() - 1 + } else { + self.depth + }] + .iter() + .enumerate() + .filter(|ms| self.is_overlay || ms.0 < 1) + .fold( + (roots, Vec::new()), + |(menu_root, mut nodes), (_i, ms)| { + let slice = + ms.slice(limits.max(), overlay_offset, self.item_height); + let _start_index = slice.start_index; + let _end_index = slice.end_index; + let children_node = ms.layout( + overlay_offset, + slice, + renderer, + menu_root, + active_tree, + ); + let node_size = children_node.size(); + intrinsic_size.height += node_size.height; + intrinsic_size.width = intrinsic_size.width.max(node_size.width); + + nodes.push(children_node); + // if popup just use len 1? + // only the last menu can have a None active index + ( + ms.index + .map_or(menu_root, |active| &menu_root[active].children), + nodes, + ) + }, + ) + }) + .map(|(_, l)| l) + .next() + .unwrap_or_default(); + + // overlay space viewport rectangle + Node::with_children( + limits.resolve(Length::Shrink, Length::Shrink, intrinsic_size), + children, + ) + .translate(Point::ORIGIN - position) + }) } + #[allow(clippy::too_many_lines)] fn on_event( &mut self, event: event::Event, layout: Layout<'_>, view_cursor: Cursor, - renderer: &Renderer, + renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + ) -> (Option<(usize, MenuState)>, event::Status) { use event::{ Event::{Mouse, Touch}, Status::{Captured, Ignored}, @@ -519,18 +557,25 @@ where }; use touch::Event::{FingerLifted, FingerMoved, FingerPressed}; - if !self.tree.state.downcast_ref::().open { - return Ignored; - }; + if !self + .tree + .inner + .with_data(|data| data.open || data.active_root.len() <= self.depth) + { + return (None, Ignored); + } let viewport = layout.bounds(); + let viewport_size = viewport.size(); let overlay_offset = Point::ORIGIN - viewport.position(); let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset; - + let menu_roots = match &mut self.menu_roots { + Cow::Borrowed(_) => panic!(), + Cow::Owned(o) => o.as_mut_slice(), + }; let menu_status = process_menu_events( - self.tree, - self.menu_roots, + self, event.clone(), view_cursor, renderer, @@ -550,23 +595,28 @@ where self.main_offset as f32, ); - match event { + let ret = match event { Mouse(WheelScrolled { delta }) => { process_scroll_events(self, delta, overlay_cursor, viewport_size, overlay_offset) .merge(menu_status) } Mouse(ButtonPressed(Left)) | Touch(FingerPressed { .. }) => { - let state = self.tree.state.downcast_mut::(); - state.pressed = true; - state.view_cursor = view_cursor; + self.tree.inner.with_data_mut(|data| { + data.pressed = true; + data.view_cursor = view_cursor; + }); Captured } Mouse(CursorMoved { position }) | Touch(FingerMoved { position, .. }) => { let view_cursor = Cursor::Available(position); let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset; - process_overlay_events( + if !self.is_overlay && !view_cursor.is_over(viewport) { + return (None, menu_status); + } + + let (new_root, status) = process_overlay_events( self, renderer, viewport_size, @@ -574,169 +624,452 @@ where view_cursor, overlay_cursor, self.cross_offset as f32, - ) - .merge(menu_status) + shell, + ); + + return (new_root, status.merge(menu_status)); } Mouse(ButtonReleased(_)) | Touch(FingerLifted { .. }) => { - let state = self.tree.state.downcast_mut::(); - state.pressed = false; + self.tree.inner.with_data_mut(|state| { + state.pressed = false; - // process close condition - if state - .view_cursor - .position() - .unwrap_or_default() - .distance(view_cursor.position().unwrap_or_default()) - < 2.0 - { - let is_inside = state - .menu_states - .iter() - .any(|ms| ms.menu_bounds.check_bounds.contains(overlay_cursor)); - - if self.close_condition.click_inside - && is_inside - && matches!( - event, - Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. }) - ) + // process close condition + if state + .view_cursor + .position() + .unwrap_or_default() + .distance(view_cursor.position().unwrap_or_default()) + < 2.0 { - state.reset(); - return Captured; + let is_inside = state.menu_states[..=if self.is_overlay { + state.active_root.len().saturating_sub(1) + } else { + self.depth + }] + .iter() + .any(|ms| ms.menu_bounds.check_bounds.contains(overlay_cursor)); + let mut needs_reset = false; + needs_reset |= self.close_condition.click_inside + && is_inside + && matches!( + event, + Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. }) + ); + + needs_reset |= self.close_condition.click_outside && !is_inside; + + if needs_reset { + #[cfg(all( + feature = "wayland", + feature = "winit", + feature = "surface-message" + ))] + if let Some(handler) = self.on_surface_action.as_ref() { + let mut root = self.window_id; + let mut depth = self.depth; + while let Some(parent) = + state.popup_id.iter().find(|(_, v)| **v == root) + { + // parent of root popup is the window, so we stop. + if depth == 0 { + break; + } + root = *parent.0; + depth = depth.saturating_sub(1); + } + shell + .publish((handler)(crate::surface::Action::DestroyPopup(root))); + } + + state.reset(); + return Captured; + } } - if self.close_condition.click_outside && !is_inside { + // close all menus when clicking inside the menu bar + if self.bar_bounds.contains(overlay_cursor) { state.reset(); - return Captured; + Captured + } else { + menu_status } - } - - // close all menus when clicking inside the menu bar - if self.bar_bounds.contains(overlay_cursor) { - state.reset(); - Captured - } else { - menu_status - } + }) } _ => menu_status, - } + }; + (None, ret) } - #[allow(unused_results)] + #[allow(unused_results, clippy::too_many_lines)] fn draw( &self, - renderer: &mut Renderer, + renderer: &mut crate::Renderer, theme: &crate::Theme, style: &renderer::Style, layout: Layout<'_>, view_cursor: Cursor, ) { - let state = self.tree.state.downcast_ref::(); - let Some(active_root) = state.active_root else { - return; - }; + self.tree.inner.with_data(|state| { + if !state.open || state.active_root.len() <= self.depth { + return; + } + let active_root = &state.active_root[..=if self.is_overlay { 0 } else { self.depth }]; + let viewport = layout.bounds(); + let viewport_size = viewport.size(); + let overlay_offset = Point::ORIGIN - viewport.position(); - let viewport = layout.bounds(); - let viewport_size = viewport.size(); - let overlay_offset = Point::ORIGIN - viewport.position(); - let render_bounds = Rectangle::new(Point::ORIGIN, viewport.size()); + let render_bounds = if self.is_overlay { + Rectangle::new(Point::ORIGIN, viewport.size()) + } else { + Rectangle::new(Point::ORIGIN, Size::INFINITY) + }; - let styling = theme.appearance(self.style); + 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), + ); + let indices = state.get_trimmed_indices(self.depth).collect::>(); + state.menu_states[if self.is_overlay { 0 } else { self.depth }..=if self.is_overlay { + state.menu_states.len() - 1 + } else { + self.depth + }] + .iter() + .zip(layout.children()) + .enumerate() + .filter(|ms: &(usize, (&MenuState, Layout<'_>))| self.is_overlay || ms.0 < 1) + .fold( + roots, + |menu_roots: &Vec>, (i, (ms, children_layout))| { + let draw_path = self.path_highlight.as_ref().is_some_and(|ph| match ph { + PathHighlight::Full => true, + PathHighlight::OmitActive => { + !indices.is_empty() && i < indices.len() - 1 + } + PathHighlight::MenuActive => self.depth == state.active_root.len() - 1, + }); - let tree = &self.tree.children[active_root].children; - let root = &self.menu_roots[active_root]; - - let indices = state.get_trimmed_indices().collect::>(); - - state - .menu_states - .iter() - .zip(layout.children()) - .enumerate() - .fold(root, |menu_root, (i, (ms, children_layout))| { - let draw_path = self.path_highlight.as_ref().map_or(false, |ph| match ph { - PathHighlight::Full => true, - PathHighlight::OmitActive => !indices.is_empty() && i < indices.len() - 1, - PathHighlight::MenuActive => i < state.menu_states.len() - 1, - }); - - // react only to the last menu - let view_cursor = if i == state.menu_states.len() - 1 { - view_cursor - } else { - Cursor::Available([-1.0; 2].into()) - }; - - let draw_menu = |r: &mut Renderer| { - // calc slice - let slice = ms.slice(viewport_size, overlay_offset, self.item_height); - let start_index = slice.start_index; - let end_index = slice.end_index; - - let children_bounds = children_layout.bounds(); - - // draw menu background - // let bounds = pad_rectangle(children_bounds, styling.background_expand.into()); - // println!("cursor: {:?}", view_cursor); - // println!("bg_bounds: {:?}", bounds); - // println!("color: {:?}\n", styling.background); - let menu_quad = renderer::Quad { - bounds: pad_rectangle(children_bounds, styling.background_expand.into()), - border: Border { - radius: styling.menu_border_radius.into(), - width: styling.border_width, - color: styling.border_color, - }, - shadow: Shadow::default(), - }; - let menu_color = styling.background; - r.fill_quad(menu_quad, menu_color); - - // draw path hightlight - if let (true, Some(active)) = (draw_path, ms.index) { - let active_bounds = children_layout - .children() - .nth(active.saturating_sub(start_index)) - .expect("No active children were found in menu?") - .bounds(); - let path_quad = renderer::Quad { - bounds: active_bounds, - border: Border { - radius: styling.menu_border_radius.into(), - ..Default::default() - }, - shadow: Shadow::default(), + // react only to the last menu + let view_cursor = if self.depth == state.active_root.len() - 1 + || i == state.menu_states.len() - 1 + { + view_cursor + } else { + Cursor::Available([-1.0; 2].into()) }; - r.fill_quad(path_quad, styling.path); - } + let draw_menu = |r: &mut crate::Renderer| { + // calc slice + let slice = ms.slice(viewport_size, overlay_offset, self.item_height); + let start_index = slice.start_index; + let end_index = slice.end_index; - // draw item - menu_root.children[start_index..=end_index] - .iter() - .zip(children_layout.children()) - .for_each(|(mt, clo)| { - mt.item.as_widget().draw( - &tree[mt.index], - r, - theme, - style, - clo, - view_cursor, - &children_layout.bounds(), - ); - }); + let children_bounds = children_layout.bounds(); + + // draw menu background + // let bounds = pad_rectangle(children_bounds, styling.background_expand.into()); + // println!("cursor: {:?}", view_cursor); + // println!("bg_bounds: {:?}", bounds); + // println!("color: {:?}\n", styling.background); + let menu_quad = renderer::Quad { + bounds: pad_rectangle( + children_bounds, + styling.background_expand.into(), + ), + border: Border { + radius: styling.menu_border_radius.into(), + width: styling.border_width, + color: styling.border_color, + }, + shadow: Shadow::default(), + }; + let menu_color = styling.background; + r.fill_quad(menu_quad, menu_color); + // draw path hightlight + if let (true, Some(active)) = (draw_path, ms.index) { + if let Some(active_layout) = children_layout + .children() + .nth(active.saturating_sub(start_index)) + { + let path_quad = renderer::Quad { + bounds: active_layout.bounds(), + border: Border { + radius: styling.menu_border_radius.into(), + ..Default::default() + }, + shadow: Shadow::default(), + }; + + r.fill_quad(path_quad, styling.path); + } + } + if start_index < menu_roots.len() { + // draw item + menu_roots[start_index..=end_index] + .iter() + .zip(children_layout.children()) + .for_each(|(mt, clo)| { + mt.item.draw( + &state.tree.children[active_root[0]].children[mt.index], + r, + theme, + style, + clo, + view_cursor, + &children_layout.bounds(), + ); + }); + } + }; + + renderer.with_layer(render_bounds, draw_menu); + + // only the last menu can have a None active index + ms.index + .map_or(menu_roots, |active| &menu_roots[active].children) + }, + ); + }); + } +} +impl overlay::Overlay + for Menu<'_, Message> +{ + fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> iced_core::layout::Node { + Menu::layout( + self, + renderer, + Limits::NONE + .min_width(bounds.width) + .max_width(bounds.width) + .min_height(bounds.height) + .max_height(bounds.height), + ) + } + + fn on_event( + &mut self, + event: iced::Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &crate::Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.on_event(event, layout, cursor, renderer, clipboard, shell) + .1 + } + + fn draw( + &self, + renderer: &mut crate::Renderer, + theme: &crate::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + ) { + self.draw(renderer, theme, style, layout, cursor); + } +} + +impl Widget + for Menu<'_, Message> +{ + fn size(&self) -> Size { + Size { + width: Length::Shrink, + height: Length::Shrink, + } + } + + fn layout( + &self, + _tree: &mut Tree, + renderer: &crate::Renderer, + limits: &iced_core::layout::Limits, + ) -> iced_core::layout::Node { + Menu::layout(self, renderer, *limits) + } + + fn draw( + &self, + _tree: &Tree, + renderer: &mut crate::Renderer, + theme: &crate::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + ) { + Menu::draw(self, renderer, theme, style, layout, cursor); + } + + #[allow(clippy::too_many_lines)] + fn on_event( + &mut self, + tree: &mut Tree, + event: iced::Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &crate::Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) -> event::Status { + let (new_root, status) = self.on_event(event, layout, cursor, renderer, clipboard, shell); + + #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + if let Some((new_root, new_ms)) = new_root { + use iced_runtime::platform_specific::wayland::popup::{ + SctkPopupSettings, SctkPositioner, + }; + let overlay_offset = Point::ORIGIN - viewport.position(); + + let overlay_cursor = cursor.position().unwrap_or_default() - overlay_offset; + + let Some((mut menu, popup_id)) = self.tree.inner.with_data_mut(|state| { + let popup_id = *state + .popup_id + .entry(self.window_id) + .or_insert_with(window::Id::unique); + let active_roots = state + .active_root + .get(self.depth) + .cloned() + .unwrap_or_default(); + + let root_bounds_list = layout + .children() + .next() + .unwrap() + .children() + .map(|lo| lo.bounds()) + .collect(); + + let mut popup_menu = Menu { + tree: self.tree.clone(), + menu_roots: Cow::Owned(Cow::into_owned(self.menu_roots.clone())), + bounds_expand: self.bounds_expand, + menu_overlays_parent: false, + close_condition: self.close_condition, + item_width: self.item_width, + item_height: self.item_height, + bar_bounds: layout.bounds(), + main_offset: self.main_offset, + cross_offset: self.cross_offset, + root_bounds_list, + path_highlight: self.path_highlight, + style: Cow::Owned(Cow::into_owned(self.style.clone())), + position: Point::new(0., 0.), + is_overlay: false, + window_id: popup_id, + depth: self.depth + 1, + on_surface_action: self.on_surface_action.clone(), }; - renderer.with_layer(render_bounds, draw_menu); + state.active_root.push(new_root); - // only the last menu can have a None active index - ms.index - .map_or(menu_root, |active| &menu_root.children[active]) + Some((popup_menu, popup_id)) + }) else { + return status; + }; + // XXX we push a new active root manually instead + init_root_popup_menu( + &mut menu, + renderer, + shell, + cursor.position().unwrap(), + layout.bounds().size(), + Vector::new(0., 0.), + layout.bounds(), + self.main_offset as f32, + ); + let (anchor_rect, gravity) = self.tree.inner.with_data_mut(|state| { + (state + .menu_states + .get(self.depth + 1) + .map(|s| s.menu_bounds.parent_bounds) + .map_or_else( + || { + let bounds = layout.bounds(); + Rectangle { + x: bounds.x as i32, + y: bounds.y as i32, + width: bounds.width as i32, + height: bounds.height as i32, + } + }, + |r| Rectangle { + x: r.x as i32, + y: r.y as i32, + width: r.width as i32, + height: r.height as i32, + }, + ), match (state.horizontal_direction, state.vertical_direction) { + (Direction::Positive, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight, + (Direction::Positive, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight, + (Direction::Negative, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft, + (Direction::Negative, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft, + }) }); + + let menu_node = Widget::layout( + &menu, + &mut Tree::empty(), + renderer, + &Limits::NONE.min_width(1.).min_height(1.), + ); + + let popup_size = menu_node.size(); + let positioner = SctkPositioner { + size: Some(( + popup_size.width.ceil() as u32 + 2, + popup_size.height.ceil() as u32 + 2, + )), + anchor_rect, + anchor: + cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::TopRight, + gravity, + reactive: true, + ..Default::default() + }; + let parent = self.window_id; + shell.publish((self.on_surface_action.as_ref().unwrap())( + crate::surface::action::simple_popup( + move || SctkPopupSettings { + parent, + id: popup_id, + positioner: positioner.clone(), + parent_size: None, + grab: true, + close_with_children: false, + input_zone: None, + }, + Some(move || { + crate::Element::from( + crate::widget::container(menu.clone()).center(Length::Fill), + ) + .map(crate::action::app) + }), + ), + )); + + return status; + } + status + } +} + +impl<'a, Message> From> + for iced::Element<'a, Message, crate::Theme, crate::Renderer> +where + Message: std::clone::Clone + 'static, +{ + fn from(value: Menu<'a, Message>) -> Self { + Self::new(value) } } @@ -749,9 +1082,93 @@ fn pad_rectangle(rect: Rectangle, padding: Padding) -> Rectangle { } } -pub(super) fn init_root_menu( - menu: &mut Menu<'_, '_, Message, Renderer>, - renderer: &Renderer, +#[allow(clippy::too_many_arguments)] +pub(super) fn init_root_menu( + menu: &mut Menu<'_, Message>, + renderer: &crate::Renderer, + shell: &mut Shell<'_, Message>, + overlay_cursor: Point, + viewport_size: Size, + overlay_offset: Vector, + bar_bounds: Rectangle, + main_offset: f32, +) { + menu.tree.inner.with_data_mut(|state| { + if !(state.menu_states.get(menu.depth).is_none() + && (!menu.is_overlay || bar_bounds.contains(overlay_cursor))) + || menu.depth > 0 + || !state.open + { + return; + } + + let mut set = false; + for (i, (&root_bounds, mt)) in menu + .root_bounds_list + .iter() + .zip(menu.menu_roots.iter()) + .enumerate() + { + if mt.children.is_empty() { + continue; + } + + if root_bounds.contains(overlay_cursor) { + let view_center = viewport_size.width * 0.5; + let rb_center = root_bounds.center_x(); + + state.horizontal_direction = if rb_center > view_center { + Direction::Negative + } else { + Direction::Positive + }; + + let aod = Aod { + horizontal: true, + vertical: true, + horizontal_overlap: true, + vertical_overlap: false, + horizontal_direction: state.horizontal_direction, + vertical_direction: state.vertical_direction, + horizontal_offset: 0.0, + vertical_offset: main_offset, + }; + let menu_bounds = MenuBounds::new( + mt, + renderer, + menu.item_width, + menu.item_height, + viewport_size, + overlay_offset, + &aod, + menu.bounds_expand, + root_bounds, + &mut state.tree.children[0].children, + menu.is_overlay, + ); + set = true; + state.active_root.push(i); + let ms = MenuState { + index: None, + scroll_offset: 0.0, + menu_bounds, + }; + state.menu_states.push(ms); + + // Hack to ensure menu opens properly + shell.invalidate_layout(); + + break; + } + } + debug_assert!(set, "Root not set"); + }); +} + +#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] +pub(super) fn init_root_popup_menu( + menu: &mut Menu<'_, Message>, + renderer: &crate::Renderer, shell: &mut Shell<'_, Message>, overlay_cursor: Point, viewport_size: Size, @@ -759,143 +1176,153 @@ pub(super) fn init_root_menu( bar_bounds: Rectangle, main_offset: f32, ) where - Renderer: renderer::Renderer, + Message: std::clone::Clone, { - let state = menu.tree.state.downcast_mut::(); - if !(state.menu_states.is_empty() && bar_bounds.contains(overlay_cursor)) { - return; - } - - for (i, (&root_bounds, mt)) in menu - .root_bounds_list - .iter() - .zip(menu.menu_roots.iter()) - .enumerate() - { - if mt.children.is_empty() { - continue; + menu.tree.inner.with_data_mut(|state| { + if !(state.menu_states.get(menu.depth).is_none() + && (!menu.is_overlay || bar_bounds.contains(overlay_cursor))) + { + return; } - if root_bounds.contains(overlay_cursor) { - let view_center = viewport_size.width * 0.5; - let rb_center = root_bounds.center_x(); + let active_roots = &state.active_root[..=menu.depth]; - state.horizontal_direction = if rb_center > view_center { - Direction::Negative - } else { - Direction::Positive - }; - - let aod = Aod { - horizontal: true, - vertical: true, - horizontal_overlap: true, - vertical_overlap: false, - horizontal_direction: state.horizontal_direction, - vertical_direction: state.vertical_direction, - horizontal_offset: 0.0, - vertical_offset: main_offset, - }; - - let menu_bounds = MenuBounds::new( - mt, - renderer, - menu.item_width, - menu.item_height, - viewport_size, - overlay_offset, - &aod, - menu.bounds_expand, - root_bounds, - &mut menu.tree.children[i].children, - ); - - state.active_root = Some(i); - state.menu_states.push(MenuState { - index: None, - scroll_offset: 0.0, - menu_bounds, + let mut set = false; + let mt = active_roots + .iter() + .skip(1) + .fold(&menu.menu_roots[active_roots[0]], |mt, next_active_root| { + &mt.children[*next_active_root] }); + let i = active_roots.last().unwrap(); + let root_bounds = menu.root_bounds_list[*i]; - // Hack to ensure menu opens properly - shell.invalidate_layout(); + assert!(!mt.children.is_empty(), "skipping menu with no children"); + let aod = Aod { + horizontal: true, + vertical: true, + horizontal_overlap: true, + vertical_overlap: false, + horizontal_direction: state.horizontal_direction, + vertical_direction: state.vertical_direction, + horizontal_offset: 0.0, + vertical_offset: main_offset, + }; + let menu_bounds = MenuBounds::new( + mt, + renderer, + menu.item_width, + menu.item_height, + viewport_size, + overlay_offset, + &aod, + menu.bounds_expand, + root_bounds, + // TODO how to select the tree for the popup + &mut state.tree.children[0].children, + menu.is_overlay, + ); - break; - } - } + let view_center = viewport_size.width * 0.5; + let rb_center = root_bounds.center_x(); + + state.horizontal_direction = if rb_center > view_center { + Direction::Negative + } else { + Direction::Positive + }; + set = true; + + let ms = MenuState { + index: None, + scroll_offset: 0.0, + menu_bounds, + }; + state.menu_states.push(ms); + + // Hack to ensure menu opens properly + shell.invalidate_layout(); + // non tree buttons arent active? + debug_assert!(set, "Root popup menu state was not set."); + }); } #[allow(clippy::too_many_arguments)] -fn process_menu_events<'b, Message, Renderer>( - tree: &'b mut Tree, - menu_roots: &'b mut [MenuTree<'_, Message, Renderer>], +fn process_menu_events( + menu: &mut Menu, event: event::Event, view_cursor: Cursor, - renderer: &Renderer, + renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, overlay_offset: Vector, -) -> event::Status -where - Renderer: renderer::Renderer, -{ +) -> event::Status { use event::Status; - let state = tree.state.downcast_mut::(); - let Some(active_root) = state.active_root else { - return Status::Ignored; + let my_state = &mut menu.tree; + let menu_roots = match &mut menu.menu_roots { + Cow::Borrowed(_) => panic!(), + Cow::Owned(o) => o.as_mut_slice(), }; + my_state.inner.with_data_mut(|state| { + if state.active_root.len() <= menu.depth { + return event::Status::Ignored; + } - let indices = state.get_trimmed_indices().collect::>(); + let Some(hover) = state.menu_states.last_mut() else { + return Status::Ignored; + }; - if indices.is_empty() { - return Status::Ignored; - } + let Some(hover_index) = hover.index else { + return Status::Ignored; + }; - // get active item - let mt = indices - .iter() - .fold(&mut menu_roots[active_root], |mt, &i| &mut mt.children[i]); + let mt = state.active_root.iter().skip(1).fold( + // then use menu states for each open menu + &mut menu_roots[state.active_root[0]], + |mt, next_active_root| &mut mt.children[*next_active_root], + ); - // widget tree - let tree = &mut tree.children[active_root].children[mt.index]; + let mt = &mut mt.children[hover_index]; + let tree = &mut state.tree.children[state.active_root[0]].children[mt.index]; - // get layout - let last_ms = &state.menu_states[indices.len() - 1]; - let child_node = last_ms.layout_single( - overlay_offset, - last_ms.index.expect("missing index within menu state."), - renderer, - mt, - tree, - ); - let child_layout = Layout::new(&child_node); + // get layout + let child_node = hover.layout_single( + overlay_offset, + hover.index.expect("missing index within menu state."), + renderer, + mt, + tree, + ); + let child_layout = Layout::new(&child_node); - // process only the last widget - mt.item.as_widget_mut().on_event( - tree, - event, - child_layout, - view_cursor, - renderer, - clipboard, - shell, - &Rectangle::default(), - ) + // process only the last widget + mt.item.on_event( + tree, + event, + child_layout, + view_cursor, + renderer, + clipboard, + shell, + &Rectangle::default(), + ) + }) } -#[allow(unused_results)] -fn process_overlay_events( - menu: &mut Menu<'_, '_, Message, Renderer>, - renderer: &Renderer, +#[allow(unused_results, clippy::too_many_lines, clippy::too_many_arguments)] +fn process_overlay_events( + menu: &mut Menu, + renderer: &crate::Renderer, viewport_size: Size, overlay_offset: Vector, view_cursor: Cursor, overlay_cursor: Point, cross_offset: f32, -) -> event::Status + _shell: &mut Shell<'_, Message>, +) -> (Option<(usize, MenuState)>, event::Status) where - Renderer: renderer::Renderer, + Message: std::clone::Clone, { use event::Status::{Captured, Ignored}; /* @@ -907,263 +1334,295 @@ where if active item is a menu: add menu // viewport space */ + let mut new_menu_root = None; - let state = menu.tree.state.downcast_mut::(); + menu.tree.inner.with_data_mut(|state| { - let Some(active_root) = state.active_root else { - if !menu.bar_bounds.contains(overlay_cursor) { - state.reset(); - } - return Ignored; - }; + /* When overlay is running, cursor_position in any widget method will go negative + but I still want Widget::draw() to react to cursor movement */ + state.view_cursor = view_cursor; - if state.pressed { - return Ignored; - } + // * remove invalid menus - /* When overlay is running, cursor_position in any widget method will go negative - but I still want Widget::draw() to react to cursor movement */ - state.view_cursor = view_cursor; - - // * remove invalid menus - let mut prev_bounds = std::iter::once(menu.bar_bounds) - .chain( - state.menu_states[..state.menu_states.len().saturating_sub(1)] - .iter() - .map(|ms| ms.menu_bounds.children_bounds), - ) - .collect::>(); - - if menu.close_condition.leave { - for i in (0..state.menu_states.len()).rev() { - let mb = &state.menu_states[i].menu_bounds; - - if mb.parent_bounds.contains(overlay_cursor) - || mb.children_bounds.contains(overlay_cursor) - || mb.offset_bounds.contains(overlay_cursor) - || (mb.check_bounds.contains(overlay_cursor) - && prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor))) - { - break; - } - prev_bounds.pop(); - state.menu_states.pop(); - } - } else { - for i in (0..state.menu_states.len()).rev() { - let mb = &state.menu_states[i].menu_bounds; - - if mb.parent_bounds.contains(overlay_cursor) - || mb.children_bounds.contains(overlay_cursor) - || prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor)) - { - break; - } - prev_bounds.pop(); - state.menu_states.pop(); - } - } - - // get indices - let indices = state - .menu_states - .iter() - .map(|ms| ms.index) - .collect::>(); - - // * update active item - let Some(last_menu_state) = state.menu_states.last_mut() else { - // no menus left - state.active_root = None; - - // keep state.open when the cursor is still inside the menu bar - // this allows the overlay to keep drawing when the cursor is - // moving aroung the menu bar - if !menu.bar_bounds.contains(overlay_cursor) { - state.open = false; - } - return Captured; - }; - - let last_menu_bounds = &last_menu_state.menu_bounds; - let last_parent_bounds = last_menu_bounds.parent_bounds; - let last_children_bounds = last_menu_bounds.children_bounds; - - if (!menu.menu_overlays_parent && last_parent_bounds.contains(overlay_cursor)) - // cursor is in the parent part - || !last_children_bounds.contains(overlay_cursor) - // cursor is outside - { - last_menu_state.index = None; - return Captured; - } - // cursor is in the children part - - // calc new index - let height_diff = (overlay_cursor.y - (last_children_bounds.y + last_menu_state.scroll_offset)) - .clamp(0.0, last_children_bounds.height - 0.001); - - let active_menu_root = &menu.menu_roots[active_root]; - - let active_menu = indices[0..indices.len().saturating_sub(1)] - .iter() - .fold(active_menu_root, |mt, i| { - &mt.children[i.expect("missing active child index in menu")] - }); - - let new_index = match menu.item_height { - ItemHeight::Uniform(u) => (height_diff / f32::from(u)).floor() as usize, - ItemHeight::Static(_) | ItemHeight::Dynamic(_) => { - let max_index = active_menu.children.len() - 1; - search_bound( - 0, - 0, - max_index, - height_diff, - &last_menu_bounds.child_positions, - &last_menu_bounds.child_sizes, + let mut prev_bounds = std::iter::once(menu.bar_bounds) + .chain( + if menu.is_overlay { + state.menu_states[..state.menu_states.len().saturating_sub(1)].iter() + } else { + state.menu_states[..menu.depth].iter() + } + .map(|s| s.menu_bounds.children_bounds), ) + .collect::>(); + + if menu.is_overlay && menu.close_condition.leave { + for i in (0..state.menu_states.len()).rev() { + let mb = &state.menu_states[i].menu_bounds; + + if mb.parent_bounds.contains(overlay_cursor) + || menu.is_overlay && mb.children_bounds.contains(overlay_cursor) + || mb.offset_bounds.contains(overlay_cursor) + || (mb.check_bounds.contains(overlay_cursor) + && prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor))) + { + break; + } + prev_bounds.pop(); + state.active_root.pop(); + state.menu_states.pop(); + } + } else if menu.is_overlay { + for i in (0..state.menu_states.len()).rev() { + let mb = &state.menu_states[i].menu_bounds; + + if mb.parent_bounds.contains(overlay_cursor) + || mb.children_bounds.contains(overlay_cursor) + || prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor)) + { + break; + } + prev_bounds.pop(); + state.active_root.pop(); + state.menu_states.pop(); + } } - }; - // set new index - last_menu_state.index = Some(new_index); + // * update active item + let menu_states_len = state.menu_states.len(); - // get new active item - let item = &active_menu.children[new_index]; + let Some(last_menu_state) = state.menu_states.get_mut(if menu.is_overlay { + menu_states_len.saturating_sub(1) + } else { + menu.depth + }) else { + if menu.is_overlay { + // no menus left + // TODO do we want to avoid this for popups? + // state.active_root.remove(menu.depth); - // * add new menu if the new item is a menu - if !item.children.is_empty() { - let item_position = Point::new( - 0.0, - last_menu_bounds.child_positions[new_index] + last_menu_state.scroll_offset, - ); - let item_size = last_menu_bounds.child_sizes[new_index]; + // keep state.open when the cursor is still inside the menu bar + // this allows the overlay to keep drawing when the cursor is + // moving aroung the menu bar + if !menu.bar_bounds.contains(overlay_cursor) { + state.open = false; + } + } - // overlay space item bounds - let item_bounds = Rectangle::new(item_position, item_size) - + (last_menu_bounds.children_bounds.position() - Point::ORIGIN); - - let aod = Aod { - horizontal: true, - vertical: true, - horizontal_overlap: false, - vertical_overlap: true, - horizontal_direction: state.horizontal_direction, - vertical_direction: state.vertical_direction, - horizontal_offset: cross_offset, - vertical_offset: 0.0, + return (new_menu_root, Captured); }; - state.menu_states.push(MenuState { - index: None, - scroll_offset: 0.0, - menu_bounds: MenuBounds::new( - item, - renderer, - menu.item_width, - menu.item_height, - viewport_size, - overlay_offset, - &aod, - menu.bounds_expand, - item_bounds, - &mut menu.tree.children[active_root].children, - ), - }); - } + let last_menu_bounds = &last_menu_state.menu_bounds; + let last_parent_bounds = last_menu_bounds.parent_bounds; + let last_children_bounds = last_menu_bounds.children_bounds; - Captured + if (menu.is_overlay && !menu.menu_overlays_parent && last_parent_bounds.contains(overlay_cursor)) + // cursor is in the parent part + || menu.is_overlay && !last_children_bounds.contains(overlay_cursor) + // cursor is outside + { + + last_menu_state.index = None; + return (new_menu_root, Captured); + } + + // calc new index + let height_diff = (overlay_cursor.y + - (last_children_bounds.y + last_menu_state.scroll_offset)) + .clamp(0.0, last_children_bounds.height - 0.001); + + let active_root = if menu.is_overlay { + &state.active_root + } else { + &state.active_root[..=menu.depth] + }; + + if state.pressed { + return (new_menu_root, Ignored); + } + let roots = active_root.iter().skip(1).fold( + &menu.menu_roots[active_root[0]].children, + |mt, next_active_root| &mt[*next_active_root].children, + ); + let tree = &mut state.tree.children[active_root[0]].children; + + let active_menu: &Vec> = roots; + let new_index = match menu.item_height { + ItemHeight::Uniform(u) => (height_diff / f32::from(u)).floor() as usize, + ItemHeight::Static(_) | ItemHeight::Dynamic(_) => { + let max_index = active_menu.len() - 1; + search_bound( + 0, + 0, + max_index, + height_diff, + &last_menu_bounds.child_positions, + &last_menu_bounds.child_sizes, + ) + } + }; + + let remove = last_menu_state + .index + .as_ref() + .is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty()); + + #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + { + if remove { + if let Some(id) = state.popup_id.remove(&menu.window_id) { + state.active_root.truncate(menu.depth + 1); + _shell.publish((menu.on_surface_action.as_ref().unwrap())({ + crate::surface::action::destroy_popup(id) + })); + } + } + } + let item = &active_menu[new_index]; + // set new index + let old_index = last_menu_state.index.replace(new_index); + + // get new active item + // * add new menu if the new item is a menu + if !item.children.is_empty() && old_index.is_none_or(|i| i != new_index) { + let item_position = Point::new( + 0.0, + last_menu_bounds.child_positions[new_index] + last_menu_state.scroll_offset, + ); + let item_size = last_menu_bounds.child_sizes[new_index]; + + // overlay space item bounds + let item_bounds = Rectangle::new(item_position, item_size) + + (last_menu_bounds.children_bounds.position() - Point::ORIGIN); + + let aod = Aod { + horizontal: true, + vertical: true, + horizontal_overlap: false, + vertical_overlap: true, + horizontal_direction: state.horizontal_direction, + vertical_direction: state.vertical_direction, + horizontal_offset: cross_offset, + vertical_offset: 0.0, + }; + let ms = MenuState { + index: None, + scroll_offset: 0.0, + menu_bounds: MenuBounds::new( + item, + renderer, + menu.item_width, + menu.item_height, + viewport_size, + overlay_offset, + &aod, + menu.bounds_expand, + item_bounds, + tree, + menu.is_overlay, + ), + }; + + new_menu_root = Some((new_index, ms.clone())); + if menu.is_overlay { + state.active_root.push(new_index); + } else { + state.menu_states.truncate(menu.depth + 1); + } + state.menu_states.push(ms); + } else if !menu.is_overlay && remove { + state.menu_states.truncate(menu.depth + 1); + } + + (new_menu_root, Captured) + }) } -fn process_scroll_events( - menu: &mut Menu<'_, '_, Message, Renderer>, +fn process_scroll_events( + menu: &mut Menu<'_, Message>, delta: mouse::ScrollDelta, overlay_cursor: Point, viewport_size: Size, overlay_offset: Vector, ) -> event::Status where - Renderer: renderer::Renderer, + Message: Clone, { use event::Status::{Captured, Ignored}; use mouse::ScrollDelta; - let state = menu.tree.state.downcast_mut::(); + menu.tree.inner.with_data_mut(|state| { + let delta_y = match delta { + ScrollDelta::Lines { y, .. } => y * 60.0, + ScrollDelta::Pixels { y, .. } => y, + }; - let delta_y = match delta { - ScrollDelta::Lines { y, .. } => y * 60.0, - ScrollDelta::Pixels { y, .. } => y, - }; + let calc_offset_bounds = |menu_state: &MenuState, viewport_size: Size| -> (f32, f32) { + // viewport space children bounds + let children_bounds = menu_state.menu_bounds.children_bounds + overlay_offset; - let calc_offset_bounds = |menu_state: &MenuState, viewport_size: Size| -> (f32, f32) { - // viewport space children bounds - let children_bounds = menu_state.menu_bounds.children_bounds + overlay_offset; + let max_offset = (0.0 - children_bounds.y).max(0.0); + let min_offset = + (viewport_size.height - (children_bounds.y + children_bounds.height)).min(0.0); + (max_offset, min_offset) + }; - let max_offset = (0.0 - children_bounds.y).max(0.0); - let min_offset = - (viewport_size.height - (children_bounds.y + children_bounds.height)).min(0.0); - (max_offset, min_offset) - }; + // update + if state.menu_states.is_empty() { + return Ignored; + } else if state.menu_states.len() == 1 { + let last_ms = &mut state.menu_states[0]; - // update - if state.menu_states.is_empty() { - return Ignored; - } else if state.menu_states.len() == 1 { - let last_ms = &mut state.menu_states[0]; - - if last_ms.index.is_none() { - return Captured; - } - - let (max_offset, min_offset) = calc_offset_bounds(last_ms, viewport_size); - last_ms.scroll_offset = (last_ms.scroll_offset + delta_y).clamp(min_offset, max_offset); - } else { - // >= 2 - let max_index = state.menu_states.len() - 1; - let last_two = &mut state.menu_states[max_index - 1..=max_index]; - - if last_two[1].index.is_some() { - // scroll the last one - let (max_offset, min_offset) = calc_offset_bounds(&last_two[1], viewport_size); - last_two[1].scroll_offset = - (last_two[1].scroll_offset + delta_y).clamp(min_offset, max_offset); - } else { - if !last_two[0] - .menu_bounds - .children_bounds - .contains(overlay_cursor) - { + if last_ms.index.is_none() { return Captured; } - // scroll the second last one - let (max_offset, min_offset) = calc_offset_bounds(&last_two[0], viewport_size); - let scroll_offset = (last_two[0].scroll_offset + delta_y).clamp(min_offset, max_offset); - let clamped_delta_y = scroll_offset - last_two[0].scroll_offset; - last_two[0].scroll_offset = scroll_offset; + let (max_offset, min_offset) = calc_offset_bounds(last_ms, viewport_size); + last_ms.scroll_offset = (last_ms.scroll_offset + delta_y).clamp(min_offset, max_offset); + } else { + // >= 2 + let max_index = state.menu_states.len() - 1; + let last_two = &mut state.menu_states[max_index - 1..=max_index]; - // update the bounds of the last one - last_two[1].menu_bounds.parent_bounds.y += clamped_delta_y; - last_two[1].menu_bounds.children_bounds.y += clamped_delta_y; - last_two[1].menu_bounds.check_bounds.y += clamped_delta_y; + if last_two[1].index.is_some() { + // scroll the last one + let (max_offset, min_offset) = calc_offset_bounds(&last_two[1], viewport_size); + last_two[1].scroll_offset = + (last_two[1].scroll_offset + delta_y).clamp(min_offset, max_offset); + } else { + if !last_two[0] + .menu_bounds + .children_bounds + .contains(overlay_cursor) + { + return Captured; + } + + // scroll the second last one + let (max_offset, min_offset) = calc_offset_bounds(&last_two[0], viewport_size); + let scroll_offset = + (last_two[0].scroll_offset + delta_y).clamp(min_offset, max_offset); + let clamped_delta_y = scroll_offset - last_two[0].scroll_offset; + last_two[0].scroll_offset = scroll_offset; + + // update the bounds of the last one + last_two[1].menu_bounds.parent_bounds.y += clamped_delta_y; + last_two[1].menu_bounds.children_bounds.y += clamped_delta_y; + last_two[1].menu_bounds.check_bounds.y += clamped_delta_y; + } } - } - Captured + Captured + }) } #[allow(clippy::pedantic)] /// Returns (children_size, child_positions, child_sizes) -fn get_children_layout( - menu_tree: &MenuTree<'_, Message, Renderer>, - renderer: &Renderer, +fn get_children_layout( + menu_tree: &MenuTree, + renderer: &crate::Renderer, item_width: ItemWidth, item_height: ItemHeight, tree: &mut [Tree], -) -> (Size, Vec, Vec) -where - Renderer: renderer::Renderer, -{ +) -> (Size, Vec, Vec) { let width = match item_width { ItemWidth::Uniform(u) => f32::from(u), ItemWidth::Static(s) => f32::from(menu_tree.width.unwrap_or(s)), @@ -1183,37 +1642,39 @@ where .children .iter() .map(|mt| { - let w = mt.item.as_widget(); - match w.size().height { - Length::Fixed(f) => Size::new(width, f), - Length::Shrink => { - let l_height = w - .layout( - &mut tree[mt.index], - renderer, - &Limits::new(Size::ZERO, Size::new(width, f32::MAX)), - ) - .size() - .height; + mt.item + .element + .with_data(|w| match w.as_widget().size().height { + Length::Fixed(f) => Size::new(width, f), + Length::Shrink => { + let l_height = w + .as_widget() + .layout( + &mut tree[mt.index], + renderer, + &Limits::new(Size::ZERO, Size::new(width, f32::MAX)), + ) + .size() + .height; - let height = if (f32::MAX - l_height) < 0.001 { - f32::from(d) - } else { - l_height - }; + let height = if (f32::MAX - l_height) < 0.001 { + f32::from(d) + } else { + l_height + }; - Size::new(width, height) - } - _ => mt.height.map_or_else( - || Size::new(width, f32::from(d)), - |h| Size::new(width, f32::from(h)), - ), - } + Size::new(width, height) + } + _ => mt.height.map_or_else( + || Size::new(width, f32::from(d)), + |h| Size::new(width, f32::from(h)), + ), + }) }) .collect(), }; - let max_index = menu_tree.children.len() - 1; + let max_index = menu_tree.children.len().saturating_sub(1); let child_positions: Vec = std::iter::once(0.0) .chain(child_sizes[0..max_index].iter().scan(0.0, |acc, x| { *acc += x.height; diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index 1f3fd4ab..d02b2b27 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -11,7 +11,7 @@ use iced_widget::core::{Element, renderer}; use crate::iced_core::{Alignment, Length}; use crate::widget::menu::action::MenuAction; use crate::widget::menu::key_bind::KeyBind; -use crate::widget::{Button, icon}; +use crate::widget::{Button, RcElementWrapper, icon}; use crate::{theme, widget}; /// Nested menu is essentially a tree of items, a menu is a collection of items @@ -23,27 +23,25 @@ use crate::{theme, widget}; /// but there's no need to explicitly distinguish them here, if a menu tree /// has children, it's a menu, otherwise it's an item #[allow(missing_debug_implementations)] -pub struct MenuTree<'a, Message, Renderer = crate::Renderer> { +#[derive(Clone)] +pub struct MenuTree { /// The menu tree will be flatten into a vector to build a linear widget tree, /// the `index` field is the index of the item in that vector pub(crate) index: usize, /// The item of the menu tree - pub(crate) item: Element<'a, Message, crate::Theme, Renderer>, + pub(crate) item: RcElementWrapper, /// The children of the menu tree - pub(crate) children: Vec>, + pub(crate) children: Vec>, /// The width of the menu tree pub(crate) width: Option, /// The height of the menu tree pub(crate) height: Option, } -impl<'a, Message, Renderer> MenuTree<'a, Message, Renderer> -where - Renderer: renderer::Renderer, -{ +impl MenuTree { /// Create a new menu tree from a widget - pub fn new(item: impl Into>) -> Self { + pub fn new(item: impl Into>) -> Self { Self { index: 0, item: item.into(), @@ -55,8 +53,8 @@ where /// Create a menu tree from a widget and a vector of sub trees pub fn with_children( - item: impl Into>, - children: Vec>>, + item: impl Into>, + children: Vec>>, ) -> Self { Self { index: 0, @@ -92,7 +90,7 @@ where /// Set the index of each item pub(crate) fn set_index(&mut self) { /// inner counting function. - fn rec(mt: &mut MenuTree<'_, Message, Renderer>, count: &mut usize) { + fn rec(mt: &mut MenuTree, count: &mut usize) { // keep items under the same menu line up mt.children.iter_mut().for_each(|c| { c.index = *count; @@ -109,18 +107,18 @@ where } /// Flatten the menu tree - pub(crate) fn flattern(&'a self) -> Vec<&Self> { + pub(crate) fn flattern(&self) -> Vec<&Self> { /// Inner flattening function - fn rec<'a, Message, Renderer>( - mt: &'a MenuTree<'a, Message, Renderer>, - flat: &mut Vec<&MenuTree<'a, Message, Renderer>>, + fn rec<'a, Message: Clone + 'static>( + mt: &'a MenuTree, + flat: &mut Vec<&'a MenuTree>, ) { mt.children.iter().for_each(|c| { flat.push(c); }); mt.children.iter().for_each(|c| { - rec(c, flat); + rec(&c, flat); }); } @@ -132,13 +130,9 @@ where } } -impl<'a, Message, Renderer> From> - for MenuTree<'a, Message, Renderer> -where - Renderer: renderer::Renderer, -{ - fn from(value: Element<'a, Message, crate::Theme, Renderer>) -> Self { - Self::new(value) +impl From> for MenuTree { + fn from(value: crate::Element<'static, Message>) -> Self { + Self::new(RcElementWrapper::new(value)) } } @@ -160,6 +154,7 @@ where .class(theme::Button::MenuItem) } +#[derive(Clone)] /// Represents a menu item that performs an action when selected or a separator between menu items. /// /// - `Action` - Represents a menu item that performs an action when selected. @@ -215,20 +210,15 @@ where /// /// # Returns /// - A vector of `MenuTree`. +#[must_use] pub fn menu_items< - 'a, A: MenuAction, L: Into> + 'static, - Message, - Renderer: renderer::Renderer + 'a, + Message: 'static + std::clone::Clone, >( key_binds: &HashMap, children: Vec>, -) -> Vec> -where - Element<'a, Message, crate::Theme, Renderer>: From>, - Message: 'a + Clone, -{ +) -> Vec> { fn find_key(action: &A, key_binds: &HashMap) -> String { for (key_bind, key_action) in key_binds { if action == key_action { @@ -249,9 +239,10 @@ where match item { MenuItem::Button(label, icon, action) => { + let l: Cow<'static, str> = label.into(); let key = find_key(&action, key_binds); let mut items = vec![ - widget::text(label).into(), + widget::text(l.clone()).into(), widget::horizontal_space().into(), widget::text(key).into(), ]; @@ -261,15 +252,18 @@ where items.insert(1, widget::Space::with_width(spacing.space_xxs).into()); } + // dbg!("button with action...", action.message()); let menu_button = menu_button(items).on_press(action.message()); - trees.push(MenuTree::::new(menu_button)); + trees.push(MenuTree::::from(Element::from(menu_button))); } MenuItem::ButtonDisabled(label, icon, action) => { + let l: Cow<'static, str> = label.into(); + let key = find_key(&action, key_binds); let mut items = vec![ - widget::text(label).into(), + widget::text(l.clone()).into(), widget::horizontal_space().into(), widget::text(key).into(), ]; @@ -281,7 +275,7 @@ where let menu_button = menu_button(items); - trees.push(MenuTree::::new(menu_button)); + trees.push(MenuTree::::from(Element::from(menu_button))); } MenuItem::CheckBox(label, icon, value, action) => { let key = find_key(&action, key_binds); @@ -311,36 +305,42 @@ where items.insert(2, widget::icon::icon(icon).size(14).into()); } - trees.push(MenuTree::new(menu_button(items).on_press(action.message()))); + trees.push(MenuTree::from(Element::from( + menu_button(items).on_press(action.message()), + ))); } MenuItem::Folder(label, children) => { - trees.push(MenuTree::::with_children( - menu_button(vec![ - widget::text(label).into(), - widget::horizontal_space().into(), - widget::icon::from_name("pan-end-symbolic") - .size(16) - .icon() - .into(), - ]) - .class( - // Menu folders have no on_press so they take on the disabled style by default - if children.is_empty() { - // This will make the folder use the disabled style if it has no children - theme::Button::MenuItem - } else { - // This will make the folder use the enabled style if it has children - theme::Button::MenuFolder - }, - ), + let l: Cow<'static, str> = label.into(); + + trees.push(MenuTree::::with_children( + RcElementWrapper::new(crate::Element::from( + menu_button::<'static, _>(vec![ + widget::text(l.clone()).into(), + widget::horizontal_space().into(), + widget::icon::from_name("pan-end-symbolic") + .size(16) + .icon() + .into(), + ]) + .class( + // Menu folders have no on_press so they take on the disabled style by default + if children.is_empty() { + // This will make the folder use the disabled style if it has no children + theme::Button::MenuItem + } else { + // This will make the folder use the enabled style if it has children + theme::Button::MenuFolder + }, + ), + )), menu_items(key_binds, children), )); } MenuItem::Divider => { if i != size - 1 { - trees.push(MenuTree::::new( + trees.push(MenuTree::::from(Element::from( widget::divider::horizontal::light(), - )); + ))); } } } diff --git a/src/widget/nav_bar.rs b/src/widget/nav_bar.rs index fef3cbe4..6923472a 100644 --- a/src/widget/nav_bar.rs +++ b/src/widget/nav_bar.rs @@ -69,7 +69,7 @@ impl<'a, Message: Clone + 'static> NavBar<'a, Message> { } #[inline] - pub fn context_menu(mut self, context_menu: Option>>) -> Self { + pub fn context_menu(mut self, context_menu: Option>>) -> Self { self.segmented_button = self.segmented_button.context_menu(context_menu); self } diff --git a/src/widget/responsive_menu_bar.rs b/src/widget/responsive_menu_bar.rs index 65c5d3eb..3d9557d0 100644 --- a/src/widget/responsive_menu_bar.rs +++ b/src/widget/responsive_menu_bar.rs @@ -9,6 +9,7 @@ use crate::{ use super::menu::{self, ItemHeight, ItemWidth}; +#[must_use] pub fn responsive_menu_bar() -> ResponsiveMenuBar { ResponsiveMenuBar::default() } @@ -33,18 +34,21 @@ impl Default for ResponsiveMenuBar { impl ResponsiveMenuBar { /// Set the item width + #[must_use] pub fn item_width(mut self, item_width: ItemWidth) -> Self { self.item_width = item_width; self } /// Set the item height + #[must_use] pub fn item_height(mut self, item_height: ItemHeight) -> Self { self.item_height = item_height; self } /// Set the spacing + #[must_use] pub fn spacing(mut self, spacing: f32) -> Self { self.spacing = spacing; self @@ -56,14 +60,14 @@ impl ResponsiveMenuBar { pub fn into_element< 'a, Message: Clone + 'static, - A: menu::Action, + A: menu::Action + Clone, S: Into> + 'static, >( self, core: &Core, key_binds: &HashMap, id: crate::widget::Id, - action_message: impl Fn(crate::surface::Action) -> Message + 'static, + action_message: impl Fn(crate::surface::Action) -> Message + Send + Sync + Clone + 'static, trees: Vec<(S, Vec>)>, ) -> Element<'a, Message> { use crate::widget::id_container; @@ -80,17 +84,21 @@ impl ResponsiveMenuBar { menu::bar( trees .into_iter() - .map(|mt| { + .map(|mt: (S, Vec>)| { menu::Tree::<_>::with_children( - menu::root(mt.0), - menu::items(key_binds, mt.1.into()), + crate::widget::RcElementWrapper::new(Element::from( + menu::root(mt.0), + )), + menu::items(key_binds, mt.1), ) }) .collect(), ) .item_width(self.item_width) .item_height(self.item_height) - .spacing(self.spacing), + .spacing(self.spacing) + .on_surface_action(action_message.clone()) + .window_id_maybe(core.main_window_id()), crate::widget::Id::new(format!("menu_bar_expanded_{id}")), ), id, @@ -123,7 +131,9 @@ impl ResponsiveMenuBar { )]) .item_height(self.item_height) .item_width(self.collapsed_item_width) - .spacing(self.spacing), + .spacing(self.spacing) + .on_surface_action(action_message.clone()) + .window_id_maybe(core.main_window_id()), crate::widget::Id::new(format!("menu_bar_collapsed_{id}")), ), id, diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 3cb64e2d..313b686d 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -17,7 +17,7 @@ use iced::clipboard::mime::AllowedMimeTypes; use iced::touch::Finger; use iced::{ Alignment, Background, Color, Event, Length, Padding, Rectangle, Size, Task, Vector, alignment, - event, keyboard, mouse, touch, + event, keyboard, mouse, touch, window, }; use iced_core::mouse::ScrollDelta; use iced_core::text::{LineHeight, Renderer as TextRenderer, Shaping, Wrapping}; @@ -127,7 +127,7 @@ where pub(super) style: Style, /// The context menu to display when a context is activated #[setters(skip)] - pub(super) context_menu: Option>>, + pub(super) context_menu: Option>>, /// Emits the ID of the item that was activated. #[setters(skip)] pub(super) on_activate: Option Message + 'static>>, @@ -198,13 +198,13 @@ where } } - pub fn context_menu(mut self, context_menu: Option>>) -> Self + pub fn context_menu(mut self, context_menu: Option>>) -> Self where - Message: 'static, + Message: Clone + 'static, { self.context_menu = context_menu.map(|menus| { vec![menu::Tree::with_children( - crate::widget::row::<'static, Message>(), + crate::Element::from(crate::widget::row::<'static, Message>()), menus, )] }); @@ -577,6 +577,7 @@ where fn state(&self) -> tree::State { #[allow(clippy::default_trait_access)] tree::State::new(LocalState { + menu_state: Default::default(), paragraphs: SecondaryMap::new(), text_hashes: SecondaryMap::new(), buttons_visible: Default::default(), @@ -955,8 +956,10 @@ where let menu_state = tree.children[0].state.downcast_mut::(); - menu_state.open = true; - menu_state.view_cursor = cursor_position; + menu_state.inner.with_data_mut(|data| { + data.open = true; + data.view_cursor = cursor_position; + }); shell.publish(on_context(key)); return event::Status::Captured; @@ -1346,7 +1349,11 @@ where let center_y = bounds.center_y(); let menu_open = !tree.children.is_empty() - && tree.children[0].state.downcast_ref::().open; + && tree.children[0] + .state + .downcast_ref::() + .inner + .with_data(|data| data.open); let key_is_active = self.model.is_active(key); let key_is_hovered = self.button_is_hovered(state, key); @@ -1556,6 +1563,7 @@ where translation: Vector, ) -> Option> { let state = tree.state.downcast_ref::(); + let menu_state = state.menu_state.clone(); let Some(entity) = state.show_context else { return None; @@ -1575,7 +1583,12 @@ where return None; }; - if !tree.children[0].state.downcast_ref::().open { + if !tree.children[0] + .state + .downcast_ref::() + .inner + .with_data(|data| data.open) + { return None; } @@ -1584,8 +1597,8 @@ where Some( crate::widget::menu::Menu { - tree: &mut tree.children[0], - menu_roots: context_menu, + tree: menu_state, + menu_roots: std::borrow::Cow::Borrowed(context_menu), bounds_expand: 16, menu_overlays_parent: true, close_condition: CloseCondition { @@ -1600,8 +1613,12 @@ where cross_offset: 0, root_bounds_list: vec![bounds], path_highlight: Some(PathHighlight::MenuActive), - style: &crate::theme::menu_bar::MenuBarStyle::Default, + style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default), position: Point::new(translation.x, translation.y), + is_overlay: true, + window_id: window::Id::NONE, + depth: 0, + on_surface_action: None, } .overlay(), ) @@ -1653,6 +1670,8 @@ where /// State that is maintained by each individual widget. pub struct LocalState { + /// Menu state + pub(crate) menu_state: MenuBarState, /// Defines how many buttons to show at a time. pub(super) buttons_visible: usize, /// Button visibility offset, when collapsed. diff --git a/src/widget/table/mod.rs b/src/widget/table/mod.rs index 7063dc8e..c546383c 100644 --- a/src/widget/table/mod.rs +++ b/src/widget/table/mod.rs @@ -20,9 +20,9 @@ pub type MultiSelectTableView<'a, Item, Category, Message> = TableView<'a, MultiSelect, Item, Category, Message>; pub type MultiSelectModel = Model; -pub fn table<'a, SelectionMode, Item, Category, Message>( - model: &'a Model, -) -> TableView<'a, SelectionMode, Item, Category, Message> +pub fn table( + model: &Model, +) -> TableView<'_, SelectionMode, Item, Category, Message> where Message: Clone, SelectionMode: Default, @@ -33,9 +33,9 @@ where TableView::new(model) } -pub fn compact_table<'a, SelectionMode, Item, Category, Message>( - model: &'a Model, -) -> CompactTableView<'a, SelectionMode, Item, Category, Message> +pub fn compact_table( + model: &Model, +) -> CompactTableView<'_, SelectionMode, Item, Category, Message> where Message: Clone, SelectionMode: Default, diff --git a/src/widget/table/widget/compact.rs b/src/widget/table/widget/compact.rs index 43a32de2..47864f6d 100644 --- a/src/widget/table/widget/compact.rs +++ b/src/widget/table/widget/compact.rs @@ -44,7 +44,7 @@ where #[setters(skip)] pub(super) on_item_mb_right: Option Message + 'static>>, #[setters(skip)] - pub(super) item_context_builder: Box Option>>>, + pub(super) item_context_builder: Box Option>>>, } impl<'a, SelectionMode, Item, Category, Message> @@ -97,7 +97,7 @@ where ] }) .flatten() - .collect::>>(); + .collect::>>(); elements.pop(); elements .apply(widget::row::with_children) @@ -247,7 +247,7 @@ where pub fn item_context(mut self, context_menu_builder: F) -> Self where - F: Fn(&Item) -> Option>> + 'static, + F: Fn(&Item) -> Option>> + 'static, Message: 'static, { self.item_context_builder = Box::new(context_menu_builder); diff --git a/src/widget/table/widget/standard.rs b/src/widget/table/widget/standard.rs index 01d0ea56..eb9ba7a4 100644 --- a/src/widget/table/widget/standard.rs +++ b/src/widget/table/widget/standard.rs @@ -51,7 +51,7 @@ where #[setters(skip)] pub(super) on_item_mb_right: Option Message + 'static>>, #[setters(skip)] - pub(super) item_context_builder: Box Option>>>, + pub(super) item_context_builder: Box Option>>>, // Item DND // === Category Interaction === @@ -64,8 +64,7 @@ where #[setters(skip)] pub(super) on_category_mb_right: Option Message + 'static>>, #[setters(skip)] - pub(super) category_context_builder: - Box Option>>>, + pub(super) category_context_builder: Box Option>>>, } impl<'a, SelectionMode, Item, Category, Message> @@ -83,7 +82,7 @@ where .model .categories .iter() - .cloned() + .copied() .map(|category| { let cat_context_tree = (val.category_context_builder)(category); @@ -167,7 +166,7 @@ where .align_y(Alignment::Center) .apply(Element::from) }) - .collect::>>() + .collect::>>() .apply(widget::row::with_children) .apply(container) .padding(val.item_padding) @@ -328,7 +327,7 @@ where pub fn item_context(mut self, context_menu_builder: F) -> Self where - F: Fn(&Item) -> Option>> + 'static, + F: Fn(&Item) -> Option>> + 'static, Message: 'static, { self.item_context_builder = Box::new(context_menu_builder); @@ -367,7 +366,7 @@ where pub fn category_context(mut self, context_menu_builder: F) -> Self where - F: Fn(Category) -> Option>> + 'static, + F: Fn(Category) -> Option>> + 'static, Message: 'static, { self.category_context_builder = Box::new(context_menu_builder); diff --git a/src/widget/wrapper.rs b/src/widget/wrapper.rs index 0579c4b2..92f26fd4 100644 --- a/src/widget/wrapper.rs +++ b/src/widget/wrapper.rs @@ -1,4 +1,5 @@ use std::{ + borrow::Borrow, cell::RefCell, rc::Rc, thread::{self, ThreadId}, @@ -14,6 +15,12 @@ pub struct RcWrapper { pub(crate) thread_id: ThreadId, } +impl Default for RcWrapper { + fn default() -> Self { + Self::new(T::default()) + } +} + impl Clone for RcWrapper { fn clone(&self) -> Self { Self { @@ -75,6 +82,12 @@ impl RcElementWrapper { } } +impl Borrow> for RcElementWrapper { + fn borrow(&self) -> &(dyn Widget + 'static) { + self + } +} + impl Widget for RcElementWrapper { fn size(&self) -> Size { self.element.with_data(|e| e.as_widget().size()) From 7f2d34ead4a3934fd7f295815ffe170c22cbade4 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 10 Jun 2025 16:55:30 -0400 Subject: [PATCH 208/556] fix(menu-bar): exit early from popup creation if no root is hovered --- iced | 2 +- src/widget/menu/menu_bar.rs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/iced b/iced index 717bc5db..fc9d49eb 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 717bc5dbfbc8f78e367e08e76a9572ee0ebc1f32 +Subproject commit fc9d49eb2f830fe394252ff6799d59ad828243bc diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index eddc4f3a..2c355bf4 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -375,13 +375,14 @@ where let hovered_root = layout .children() .position(|lo| view_cursor.is_over(lo.bounds())); - - if old_active_root - .zip(hovered_root) - .is_some_and(|r| r.0 == r.1) + if hovered_root.is_none() + || old_active_root + .zip(hovered_root) + .is_some_and(|r| r.0 == r.1) { return; } + let (id, root_list) = my_state.inner.with_data_mut(|state| { if let Some(id) = state.popup_id.get(&self.window_id).copied() { // close existing popups From 96416c2a3fc217627308ea877979ffd25661b68d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 10 Jun 2025 16:56:02 -0400 Subject: [PATCH 209/556] fix(button): return from draw if there is no content layout --- src/widget/button/widget.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index aa8f0c32..da8612f7 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -437,7 +437,11 @@ impl<'a, Message: 'a + Clone> Widget if !viewport.intersects(&bounds) { return; } - let content_layout = layout.children().next().unwrap(); + + // FIXME: Why is there no content layout + let Some(content_layout) = layout.children().next() else { + return; + }; let mut headerbar_alpha = None; From f835afa59c3890ee0e16933bbf7d03263447c285 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 11 Jun 2025 09:24:02 +0200 Subject: [PATCH 210/556] fix(segmented_button): unfocus when clicking out of bounds --- src/widget/segmented_button/widget.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 313b686d..a94763d6 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -935,6 +935,7 @@ where } if can_activate { + eprintln!("can activate focus"); shell.publish(on_activate(key)); state.focused = true; state.focused_item = Item::Tab(key); @@ -1036,6 +1037,16 @@ where } } } + } else if state.focused { + // Unfocus on clicks outside of the boundaries of the segmented button. + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) = event + { + state.focused = true; + state.focused_item = Item::None; + state.pressed_item = None; + return event::Status::Ignored; + } } if state.focused { @@ -1724,11 +1735,13 @@ impl operation::Focusable for LocalState { } fn focus(&mut self) { + eprintln!("focus"); self.focused = true; self.focused_item = Item::Set; } fn unfocus(&mut self) { + eprintln!("unfocus"); self.focused = false; self.focused_item = Item::None; self.show_context = None; From 8edbbec1e8983c7a30f8867688f432798ec99af7 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 11 Jun 2025 09:25:21 +0200 Subject: [PATCH 211/556] fix!(desktop): support launching terminal-based desktop entries --- Cargo.toml | 2 ++ src/desktop.rs | 30 +++++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e9934e45..a90b0e73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ rfd = ["dep:rfd"] # Enables desktop files helpers desktop = [ "process", + "dep:cosmic-settings-config", "dep:freedesktop-desktop-entry", "dep:mime", "dep:shlex", @@ -105,6 +106,7 @@ auto_enums = "0.8.7" cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "178eb0b", optional = true } chrono = "0.4.40" cosmic-config = { path = "cosmic-config" } +cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } css-color = "0.2.8" derive_setters = "0.1.6" diff --git a/src/desktop.rs b/src/desktop.rs index c9c4c491..34673f91 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -47,6 +47,7 @@ pub struct DesktopEntryData { pub desktop_actions: Vec, pub mime_types: Vec, pub prefers_dgpu: bool, + pub terminal: bool, } #[cfg(not(windows))] @@ -196,6 +197,7 @@ impl DesktopEntryData { }) .unwrap_or_default(), prefers_dgpu: de.prefers_non_default_gpu(), + terminal: de.terminal(), path: Some(de.path), } } @@ -203,14 +205,36 @@ impl DesktopEntryData { #[cfg(not(windows))] #[cold] -pub async fn spawn_desktop_exec(exec: S, env_vars: I, app_id: Option<&str>) -where +pub async fn spawn_desktop_exec( + exec: S, + env_vars: I, + app_id: Option<&str>, + terminal: bool, +) where S: AsRef, I: IntoIterator, K: AsRef, V: AsRef, { - let mut exec = shlex::Shlex::new(exec.as_ref()); + let term_exec; + + let exec_str = if terminal { + let term = cosmic_settings_config::shortcuts::context() + .ok() + .and_then(|config| { + cosmic_settings_config::shortcuts::system_actions(&config) + .get(&cosmic_settings_config::shortcuts::action::System::Terminal) + .cloned() + }) + .unwrap_or_else(|| String::from("cosmic-term")); + + term_exec = format!("{term} -- {}", exec.as_ref()); + &term_exec + } else { + exec.as_ref() + }; + + let mut exec = shlex::Shlex::new(exec_str); let executable = match exec.next() { Some(executable) if !executable.contains('=') => executable, From 3f4a50ee2c3c04732b28497572e86101b4d9a055 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 11 Jun 2025 11:49:32 +0200 Subject: [PATCH 212/556] chore: remove eprintln logs --- src/widget/segmented_button/widget.rs | 3 --- src/widget/text_input/input.rs | 1 - 2 files changed, 4 deletions(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index a94763d6..07e6cbc1 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -935,7 +935,6 @@ where } if can_activate { - eprintln!("can activate focus"); shell.publish(on_activate(key)); state.focused = true; state.focused_item = Item::Tab(key); @@ -1735,13 +1734,11 @@ impl operation::Focusable for LocalState { } fn focus(&mut self) { - eprintln!("focus"); self.focused = true; self.focused_item = Item::Set; } fn unfocus(&mut self) { - eprintln!("unfocus"); self.focused = false; self.focused_item = Item::None; self.show_context = None; diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index eed1bed1..06a193b9 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -882,7 +882,6 @@ where if let Some(on_unfocus) = self.on_unfocus.as_ref() { if state.emit_unfocus { state.emit_unfocus = false; - eprintln!("unfocus"); shell.publish(on_unfocus.clone()); } } From 4c6061d40a17c0cbcc2ec66edc15ca3e476b14ff Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 11 Jun 2025 11:03:14 -0400 Subject: [PATCH 213/556] fix(menu inner): avoid unnecessary panic in debug builds. --- src/widget/menu/menu_inner.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 8ebca090..c41cded2 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -1161,7 +1161,6 @@ pub(super) fn init_root_menu( break; } } - debug_assert!(set, "Root not set"); }); } @@ -1242,8 +1241,6 @@ pub(super) fn init_root_popup_menu( // Hack to ensure menu opens properly shell.invalidate_layout(); - // non tree buttons arent active? - debug_assert!(set, "Root popup menu state was not set."); }); } From ba72aed6fbb574131d62e917cc22581af0d2a39e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 11 Jun 2025 14:50:25 -0400 Subject: [PATCH 214/556] feat: context menu popups --- examples/context-menu/src/main.rs | 20 ++- src/widget/context_menu.rs | 264 +++++++++++++++++++++++++++++- src/widget/menu.rs | 2 +- src/widget/menu/menu_bar.rs | 2 +- src/widget/menu/menu_inner.rs | 10 +- 5 files changed, 283 insertions(+), 15 deletions(-) diff --git a/examples/context-menu/src/main.rs b/examples/context-menu/src/main.rs index 4a307840..c744f963 100644 --- a/examples/context-menu/src/main.rs +++ b/examples/context-menu/src/main.rs @@ -27,9 +27,8 @@ fn main() -> Result<(), Box> { #[derive(Clone, Debug)] pub enum Message { Clicked, - ShowContext, WindowClose, - ShowWindowMenu, + Surface(cosmic::surface::Action), ToggleHideContent, WindowNew, } @@ -85,7 +84,19 @@ impl cosmic::Application for App { /// Handle application events here. fn update(&mut self, message: Self::Message) -> Task { - self.button_label = format!("Clicked {message:?}"); + match message { + Message::Clicked => { + self.button_label = format!("Clicked {message:?}"); + } + Message::Surface(action) => { + return cosmic::task::message(cosmic::Action::Cosmic( + cosmic::app::Action::Surface(action), + )); + } + Message::WindowClose => {} + Message::ToggleHideContent => {} + Message::WindowNew => {} + } Task::none() } @@ -95,7 +106,8 @@ impl cosmic::Application for App { let widget = cosmic::widget::context_menu( cosmic::widget::button::text(self.button_label.to_string()).on_press(Message::Clicked), self.context_menu(), - ); + ) + .on_surface_action(Message::Surface); let centered = cosmic::widget::container(widget) .width(iced::Length::Fill) diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 6769dff2..a00ae751 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -4,7 +4,8 @@ //! A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation. use crate::widget::menu::{ - self, CloseCondition, ItemHeight, ItemWidth, MenuBarState, PathHighlight, menu_roots_diff, + self, CloseCondition, Direction, ItemHeight, ItemWidth, MenuBarState, PathHighlight, + init_root_menu, menu_roots_diff, }; use derive_setters::Setters; use iced::touch::Finger; @@ -12,6 +13,7 @@ use iced::{Event, Vector, window}; use iced_core::widget::{Tree, Widget, tree}; use iced_core::{Length, Point, Size, event, mouse, touch}; use std::collections::HashSet; +use std::sync::Arc; /// A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation. pub fn context_menu( @@ -27,6 +29,8 @@ pub fn context_menu( menus, )] }), + window_id: window::Id::RESERVED, + on_surface_action: None, }; if let Some(ref mut context_menu) = this.context_menu { @@ -44,6 +48,156 @@ pub struct ContextMenu<'a, Message> { content: crate::Element<'a, Message>, #[setters(skip)] context_menu: Option>>, + pub window_id: window::Id, + #[setters(skip)] + pub(crate) on_surface_action: + Option Message + Send + Sync + 'static>>, +} + +impl ContextMenu<'_, Message> { + #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + #[allow(clippy::too_many_lines)] + fn create_popup( + &mut self, + layout: iced_core::Layout<'_>, + view_cursor: iced_core::mouse::Cursor, + renderer: &crate::Renderer, + shell: &mut iced_core::Shell<'_, Message>, + viewport: &iced::Rectangle, + my_state: &mut LocalState, + ) { + if self.window_id != window::Id::NONE && self.on_surface_action.is_some() { + use crate::{surface::action::destroy_popup, widget::menu::Menu}; + use iced_runtime::platform_specific::wayland::popup::{ + SctkPopupSettings, SctkPositioner, + }; + + let mut bounds = layout.bounds(); + bounds.x = my_state.context_cursor.x; + bounds.y = my_state.context_cursor.y; + + let (id, root_list) = my_state.menu_bar_state.inner.with_data_mut(|state| { + if let Some(id) = state.popup_id.get(&self.window_id).copied() { + // close existing popups + state.menu_states.clear(); + state.active_root.clear(); + shell.publish(self.on_surface_action.as_ref().unwrap()(destroy_popup(id))); + state.view_cursor = view_cursor; + ( + id, + layout.children().map(|lo| lo.bounds()).collect::>(), + ) + } else { + ( + window::Id::unique(), + layout.children().map(|lo| lo.bounds()).collect(), + ) + } + }); + let Some(context_menu) = self.context_menu.as_mut() else { + return; + }; + + let mut popup_menu: Menu<'static, _> = Menu { + tree: my_state.menu_bar_state.clone(), + menu_roots: std::borrow::Cow::Owned(context_menu.clone()), + bounds_expand: 16, + menu_overlays_parent: true, + close_condition: CloseCondition { + leave: false, + click_outside: true, + click_inside: true, + }, + item_width: ItemWidth::Uniform(240), + item_height: ItemHeight::Dynamic(40), + bar_bounds: bounds, + main_offset: -(bounds.height as i32), + cross_offset: 0, + root_bounds_list: vec![bounds], + path_highlight: Some(PathHighlight::MenuActive), + style: std::borrow::Cow::Owned(crate::theme::menu_bar::MenuBarStyle::Default), + position: Point::new(0., 0.), + is_overlay: false, + window_id: id, + depth: 0, + on_surface_action: self.on_surface_action.clone(), + }; + + init_root_menu( + &mut popup_menu, + renderer, + shell, + view_cursor.position().unwrap(), + viewport.size(), + Vector::new(0., 0.), + layout.bounds(), + -bounds.height, + ); + let (anchor_rect, gravity) = my_state.menu_bar_state.inner.with_data_mut(|state| { + use iced::Rectangle; + + state.popup_id.insert(self.window_id, id); + ({ + let pos = view_cursor.position().unwrap_or_default(); + Rectangle { + x: pos.x as i32, + y: pos.y as i32, + width: 1, + height: 1, + } + }, + match (state.horizontal_direction, state.vertical_direction) { + (Direction::Positive, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight, + (Direction::Positive, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight, + (Direction::Negative, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft, + (Direction::Negative, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft, + }) + }); + + let menu_node = + popup_menu.layout(renderer, iced::Limits::NONE.min_width(1.).min_height(1.)); + let popup_size = menu_node.size(); + let positioner = SctkPositioner { + size: Some(( + popup_size.width.ceil() as u32 + 2, + popup_size.height.ceil() as u32 + 2, + )), + anchor_rect, + anchor: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::None, + gravity: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight, + reactive: true, + ..Default::default() + }; + let parent = self.window_id; + shell.publish((self.on_surface_action.as_ref().unwrap())( + crate::surface::action::simple_popup( + move || SctkPopupSettings { + parent, + id, + positioner: positioner.clone(), + parent_size: None, + grab: true, + close_with_children: false, + input_zone: None, + }, + Some(move || { + crate::Element::from( + crate::widget::container(popup_menu.clone()).center(Length::Fill), + ) + .map(crate::action::app) + }), + ), + )); + } + } + + pub fn on_surface_action( + mut self, + handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static, + ) -> Self { + self.on_surface_action = Some(Arc::new(handler)); + self + } } impl Widget @@ -155,6 +309,7 @@ impl Widget .operate(&mut tree.children[0], layout, renderer, operation); } + #[allow(clippy::too_many_lines)] fn on_event( &mut self, tree: &mut Tree, @@ -169,6 +324,25 @@ impl Widget let state = tree.state.downcast_mut::(); let bounds = layout.bounds(); + // XXX this should reset the state if there are no other copies of the state, which implies no dropdown menus open. + let reset = self.window_id != window::Id::NONE + && state + .menu_bar_state + .inner + .with_data(|d| !d.open && !d.active_root.is_empty()); + + let open = state.menu_bar_state.inner.with_data_mut(|state| { + if reset { + if let Some(popup_id) = state.popup_id.get(&self.window_id).copied() { + if let Some(handler) = self.on_surface_action.as_ref() { + shell.publish((handler)(crate::surface::Action::DestroyPopup(popup_id))); + state.reset(); + } + } + } + state.open + }); + if cursor.is_over(bounds) { let fingers_pressed = state.fingers_pressed.len(); @@ -181,6 +355,29 @@ impl Widget state.fingers_pressed.remove(&id); } + Event::Window(window::Event::Focused) => { + #[cfg(all( + feature = "wayland", + feature = "winit", + feature = "surface-message" + ))] + state.menu_bar_state.inner.with_data_mut(|state| { + if let Some(id) = state.popup_id.remove(&self.window_id) { + state.menu_states.clear(); + state.active_root.clear(); + state.open = false; + + { + let surface_action = self.on_surface_action.as_ref().unwrap(); + shell.publish(surface_action( + crate::surface::action::destroy_popup(id), + )); + } + state.view_cursor = cursor; + } + }); + } + _ => (), } @@ -190,13 +387,64 @@ impl Widget { state.context_cursor = cursor.position().unwrap_or_default(); let state = tree.state.downcast_mut::(); - state.menu_bar_state.inner.with_data_mut(|state| { state.open = true; state.view_cursor = cursor; }); + #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + self.create_popup(layout, cursor, renderer, shell, viewport, state); return event::Status::Captured; + } else if right_button_released(&event) + || (touch_lifted(&event)) + || left_button_released(&event) + { + #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + state.menu_bar_state.inner.with_data_mut(|state| { + if let Some(id) = state.popup_id.remove(&self.window_id) { + state.menu_states.clear(); + state.active_root.clear(); + state.open = false; + + { + let surface_action = self.on_surface_action.as_ref().unwrap(); + + shell + .publish(surface_action(crate::surface::action::destroy_popup(id))); + } + state.view_cursor = cursor; + } + }); + } + } else if open { + match event { + Event::Mouse(mouse::Event::ButtonReleased( + mouse::Button::Right | mouse::Button::Left, + )) + | Event::Touch(touch::Event::FingerLifted { .. }) => { + #[cfg(all( + feature = "wayland", + feature = "winit", + feature = "surface-message" + ))] + state.menu_bar_state.inner.with_data_mut(|state| { + if let Some(id) = state.popup_id.remove(&self.window_id) { + state.menu_states.clear(); + state.active_root.clear(); + state.open = false; + + { + let surface_action = self.on_surface_action.as_ref().unwrap(); + + shell.publish(surface_action( + crate::surface::action::destroy_popup(id), + )); + } + state.view_cursor = cursor; + } + }); + } + _ => (), } } @@ -219,6 +467,11 @@ impl Widget _renderer: &crate::Renderer, translation: Vector, ) -> Option> { + #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + if self.window_id != window::Id::NONE && self.on_surface_action.is_some() { + return None; + } + let state = tree.state.downcast_ref::(); let context_menu = self.context_menu.as_mut()?; @@ -287,6 +540,13 @@ fn right_button_released(event: &Event) -> bool { ) } +fn left_button_released(event: &Event) -> bool { + matches!( + event, + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left,)) + ) +} + fn touch_lifted(event: &Event) -> bool { matches!(event, Event::Touch(touch::Event::FingerLifted { .. })) } diff --git a/src/widget/menu.rs b/src/widget/menu.rs index 2b54bf6e..9d4ce4b1 100644 --- a/src/widget/menu.rs +++ b/src/widget/menu.rs @@ -74,5 +74,5 @@ pub use menu_tree::{ pub use crate::style::menu_bar::{Appearance, StyleSheet}; pub(crate) use menu_bar::{menu_roots_children, menu_roots_diff}; -pub(crate) use menu_inner::Menu; pub use menu_inner::{CloseCondition, ItemHeight, ItemWidth, PathHighlight}; +pub(crate) use menu_inner::{Direction, Menu, init_root_menu}; diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 2c355bf4..66a4b9b9 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -67,7 +67,7 @@ impl MenuBarStateInner { .map(|ms| ms.index.expect("No indices were found in the menu state.")) } - pub(super) fn reset(&mut self) { + pub(crate) fn reset(&mut self) { self.open = false; self.active_root = Vec::new(); self.menu_states.clear(); diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index c41cded2..595632ad 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -297,7 +297,7 @@ impl MenuBounds { #[derive(Clone)] pub(crate) struct MenuState { /// The index of the active menu item - pub(super) index: Option, + pub(crate) index: Option, scroll_offset: f32, pub menu_bounds: MenuBounds, } @@ -1083,7 +1083,7 @@ fn pad_rectangle(rect: Rectangle, padding: Padding) -> Rectangle { } #[allow(clippy::too_many_arguments)] -pub(super) fn init_root_menu( +pub(crate) fn init_root_menu( menu: &mut Menu<'_, Message>, renderer: &crate::Renderer, shell: &mut Shell<'_, Message>, @@ -1102,7 +1102,6 @@ pub(super) fn init_root_menu( return; } - let mut set = false; for (i, (&root_bounds, mt)) in menu .root_bounds_list .iter() @@ -1117,7 +1116,7 @@ pub(super) fn init_root_menu( let view_center = viewport_size.width * 0.5; let rb_center = root_bounds.center_x(); - state.horizontal_direction = if rb_center > view_center { + state.horizontal_direction = if menu.is_overlay && rb_center > view_center { Direction::Negative } else { Direction::Positive @@ -1146,7 +1145,6 @@ pub(super) fn init_root_menu( &mut state.tree.children[0].children, menu.is_overlay, ); - set = true; state.active_root.push(i); let ms = MenuState { index: None, @@ -1186,7 +1184,6 @@ pub(super) fn init_root_popup_menu( let active_roots = &state.active_root[..=menu.depth]; - let mut set = false; let mt = active_roots .iter() .skip(1) @@ -1230,7 +1227,6 @@ pub(super) fn init_root_popup_menu( } else { Direction::Positive }; - set = true; let ms = MenuState { index: None, From 00ba16fe01e6d22170d69572505eb282ff0bdec3 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 12 Jun 2025 11:40:50 -0400 Subject: [PATCH 215/556] refactor(menu): fallback behavior for non wayland windowing system --- Cargo.toml | 1 + iced | 2 +- src/app/action.rs | 4 + src/app/cosmic.rs | 75 ++++++++++- src/widget/context_menu.rs | 98 ++++++++------- src/widget/menu/menu_bar.rs | 17 ++- src/widget/menu/menu_inner.rs | 231 +++++++++++++++++----------------- 7 files changed, 268 insertions(+), 160 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a90b0e73..132aac8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,6 +120,7 @@ libc = { version = "0.2.171", optional = true } license = { version = "3.6.0", optional = true } mime = { version = "0.3.17", optional = true } palette = "0.7.6" +raw-window-handle = "0.6" rfd = { version = "0.15.3", default-features = false, features = [ "xdg-portal", ], optional = true } diff --git a/iced b/iced index fc9d49eb..2d1511d0 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit fc9d49eb2f830fe394252ff6799d59ad828243bc +Subproject commit 2d1511d0cf0296e6f0cfcfcd13f2a1aa334c6915 diff --git a/src/app/action.rs b/src/app/action.rs index 44655ffa..0f05a6a6 100644 --- a/src/app/action.rs +++ b/src/app/action.rs @@ -36,6 +36,8 @@ pub enum Action { NavBar(nav_bar::Id), /// Activates a context menu for an item from the nav bar. NavBarContext(nav_bar::Id), + /// A new window was opened. + Opened(iced::window::Id), /// Set scaling factor ScaleFactor(f32), /// Show the window menu @@ -60,6 +62,8 @@ pub enum Action { ToolkitConfig(CosmicTk), /// Window focus lost Unfocus(iced::window::Id), + /// Windowing system initialized + WindowingSystemInitialized, /// Updates the window maximized state WindowMaximized(iced::window::Id, bool), /// Updates the tracked window geometry. diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 055ff947..f2de0f5e 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -19,6 +19,65 @@ use iced::{Task, window}; use iced_futures::event::listen_with; use palette::color_difference::EuclideanDistance; +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[non_exhaustive] +pub enum WindowingSystem { + UiKit, + AppKit, + Orbital, + OhosNdk, + Xlib, + Xcb, + Wayland, + Drm, + Gbm, + Win32, + WinRt, + Web, + WebCanvas, + WebOffscreenCanvas, + AndroidNdk, + Haiku, +} + +pub(crate) static WINDOWING_SYSTEM: std::sync::OnceLock = + std::sync::OnceLock::new(); + +pub fn windowing_system() -> Option { + WINDOWING_SYSTEM.get().copied() +} + +fn init_windowing_system(handle: raw_window_handle::WindowHandle) -> crate::Action { + let raw: &raw_window_handle::RawWindowHandle = handle.as_ref(); + let system = match raw { + window::raw_window_handle::RawWindowHandle::UiKit(_) => WindowingSystem::UiKit, + window::raw_window_handle::RawWindowHandle::AppKit(_) => WindowingSystem::AppKit, + window::raw_window_handle::RawWindowHandle::Orbital(_) => WindowingSystem::Orbital, + window::raw_window_handle::RawWindowHandle::OhosNdk(_) => WindowingSystem::OhosNdk, + window::raw_window_handle::RawWindowHandle::Xlib(_) => WindowingSystem::Xlib, + window::raw_window_handle::RawWindowHandle::Xcb(_) => WindowingSystem::Xcb, + window::raw_window_handle::RawWindowHandle::Wayland(_) => WindowingSystem::Wayland, + window::raw_window_handle::RawWindowHandle::Web(_) => WindowingSystem::Web, + window::raw_window_handle::RawWindowHandle::WebCanvas(_) => WindowingSystem::WebCanvas, + window::raw_window_handle::RawWindowHandle::WebOffscreenCanvas(_) => { + WindowingSystem::WebOffscreenCanvas + } + window::raw_window_handle::RawWindowHandle::AndroidNdk(_) => WindowingSystem::AndroidNdk, + window::raw_window_handle::RawWindowHandle::Haiku(_) => WindowingSystem::Haiku, + window::raw_window_handle::RawWindowHandle::Drm(_) => WindowingSystem::Drm, + window::raw_window_handle::RawWindowHandle::Gbm(_) => WindowingSystem::Gbm, + window::raw_window_handle::RawWindowHandle::Win32(_) => WindowingSystem::Win32, + window::raw_window_handle::RawWindowHandle::WinRt(_) => WindowingSystem::WinRt, + _ => { + tracing::warn!("Unknown windowing system: {raw:?}"); + return crate::Action::Cosmic(Action::WindowingSystemInitialized); + } + }; + + _ = WINDOWING_SYSTEM.set(system); + crate::Action::Cosmic(Action::WindowingSystemInitialized) +} + #[derive(Default)] pub struct Cosmic { pub app: App, @@ -41,10 +100,17 @@ where use iced_futures::futures::executor::block_on; core.settings_daemon = block_on(cosmic_config::dbus::settings_daemon_proxy()).ok(); } + let id = core.main_window_id().unwrap_or(window::Id::RESERVED); let (model, command) = T::init(core, flags); - (Self::new(model), command) + ( + Self::new(model), + Task::batch(vec![ + command, + iced_runtime::window::run_with_handle(id, init_windowing_system), + ]), + ) } #[cfg(not(feature = "multi-window"))] @@ -57,6 +123,7 @@ where self.app.title(id).to_string() } + #[allow(clippy::too_many_lines)] pub fn surface_update( &mut self, _surface_message: crate::surface::Action, @@ -255,6 +322,9 @@ where iced::Event::Window(window::Event::Resized(iced::Size { width, height })) => { return Some(Action::WindowResize(id, width, height)); } + iced::Event::Window(window::Event::Opened { .. }) => { + return Some(Action::Opened(id)); + } iced::Event::Window(window::Event::Closed) => { return Some(Action::SurfaceClosed(id)); } @@ -786,6 +856,9 @@ impl Cosmic { let core = self.app.core_mut(); core.applet.suggested_bounds = b; } + Action::Opened(id) => { + return iced_runtime::window::run_with_handle(id, init_windowing_system); + } _ => {} } diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index a00ae751..b1014cf4 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -3,6 +3,8 @@ //! A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation. +#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] +use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; use crate::widget::menu::{ self, CloseCondition, Direction, ItemHeight, ItemWidth, MenuBarState, PathHighlight, init_root_menu, menu_roots_diff, @@ -361,21 +363,23 @@ impl Widget feature = "winit", feature = "surface-message" ))] - state.menu_bar_state.inner.with_data_mut(|state| { - if let Some(id) = state.popup_id.remove(&self.window_id) { - state.menu_states.clear(); - state.active_root.clear(); - state.open = false; + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { + state.menu_bar_state.inner.with_data_mut(|state| { + if let Some(id) = state.popup_id.remove(&self.window_id) { + state.menu_states.clear(); + state.active_root.clear(); + state.open = false; - { - let surface_action = self.on_surface_action.as_ref().unwrap(); - shell.publish(surface_action( - crate::surface::action::destroy_popup(id), - )); + { + let surface_action = self.on_surface_action.as_ref().unwrap(); + shell.publish(surface_action( + crate::surface::action::destroy_popup(id), + )); + } + state.view_cursor = cursor; } - state.view_cursor = cursor; - } - }); + }); + } } _ => (), @@ -392,7 +396,9 @@ impl Widget state.view_cursor = cursor; }); #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] - self.create_popup(layout, cursor, renderer, shell, viewport, state); + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { + self.create_popup(layout, cursor, renderer, shell, viewport, state); + } return event::Status::Captured; } else if right_button_released(&event) @@ -400,33 +406,7 @@ impl Widget || left_button_released(&event) { #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] - state.menu_bar_state.inner.with_data_mut(|state| { - if let Some(id) = state.popup_id.remove(&self.window_id) { - state.menu_states.clear(); - state.active_root.clear(); - state.open = false; - - { - let surface_action = self.on_surface_action.as_ref().unwrap(); - - shell - .publish(surface_action(crate::surface::action::destroy_popup(id))); - } - state.view_cursor = cursor; - } - }); - } - } else if open { - match event { - Event::Mouse(mouse::Event::ButtonReleased( - mouse::Button::Right | mouse::Button::Left, - )) - | Event::Touch(touch::Event::FingerLifted { .. }) => { - #[cfg(all( - feature = "wayland", - feature = "winit", - feature = "surface-message" - ))] + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { state.menu_bar_state.inner.with_data_mut(|state| { if let Some(id) = state.popup_id.remove(&self.window_id) { state.menu_states.clear(); @@ -444,6 +424,37 @@ impl Widget } }); } + } + } else if open { + match event { + Event::Mouse(mouse::Event::ButtonReleased( + mouse::Button::Right | mouse::Button::Left, + )) + | Event::Touch(touch::Event::FingerLifted { .. }) => { + #[cfg(all( + feature = "wayland", + feature = "winit", + feature = "surface-message" + ))] + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { + state.menu_bar_state.inner.with_data_mut(|state| { + if let Some(id) = state.popup_id.remove(&self.window_id) { + state.menu_states.clear(); + state.active_root.clear(); + state.open = false; + + { + let surface_action = self.on_surface_action.as_ref().unwrap(); + + shell.publish(surface_action( + crate::surface::action::destroy_popup(id), + )); + } + state.view_cursor = cursor; + } + }); + } + } _ => (), } } @@ -468,7 +479,10 @@ impl Widget translation: Vector, ) -> Option> { #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] - if self.window_id != window::Id::NONE && self.on_surface_action.is_some() { + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) + && self.window_id != window::Id::NONE + && self.on_surface_action.is_some() + { return None; } diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 66a4b9b9..707aebdc 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -9,6 +9,8 @@ use super::{ }, menu_tree::MenuTree, }; +#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] +use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; use crate::{ Renderer, style::menu_bar::StyleSheet, @@ -629,13 +631,17 @@ where return event::Status::Ignored; } #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] - self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state); + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { + self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state); + } } Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered) if open && view_cursor.is_over(layout.bounds()) => { #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] - self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state); + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { + self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state); + } } _ => (), } @@ -710,7 +716,12 @@ where translation: Vector, ) -> Option> { #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] - return None; + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) + && self.on_surface_action.is_some() + && self.window_id != window::Id::NONE + { + return None; + } let state = tree.state.downcast_ref::(); if state.inner.with_data(|state| !state.open) { diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 595632ad..ecf5f8a7 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -4,6 +4,8 @@ use std::{borrow::Cow, sync::Arc}; use super::{menu_bar::MenuBarState, menu_tree::MenuTree}; +#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] +use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; use crate::style::menu_bar::StyleSheet; use iced::window; @@ -665,21 +667,24 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { feature = "winit", feature = "surface-message" ))] - if let Some(handler) = self.on_surface_action.as_ref() { - let mut root = self.window_id; - let mut depth = self.depth; - while let Some(parent) = - state.popup_id.iter().find(|(_, v)| **v == root) - { - // parent of root popup is the window, so we stop. - if depth == 0 { - break; + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { + if let Some(handler) = self.on_surface_action.as_ref() { + let mut root = self.window_id; + let mut depth = self.depth; + while let Some(parent) = + state.popup_id.iter().find(|(_, v)| **v == root) + { + // parent of root popup is the window, so we stop. + if depth == 0 { + break; + } + root = *parent.0; + depth = depth.saturating_sub(1); } - root = *parent.0; - depth = depth.saturating_sub(1); + shell.publish((handler)(crate::surface::Action::DestroyPopup( + root, + ))); } - shell - .publish((handler)(crate::surface::Action::DestroyPopup(root))); } state.reset(); @@ -922,72 +927,73 @@ impl Widget Widget Widget Date: Thu, 12 Jun 2025 13:25:29 -0400 Subject: [PATCH 216/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 2d1511d0..9312d3c2 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 2d1511d0cf0296e6f0cfcfcd13f2a1aa334c6915 +Subproject commit 9312d3c29b6308d714725a20342375a6145359d9 From 7d7274b8010da57c7e2d81e90614e8df473ff19e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 17 Jun 2025 23:52:28 -0400 Subject: [PATCH 217/556] fix(header-bar): allocate space that accounts for window controls --- src/widget/header_bar.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index fdc9d962..6f7c35fd 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -307,6 +307,9 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { let center = std::mem::take(&mut self.center); let mut end = std::mem::take(&mut self.end); + let window_control_cnt = self.on_close.is_some() as usize + + self.on_maximize.is_some() as usize + + self.on_minimize.is_some() as usize; // Also packs the window controls at the very end. end.push(self.window_controls()); @@ -327,8 +330,9 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { } } }; - let portion = ((start.len().max(end.len()) as f32 / center.len().max(1) as f32).round() - as u16) + let portion = ((start.len().max(end.len() + window_control_cnt) as f32 + / center.len().max(1) as f32) + .round() as u16) .max(1); // Creates the headerbar widget. let mut widget = widget::row::with_capacity(3) From 90ed634b0680f546bab7e8d04f0989d718d721fa Mon Sep 17 00:00:00 2001 From: Adrian Geipert Date: Wed, 18 Jun 2025 09:11:22 +0200 Subject: [PATCH 218/556] chore: update syn to v2 --- cosmic-config-derive/Cargo.toml | 4 ++-- cosmic-config-derive/src/lib.rs | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/cosmic-config-derive/Cargo.toml b/cosmic-config-derive/Cargo.toml index 46d79658..55eeb871 100644 --- a/cosmic-config-derive/Cargo.toml +++ b/cosmic-config-derive/Cargo.toml @@ -8,5 +8,5 @@ edition = "2021" proc-macro = true [dependencies] -syn = "1.0" -quote = "1.0" \ No newline at end of file +syn = "2.0" +quote = "1.0" diff --git a/cosmic-config-derive/src/lib.rs b/cosmic-config-derive/src/lib.rs index e1ea70fe..668154cd 100644 --- a/cosmic-config-derive/src/lib.rs +++ b/cosmic-config-derive/src/lib.rs @@ -17,12 +17,16 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream { let version = attributes .iter() .find_map(|attr| { - if attr.path.is_ident("version") { - match attr.parse_meta() { - Ok(syn::Meta::NameValue(syn::MetaNameValue { - lit: syn::Lit::Int(lit_int), + if attr.path().is_ident("version") { + match attr.meta { + syn::Meta::NameValue(syn::MetaNameValue { + value: + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Int(ref lit_int), + .. + }), .. - })) => Some(lit_int.base10_parse::().unwrap()), + }) => Some(lit_int.base10_parse::().unwrap()), _ => None, } } else { From bf9fc4c29f903e41928d1118a87cdc3636617ef2 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 18 Jun 2025 15:27:04 -0400 Subject: [PATCH 219/556] fix(menu): disable slide_x for repositioned nested popups --- src/widget/menu/menu_inner.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index ecf5f8a7..6a3eb093 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -1030,18 +1030,20 @@ impl Widget Date: Wed, 18 Jun 2025 15:50:31 -0400 Subject: [PATCH 220/556] improv: use full root menu width when using wayland popups --- src/widget/responsive_menu_bar.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/widget/responsive_menu_bar.rs b/src/widget/responsive_menu_bar.rs index 3d9557d0..3c9151e7 100644 --- a/src/widget/responsive_menu_bar.rs +++ b/src/widget/responsive_menu_bar.rs @@ -24,7 +24,21 @@ pub struct ResponsiveMenuBar { impl Default for ResponsiveMenuBar { fn default() -> ResponsiveMenuBar { ResponsiveMenuBar { - collapsed_item_width: ItemWidth::Static(84), + collapsed_item_width: { + #[cfg(all(feature = "winit", feature = "wayland"))] + if matches!( + crate::app::cosmic::WINDOWING_SYSTEM.get(), + Some(crate::app::cosmic::WindowingSystem::Wayland) + ) { + ItemWidth::Static(150) + } else { + ItemWidth::Static(84) + } + #[cfg(not(all(feature = "winit", feature = "wayland")))] + { + ItemWidth::Static(84) + } + }, item_width: ItemWidth::Uniform(150), item_height: ItemHeight::Uniform(30), spacing: 0., From 6be54038528ab2fc3f7d92b634d329a6424c7428 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 18 Jun 2025 17:06:36 -0400 Subject: [PATCH 221/556] improv(header): remove title if condensed and better handle large fixed size elements --- src/app/mod.rs | 3 ++- src/widget/header_bar.rs | 40 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index eec9741c..890a3429 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -703,7 +703,8 @@ impl ApplicationExt for App { .title(&core.window.header_title) .on_drag(crate::Action::Cosmic(Action::Drag)) .on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu)) - .on_double_click(crate::Action::Cosmic(Action::Maximize)); + .on_double_click(crate::Action::Cosmic(Action::Maximize)) + .is_condensed(is_condensed); if self.nav_model().is_some() { let toggle = crate::widget::nav_bar_toggle() diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 6f7c35fd..3603cc68 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -26,6 +26,7 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { maximized: false, is_ssd: false, on_double_click: None, + is_condensed: false, } } @@ -84,6 +85,9 @@ pub struct HeaderBar<'a, Message> { /// HeaderBar used for server-side decorations is_ssd: bool, + + /// Whether the headerbar should be compact + is_condensed: bool, } impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { @@ -294,6 +298,7 @@ impl Widget } impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { + #[allow(clippy::too_many_lines)] /// Converts the headerbar builder into an Iced element. pub fn view(mut self) -> Element<'a, Message> { let Spacing { @@ -330,10 +335,37 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { } } }; - let portion = ((start.len().max(end.len() + window_control_cnt) as f32 + + let acc_count = |v: &[Element<'a, Message>]| { + v.iter().fold(0, |acc, e| { + acc + match e.as_widget().size().width { + Length::Fixed(w) if w > 30. => (w / 30.0).ceil() as usize, + _ => 1, + } + }) + }; + + let left_len = acc_count(&start); + let right_len = acc_count(&end); + + let portion = ((left_len.max(right_len + window_control_cnt) as f32 / center.len().max(1) as f32) .round() as u16) .max(1); + let (left_portion, right_portion) = + if center.is_empty() && (self.title.is_empty() || self.is_condensed) { + let left_to_right_ratio = left_len as f32 / right_len.max(1) as f32; + let right_to_left_ratio = right_len as f32 / left_len.max(1) as f32; + if right_to_left_ratio > 2. { + (1, 2) + } else if left_to_right_ratio > 2. { + (2, 1) + } else { + (left_len as u16, (right_len + window_control_cnt) as u16) + } + } else { + (portion, portion) + }; // Creates the headerbar widget. let mut widget = widget::row::with_capacity(3) // If elements exist in the start region, append them here. @@ -343,7 +375,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .align_y(iced::Alignment::Center) .apply(widget::container) .align_x(iced::Alignment::Start) - .width(Length::FillPortion(portion)), + .width(Length::FillPortion(left_portion)), ) // If elements exist in the center region, use them here. // This will otherwise use the title as a widget if a title was defined. @@ -356,7 +388,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .center_x(Length::Fill) .into(), ) - } else if !self.title.is_empty() { + } else if !self.title.is_empty() && !self.is_condensed { Some(self.title_widget()) } else { None @@ -367,7 +399,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .align_y(iced::Alignment::Center) .apply(widget::container) .align_x(iced::Alignment::End) - .width(Length::FillPortion(portion)), + .width(Length::FillPortion(right_portion)), ) .align_y(iced::Alignment::Center) .height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32)) From 5be9611c8a80825b478023743dde16912a21948b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 19 Jun 2025 09:36:07 -0400 Subject: [PATCH 222/556] fix(segmented-button): context menu state management --- src/widget/segmented_button/widget.rs | 30 +++++++-------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 07e6cbc1..1c92d0b2 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -647,16 +647,9 @@ where // Diff the context menu if let Some(context_menu) = &mut self.context_menu { - if tree.children.is_empty() { - let mut child_tree = Tree::empty(); - child_tree.state = tree::State::new(MenuBarState::default()); - tree.children.push(child_tree); - } else { - tree.children.truncate(1); - } - menu_roots_diff(context_menu, &mut tree.children[0]); - } else { - tree.children.clear(); + state.menu_state.inner.with_data_mut(|inner| { + menu_roots_diff(context_menu, &mut inner.tree); + }); } } @@ -954,9 +947,7 @@ where state.context_cursor = cursor_position.position().unwrap_or_default(); - let menu_state = - tree.children[0].state.downcast_mut::(); - menu_state.inner.with_data_mut(|data| { + state.menu_state.inner.with_data_mut(|data| { data.open = true; data.view_cursor = cursor_position; }); @@ -1593,22 +1584,17 @@ where return None; }; - if !tree.children[0] - .state - .downcast_ref::() - .inner - .with_data(|data| data.open) - { + if !menu_state.inner.with_data(|data| data.open) { + // If the menu is not open, we don't need to show it. return None; } - bounds.x = state.context_cursor.x; bounds.y = state.context_cursor.y; Some( crate::widget::menu::Menu { tree: menu_state, - menu_roots: std::borrow::Cow::Borrowed(context_menu), + menu_roots: std::borrow::Cow::Owned(context_menu.clone()), bounds_expand: 16, menu_overlays_parent: true, close_condition: CloseCondition { @@ -1619,7 +1605,7 @@ where item_width: ItemWidth::Uniform(240), item_height: ItemHeight::Dynamic(40), bar_bounds: bounds, - main_offset: -(bounds.height as i32), + main_offset: -bounds.height as i32, cross_offset: 0, root_bounds_list: vec![bounds], path_highlight: Some(PathHighlight::MenuActive), From 90ad3e9e1b763aae2790ffc4503dfa28973b86bb Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 23 Jun 2025 17:13:58 +0200 Subject: [PATCH 223/556] improv(cosmic-config): use notifier debouncer on inotify watchers --- cosmic-config/Cargo.toml | 1 + cosmic-config/src/lib.rs | 20 +++++++++++--------- cosmic-config/src/subscription.rs | 3 ++- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/cosmic-config/Cargo.toml b/cosmic-config/Cargo.toml index a79237c8..424ea262 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -26,6 +26,7 @@ dirs.workspace = true tokio = { version = "1.44", optional = true, features = ["time"] } async-std = { version = "1.13", optional = true } tracing = "0.1" +notify-debouncer-full = "0.5.0" [target.'cfg(unix)'.dependencies] xdg = "2.5" diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index beab95fb..1f0c8846 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -1,15 +1,15 @@ //! Integrations for cosmic-config — the cosmic configuration system. use notify::{ - event::{EventKind, ModifyKind}, - Watcher, + event::{EventKind, ModifyKind}, RecommendedWatcher, Watcher }; +use notify_debouncer_full::{DebouncedEvent, Debouncer, RecommendedCache}; use serde::{de::DeserializeOwned, Serialize}; use std::{ fmt, fs, io::Write, path::{Path, PathBuf}, - sync::Mutex, + sync::Mutex, time::Duration, }; #[cfg(feature = "subscription")] @@ -244,7 +244,7 @@ impl Config { // This may end up being an mpsc channel instead of a function // See EventHandler in the notify crate: https://docs.rs/notify/latest/notify/trait.EventHandler.html // Having a callback allows for any application abstraction to be used - pub fn watch(&self, f: F) -> Result + pub fn watch(&self, f: F) -> Result, Error> // Argument is an array of all keys that changed in that specific transaction //TODO: simplify F requirements where @@ -256,10 +256,11 @@ impl Config { }; let user_path_clone = user_path.clone(); let mut watcher = - notify::recommended_watcher(move |event_res: Result| { - match &event_res { - Ok(event) => { - match &event.kind { + notify_debouncer_full::new_debouncer(Duration::from_secs(1), None, move |event_res: Result, Vec>| { + match event_res { + Ok(events) => { + for event in events { + match &event.event.kind { EventKind::Access(_) | EventKind::Modify(ModifyKind::Metadata(_)) => { // Data not mutated return; @@ -287,8 +288,9 @@ impl Config { if !keys.is_empty() { f(&watch_config, &keys); } + } } - Err(_err) => { + Err(_errs) => { //TODO: handle errors } } diff --git a/cosmic-config/src/subscription.rs b/cosmic-config/src/subscription.rs index 64255954..591b85ea 100644 --- a/cosmic-config/src/subscription.rs +++ b/cosmic-config/src/subscription.rs @@ -1,13 +1,14 @@ use iced_futures::futures::{SinkExt, Stream}; use iced_futures::{futures::channel::mpsc, stream}; use notify::RecommendedWatcher; +use notify_debouncer_full::{Debouncer, RecommendedCache}; use std::{borrow::Cow, hash::Hash}; use crate::{Config, CosmicConfigEntry}; pub enum ConfigState { Init(Cow<'static, str>, u64, bool), - Waiting(T, RecommendedWatcher, mpsc::Receiver>, Config), + Waiting(T, Debouncer, mpsc::Receiver>, Config), Failed, } From 1af2f4ffe57b402000c5982e3008395191e5cadd Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 23 Jun 2025 17:50:28 +0200 Subject: [PATCH 224/556] chore: format --- cosmic-config/src/lib.rs | 58 +++++++++++++++++-------------- cosmic-config/src/subscription.rs | 7 +++- src/theme/mod.rs | 52 +++++++++++++-------------- 3 files changed, 64 insertions(+), 53 deletions(-) diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 1f0c8846..fecfdb75 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -1,7 +1,8 @@ //! Integrations for cosmic-config — the cosmic configuration system. use notify::{ - event::{EventKind, ModifyKind}, RecommendedWatcher, Watcher + event::{EventKind, ModifyKind}, + RecommendedWatcher, Watcher, }; use notify_debouncer_full::{DebouncedEvent, Debouncer, RecommendedCache}; use serde::{de::DeserializeOwned, Serialize}; @@ -9,7 +10,8 @@ use std::{ fmt, fs, io::Write, path::{Path, PathBuf}, - sync::Mutex, time::Duration, + sync::Mutex, + time::Duration, }; #[cfg(feature = "subscription")] @@ -255,46 +257,50 @@ impl Config { return Err(Error::NoConfigDirectory); }; let user_path_clone = user_path.clone(); - let mut watcher = - notify_debouncer_full::new_debouncer(Duration::from_secs(1), None, move |event_res: Result, Vec>| { + let mut watcher = notify_debouncer_full::new_debouncer( + Duration::from_secs(1), + None, + move |event_res: Result, Vec>| { match event_res { Ok(events) => { for event in events { match &event.event.kind { - EventKind::Access(_) | EventKind::Modify(ModifyKind::Metadata(_)) => { - // Data not mutated - return; + EventKind::Access(_) + | EventKind::Modify(ModifyKind::Metadata(_)) => { + // Data not mutated + return; + } + _ => {} } - _ => {} - } - let mut keys = Vec::new(); - for path in &event.paths { - match path.strip_prefix(&user_path_clone) { - Ok(key_path) => { - if let Some(key) = key_path.to_str() { - // Skip any .atomicwrite temporary files - if key.starts_with(".atomicwrite") { - continue; + let mut keys = Vec::new(); + for path in &event.paths { + match path.strip_prefix(&user_path_clone) { + Ok(key_path) => { + if let Some(key) = key_path.to_str() { + // Skip any .atomicwrite temporary files + if key.starts_with(".atomicwrite") { + continue; + } + keys.push(key.to_string()); } - keys.push(key.to_string()); + } + Err(_err) => { + //TODO: handle errors } } - Err(_err) => { - //TODO: handle errors - } } - } - if !keys.is_empty() { - f(&watch_config, &keys); - } + if !keys.is_empty() { + f(&watch_config, &keys); + } } } Err(_errs) => { //TODO: handle errors } } - })?; + }, + )?; watcher.watch(user_path, notify::RecursiveMode::NonRecursive)?; Ok(watcher) } diff --git a/cosmic-config/src/subscription.rs b/cosmic-config/src/subscription.rs index 591b85ea..2d2c5117 100644 --- a/cosmic-config/src/subscription.rs +++ b/cosmic-config/src/subscription.rs @@ -8,7 +8,12 @@ use crate::{Config, CosmicConfigEntry}; pub enum ConfigState { Init(Cow<'static, str>, u64, bool), - Waiting(T, Debouncer, mpsc::Receiver>, Config), + Waiting( + T, + Debouncer, + mpsc::Receiver>, + Config, + ), Failed, } diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 7fbb5bad..5e335f59 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -85,33 +85,33 @@ pub fn is_high_contrast() -> bool { active_type().is_high_contrast() } -/// Watches for changes to the system's theme preference. -#[cold] -pub fn subscription(is_dark: bool) -> Subscription { - config_subscription::<_, crate::cosmic_theme::Theme>( - ( - std::any::TypeId::of::(), - is_dark, - ), - if is_dark { - cosmic_theme::DARK_THEME_ID - } else { - cosmic_theme::LIGHT_THEME_ID - } - .into(), - crate::cosmic_theme::Theme::VERSION, - ) - .map(|res| { - for error in res.errors.into_iter().filter(cosmic_config::Error::is_err) { - tracing::error!( - ?error, - "error while watching system theme preference changes" - ); - } +// /// Watches for changes to the system's theme preference. +// #[cold] +// pub fn subscription(is_dark: bool) -> Subscription { +// config_subscription::<_, crate::cosmic_theme::Theme>( +// ( +// std::any::TypeId::of::(), +// is_dark, +// ), +// if is_dark { +// cosmic_theme::DARK_THEME_ID +// } else { +// cosmic_theme::LIGHT_THEME_ID +// } +// .into(), +// crate::cosmic_theme::Theme::VERSION, +// ) +// .map(|res| { +// for error in res.errors.into_iter().filter(cosmic_config::Error::is_err) { +// tracing::error!( +// ?error, +// "error while watching system theme preference changes" +// ); +// } - Theme::system(Arc::new(res.config)) - }) -} +// Theme::system(Arc::new(res.config)) +// }) +// } pub fn system_dark() -> Theme { let Ok(helper) = crate::cosmic_theme::Theme::dark_config() else { From a85b3693994ef2b8275eca6a9eccc86a2d7e9f86 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Mon, 23 Jun 2025 11:20:48 -0600 Subject: [PATCH 225/556] Fix config watching --- cosmic-config/Cargo.toml | 1 - cosmic-config/src/lib.rs | 61 ++++++++++++++----------------- cosmic-config/src/subscription.rs | 3 +- 3 files changed, 29 insertions(+), 36 deletions(-) diff --git a/cosmic-config/Cargo.toml b/cosmic-config/Cargo.toml index 424ea262..a79237c8 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -26,7 +26,6 @@ dirs.workspace = true tokio = { version = "1.44", optional = true, features = ["time"] } async-std = { version = "1.13", optional = true } tracing = "0.1" -notify-debouncer-full = "0.5.0" [target.'cfg(unix)'.dependencies] xdg = "2.5" diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index fecfdb75..0f846562 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -4,7 +4,6 @@ use notify::{ event::{EventKind, ModifyKind}, RecommendedWatcher, Watcher, }; -use notify_debouncer_full::{DebouncedEvent, Debouncer, RecommendedCache}; use serde::{de::DeserializeOwned, Serialize}; use std::{ fmt, fs, @@ -246,7 +245,7 @@ impl Config { // This may end up being an mpsc channel instead of a function // See EventHandler in the notify crate: https://docs.rs/notify/latest/notify/trait.EventHandler.html // Having a callback allows for any application abstraction to be used - pub fn watch(&self, f: F) -> Result, Error> + pub fn watch(&self, f: F) -> Result // Argument is an array of all keys that changed in that specific transaction //TODO: simplify F requirements where @@ -257,51 +256,47 @@ impl Config { return Err(Error::NoConfigDirectory); }; let user_path_clone = user_path.clone(); - let mut watcher = notify_debouncer_full::new_debouncer( - Duration::from_secs(1), - None, - move |event_res: Result, Vec>| { + let mut watcher = notify::recommended_watcher( + move |event_res: Result| { match event_res { - Ok(events) => { - for event in events { - match &event.event.kind { - EventKind::Access(_) - | EventKind::Modify(ModifyKind::Metadata(_)) => { - // Data not mutated - return; - } - _ => {} + Ok(event) => { + match &event.kind { + EventKind::Access(_) + | EventKind::Modify(ModifyKind::Metadata(_)) => { + // Data not mutated + return; } + _ => {} + } - let mut keys = Vec::new(); - for path in &event.paths { - match path.strip_prefix(&user_path_clone) { - Ok(key_path) => { - if let Some(key) = key_path.to_str() { - // Skip any .atomicwrite temporary files - if key.starts_with(".atomicwrite") { - continue; - } - keys.push(key.to_string()); + let mut keys = Vec::new(); + for path in &event.paths { + match path.strip_prefix(&user_path_clone) { + Ok(key_path) => { + if let Some(key) = key_path.to_str() { + // Skip any .atomicwrite temporary files + if key.starts_with(".atomicwrite") { + continue; } - } - Err(_err) => { - //TODO: handle errors + keys.push(key.to_string()); } } - } - if !keys.is_empty() { - f(&watch_config, &keys); + Err(_err) => { + //TODO: handle errors + } } } + if !keys.is_empty() { + f(&watch_config, &keys); + } } - Err(_errs) => { + Err(_err) => { //TODO: handle errors } } }, )?; - watcher.watch(user_path, notify::RecursiveMode::NonRecursive)?; + watcher.watch(user_path, notify::RecursiveMode::Recursive)?; Ok(watcher) } diff --git a/cosmic-config/src/subscription.rs b/cosmic-config/src/subscription.rs index 2d2c5117..3e54c1c3 100644 --- a/cosmic-config/src/subscription.rs +++ b/cosmic-config/src/subscription.rs @@ -1,7 +1,6 @@ use iced_futures::futures::{SinkExt, Stream}; use iced_futures::{futures::channel::mpsc, stream}; use notify::RecommendedWatcher; -use notify_debouncer_full::{Debouncer, RecommendedCache}; use std::{borrow::Cow, hash::Hash}; use crate::{Config, CosmicConfigEntry}; @@ -10,7 +9,7 @@ pub enum ConfigState { Init(Cow<'static, str>, u64, bool), Waiting( T, - Debouncer, + RecommendedWatcher, mpsc::Receiver>, Config, ), From 7fb514bfac0ebf6ae6a1f045e8bdb976544d69b5 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 25 Jun 2025 17:51:08 -0400 Subject: [PATCH 226/556] fix(menu): only draw within intersection of viewport bounds --- src/widget/menu/menu_inner.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 6a3eb093..18b4433f 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -781,7 +781,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { // println!("color: {:?}\n", styling.background); let menu_quad = renderer::Quad { bounds: pad_rectangle( - children_bounds, + children_bounds.intersection(&viewport).unwrap_or_default(), styling.background_expand.into(), ), border: Border { @@ -800,7 +800,10 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { .nth(active.saturating_sub(start_index)) { let path_quad = renderer::Quad { - bounds: active_layout.bounds(), + bounds: active_layout + .bounds() + .intersection(&viewport) + .unwrap_or_default(), border: Border { radius: styling.menu_border_radius.into(), ..Default::default() @@ -824,7 +827,10 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { style, clo, view_cursor, - &children_layout.bounds(), + &children_layout + .bounds() + .intersection(&viewport) + .unwrap_or_default(), ); }); } From 7555d9dfd122c4865d09e4aad03e5c4efbb50c64 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 25 Jun 2025 17:52:03 -0400 Subject: [PATCH 227/556] cargo fmt --- cosmic-config/src/lib.rs | 10 ++++------ cosmic-config/src/subscription.rs | 7 +------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 0f846562..7a392fde 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -256,13 +256,12 @@ impl Config { return Err(Error::NoConfigDirectory); }; let user_path_clone = user_path.clone(); - let mut watcher = notify::recommended_watcher( - move |event_res: Result| { + let mut watcher = + notify::recommended_watcher(move |event_res: Result| { match event_res { Ok(event) => { match &event.kind { - EventKind::Access(_) - | EventKind::Modify(ModifyKind::Metadata(_)) => { + EventKind::Access(_) | EventKind::Modify(ModifyKind::Metadata(_)) => { // Data not mutated return; } @@ -294,8 +293,7 @@ impl Config { //TODO: handle errors } } - }, - )?; + })?; watcher.watch(user_path, notify::RecursiveMode::Recursive)?; Ok(watcher) } diff --git a/cosmic-config/src/subscription.rs b/cosmic-config/src/subscription.rs index 3e54c1c3..64255954 100644 --- a/cosmic-config/src/subscription.rs +++ b/cosmic-config/src/subscription.rs @@ -7,12 +7,7 @@ use crate::{Config, CosmicConfigEntry}; pub enum ConfigState { Init(Cow<'static, str>, u64, bool), - Waiting( - T, - RecommendedWatcher, - mpsc::Receiver>, - Config, - ), + Waiting(T, RecommendedWatcher, mpsc::Receiver>, Config), Failed, } From 46cbce033b3863b4633032f7bc90f92116ad4c31 Mon Sep 17 00:00:00 2001 From: Joshua Megnauth <48846352+joshuamegnauth54@users.noreply.github.com> Date: Thu, 26 Jun 2025 00:36:52 -0400 Subject: [PATCH 228/556] fix(header_bar): Windows build fix --- src/desktop.rs | 1 + src/widget/header_bar.rs | 12 ++++-------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/desktop.rs b/src/desktop.rs index 34673f91..d41f29a2 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -10,6 +10,7 @@ pub trait IconSourceExt { fn as_cosmic_icon(&self) -> crate::widget::icon::Icon; } +#[cfg(not(windows))] impl IconSourceExt for fde::IconSource { fn as_cosmic_icon(&self) -> crate::widget::icon::Icon { match self { diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 3603cc68..53c14e0b 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -454,14 +454,10 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { #[cfg(not(target_os = "linux"))] let icon = { - widget::icon::from_svg_bytes(include_bytes!(concat!( - "../../res/icons/", - $name, - ".svg" - ))) - .symbolic(true) - .apply(widget::button::icon) - .padding(8) + widget::icon::from_path(concat!("../../res/icons/", $name, ".svg").into()) + .symbolic(true) + .apply(widget::button::icon) + .padding(8) }; icon.class(crate::theme::Button::HeaderBar) From dfdca0ef81985d260d584f1e4f7a6c1473678356 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 27 Jun 2025 09:53:20 -0600 Subject: [PATCH 229/556] fix(menu): make shortcut text 75 percent opacity --- src/widget/menu/menu_tree.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index d02b2b27..8cf890f2 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -6,6 +6,7 @@ use std::borrow::Cow; use std::collections::HashMap; use std::rc::Rc; +use iced::advanced::widget::text::Style as TextStyle; use iced_widget::core::{Element, renderer}; use crate::iced_core::{Alignment, Length}; @@ -228,6 +229,15 @@ pub fn menu_items< String::new() } + fn key_style(theme: &crate::Theme) -> TextStyle { + let mut color = theme.cosmic().background.component.on; + color.alpha *= 0.75; + TextStyle { + color: Some(color.into()), + } + } + let key_class = theme::Text::Custom(key_style); + let size = children.len(); children @@ -244,7 +254,7 @@ pub fn menu_items< let mut items = vec![ widget::text(l.clone()).into(), widget::horizontal_space().into(), - widget::text(key).into(), + widget::text(key).class(key_class).into(), ]; if let Some(icon) = icon { @@ -265,7 +275,7 @@ pub fn menu_items< let mut items = vec![ widget::text(l.clone()).into(), widget::horizontal_space().into(), - widget::text(key).into(), + widget::text(key).class(key_class).into(), ]; if let Some(icon) = icon { @@ -297,7 +307,7 @@ pub fn menu_items< widget::Space::with_width(spacing.space_xxs).into(), widget::text(label).align_x(iced::Alignment::Start).into(), widget::horizontal_space().into(), - widget::text(key).into(), + widget::text(key).class(key_class).into(), ]; if let Some(icon) = icon { From 52b802a11a0fafcb2dd84f3e06840d943aa8d933 Mon Sep 17 00:00:00 2001 From: 8roken <211849604+8roken@users.noreply.github.com> Date: Fri, 27 Jun 2025 14:44:20 -0400 Subject: [PATCH 230/556] fix(cosmic-config): Avoid dual notifications in transaction commits When a transaction gets committed, the files gets written to a file in the .atomicwrite[0-9a-Z] folder and then gets moved to their final location. The watcher will emit two events: - Modify(Name(To)) - Modify(Name(Both) The last one will include both the source and the destination and is essentially a duplicate of the first event. By discarding this event, behavior seems to be the same, and all consumers of those events get only notified once instead of twice when a configuration changes. --- cosmic-config/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 7a392fde..1a3ec66a 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -1,7 +1,7 @@ //! Integrations for cosmic-config — the cosmic configuration system. use notify::{ - event::{EventKind, ModifyKind}, + event::{EventKind, ModifyKind, RenameMode}, RecommendedWatcher, Watcher, }; use serde::{de::DeserializeOwned, Serialize}; @@ -261,7 +261,9 @@ impl Config { match event_res { Ok(event) => { match &event.kind { - EventKind::Access(_) | EventKind::Modify(ModifyKind::Metadata(_)) => { + EventKind::Access(_) + | EventKind::Modify(ModifyKind::Metadata(_)) + | EventKind::Modify(ModifyKind::Name(RenameMode::Both)) => { // Data not mutated return; } From aaa4b83577a70c15af8b91d1fb161e2a2931596b Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 1 Jul 2025 09:30:27 -0600 Subject: [PATCH 231/556] Fix bundling of header bar icons --- src/widget/header_bar.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 53c14e0b..3603cc68 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -454,10 +454,14 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { #[cfg(not(target_os = "linux"))] let icon = { - widget::icon::from_path(concat!("../../res/icons/", $name, ".svg").into()) - .symbolic(true) - .apply(widget::button::icon) - .padding(8) + widget::icon::from_svg_bytes(include_bytes!(concat!( + "../../res/icons/", + $name, + ".svg" + ))) + .symbolic(true) + .apply(widget::button::icon) + .padding(8) }; icon.class(crate::theme::Button::HeaderBar) From 50367b96e3bb9a2a2a62a6c51cd71fc2858e61ed Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 8 Jul 2025 17:00:07 -0400 Subject: [PATCH 232/556] fix(headerbar): handle zero length segments --- src/app/cosmic.rs | 4 ++-- src/widget/header_bar.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index f2de0f5e..3a75871d 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -452,11 +452,11 @@ where if let Some(v) = self.surface_views.get(&id) { return v(&self.app); } - if !self + if self .app .core() .main_window_id() - .is_some_and(|main_id| main_id == id) + .is_none_or(|main_id| main_id != id) { return self.app.view_window(id).map(crate::Action::App); } diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 3603cc68..8eec9ef4 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -356,9 +356,9 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { if center.is_empty() && (self.title.is_empty() || self.is_condensed) { let left_to_right_ratio = left_len as f32 / right_len.max(1) as f32; let right_to_left_ratio = right_len as f32 / left_len.max(1) as f32; - if right_to_left_ratio > 2. { + if right_to_left_ratio > 2. || left_len < 1 { (1, 2) - } else if left_to_right_ratio > 2. { + } else if left_to_right_ratio > 2. || right_len < 1 { (2, 1) } else { (left_len as u16, (right_len + window_control_cnt) as u16) From 0943f131c2590b097fe800c403274ccfed63607c Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 16 Jul 2025 13:48:08 -0400 Subject: [PATCH 233/556] refactor: track focus chain --- cosmic-config/src/lib.rs | 1 - src/app/cosmic.rs | 66 +++++++++++++++++++++++++++++++++++----- src/app/mod.rs | 5 +-- src/core.rs | 15 ++++++--- 4 files changed, 73 insertions(+), 14 deletions(-) diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 1a3ec66a..e408eac5 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -10,7 +10,6 @@ use std::{ io::Write, path::{Path, PathBuf}, sync::Mutex, - time::Duration, }; #[cfg(feature = "subscription")] diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 3a75871d..a2cdeb87 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -17,6 +17,8 @@ use iced::Application as IcedApplication; use iced::event::wayland; use iced::{Task, window}; use iced_futures::event::listen_with; +#[cfg(feature = "wayland")] +use iced_winit::SurfaceIdWrapper; use palette::color_difference::EuclideanDistance; #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] @@ -84,7 +86,11 @@ pub struct Cosmic { #[cfg(feature = "wayland")] pub surface_views: HashMap< window::Id, - Box Fn(&'a App) -> Element<'a, crate::Action>>, + ( + Option, + SurfaceIdWrapper, + Box Fn(&'a App) -> Element<'a, crate::Action>>, + ), >, } @@ -449,7 +455,7 @@ where #[cfg(feature = "multi-window")] pub fn view(&self, id: window::Id) -> Element> { #[cfg(feature = "wayland")] - if let Some(v) = self.surface_views.get(&id) { + if let Some((_, _, v)) = self.surface_views.get(&id) { return v(&self.app); } if self @@ -841,13 +847,45 @@ impl Cosmic { } Action::Focus(f) => { - self.app.core_mut().focused_window = Some(f); + #[cfg(all( + feature = "wayland", + feature = "multi-window", + feature = "surface-message" + ))] + if let Some(( + parent, + SurfaceIdWrapper::Subsurface(_) | SurfaceIdWrapper::Popup(_), + _, + )) = self.surface_views.get(&f) + { + // If the parent is already focused, push the new focus + // to the end of the focus chain. + if parent.is_some_and(|p| self.app.core().focused_window.last() == Some(&p)) { + self.app.core_mut().focused_window.push(f); + return iced::Task::none(); + } else { + // set the whole parent chain to the focus chain + let mut parent_chain = vec![f]; + let mut cur = *parent; + while let Some(p) = cur { + parent_chain.push(p); + cur = self + .surface_views + .get(&p) + .and_then(|(parent, _, _)| *parent); + } + parent_chain.reverse(); + self.app.core_mut().focused_window = parent_chain; + return iced::Task::none(); + } + } + self.app.core_mut().focused_window = vec![f]; } Action::Unfocus(id) => { let core = self.app.core_mut(); - if core.focused_window.as_ref().is_some_and(|cur| *cur == id) { - core.focused_window = None; + if core.focused_window().as_ref().is_some_and(|cur| *cur == id) { + core.focused_window.pop(); } } #[cfg(feature = "applet")] @@ -886,7 +924,14 @@ impl Cosmic { ) -> Task> { use iced_winit::commands::subsurface::get_subsurface; - self.surface_views.insert(settings.id, view); + self.surface_views.insert( + settings.id, + ( + Some(settings.parent), + SurfaceIdWrapper::Subsurface(settings.id), + view, + ), + ); get_subsurface(settings) } @@ -901,7 +946,14 @@ impl Cosmic { ) -> Task> { use iced_winit::commands::popup::get_popup; - self.surface_views.insert(settings.id, view); + self.surface_views.insert( + settings.id, + ( + Some(settings.parent), + SurfaceIdWrapper::Popup(settings.id), + view, + ), + ); get_popup(settings) } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 890a3429..35b55f02 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -548,8 +548,9 @@ impl ApplicationExt for App { let show_context = core.window.show_context; let nav_bar_active = core.nav_bar_active(); let focused = core - .focused_window() - .is_some_and(|i| Some(i) == self.core().main_window_id()); + .focus_chain() + .iter() + .any(|i| Some(*i) == self.core().main_window_id()); let border_padding = if sharp_corners { 8 } else { 7 }; diff --git a/src/core.rs b/src/core.rs index 744b33b7..4b9811ec 100644 --- a/src/core.rs +++ b/src/core.rs @@ -64,7 +64,7 @@ pub struct Core { scale_factor: f32, /// Window focus state - pub(super) focused_window: Option, + pub(super) focused_window: Vec, pub(super) theme_sub_counter: u64, /// Last known system theme @@ -141,7 +141,7 @@ impl Default for Core { height: 0., width: 0., }, - focused_window: None, + focused_window: Vec::new(), #[cfg(feature = "applet")] applet: crate::applet::Context::default(), #[cfg(feature = "single-instance")] @@ -384,8 +384,15 @@ impl Core { /// Get the current focused window if it exists #[must_use] #[inline] - pub const fn focused_window(&self) -> Option { - self.focused_window + pub fn focused_window(&self) -> Option { + self.focused_window.last().copied() + } + + /// Get the current focus chain of windows + #[must_use] + #[inline] + pub fn focus_chain(&self) -> &[window::Id] { + &self.focused_window } /// Whether the application should use a dark theme, according to the system From 364af2bcdfd7599813ec8510a33d171955d7468c Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 16 Jul 2025 20:20:08 -0400 Subject: [PATCH 234/556] refactor: introduce new palette colors for control tint neutral colors will not be tinted anymore --- cosmic-theme/src/model/cosmic_palette.rs | 23 +++++ cosmic-theme/src/model/dark.ron | 2 +- cosmic-theme/src/model/light.ron | 2 +- cosmic-theme/src/model/theme.rs | 106 +++++++++++------------ 4 files changed, 78 insertions(+), 55 deletions(-) diff --git a/cosmic-theme/src/model/cosmic_palette.rs b/cosmic-theme/src/model/cosmic_palette.rs index 6a189089..2c4ddfbd 100644 --- a/cosmic-theme/src/model/cosmic_palette.rs +++ b/cosmic-theme/src/model/cosmic_palette.rs @@ -132,6 +132,29 @@ pub struct CosmicPaletteInner { /// A wider spread of dark colors for more general use. pub neutral_10: Srgba, + /// Tinted control colors. + pub control_0: Srgba, + /// Tinted control colors. + pub control_1: Srgba, + /// Tinted control colors. + pub control_2: Srgba, + /// Tinted control colors. + pub control_3: Srgba, + /// Tinted control colors. + pub control_4: Srgba, + /// Tinted control colors. + pub control_5: Srgba, + /// Tinted control colors. + pub control_6: Srgba, + /// Tinted control colors. + pub control_7: Srgba, + /// Tinted control colors. + pub control_8: Srgba, + /// Tinted control colors. + pub control_9: Srgba, + /// Tinted control colors. + pub control_10: Srgba, + /// Potential Accent Color Combos pub accent_blue: Srgba, /// Potential Accent Color Combos diff --git a/cosmic-theme/src/model/dark.ron b/cosmic-theme/src/model/dark.ron index 4453b8bf..f4531efd 100644 --- a/cosmic-theme/src/model/dark.ron +++ b/cosmic-theme/src/model/dark.ron @@ -1 +1 @@ -Dark((name:"cosmic-dark",bright_red:(red:1.0,green:0.62745098,blue:0.60392157,alpha:1.0),bright_green:(red:0.36862745,green:0.85882352,blue:0.54901960,alpha:1.0),bright_orange:(red:1.0,green:0.63921569,blue:0.49019608,alpha:1.0),gray_1:(red:0.10588235,green:0.10588235,blue:0.10588235,alpha:1.0),gray_2:(red:0.14901961,green:0.14901961,blue:0.14901961,alpha:1.0),neutral_0:(red:0.0,green:0.0,blue:0.0,alpha:1.0),neutral_1:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_2:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_3:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_4:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_7:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_8:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_9:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_10:(red:1.0,green:1.0,blue:1.0,alpha:1.0),accent_blue:(red:0.3882353,green:0.81568627,blue:0.87450981,alpha:1.0),accent_indigo:(red:0.63137255,green:0.75294118,blue:0.92156863,alpha:1.0),accent_purple:(red:0.90588235,green:0.61176471,blue:0.99607843,alpha:1.0),accent_pink:(red:1.0,green:0.61176471,blue:0.69411765,alpha:1.0),accent_red:(red:0.99215686,green:0.63137255,blue:0.62745098,alpha:1.0),accent_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),accent_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),accent_green:(red:0.57254902,green:0.81176471,blue:0.61176471,alpha:1.0),accent_warm_grey:(red:0.79215686,green:0.72941176,blue:0.70588235,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),ext_yellow:(red:0.99607843,green:0.85882353,blue:0.25098039,alpha:1.0),ext_blue:(red:0.28235294,green:0.72549020,blue:0.78039216,alpha:1.0),ext_purple:(red:0.81176471,green:0.49019608,blue:1.0,alpha:1.0),ext_pink:(red:0.97647059,green:0.22745098,blue:0.51372549,alpha:1.0),ext_indigo:(red:0.24313725,green:0.53333333,blue:1.0,alpha:1.0))) +Dark((name:"cosmic-dark",bright_red:(red:1.0,green:0.62745098,blue:0.60392157,alpha:1.0),bright_green:(red:0.36862745,green:0.85882352,blue:0.54901960,alpha:1.0),bright_orange:(red:1.0,green:0.63921569,blue:0.49019608,alpha:1.0),gray_1:(red:0.10588235,green:0.10588235,blue:0.10588235,alpha:1.0),gray_2:(red:0.14901961,green:0.14901961,blue:0.14901961,alpha:1.0),control_0:(red:0.0,green:0.0,blue:0.0,alpha:1.0),control_1:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),control_2:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),control_3:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),control_4:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),control_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),control_6:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),control_7:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),control_8:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),control_9:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),control_10:(red:1.0,green:1.0,blue:1.0,alpha:1.0),neutral_0:(red:0.0,green:0.0,blue:0.0,alpha:1.0),neutral_1:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_2:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_3:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_4:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_7:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_8:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_9:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_10:(red:1.0,green:1.0,blue:1.0,alpha:1.0),accent_blue:(red:0.3882353,green:0.81568627,blue:0.87450981,alpha:1.0),accent_indigo:(red:0.63137255,green:0.75294118,blue:0.92156863,alpha:1.0),accent_purple:(red:0.90588235,green:0.61176471,blue:0.99607843,alpha:1.0),accent_pink:(red:1.0,green:0.61176471,blue:0.69411765,alpha:1.0),accent_red:(red:0.99215686,green:0.63137255,blue:0.62745098,alpha:1.0),accent_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),accent_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),accent_green:(red:0.57254902,green:0.81176471,blue:0.61176471,alpha:1.0),accent_warm_grey:(red:0.79215686,green:0.72941176,blue:0.70588235,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),ext_yellow:(red:0.99607843,green:0.85882353,blue:0.25098039,alpha:1.0),ext_blue:(red:0.28235294,green:0.72549020,blue:0.78039216,alpha:1.0),ext_purple:(red:0.81176471,green:0.49019608,blue:1.0,alpha:1.0),ext_pink:(red:0.97647059,green:0.22745098,blue:0.51372549,alpha:1.0),ext_indigo:(red:0.24313725,green:0.53333333,blue:1.0,alpha:1.0))) diff --git a/cosmic-theme/src/model/light.ron b/cosmic-theme/src/model/light.ron index 29b3ad65..06be1a49 100644 --- a/cosmic-theme/src/model/light.ron +++ b/cosmic-theme/src/model/light.ron @@ -1 +1 @@ -Light((name:"cosmic-light",bright_red:(red:0.53725490,green:0.01568627,blue:0.09411765,alpha:1.0),bright_green:(red:0.0,green:0.34117647,blue:0.17254901,alpha:1.0),bright_orange:(red:0.47450980,green:0.17254902,blue:0.0,alpha:1.0),gray_1:(red:0.84313725,green:0.84313725,blue:0.84313725,alpha:1.0),gray_2:(red:0.89411765,green:0.89411765,blue:0.89411765,alpha:1.0),neutral_0:(red:1.0,green:1.0,blue:1.0,alpha:1.0),neutral_1:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_2:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_3:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_4:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_7:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_8:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_9:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_10:(red:0.0,green:0.0,blue:0.0,alpha:1.0),accent_blue:(red:0.0,green:0.32156863,blue:0.35294118,alpha:1.0),accent_indigo:(red:0.18039216,green:0.28627451,blue:0.42745098,alpha:1.0),accent_purple:(red:0.40784314,green:0.12941176,blue:0.48627451,alpha:1.0),accent_pink:(red:0.52549020,green:0.01568627,blue:0.22745098,alpha:1.0),accent_red:(red:0.47058824,green:0.16078431,blue:0.18039216,alpha:1.0),accent_orange:(red:0.38431373,green:0.25098039,blue:0.0,alpha:1.0),accent_yellow:(red:0.32549020,green:0.28235294,blue:0.0,alpha:1.0),accent_green:(red:0.09411765,green:0.33333333,blue:0.16078431,alpha:1.0),accent_warm_grey:(red:0.33333333,green:0.27843137,blue:0.25882353,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:0.98431373,green:0.72156863,blue:0.42352941,alpha:1.0),ext_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),ext_blue:(red:0.41568627,green:0.79215686,blue:0.84705882,alpha:1.0),ext_purple:(red:0.83529412,green:0.54901961,blue:1.0,alpha:1.0),ext_pink:(red:1.0,green:0.61176471,blue:0.86666667,alpha:1.0),ext_indigo:(red:0.58431373,green:0.76862745,blue:0.98823529,alpha:1.0))) +Light((name:"cosmic-light",bright_red:(red:0.53725490,green:0.01568627,blue:0.09411765,alpha:1.0),bright_green:(red:0.0,green:0.34117647,blue:0.17254901,alpha:1.0),bright_orange:(red:0.47450980,green:0.17254902,blue:0.0,alpha:1.0),gray_1:(red:0.84313725,green:0.84313725,blue:0.84313725,alpha:1.0),gray_2:(red:0.89411765,green:0.89411765,blue:0.89411765,alpha:1.0),control_0:(red:1.0,green:1.0,blue:1.0,alpha:1.0),control_1:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),control_2:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),control_3:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),control_4:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),control_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),control_6:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),control_7:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),control_8:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),control_9:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),control_10:(red:0.0,green:0.0,blue:0.0,alpha:1.0),neutral_0:(red:1.0,green:1.0,blue:1.0,alpha:1.0),neutral_1:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_2:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_3:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_4:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_7:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_8:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_9:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_10:(red:0.0,green:0.0,blue:0.0,alpha:1.0),accent_blue:(red:0.0,green:0.32156863,blue:0.35294118,alpha:1.0),accent_indigo:(red:0.18039216,green:0.28627451,blue:0.42745098,alpha:1.0),accent_purple:(red:0.40784314,green:0.12941176,blue:0.48627451,alpha:1.0),accent_pink:(red:0.52549020,green:0.01568627,blue:0.22745098,alpha:1.0),accent_red:(red:0.47058824,green:0.16078431,blue:0.18039216,alpha:1.0),accent_orange:(red:0.38431373,green:0.25098039,blue:0.0,alpha:1.0),accent_yellow:(red:0.32549020,green:0.28235294,blue:0.0,alpha:1.0),accent_green:(red:0.09411765,green:0.33333333,blue:0.16078431,alpha:1.0),accent_warm_grey:(red:0.33333333,green:0.27843137,blue:0.25882353,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:0.98431373,green:0.72156863,blue:0.42352941,alpha:1.0),ext_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),ext_blue:(red:0.41568627,green:0.79215686,blue:0.84705882,alpha:1.0),ext_purple:(red:0.83529412,green:0.54901961,blue:1.0,alpha:1.0),ext_pink:(red:1.0,green:0.61176471,blue:0.86666667,alpha:1.0),ext_indigo:(red:0.58431373,green:0.76862745,blue:0.98823529,alpha:1.0))) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index d159b405..3e30a1ad 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -904,17 +904,17 @@ impl ThemeBuilder { } let p = palette.as_mut(); - p.neutral_0 = neutral_steps_arr[0]; - p.neutral_1 = neutral_steps_arr[1]; - p.neutral_2 = neutral_steps_arr[2]; - p.neutral_3 = neutral_steps_arr[3]; - p.neutral_4 = neutral_steps_arr[4]; - p.neutral_5 = neutral_steps_arr[5]; - p.neutral_6 = neutral_steps_arr[6]; - p.neutral_7 = neutral_steps_arr[7]; - p.neutral_8 = neutral_steps_arr[8]; - p.neutral_9 = neutral_steps_arr[9]; - p.neutral_10 = neutral_steps_arr[10]; + p.control_0 = neutral_steps_arr[0]; + p.control_1 = neutral_steps_arr[1]; + p.control_2 = neutral_steps_arr[2]; + p.control_3 = neutral_steps_arr[3]; + p.control_4 = neutral_steps_arr[4]; + p.control_5 = neutral_steps_arr[5]; + p.control_6 = neutral_steps_arr[6]; + p.control_7 = neutral_steps_arr[7]; + p.control_8 = neutral_steps_arr[8]; + p.control_9 = neutral_steps_arr[9]; + p.control_10 = neutral_steps_arr[10]; } let p_ref = palette.as_ref(); @@ -934,24 +934,24 @@ impl ThemeBuilder { let bg_index = color_index(bg, step_array.len()); let mut component_hovered_overlay = if bg_index < 91 { - p_ref.neutral_10 + p_ref.control_10 } else { - p_ref.neutral_0 + p_ref.control_0 }; component_hovered_overlay.alpha = 0.1; let mut component_pressed_overlay = component_hovered_overlay; component_pressed_overlay.alpha = 0.2; - // Standard button background is neutral 7 with 25% opacity + // Standard button background is control 7 with 25% opacity let button_bg = { - let mut color = p_ref.neutral_7; + let mut color = p_ref.control_7; color.alpha = 0.25; color }; let (mut button_hovered_overlay, mut button_pressed_overlay) = - (p_ref.neutral_5, p_ref.neutral_2); + (p_ref.control_5, p_ref.control_2); button_hovered_overlay.alpha = 0.2; button_pressed_overlay.alpha = 0.5; @@ -959,7 +959,7 @@ impl ThemeBuilder { let on_bg_component = get_text( color_index(bg_component, step_array.len()), &step_array, - &p_ref.neutral_8, + &p_ref.control_8, text_steps_array.as_deref(), ); @@ -967,17 +967,17 @@ impl ThemeBuilder { let container_bg = if let Some(primary_container_bg_color) = primary_container_bg { primary_container_bg_color } else { - get_surface_color(bg_index, 5, &step_array, is_dark, &p_ref.neutral_1) + get_surface_color(bg_index, 5, &step_array, is_dark, &p_ref.control_1) }; let base_index: usize = color_index(container_bg, step_array.len()); let component_base = - get_surface_color(base_index, 6, &step_array, is_dark, &p_ref.neutral_3); + get_surface_color(base_index, 6, &step_array, is_dark, &p_ref.control_3); component_hovered_overlay = if base_index < 91 { - p_ref.neutral_10 + p_ref.control_10 } else { - p_ref.neutral_0 + p_ref.control_0 }; component_hovered_overlay.alpha = 0.1; @@ -991,22 +991,22 @@ impl ThemeBuilder { get_text( color_index(component_base, step_array.len()), &step_array, - &p_ref.neutral_8, + &p_ref.control_8, text_steps_array.as_deref(), ), component_hovered_overlay, component_pressed_overlay, is_high_contrast, - p_ref.neutral_8, + p_ref.control_8, ), container_bg, get_text( base_index, &step_array, - &p_ref.neutral_8, + &p_ref.control_8, text_steps_array.as_deref(), ), - get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.neutral_6), + get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.control_6), is_high_contrast, ); @@ -1072,16 +1072,16 @@ impl ThemeBuilder { component_hovered_overlay, component_pressed_overlay, is_high_contrast, - p_ref.neutral_8, + p_ref.control_8, ), bg, get_text( bg_index, &step_array, - &p_ref.neutral_8, + &p_ref.control_8, text_steps_array.as_deref(), ), - get_small_widget_color(bg_index, 5, &neutral_steps, &p_ref.neutral_6), + get_small_widget_color(bg_index, 5, &neutral_steps, &p_ref.control_6), is_high_contrast, ), primary, @@ -1089,17 +1089,17 @@ impl ThemeBuilder { let container_bg = if let Some(secondary_container_bg) = secondary_container_bg { secondary_container_bg } else { - get_surface_color(bg_index, 10, &step_array, is_dark, &p_ref.neutral_2) + get_surface_color(bg_index, 10, &step_array, is_dark, &p_ref.control_2) }; let base_index = color_index(container_bg, step_array.len()); let secondary_component = - get_surface_color(base_index, 3, &step_array, is_dark, &p_ref.neutral_4); + get_surface_color(base_index, 3, &step_array, is_dark, &p_ref.control_4); component_hovered_overlay = if base_index < 91 { - p_ref.neutral_10 + p_ref.control_10 } else { - p_ref.neutral_0 + p_ref.control_0 }; component_hovered_overlay.alpha = 0.1; @@ -1113,36 +1113,36 @@ impl ThemeBuilder { get_text( color_index(secondary_component, step_array.len()), &step_array, - &p_ref.neutral_8, + &p_ref.control_8, text_steps_array.as_deref(), ), component_hovered_overlay, component_pressed_overlay, is_high_contrast, - p_ref.neutral_8, + p_ref.control_8, ), container_bg, get_text( base_index, &step_array, - &p_ref.neutral_8, + &p_ref.control_8, text_steps_array.as_deref(), ), - get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.neutral_6), + get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.control_6), is_high_contrast, ) }, accent: Component::colored_component( accent, - p_ref.neutral_0, + p_ref.control_0, accent, button_hovered_overlay, button_pressed_overlay, ), accent_button: Component::colored_button( accent, - p_ref.neutral_1, - p_ref.neutral_0, + p_ref.control_1, + p_ref.control_0, accent, button_hovered_overlay, button_pressed_overlay, @@ -1154,19 +1154,19 @@ impl ThemeBuilder { button_hovered_overlay, button_pressed_overlay, is_high_contrast, - p_ref.neutral_8, + p_ref.control_8, ), destructive: Component::colored_component( destructive, - p_ref.neutral_0, + p_ref.control_0, accent, button_hovered_overlay, button_pressed_overlay, ), destructive_button: Component::colored_button( destructive, - p_ref.neutral_1, - p_ref.neutral_0, + p_ref.control_1, + p_ref.control_0, accent, button_hovered_overlay, button_pressed_overlay, @@ -1174,11 +1174,11 @@ impl ThemeBuilder { icon_button: Component::component( Srgba::new(0.0, 0.0, 0.0, 0.0), accent, - p_ref.neutral_8, + p_ref.control_8, button_hovered_overlay, button_pressed_overlay, is_high_contrast, - p_ref.neutral_8, + p_ref.control_8, ), link_button: { let mut component = Component::component( @@ -1188,7 +1188,7 @@ impl ThemeBuilder { Srgba::new(0.0, 0.0, 0.0, 0.0), Srgba::new(0.0, 0.0, 0.0, 0.0), is_high_contrast, - p_ref.neutral_8, + p_ref.control_8, ); let mut on_50 = component.on; @@ -1199,15 +1199,15 @@ impl ThemeBuilder { }, success: Component::colored_component( success, - p_ref.neutral_0, + p_ref.control_0, accent, button_hovered_overlay, button_pressed_overlay, ), success_button: Component::colored_button( success, - p_ref.neutral_1, - p_ref.neutral_0, + p_ref.control_1, + p_ref.control_0, accent, button_hovered_overlay, button_pressed_overlay, @@ -1219,19 +1219,19 @@ impl ThemeBuilder { button_hovered_overlay, button_pressed_overlay, is_high_contrast, - p_ref.neutral_8, + p_ref.control_8, ), warning: Component::colored_component( warning, - p_ref.neutral_0, + p_ref.control_0, accent, button_hovered_overlay, button_pressed_overlay, ), warning_button: Component::colored_button( warning, - p_ref.neutral_10, - p_ref.neutral_0, + p_ref.control_10, + p_ref.control_0, accent, button_hovered_overlay, button_pressed_overlay, From 0041fc2d12cf7b4318fbc051d9301f4d37be085b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 17 Jul 2025 14:46:16 -0400 Subject: [PATCH 235/556] Revert "refactor: introduce new palette colors for control tint" This reverts commit b8f9dc6cb0af2115ff0a0ec2ff9d35076ace16b8. --- cosmic-theme/src/model/cosmic_palette.rs | 23 ----- cosmic-theme/src/model/dark.ron | 2 +- cosmic-theme/src/model/light.ron | 2 +- cosmic-theme/src/model/theme.rs | 106 +++++++++++------------ 4 files changed, 55 insertions(+), 78 deletions(-) diff --git a/cosmic-theme/src/model/cosmic_palette.rs b/cosmic-theme/src/model/cosmic_palette.rs index 2c4ddfbd..6a189089 100644 --- a/cosmic-theme/src/model/cosmic_palette.rs +++ b/cosmic-theme/src/model/cosmic_palette.rs @@ -132,29 +132,6 @@ pub struct CosmicPaletteInner { /// A wider spread of dark colors for more general use. pub neutral_10: Srgba, - /// Tinted control colors. - pub control_0: Srgba, - /// Tinted control colors. - pub control_1: Srgba, - /// Tinted control colors. - pub control_2: Srgba, - /// Tinted control colors. - pub control_3: Srgba, - /// Tinted control colors. - pub control_4: Srgba, - /// Tinted control colors. - pub control_5: Srgba, - /// Tinted control colors. - pub control_6: Srgba, - /// Tinted control colors. - pub control_7: Srgba, - /// Tinted control colors. - pub control_8: Srgba, - /// Tinted control colors. - pub control_9: Srgba, - /// Tinted control colors. - pub control_10: Srgba, - /// Potential Accent Color Combos pub accent_blue: Srgba, /// Potential Accent Color Combos diff --git a/cosmic-theme/src/model/dark.ron b/cosmic-theme/src/model/dark.ron index f4531efd..4453b8bf 100644 --- a/cosmic-theme/src/model/dark.ron +++ b/cosmic-theme/src/model/dark.ron @@ -1 +1 @@ -Dark((name:"cosmic-dark",bright_red:(red:1.0,green:0.62745098,blue:0.60392157,alpha:1.0),bright_green:(red:0.36862745,green:0.85882352,blue:0.54901960,alpha:1.0),bright_orange:(red:1.0,green:0.63921569,blue:0.49019608,alpha:1.0),gray_1:(red:0.10588235,green:0.10588235,blue:0.10588235,alpha:1.0),gray_2:(red:0.14901961,green:0.14901961,blue:0.14901961,alpha:1.0),control_0:(red:0.0,green:0.0,blue:0.0,alpha:1.0),control_1:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),control_2:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),control_3:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),control_4:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),control_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),control_6:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),control_7:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),control_8:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),control_9:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),control_10:(red:1.0,green:1.0,blue:1.0,alpha:1.0),neutral_0:(red:0.0,green:0.0,blue:0.0,alpha:1.0),neutral_1:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_2:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_3:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_4:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_7:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_8:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_9:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_10:(red:1.0,green:1.0,blue:1.0,alpha:1.0),accent_blue:(red:0.3882353,green:0.81568627,blue:0.87450981,alpha:1.0),accent_indigo:(red:0.63137255,green:0.75294118,blue:0.92156863,alpha:1.0),accent_purple:(red:0.90588235,green:0.61176471,blue:0.99607843,alpha:1.0),accent_pink:(red:1.0,green:0.61176471,blue:0.69411765,alpha:1.0),accent_red:(red:0.99215686,green:0.63137255,blue:0.62745098,alpha:1.0),accent_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),accent_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),accent_green:(red:0.57254902,green:0.81176471,blue:0.61176471,alpha:1.0),accent_warm_grey:(red:0.79215686,green:0.72941176,blue:0.70588235,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),ext_yellow:(red:0.99607843,green:0.85882353,blue:0.25098039,alpha:1.0),ext_blue:(red:0.28235294,green:0.72549020,blue:0.78039216,alpha:1.0),ext_purple:(red:0.81176471,green:0.49019608,blue:1.0,alpha:1.0),ext_pink:(red:0.97647059,green:0.22745098,blue:0.51372549,alpha:1.0),ext_indigo:(red:0.24313725,green:0.53333333,blue:1.0,alpha:1.0))) +Dark((name:"cosmic-dark",bright_red:(red:1.0,green:0.62745098,blue:0.60392157,alpha:1.0),bright_green:(red:0.36862745,green:0.85882352,blue:0.54901960,alpha:1.0),bright_orange:(red:1.0,green:0.63921569,blue:0.49019608,alpha:1.0),gray_1:(red:0.10588235,green:0.10588235,blue:0.10588235,alpha:1.0),gray_2:(red:0.14901961,green:0.14901961,blue:0.14901961,alpha:1.0),neutral_0:(red:0.0,green:0.0,blue:0.0,alpha:1.0),neutral_1:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_2:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_3:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_4:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_7:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_8:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_9:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_10:(red:1.0,green:1.0,blue:1.0,alpha:1.0),accent_blue:(red:0.3882353,green:0.81568627,blue:0.87450981,alpha:1.0),accent_indigo:(red:0.63137255,green:0.75294118,blue:0.92156863,alpha:1.0),accent_purple:(red:0.90588235,green:0.61176471,blue:0.99607843,alpha:1.0),accent_pink:(red:1.0,green:0.61176471,blue:0.69411765,alpha:1.0),accent_red:(red:0.99215686,green:0.63137255,blue:0.62745098,alpha:1.0),accent_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),accent_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),accent_green:(red:0.57254902,green:0.81176471,blue:0.61176471,alpha:1.0),accent_warm_grey:(red:0.79215686,green:0.72941176,blue:0.70588235,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),ext_yellow:(red:0.99607843,green:0.85882353,blue:0.25098039,alpha:1.0),ext_blue:(red:0.28235294,green:0.72549020,blue:0.78039216,alpha:1.0),ext_purple:(red:0.81176471,green:0.49019608,blue:1.0,alpha:1.0),ext_pink:(red:0.97647059,green:0.22745098,blue:0.51372549,alpha:1.0),ext_indigo:(red:0.24313725,green:0.53333333,blue:1.0,alpha:1.0))) diff --git a/cosmic-theme/src/model/light.ron b/cosmic-theme/src/model/light.ron index 06be1a49..29b3ad65 100644 --- a/cosmic-theme/src/model/light.ron +++ b/cosmic-theme/src/model/light.ron @@ -1 +1 @@ -Light((name:"cosmic-light",bright_red:(red:0.53725490,green:0.01568627,blue:0.09411765,alpha:1.0),bright_green:(red:0.0,green:0.34117647,blue:0.17254901,alpha:1.0),bright_orange:(red:0.47450980,green:0.17254902,blue:0.0,alpha:1.0),gray_1:(red:0.84313725,green:0.84313725,blue:0.84313725,alpha:1.0),gray_2:(red:0.89411765,green:0.89411765,blue:0.89411765,alpha:1.0),control_0:(red:1.0,green:1.0,blue:1.0,alpha:1.0),control_1:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),control_2:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),control_3:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),control_4:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),control_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),control_6:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),control_7:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),control_8:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),control_9:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),control_10:(red:0.0,green:0.0,blue:0.0,alpha:1.0),neutral_0:(red:1.0,green:1.0,blue:1.0,alpha:1.0),neutral_1:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_2:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_3:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_4:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_7:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_8:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_9:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_10:(red:0.0,green:0.0,blue:0.0,alpha:1.0),accent_blue:(red:0.0,green:0.32156863,blue:0.35294118,alpha:1.0),accent_indigo:(red:0.18039216,green:0.28627451,blue:0.42745098,alpha:1.0),accent_purple:(red:0.40784314,green:0.12941176,blue:0.48627451,alpha:1.0),accent_pink:(red:0.52549020,green:0.01568627,blue:0.22745098,alpha:1.0),accent_red:(red:0.47058824,green:0.16078431,blue:0.18039216,alpha:1.0),accent_orange:(red:0.38431373,green:0.25098039,blue:0.0,alpha:1.0),accent_yellow:(red:0.32549020,green:0.28235294,blue:0.0,alpha:1.0),accent_green:(red:0.09411765,green:0.33333333,blue:0.16078431,alpha:1.0),accent_warm_grey:(red:0.33333333,green:0.27843137,blue:0.25882353,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:0.98431373,green:0.72156863,blue:0.42352941,alpha:1.0),ext_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),ext_blue:(red:0.41568627,green:0.79215686,blue:0.84705882,alpha:1.0),ext_purple:(red:0.83529412,green:0.54901961,blue:1.0,alpha:1.0),ext_pink:(red:1.0,green:0.61176471,blue:0.86666667,alpha:1.0),ext_indigo:(red:0.58431373,green:0.76862745,blue:0.98823529,alpha:1.0))) +Light((name:"cosmic-light",bright_red:(red:0.53725490,green:0.01568627,blue:0.09411765,alpha:1.0),bright_green:(red:0.0,green:0.34117647,blue:0.17254901,alpha:1.0),bright_orange:(red:0.47450980,green:0.17254902,blue:0.0,alpha:1.0),gray_1:(red:0.84313725,green:0.84313725,blue:0.84313725,alpha:1.0),gray_2:(red:0.89411765,green:0.89411765,blue:0.89411765,alpha:1.0),neutral_0:(red:1.0,green:1.0,blue:1.0,alpha:1.0),neutral_1:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_2:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_3:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_4:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_7:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_8:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_9:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_10:(red:0.0,green:0.0,blue:0.0,alpha:1.0),accent_blue:(red:0.0,green:0.32156863,blue:0.35294118,alpha:1.0),accent_indigo:(red:0.18039216,green:0.28627451,blue:0.42745098,alpha:1.0),accent_purple:(red:0.40784314,green:0.12941176,blue:0.48627451,alpha:1.0),accent_pink:(red:0.52549020,green:0.01568627,blue:0.22745098,alpha:1.0),accent_red:(red:0.47058824,green:0.16078431,blue:0.18039216,alpha:1.0),accent_orange:(red:0.38431373,green:0.25098039,blue:0.0,alpha:1.0),accent_yellow:(red:0.32549020,green:0.28235294,blue:0.0,alpha:1.0),accent_green:(red:0.09411765,green:0.33333333,blue:0.16078431,alpha:1.0),accent_warm_grey:(red:0.33333333,green:0.27843137,blue:0.25882353,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:0.98431373,green:0.72156863,blue:0.42352941,alpha:1.0),ext_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),ext_blue:(red:0.41568627,green:0.79215686,blue:0.84705882,alpha:1.0),ext_purple:(red:0.83529412,green:0.54901961,blue:1.0,alpha:1.0),ext_pink:(red:1.0,green:0.61176471,blue:0.86666667,alpha:1.0),ext_indigo:(red:0.58431373,green:0.76862745,blue:0.98823529,alpha:1.0))) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 3e30a1ad..d159b405 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -904,17 +904,17 @@ impl ThemeBuilder { } let p = palette.as_mut(); - p.control_0 = neutral_steps_arr[0]; - p.control_1 = neutral_steps_arr[1]; - p.control_2 = neutral_steps_arr[2]; - p.control_3 = neutral_steps_arr[3]; - p.control_4 = neutral_steps_arr[4]; - p.control_5 = neutral_steps_arr[5]; - p.control_6 = neutral_steps_arr[6]; - p.control_7 = neutral_steps_arr[7]; - p.control_8 = neutral_steps_arr[8]; - p.control_9 = neutral_steps_arr[9]; - p.control_10 = neutral_steps_arr[10]; + p.neutral_0 = neutral_steps_arr[0]; + p.neutral_1 = neutral_steps_arr[1]; + p.neutral_2 = neutral_steps_arr[2]; + p.neutral_3 = neutral_steps_arr[3]; + p.neutral_4 = neutral_steps_arr[4]; + p.neutral_5 = neutral_steps_arr[5]; + p.neutral_6 = neutral_steps_arr[6]; + p.neutral_7 = neutral_steps_arr[7]; + p.neutral_8 = neutral_steps_arr[8]; + p.neutral_9 = neutral_steps_arr[9]; + p.neutral_10 = neutral_steps_arr[10]; } let p_ref = palette.as_ref(); @@ -934,24 +934,24 @@ impl ThemeBuilder { let bg_index = color_index(bg, step_array.len()); let mut component_hovered_overlay = if bg_index < 91 { - p_ref.control_10 + p_ref.neutral_10 } else { - p_ref.control_0 + p_ref.neutral_0 }; component_hovered_overlay.alpha = 0.1; let mut component_pressed_overlay = component_hovered_overlay; component_pressed_overlay.alpha = 0.2; - // Standard button background is control 7 with 25% opacity + // Standard button background is neutral 7 with 25% opacity let button_bg = { - let mut color = p_ref.control_7; + let mut color = p_ref.neutral_7; color.alpha = 0.25; color }; let (mut button_hovered_overlay, mut button_pressed_overlay) = - (p_ref.control_5, p_ref.control_2); + (p_ref.neutral_5, p_ref.neutral_2); button_hovered_overlay.alpha = 0.2; button_pressed_overlay.alpha = 0.5; @@ -959,7 +959,7 @@ impl ThemeBuilder { let on_bg_component = get_text( color_index(bg_component, step_array.len()), &step_array, - &p_ref.control_8, + &p_ref.neutral_8, text_steps_array.as_deref(), ); @@ -967,17 +967,17 @@ impl ThemeBuilder { let container_bg = if let Some(primary_container_bg_color) = primary_container_bg { primary_container_bg_color } else { - get_surface_color(bg_index, 5, &step_array, is_dark, &p_ref.control_1) + get_surface_color(bg_index, 5, &step_array, is_dark, &p_ref.neutral_1) }; let base_index: usize = color_index(container_bg, step_array.len()); let component_base = - get_surface_color(base_index, 6, &step_array, is_dark, &p_ref.control_3); + get_surface_color(base_index, 6, &step_array, is_dark, &p_ref.neutral_3); component_hovered_overlay = if base_index < 91 { - p_ref.control_10 + p_ref.neutral_10 } else { - p_ref.control_0 + p_ref.neutral_0 }; component_hovered_overlay.alpha = 0.1; @@ -991,22 +991,22 @@ impl ThemeBuilder { get_text( color_index(component_base, step_array.len()), &step_array, - &p_ref.control_8, + &p_ref.neutral_8, text_steps_array.as_deref(), ), component_hovered_overlay, component_pressed_overlay, is_high_contrast, - p_ref.control_8, + p_ref.neutral_8, ), container_bg, get_text( base_index, &step_array, - &p_ref.control_8, + &p_ref.neutral_8, text_steps_array.as_deref(), ), - get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.control_6), + get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.neutral_6), is_high_contrast, ); @@ -1072,16 +1072,16 @@ impl ThemeBuilder { component_hovered_overlay, component_pressed_overlay, is_high_contrast, - p_ref.control_8, + p_ref.neutral_8, ), bg, get_text( bg_index, &step_array, - &p_ref.control_8, + &p_ref.neutral_8, text_steps_array.as_deref(), ), - get_small_widget_color(bg_index, 5, &neutral_steps, &p_ref.control_6), + get_small_widget_color(bg_index, 5, &neutral_steps, &p_ref.neutral_6), is_high_contrast, ), primary, @@ -1089,17 +1089,17 @@ impl ThemeBuilder { let container_bg = if let Some(secondary_container_bg) = secondary_container_bg { secondary_container_bg } else { - get_surface_color(bg_index, 10, &step_array, is_dark, &p_ref.control_2) + get_surface_color(bg_index, 10, &step_array, is_dark, &p_ref.neutral_2) }; let base_index = color_index(container_bg, step_array.len()); let secondary_component = - get_surface_color(base_index, 3, &step_array, is_dark, &p_ref.control_4); + get_surface_color(base_index, 3, &step_array, is_dark, &p_ref.neutral_4); component_hovered_overlay = if base_index < 91 { - p_ref.control_10 + p_ref.neutral_10 } else { - p_ref.control_0 + p_ref.neutral_0 }; component_hovered_overlay.alpha = 0.1; @@ -1113,36 +1113,36 @@ impl ThemeBuilder { get_text( color_index(secondary_component, step_array.len()), &step_array, - &p_ref.control_8, + &p_ref.neutral_8, text_steps_array.as_deref(), ), component_hovered_overlay, component_pressed_overlay, is_high_contrast, - p_ref.control_8, + p_ref.neutral_8, ), container_bg, get_text( base_index, &step_array, - &p_ref.control_8, + &p_ref.neutral_8, text_steps_array.as_deref(), ), - get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.control_6), + get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.neutral_6), is_high_contrast, ) }, accent: Component::colored_component( accent, - p_ref.control_0, + p_ref.neutral_0, accent, button_hovered_overlay, button_pressed_overlay, ), accent_button: Component::colored_button( accent, - p_ref.control_1, - p_ref.control_0, + p_ref.neutral_1, + p_ref.neutral_0, accent, button_hovered_overlay, button_pressed_overlay, @@ -1154,19 +1154,19 @@ impl ThemeBuilder { button_hovered_overlay, button_pressed_overlay, is_high_contrast, - p_ref.control_8, + p_ref.neutral_8, ), destructive: Component::colored_component( destructive, - p_ref.control_0, + p_ref.neutral_0, accent, button_hovered_overlay, button_pressed_overlay, ), destructive_button: Component::colored_button( destructive, - p_ref.control_1, - p_ref.control_0, + p_ref.neutral_1, + p_ref.neutral_0, accent, button_hovered_overlay, button_pressed_overlay, @@ -1174,11 +1174,11 @@ impl ThemeBuilder { icon_button: Component::component( Srgba::new(0.0, 0.0, 0.0, 0.0), accent, - p_ref.control_8, + p_ref.neutral_8, button_hovered_overlay, button_pressed_overlay, is_high_contrast, - p_ref.control_8, + p_ref.neutral_8, ), link_button: { let mut component = Component::component( @@ -1188,7 +1188,7 @@ impl ThemeBuilder { Srgba::new(0.0, 0.0, 0.0, 0.0), Srgba::new(0.0, 0.0, 0.0, 0.0), is_high_contrast, - p_ref.control_8, + p_ref.neutral_8, ); let mut on_50 = component.on; @@ -1199,15 +1199,15 @@ impl ThemeBuilder { }, success: Component::colored_component( success, - p_ref.control_0, + p_ref.neutral_0, accent, button_hovered_overlay, button_pressed_overlay, ), success_button: Component::colored_button( success, - p_ref.control_1, - p_ref.control_0, + p_ref.neutral_1, + p_ref.neutral_0, accent, button_hovered_overlay, button_pressed_overlay, @@ -1219,19 +1219,19 @@ impl ThemeBuilder { button_hovered_overlay, button_pressed_overlay, is_high_contrast, - p_ref.control_8, + p_ref.neutral_8, ), warning: Component::colored_component( warning, - p_ref.control_0, + p_ref.neutral_0, accent, button_hovered_overlay, button_pressed_overlay, ), warning_button: Component::colored_button( warning, - p_ref.control_10, - p_ref.control_0, + p_ref.neutral_10, + p_ref.neutral_0, accent, button_hovered_overlay, button_pressed_overlay, From 7748e59ae6576d93fc8b9594b1e159682e8ab844 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 17 Jul 2025 14:48:54 -0400 Subject: [PATCH 236/556] refactor: better method of implementing tinted control colors --- cosmic-theme/src/model/theme.rs | 211 +++++++++++++++++++++++--------- 1 file changed, 155 insertions(+), 56 deletions(-) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index d159b405..4d42ad3f 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -100,6 +100,10 @@ pub struct Theme { /// accent text colors /// If None, accent base color is the accent text color. pub accent_text: Option, + /// control tint color + pub control_tint: Option, + /// text tint color + pub text_tint: Option, } impl Default for Theme { @@ -164,6 +168,109 @@ impl Theme { todo!(); } + #[must_use] + #[allow(clippy::doc_markdown)] + #[inline] + /// get control_0 color + pub fn control_0(&self) -> Srgba { + self.tint_neutral(self.palette.neutral_0) + } + + #[must_use] + #[allow(clippy::doc_markdown)] + #[inline] + /// get control_1 color + pub fn control_1(&self) -> Srgba { + self.tint_neutral(self.palette.neutral_1) + } + + #[must_use] + #[allow(clippy::doc_markdown)] + #[inline] + /// get control_2 color + pub fn control_2(&self) -> Srgba { + self.tint_neutral(self.palette.neutral_2) + } + + #[must_use] + #[allow(clippy::doc_markdown)] + #[inline] + /// get control_3 color + pub fn control_3(&self) -> Srgba { + self.tint_neutral(self.palette.neutral_3) + } + + #[must_use] + #[allow(clippy::doc_markdown)] + #[inline] + /// get control_3 color + pub fn control_4(&self) -> Srgba { + self.tint_neutral(self.palette.neutral_4) + } + + #[must_use] + #[allow(clippy::doc_markdown)] + #[inline] + /// get control_3 color + pub fn control_5(&self) -> Srgba { + self.tint_neutral(self.palette.neutral_5) + } + + #[must_use] + #[allow(clippy::doc_markdown)] + #[inline] + /// get control_3 color + pub fn control_6(&self) -> Srgba { + self.tint_neutral(self.palette.neutral_6) + } + + #[must_use] + #[allow(clippy::doc_markdown)] + #[inline] + /// get control_3 color + pub fn control_7(&self) -> Srgba { + self.tint_neutral(self.palette.neutral_7) + } + + #[must_use] + #[allow(clippy::doc_markdown)] + #[inline] + /// get control_3 color + pub fn control_8(&self) -> Srgba { + self.tint_neutral(self.palette.neutral_8) + } + + #[must_use] + #[allow(clippy::doc_markdown)] + #[inline] + /// get control_3 color + pub fn control_9(&self) -> Srgba { + self.tint_neutral(self.palette.neutral_9) + } + + #[must_use] + #[allow(clippy::doc_markdown)] + #[inline] + /// get control_3 color + pub fn control_10(&self) -> Srgba { + self.tint_neutral(self.palette.neutral_10) + } + + #[must_use] + #[allow(clippy::doc_markdown)] + #[inline] + /// get @accent_color + fn tint_neutral(&self, neutral: Srgba) -> Srgba { + let Some(tint) = self.control_tint else { + return neutral; + }; + let mut oklch_neutral: Oklcha = neutral.into_color(); + let oklch_tint: Oklcha = tint.into_color(); + oklch_neutral.hue = oklch_tint.hue; + oklch_neutral.chroma = oklch_tint.chroma; + oklch_neutral.into_color() + } + // TODO convenient getter functions for each named color variable #[must_use] #[allow(clippy::doc_markdown)] @@ -897,25 +1004,15 @@ impl ThemeBuilder { let text_steps_array = text_tint.map(|c| steps(c, NonZeroUsize::new(100).unwrap())); - if let Some(neutral_tint) = neutral_tint { + let control_steps_array = if let Some(neutral_tint) = neutral_tint { let mut neutral_steps_arr = steps(neutral_tint, NonZeroUsize::new(11).unwrap()); if !is_dark { neutral_steps_arr.reverse(); } - - let p = palette.as_mut(); - p.neutral_0 = neutral_steps_arr[0]; - p.neutral_1 = neutral_steps_arr[1]; - p.neutral_2 = neutral_steps_arr[2]; - p.neutral_3 = neutral_steps_arr[3]; - p.neutral_4 = neutral_steps_arr[4]; - p.neutral_5 = neutral_steps_arr[5]; - p.neutral_6 = neutral_steps_arr[6]; - p.neutral_7 = neutral_steps_arr[7]; - p.neutral_8 = neutral_steps_arr[8]; - p.neutral_9 = neutral_steps_arr[9]; - p.neutral_10 = neutral_steps_arr[10]; - } + neutral_steps_arr + } else { + steps(palette.as_ref().neutral_2, NonZeroUsize::new(11).unwrap()) + }; let p_ref = palette.as_ref(); @@ -934,9 +1031,9 @@ impl ThemeBuilder { let bg_index = color_index(bg, step_array.len()); let mut component_hovered_overlay = if bg_index < 91 { - p_ref.neutral_10 + control_steps_array[10] } else { - p_ref.neutral_0 + control_steps_array[0] }; component_hovered_overlay.alpha = 0.1; @@ -945,13 +1042,13 @@ impl ThemeBuilder { // Standard button background is neutral 7 with 25% opacity let button_bg = { - let mut color = p_ref.neutral_7; + let mut color = control_steps_array[7]; color.alpha = 0.25; color }; let (mut button_hovered_overlay, mut button_pressed_overlay) = - (p_ref.neutral_5, p_ref.neutral_2); + (control_steps_array[5], control_steps_array[2]); button_hovered_overlay.alpha = 0.2; button_pressed_overlay.alpha = 0.5; @@ -959,7 +1056,7 @@ impl ThemeBuilder { let on_bg_component = get_text( color_index(bg_component, step_array.len()), &step_array, - &p_ref.neutral_8, + &control_steps_array[8], text_steps_array.as_deref(), ); @@ -967,17 +1064,17 @@ impl ThemeBuilder { let container_bg = if let Some(primary_container_bg_color) = primary_container_bg { primary_container_bg_color } else { - get_surface_color(bg_index, 5, &step_array, is_dark, &p_ref.neutral_1) + get_surface_color(bg_index, 5, &step_array, is_dark, &control_steps_array[1]) }; let base_index: usize = color_index(container_bg, step_array.len()); let component_base = - get_surface_color(base_index, 6, &step_array, is_dark, &p_ref.neutral_3); + get_surface_color(base_index, 6, &step_array, is_dark, &control_steps_array[3]); component_hovered_overlay = if base_index < 91 { - p_ref.neutral_10 + control_steps_array[10] } else { - p_ref.neutral_0 + control_steps_array[0] }; component_hovered_overlay.alpha = 0.1; @@ -991,22 +1088,22 @@ impl ThemeBuilder { get_text( color_index(component_base, step_array.len()), &step_array, - &p_ref.neutral_8, + &control_steps_array[8], text_steps_array.as_deref(), ), component_hovered_overlay, component_pressed_overlay, is_high_contrast, - p_ref.neutral_8, + control_steps_array[8], ), container_bg, get_text( base_index, &step_array, - &p_ref.neutral_8, + &control_steps_array[8], text_steps_array.as_deref(), ), - get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.neutral_6), + get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]), is_high_contrast, ); @@ -1072,16 +1169,16 @@ impl ThemeBuilder { component_hovered_overlay, component_pressed_overlay, is_high_contrast, - p_ref.neutral_8, + control_steps_array[8], ), bg, get_text( bg_index, &step_array, - &p_ref.neutral_8, + &control_steps_array[8], text_steps_array.as_deref(), ), - get_small_widget_color(bg_index, 5, &neutral_steps, &p_ref.neutral_6), + get_small_widget_color(bg_index, 5, &neutral_steps, &control_steps_array[6]), is_high_contrast, ), primary, @@ -1089,17 +1186,17 @@ impl ThemeBuilder { let container_bg = if let Some(secondary_container_bg) = secondary_container_bg { secondary_container_bg } else { - get_surface_color(bg_index, 10, &step_array, is_dark, &p_ref.neutral_2) + get_surface_color(bg_index, 10, &step_array, is_dark, &control_steps_array[2]) }; let base_index = color_index(container_bg, step_array.len()); let secondary_component = - get_surface_color(base_index, 3, &step_array, is_dark, &p_ref.neutral_4); + get_surface_color(base_index, 3, &step_array, is_dark, &control_steps_array[4]); component_hovered_overlay = if base_index < 91 { - p_ref.neutral_10 + control_steps_array[10] } else { - p_ref.neutral_0 + control_steps_array[0] }; component_hovered_overlay.alpha = 0.1; @@ -1113,36 +1210,36 @@ impl ThemeBuilder { get_text( color_index(secondary_component, step_array.len()), &step_array, - &p_ref.neutral_8, + &control_steps_array[8], text_steps_array.as_deref(), ), component_hovered_overlay, component_pressed_overlay, is_high_contrast, - p_ref.neutral_8, + control_steps_array[8], ), container_bg, get_text( base_index, &step_array, - &p_ref.neutral_8, + &control_steps_array[8], text_steps_array.as_deref(), ), - get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.neutral_6), + get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]), is_high_contrast, ) }, accent: Component::colored_component( accent, - p_ref.neutral_0, + control_steps_array[0], accent, button_hovered_overlay, button_pressed_overlay, ), accent_button: Component::colored_button( accent, - p_ref.neutral_1, - p_ref.neutral_0, + control_steps_array[1], + control_steps_array[0], accent, button_hovered_overlay, button_pressed_overlay, @@ -1154,19 +1251,19 @@ impl ThemeBuilder { button_hovered_overlay, button_pressed_overlay, is_high_contrast, - p_ref.neutral_8, + control_steps_array[8], ), destructive: Component::colored_component( destructive, - p_ref.neutral_0, + control_steps_array[0], accent, button_hovered_overlay, button_pressed_overlay, ), destructive_button: Component::colored_button( destructive, - p_ref.neutral_1, - p_ref.neutral_0, + control_steps_array[1], + control_steps_array[0], accent, button_hovered_overlay, button_pressed_overlay, @@ -1174,11 +1271,11 @@ impl ThemeBuilder { icon_button: Component::component( Srgba::new(0.0, 0.0, 0.0, 0.0), accent, - p_ref.neutral_8, + control_steps_array[8], button_hovered_overlay, button_pressed_overlay, is_high_contrast, - p_ref.neutral_8, + control_steps_array[8], ), link_button: { let mut component = Component::component( @@ -1188,7 +1285,7 @@ impl ThemeBuilder { Srgba::new(0.0, 0.0, 0.0, 0.0), Srgba::new(0.0, 0.0, 0.0, 0.0), is_high_contrast, - p_ref.neutral_8, + control_steps_array[8], ); let mut on_50 = component.on; @@ -1199,15 +1296,15 @@ impl ThemeBuilder { }, success: Component::colored_component( success, - p_ref.neutral_0, + control_steps_array[0], accent, button_hovered_overlay, button_pressed_overlay, ), success_button: Component::colored_button( success, - p_ref.neutral_1, - p_ref.neutral_0, + control_steps_array[1], + control_steps_array[0], accent, button_hovered_overlay, button_pressed_overlay, @@ -1219,19 +1316,19 @@ impl ThemeBuilder { button_hovered_overlay, button_pressed_overlay, is_high_contrast, - p_ref.neutral_8, + control_steps_array[8], ), warning: Component::colored_component( warning, - p_ref.neutral_0, + control_steps_array[0], accent, button_hovered_overlay, button_pressed_overlay, ), warning_button: Component::colored_button( warning, - p_ref.neutral_10, - p_ref.neutral_0, + control_steps_array[10], + control_steps_array[0], accent, button_hovered_overlay, button_pressed_overlay, @@ -1246,6 +1343,8 @@ impl ThemeBuilder { window_hint, is_frosted, accent_text, + control_tint: neutral_tint, + text_tint, }; theme.spacing = spacing; theme.corner_radii = corner_radii; From ec7a531539ab0fbb3c488b184a27dd273e61cde7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Fri, 20 Jun 2025 01:56:04 +0200 Subject: [PATCH 237/556] chore: use `with_alpha()` where applicable --- cosmic-theme/src/model/derivation.rs | 20 +++------ cosmic-theme/src/model/theme.rs | 29 +++++-------- cosmic-theme/src/output/gtk4_output.rs | 5 +-- src/theme/style/iced.rs | 57 +++++++++----------------- src/theme/style/segmented_button.rs | 35 ++++++++-------- src/widget/spin_button.rs | 2 - 6 files changed, 57 insertions(+), 91 deletions(-) diff --git a/cosmic-theme/src/model/derivation.rs b/cosmic-theme/src/model/derivation.rs index f4147c2c..bcc4990f 100644 --- a/cosmic-theme/src/model/derivation.rs +++ b/cosmic-theme/src/model/derivation.rs @@ -1,4 +1,4 @@ -use palette::Srgba; +use palette::{Srgba, WithAlpha}; use serde::{Deserialize, Serialize}; use crate::composite::over; @@ -27,9 +27,7 @@ impl Container { mut small_widget: Srgba, is_high_contrast: bool, ) -> Self { - let mut divider_c = on; - divider_c.alpha = if is_high_contrast { 0.5 } else { 0.2 }; - + let divider_c = on.with_alpha(if is_high_contrast { 0.5 } else { 0.2 }); small_widget.alpha = 0.25; Self { @@ -115,13 +113,11 @@ impl Component { hovered: Srgba, pressed: Srgba, ) -> Self { - let base: Srgba = base; let mut base_50 = base; base_50.alpha *= 0.5; let on_20 = neutral; - let mut on_50: Srgba = on_20; - on_50.alpha = 0.5; + let on_50 = on_20.with_alpha(0.5); Component { base, @@ -151,8 +147,7 @@ impl Component { let mut component = Component::colored_component(base, overlay, accent, hovered, pressed); component.on = on_button; - let mut on_disabled = on_button; - on_disabled.alpha = 0.5; + let on_disabled = on_button.with_alpha(0.5); component.on_disabled = on_disabled; component @@ -172,11 +167,8 @@ impl Component { let mut base_50 = base; base_50.alpha *= 0.5; - let mut on_20 = on_component; - let mut on_50 = on_20; - - on_20.alpha = 0.2; - on_50.alpha = 0.5; + let on_20 = on_component.with_alpha(0.2); + let on_50 = on_20.with_alpha(0.5); let mut disabled_border = border; disabled_border.alpha *= 0.5; diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 4d42ad3f..6b9d1783 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -1,11 +1,13 @@ use crate::{ composite::over, - steps::{color_index, get_index, get_small_widget_color, get_surface_color, get_text, steps}, + steps::{color_index, get_small_widget_color, get_surface_color, get_text, steps}, Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, Spacing, ThemeMode, DARK_PALETTE, LIGHT_PALETTE, NAME, }; use cosmic_config::{Config, CosmicConfigEntry}; -use palette::{color_difference::Wcag21RelativeContrast, rgb::Rgb, IntoColor, Oklcha, Srgb, Srgba}; +use palette::{ + color_difference::Wcag21RelativeContrast, rgb::Rgb, IntoColor, Oklcha, Srgb, Srgba, WithAlpha, +}; use serde::{Deserialize, Serialize}; use std::num::NonZeroUsize; @@ -309,9 +311,7 @@ impl Theme { #[inline] /// get @small_widget_divider pub fn small_widget_divider(&self) -> Srgba { - let mut neutral_9 = self.palette.neutral_9; - neutral_9.alpha = 0.2; - neutral_9 + self.palette.neutral_9.with_alpha(0.2) } // Containers @@ -1041,16 +1041,12 @@ impl ThemeBuilder { component_pressed_overlay.alpha = 0.2; // Standard button background is neutral 7 with 25% opacity - let button_bg = { - let mut color = control_steps_array[7]; - color.alpha = 0.25; - color - }; + let button_bg = control_steps_array[7].with_alpha(0.25); - let (mut button_hovered_overlay, mut button_pressed_overlay) = - (control_steps_array[5], control_steps_array[2]); - button_hovered_overlay.alpha = 0.2; - button_pressed_overlay.alpha = 0.5; + let (button_hovered_overlay, button_pressed_overlay) = ( + control_steps_array[5].with_alpha(0.2), + control_steps_array[2].with_alpha(0.5), + ); let bg_component = get_surface_color(bg_index, 8, &step_array, is_dark, &p_ref.neutral_2); let on_bg_component = get_text( @@ -1288,10 +1284,7 @@ impl ThemeBuilder { control_steps_array[8], ); - let mut on_50 = component.on; - on_50.alpha = 0.5; - - component.on_disabled = over(on_50, component.base); + component.on_disabled = over(component.on.with_alpha(0.5), component.base); component }, success: Component::colored_component( diff --git a/cosmic-theme/src/output/gtk4_output.rs b/cosmic-theme/src/output/gtk4_output.rs index c172e4ec..9d7210f0 100644 --- a/cosmic-theme/src/output/gtk4_output.rs +++ b/cosmic-theme/src/output/gtk4_output.rs @@ -1,5 +1,5 @@ use crate::{composite::over, steps::steps, Component, Theme}; -use palette::{rgb::Rgba, Darken, IntoColor, Lighten, Srgba}; +use palette::{rgb::Rgba, Darken, IntoColor, Lighten, Srgba, WithAlpha}; use std::{ fs::{self, File}, io::{self, Write}, @@ -75,8 +75,7 @@ impl Theme { Rgba::new(0.0, 0.0, 0.0, 0.08) }); - let mut inverted_bg_divider = background.base; - inverted_bg_divider.alpha = 0.5; + let inverted_bg_divider = background.base.with_alpha(0.5); let scrollbar_outline = to_rgba(inverted_bg_divider); let mut css = format! {r#"/* GENERATED BY COSMIC */ diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index b284e4e3..f62e5825 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -16,6 +16,7 @@ use iced::{ }; use iced_core::{Background, Border, Color, Shadow, Vector}; use iced_widget::{pane_grid::Highlight, text_editor, text_input}; +use palette::WithAlpha; use std::rc::Rc; pub mod application { @@ -720,9 +721,8 @@ impl slider::Catalog for Theme { border_radius: cosmic.corner_radii.radius_m.into(), }; appearance.handle.border_width = 3.0; - let mut border_color = self.cosmic().palette.neutral_10; - border_color.alpha = 0.1; - appearance.handle.border_color = border_color.into(); + appearance.handle.border_color = + self.cosmic().palette.neutral_10.with_alpha(0.1).into(); appearance } Slider::Custom { hovered, .. } => hovered(self), @@ -736,15 +736,12 @@ impl slider::Catalog for Theme { border_radius: cosmic.corner_radii.radius_m.into(), }; appearance.handle.border_width = 3.0; - let mut border_color = self.cosmic().palette.neutral_10; - border_color.alpha = 0.1; - appearance.handle.border_color = border_color.into(); + appearance.handle.border_color = + self.cosmic().palette.neutral_10.with_alpha(0.1).into(); appearance }; - let mut border_color = self.cosmic().palette.neutral_10; - border_color.alpha = 0.2; - style.handle.border_color = border_color.into(); - + style.handle.border_color = + self.cosmic().palette.neutral_10.with_alpha(0.2).into(); style } Slider::Custom { dragging, .. } => dragging(self), @@ -824,8 +821,6 @@ impl radio::Catalog for Theme { fn style(&self, class: &Self::Class<'_>, status: radio::Status) -> radio::Style { let theme = self.cosmic(); - let mut neutral_10 = theme.palette.neutral_10; - neutral_10.alpha = 0.1; match status { radio::Status::Active { is_selected } => radio::Style { @@ -850,7 +845,7 @@ impl radio::Catalog for Theme { Color::from(theme.accent.base).into() } else { // TODO: this seems to be defined weirdly in FIGMA - Color::from(neutral_10).into() + Color::from(theme.palette.neutral_10.with_alpha(0.1)).into() }, dot_color: theme.accent.on.into(), border_width: 1.0, @@ -877,8 +872,7 @@ impl toggler::Catalog for Theme { fn style(&self, class: &Self::Class<'_>, status: toggler::Status) -> toggler::Style { let cosmic = self.cosmic(); const HANDLE_MARGIN: f32 = 2.0; - let mut neutral_10 = cosmic.palette.neutral_10; - neutral_10.alpha = 0.1; + let neutral_10 = cosmic.palette.neutral_10.with_alpha(0.1); let mut active = toggler::Style { background: if matches!(status, toggler::Status::Active { is_toggled: true }) { @@ -1098,8 +1092,7 @@ impl scrollable::Catalog for Theme { match status { scrollable::Status::Active => { let cosmic = self.cosmic(); - let mut neutral_5 = cosmic.palette.neutral_5; - neutral_5.alpha = 0.7; + let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7); let mut a = scrollable::Style { container: iced_container::transparent(self), vertical_rail: scrollable::Rail { @@ -1134,8 +1127,7 @@ impl scrollable::Catalog for Theme { }; if matches!(class, Scrollable::Permanent) { - let mut neutral_3 = cosmic.palette.neutral_3; - neutral_3.alpha = 0.7; + let neutral_3 = cosmic.palette.neutral_3.with_alpha(0.7); a.horizontal_rail.background = Some(Background::Color(neutral_3.into())); a.vertical_rail.background = Some(Background::Color(neutral_3.into())); } @@ -1145,14 +1137,12 @@ impl scrollable::Catalog for Theme { // TODO handle vertical / horizontal scrollable::Status::Hovered { .. } | scrollable::Status::Dragged { .. } => { let cosmic = self.cosmic(); - let mut neutral_5 = cosmic.palette.neutral_5; - neutral_5.alpha = 0.7; + let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7); // TODO hover // if is_mouse_over_scrollbar { - // let mut hover_overlay = cosmic.palette.neutral_0; - // hover_overlay.alpha = 0.2; + // let hover_overlay = cosmic.palette.neutral_0.with_alpha(0.2); // neutral_5 = over(hover_overlay, neutral_5); // } let mut a: scrollable::Style = scrollable::Style { @@ -1189,8 +1179,7 @@ impl scrollable::Catalog for Theme { }; if matches!(class, Scrollable::Permanent) { - let mut neutral_3 = cosmic.palette.neutral_3; - neutral_3.alpha = 0.7; + let neutral_3 = cosmic.palette.neutral_3.with_alpha(0.7); a.horizontal_rail.background = Some(Background::Color(neutral_3.into())); a.vertical_rail.background = Some(Background::Color(neutral_3.into())); } @@ -1289,13 +1278,11 @@ impl text_input::Catalog for Theme { fn style(&self, class: &Self::Class<'_>, status: text_input::Status) -> text_input::Style { let palette = self.cosmic(); - let mut bg = palette.palette.neutral_7; - bg.alpha = 0.25; + let bg = palette.palette.neutral_7.with_alpha(0.25); - let mut neutral_9 = palette.palette.neutral_9; + let neutral_9 = palette.palette.neutral_9; let value = neutral_9.into(); - neutral_9.alpha = 0.7; - let placeholder = neutral_9.into(); + let placeholder = neutral_9.with_alpha(0.7).into(); let selection = palette.accent.base.into(); let mut appearance = match class { @@ -1327,8 +1314,7 @@ impl text_input::Catalog for Theme { match status { text_input::Status::Active => appearance, text_input::Status::Hovered => { - let mut bg = palette.palette.neutral_7; - bg.alpha = 0.25; + let bg = palette.palette.neutral_7.with_alpha(0.25); match class { TextInput::Default => text_input::Style { @@ -1357,8 +1343,7 @@ impl text_input::Catalog for Theme { } } text_input::Status::Focused => { - let mut bg = palette.palette.neutral_7; - bg.alpha = 0.25; + let bg = palette.palette.neutral_7.with_alpha(0.25); match class { TextInput::Default => text_input::Style { @@ -1433,9 +1418,7 @@ impl iced_widget::text_editor::Catalog for Theme { let selection = cosmic.accent.base.into(); let value = cosmic.palette.neutral_9.into(); - let mut placeholder = cosmic.palette.neutral_9; - placeholder.alpha = 0.7; - let placeholder = placeholder.into(); + let placeholder = cosmic.palette.neutral_9.with_alpha(0.7).into(); let icon = cosmic.background.on.into(); match status { diff --git a/src/theme/style/segmented_button.rs b/src/theme/style/segmented_button.rs index 70e2c937..c4d81151 100644 --- a/src/theme/style/segmented_button.rs +++ b/src/theme/style/segmented_button.rs @@ -7,6 +7,7 @@ use crate::widget::segmented_button::{Appearance, ItemAppearance, StyleSheet}; use crate::{theme::Theme, widget::segmented_button::ItemStatusAppearance}; use cosmic_theme::{Component, Container}; use iced_core::{Background, border::Radius}; +use palette::WithAlpha; #[derive(Default)] pub enum SegmentedButton { @@ -166,19 +167,19 @@ mod horizontal { use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance}; use cosmic_theme::Component; use iced_core::{Background, border::Radius}; + use palette::WithAlpha; pub fn selection_active( cosmic: &cosmic_theme::Theme, component: &Component, ) -> ItemStatusAppearance { - let mut color = cosmic.palette.neutral_5; - color.alpha = 0.2; - let rad_m = cosmic.corner_radii.radius_m; let rad_0 = cosmic.corner_radii.radius_0; ItemStatusAppearance { - background: Some(Background::Color(color.into())), + background: Some(Background::Color( + cosmic.palette.neutral_5.with_alpha(0.2).into(), + )), first: ItemAppearance { border_radius: Radius::from([rad_m[0], rad_0[1], rad_0[2], rad_m[3]]), ..Default::default() @@ -196,12 +197,12 @@ mod horizontal { } pub fn tab_bar_active(cosmic: &cosmic_theme::Theme) -> ItemStatusAppearance { - let mut neutral_5 = cosmic.palette.neutral_5; - neutral_5.alpha = 0.2; let rad_s = cosmic.corner_radii.radius_s; let rad_0 = cosmic.corner_radii.radius_0; ItemStatusAppearance { - background: Some(Background::Color(neutral_5.into())), + background: Some(Background::Color( + cosmic.palette.neutral_5.with_alpha(0.2).into(), + )), first: ItemAppearance { border_radius: Radius::from([rad_s[0], rad_s[1], rad_0[2], rad_0[3]]), border_bottom: Some((4.0, cosmic.accent.base.into())), @@ -240,10 +241,10 @@ pub fn hover( component: &Component, default: &ItemStatusAppearance, ) -> ItemStatusAppearance { - let mut color = cosmic.palette.neutral_8; - color.alpha = 0.2; ItemStatusAppearance { - background: Some(Background::Color(color.into())), + background: Some(Background::Color( + cosmic.palette.neutral_8.with_alpha(0.2).into(), + )), text_color: cosmic.accent.base.into(), ..*default } @@ -253,19 +254,19 @@ mod vertical { use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance}; use cosmic_theme::Component; use iced_core::{Background, border::Radius}; + use palette::WithAlpha; pub fn selection_active( cosmic: &cosmic_theme::Theme, component: &Component, ) -> ItemStatusAppearance { - let mut color = component.selected_state_color(); - color.alpha = 0.3; - let rad_0 = cosmic.corner_radii.radius_0; let rad_m = cosmic.corner_radii.radius_m; ItemStatusAppearance { - background: Some(Background::Color(color.into())), + background: Some(Background::Color( + component.selected_state_color().with_alpha(0.3).into(), + )), first: ItemAppearance { border_radius: Radius::from([rad_m[0], rad_m[1], rad_0[2], rad_0[3]]), ..Default::default() @@ -283,10 +284,10 @@ mod vertical { } pub fn tab_bar_active(cosmic: &cosmic_theme::Theme) -> ItemStatusAppearance { - let mut neutral_5 = cosmic.palette.neutral_5; - neutral_5.alpha = 0.2; ItemStatusAppearance { - background: Some(Background::Color(neutral_5.into())), + background: Some(Background::Color( + cosmic.palette.neutral_5.with_alpha(0.2).into(), + )), first: ItemAppearance { border_radius: cosmic.corner_radii.radius_m.into(), ..Default::default() diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index 0dba4109..8186a689 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -219,8 +219,6 @@ where #[allow(clippy::trivially_copy_pass_by_ref)] fn container_style(theme: &crate::Theme) -> iced_widget::container::Style { let cosmic_theme = &theme.cosmic(); - let mut neutral_10 = cosmic_theme.palette.neutral_10; - neutral_10.alpha = 0.1; let accent = &cosmic_theme.accent; let corners = &cosmic_theme.corner_radii; let border = if theme.theme_type.is_high_contrast() { From 38dde24f96658ae90f0d654d28b965044196dc40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Fri, 20 Jun 2025 01:54:56 +0200 Subject: [PATCH 238/556] chore(applet): add spacing field --- src/applet/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/applet/mod.rs b/src/applet/mod.rs index e0ab993c..ded92cf6 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -21,7 +21,7 @@ use crate::{ }; pub use cosmic_panel_config; use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize}; -use iced_core::{Layout, Padding, Shadow}; +use iced_core::{Padding, Shadow}; use iced_widget::runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner}; use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity}; use std::{borrow::Cow, num::NonZeroU32, rc::Rc, sync::LazyLock, time::Duration}; @@ -39,6 +39,7 @@ static TOOLTIP_WINDOW_ID: LazyLock = LazyLock::new(window::Id::uniqu pub struct Context { pub size: Size, pub anchor: PanelAnchor, + pub spacing: u32, pub background: CosmicPanelBackground, pub output_name: String, pub panel_type: PanelType, @@ -93,6 +94,10 @@ impl Default for Context { .ok() .and_then(|size| ron::from_str(size.as_str()).ok()) .unwrap_or(PanelAnchor::Top), + spacing: std::env::var("COSMIC_PANEL_SPACING") + .ok() + .and_then(|size| ron::from_str(size.as_str()).ok()) + .unwrap_or(4), background: std::env::var("COSMIC_PANEL_BACKGROUND") .ok() .and_then(|size| ron::from_str(size.as_str()).ok()) From b8eaad2a7e0f853efefef5e4d3f8f88ec9d0297f Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 21 Jul 2025 13:59:09 -0700 Subject: [PATCH 239/556] feat: add `dbus_connection()` method to `app::Application` trait --- src/app/action.rs | 2 ++ src/app/cosmic.rs | 5 +++++ src/app/mod.rs | 8 ++++++++ src/dbus_activation.rs | 6 ++++++ 4 files changed, 21 insertions(+) diff --git a/src/app/action.rs b/src/app/action.rs index 0f05a6a6..cbdd1a55 100644 --- a/src/app/action.rs +++ b/src/app/action.rs @@ -22,6 +22,8 @@ pub enum Action { Close, /// Closes or shows the context drawer. ContextDrawer(bool), + #[cfg(feature = "single-instance")] + DbusConnection(zbus::Connection), /// Requests to drag the window. Drag, /// Window focus changed diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index a2cdeb87..00ce3ddc 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -761,6 +761,11 @@ impl Cosmic { } } + #[cfg(feature = "single-instance")] + Action::DbusConnection(conn) => { + return self.app.dbus_connection(conn); + } + #[cfg(feature = "xdg-portal")] Action::DesktopSettings(crate::theme::portal::Desktop::ColorScheme(s)) => { use ashpd::desktop::settings::ColorScheme; diff --git a/src/app/mod.rs b/src/app/mod.rs index 35b55f02..1b10b68d 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -437,6 +437,14 @@ where fn dbus_activation(&mut self, msg: crate::dbus_activation::Message) -> Task { Task::none() } + + /// Invoked on connect to dbus session socket used for dbus activation + /// + /// Can be used to expose custom interfaces on the same owned name. + #[cfg(feature = "single-instance")] + fn dbus_connection(&mut self, conn: zbus::Connection) -> Task { + Task::none() + } } /// Methods automatically derived for all types implementing [`Application`]. diff --git a/src/dbus_activation.rs b/src/dbus_activation.rs index 9880a7d0..c8931dd4 100644 --- a/src/dbus_activation.rs +++ b/src/dbus_activation.rs @@ -44,6 +44,12 @@ pub fn subscription() -> Subscription Date: Mon, 21 Jul 2025 10:49:47 -0400 Subject: [PATCH 240/556] chore: theme color updates --- cosmic-theme/src/model/theme.rs | 4 +- src/theme/style/button.rs | 12 ++-- src/theme/style/dropdown.rs | 2 +- src/theme/style/iced.rs | 91 +++++++++++++++++++---------- src/theme/style/segmented_button.rs | 14 ++--- src/widget/color_picker/mod.rs | 8 +-- src/widget/menu/menu_tree.rs | 3 +- src/widget/spin_button.rs | 6 +- 8 files changed, 84 insertions(+), 56 deletions(-) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 6b9d1783..e84417e1 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -1277,7 +1277,7 @@ impl ThemeBuilder { let mut component = Component::component( Srgba::new(0.0, 0.0, 0.0, 0.0), accent, - accent, + accent_text.unwrap_or(accent), Srgba::new(0.0, 0.0, 0.0, 0.0), Srgba::new(0.0, 0.0, 0.0, 0.0), is_high_contrast, @@ -1305,7 +1305,7 @@ impl ThemeBuilder { text_button: Component::component( Srgba::new(0.0, 0.0, 0.0, 0.0), accent, - accent, + accent_text.unwrap_or(accent), button_hovered_overlay, button_pressed_overlay, is_high_contrast, diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index 8459908d..1073fe85 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -99,7 +99,7 @@ pub fn appearance( Button::Image => { appearance.background = None; - appearance.text_color = Some(cosmic.accent.base.into()); + appearance.text_color = Some(cosmic.accent_text_color().into()); appearance.icon_color = Some(cosmic.accent.base.into()); corner_radii = &cosmic.corner_radii.radius_s; @@ -119,7 +119,7 @@ pub fn appearance( Button::Link => { appearance.background = None; appearance.icon_color = Some(cosmic.accent.base.into()); - appearance.text_color = Some(cosmic.accent.base.into()); + appearance.text_color = Some(cosmic.accent_text_color().into()); corner_radii = &cosmic.corner_radii.radius_0; } @@ -156,7 +156,7 @@ pub fn appearance( appearance.background = Some(Background::Color(cosmic.primary.component.hover.into())); appearance.icon_color = Some(cosmic.accent.base.into()); - appearance.text_color = Some(cosmic.accent.base.into()); + appearance.text_color = Some(cosmic.accent_text_color().into()); } else { appearance.background = Some(Background::Color(background)); appearance.icon_color = icon; @@ -203,7 +203,7 @@ impl Catalog for crate::Theme { Button::Icon | Button::IconVertical | Button::HeaderBar ) && selected { - Some(self.cosmic().accent_color().into()) + Some(self.cosmic().accent_text_color().into()) } else { Some(component.on.into()) }; @@ -249,7 +249,7 @@ impl Catalog for crate::Theme { Button::Icon | Button::IconVertical | Button::HeaderBar ) && selected { - Some(self.cosmic().accent_color().into()) + Some(self.cosmic().accent_text_color().into()) } else { Some(component.on.into()) }; @@ -270,7 +270,7 @@ impl Catalog for crate::Theme { Button::Icon | Button::IconVertical | Button::HeaderBar ) && selected { - Some(self.cosmic().accent_color().into()) + Some(self.cosmic().accent_text_color().into()) } else { Some(component.on.into()) }; diff --git a/src/theme/style/dropdown.rs b/src/theme/style/dropdown.rs index 02c69c2a..cc89a399 100644 --- a/src/theme/style/dropdown.rs +++ b/src/theme/style/dropdown.rs @@ -21,7 +21,7 @@ impl dropdown::menu::StyleSheet for Theme { hovered_text_color: cosmic.on_bg_color().into(), hovered_background: Background::Color(cosmic.primary.component.hover.into()), - selected_text_color: cosmic.accent.base.into(), + selected_text_color: cosmic.accent_text_color().into(), selected_background: Background::Color(cosmic.primary.component.hover.into()), description_color: cosmic.primary.component.on_disabled.into(), diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index f62e5825..e3da3f98 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -156,8 +156,8 @@ impl Button { Self::Positive => &cosmic.success_button, Self::Destructive => &cosmic.destructive_button, Self::Text => &cosmic.text_button, - Self::Link => &cosmic.accent_button, - Self::LinkActive => &cosmic.accent_button, + Self::Link => &cosmic.link_button, + Self::LinkActive => &cosmic.link_button, Self::Transparent => &TRANSPARENT_COMPONENT, Self::Deactivated => &theme.current_container().component, Self::Card => &theme.current_container().component, @@ -190,6 +190,7 @@ impl iced_checkbox::Catalog for Theme { Checkbox::default() } + #[allow(clippy::too_many_lines)] fn style( &self, class: &Self::Class<'_>, @@ -208,7 +209,7 @@ impl iced_checkbox::Catalog for Theme { background: Background::Color(if is_checked { cosmic.accent.base.into() } else { - cosmic.button.base.into() + cosmic.background.small_widget.into() }), icon_color: cosmic.accent.on.into(), border: Border { @@ -217,7 +218,7 @@ impl iced_checkbox::Catalog for Theme { color: if is_checked { cosmic.accent.base } else { - cosmic.button.border + cosmic.palette.neutral_8 } .into(), }, @@ -251,7 +252,7 @@ impl iced_checkbox::Catalog for Theme { color: if is_checked { cosmic.success.base } else { - cosmic.button.border + cosmic.palette.neutral_8 } .into(), }, @@ -504,7 +505,7 @@ impl iced_container::Catalog for Theme { Container::HeaderBar { focused } => { let (icon_color, text_color) = if *focused { ( - Color::from(cosmic.accent.base), + Color::from(cosmic.accent_text_color()), Color::from(cosmic.background.on), ) } else { @@ -667,9 +668,9 @@ impl slider::Catalog for Theme { ( cosmic.accent_text_color(), if is_dark { - cosmic.palette.neutral_6 + cosmic.palette.neutral_5 } else { - cosmic.palette.neutral_4 + cosmic.palette.neutral_3 }, ) } else { @@ -828,15 +829,14 @@ impl radio::Catalog for Theme { Color::from(theme.accent.base).into() } else { // TODO: this seems to be defined weirdly in FIGMA - Color::from(theme.background.base).into() + Color::from(theme.background.small_widget).into() }, dot_color: theme.accent.on.into(), border_width: 1.0, border_color: if is_selected { Color::from(theme.accent.base) } else { - // TODO: this seems to be defined weirdly in FIGMA - Color::from(theme.palette.neutral_7) + Color::from(theme.palette.neutral_8) }, text_color: None, }, @@ -844,8 +844,7 @@ impl radio::Catalog for Theme { background: if is_selected { Color::from(theme.accent.base).into() } else { - // TODO: this seems to be defined weirdly in FIGMA - Color::from(theme.palette.neutral_10.with_alpha(0.1)).into() + Color::from(theme.background.small_widget.with_alpha(0.2)).into() }, dot_color: theme.accent.on.into(), border_width: 1.0, @@ -878,7 +877,11 @@ impl toggler::Catalog for Theme { background: if matches!(status, toggler::Status::Active { is_toggled: true }) { cosmic.accent.base.into() } else { - cosmic.palette.neutral_5.into() + if cosmic.is_dark { + cosmic.palette.neutral_6.into() + } else { + cosmic.palette.neutral_5.into() + } }, foreground: cosmic.palette.neutral_2.into(), border_radius: cosmic.radius_xl().into(), @@ -900,7 +903,14 @@ impl toggler::Catalog for Theme { background: if is_active { over(neutral_10, cosmic.accent_color()) } else { - over(neutral_10, cosmic.palette.neutral_5) + over( + neutral_10, + if cosmic.is_dark { + cosmic.palette.neutral_6 + } else { + cosmic.palette.neutral_5 + }, + ) } .into(), ..active @@ -1092,7 +1102,8 @@ impl scrollable::Catalog for Theme { match status { scrollable::Status::Active => { let cosmic = self.cosmic(); - let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7); + let mut neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7); + let mut neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7); let mut a = scrollable::Style { container: iced_container::transparent(self), vertical_rail: scrollable::Rail { @@ -1102,7 +1113,11 @@ impl scrollable::Catalog for Theme { }, background: None, scroller: scrollable::Scroller { - color: neutral_5.into(), + color: if cosmic.is_dark { + neutral_6.into() + } else { + neutral_5.into() + }, border: Border { radius: cosmic.corner_radii.radius_s.into(), ..Default::default() @@ -1116,7 +1131,11 @@ impl scrollable::Catalog for Theme { }, background: None, scroller: scrollable::Scroller { - color: neutral_5.into(), + color: if cosmic.is_dark { + neutral_6.into() + } else { + neutral_5.into() + }, border: Border { radius: cosmic.corner_radii.radius_s.into(), ..Default::default() @@ -1137,9 +1156,9 @@ impl scrollable::Catalog for Theme { // TODO handle vertical / horizontal scrollable::Status::Hovered { .. } | scrollable::Status::Dragged { .. } => { let cosmic = self.cosmic(); - let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7); - - // TODO hover + let mut neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7); + let mut neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7); + let mut neutral_3 = cosmic.palette.neutral_3; // if is_mouse_over_scrollbar { // let hover_overlay = cosmic.palette.neutral_0.with_alpha(0.2); @@ -1154,7 +1173,11 @@ impl scrollable::Catalog for Theme { }, background: None, scroller: scrollable::Scroller { - color: neutral_5.into(), + color: if cosmic.is_dark { + neutral_6.into() + } else { + neutral_5.into() + }, border: Border { radius: cosmic.corner_radii.radius_s.into(), ..Default::default() @@ -1168,7 +1191,11 @@ impl scrollable::Catalog for Theme { }, background: None, scroller: scrollable::Scroller { - color: neutral_5.into(), + color: if cosmic.is_dark { + neutral_6.into() + } else { + neutral_5.into() + }, border: Border { radius: cosmic.corner_radii.radius_s.into(), ..Default::default() @@ -1179,9 +1206,13 @@ impl scrollable::Catalog for Theme { }; if matches!(class, Scrollable::Permanent) { - let neutral_3 = cosmic.palette.neutral_3.with_alpha(0.7); - a.horizontal_rail.background = Some(Background::Color(neutral_3.into())); - a.vertical_rail.background = Some(Background::Color(neutral_3.into())); + let small_widget_container = + cosmic.background.small_widget.clone().with_alpha(0.7); + + a.horizontal_rail.background = + Some(Background::Color(small_widget_container.into())); + a.vertical_rail.background = + Some(Background::Color(small_widget_container.into())); } a @@ -1250,7 +1281,7 @@ impl iced_widget::text::Catalog for Theme { fn style(&self, class: &Self::Class<'_>) -> iced_widget::text::Style { match class { Text::Accent => iced_widget::text::Style { - color: Some(self.cosmic().accent.base.into()), + color: Some(self.cosmic().accent_text_color().into()), }, Text::Default => iced_widget::text::Style { color: None }, Text::Color(c) => iced_widget::text::Style { color: Some(*c) }, @@ -1278,7 +1309,7 @@ impl text_input::Catalog for Theme { fn style(&self, class: &Self::Class<'_>, status: text_input::Status) -> text_input::Style { let palette = self.cosmic(); - let bg = palette.palette.neutral_7.with_alpha(0.25); + let mut bg = palette.background.small_widget.with_alpha(0.25); let neutral_9 = palette.palette.neutral_9; let value = neutral_9.into(); @@ -1314,7 +1345,7 @@ impl text_input::Catalog for Theme { match status { text_input::Status::Active => appearance, text_input::Status::Hovered => { - let bg = palette.palette.neutral_7.with_alpha(0.25); + let mut bg = palette.background.small_widget.with_alpha(0.25); match class { TextInput::Default => text_input::Style { @@ -1343,7 +1374,7 @@ impl text_input::Catalog for Theme { } } text_input::Status::Focused => { - let bg = palette.palette.neutral_7.with_alpha(0.25); + let mut bg = palette.background.small_widget.with_alpha(0.25); match class { TextInput::Default => text_input::Style { diff --git a/src/theme/style/segmented_button.rs b/src/theme/style/segmented_button.rs index c4d81151..ff759715 100644 --- a/src/theme/style/segmented_button.rs +++ b/src/theme/style/segmented_button.rs @@ -192,7 +192,7 @@ mod horizontal { border_radius: Radius::from([rad_0[0], rad_m[1], rad_m[2], rad_0[3]]), ..Default::default() }, - text_color: cosmic.accent.base.into(), + text_color: cosmic.accent_text_color().into(), } } @@ -218,7 +218,7 @@ mod horizontal { border_bottom: Some((4.0, cosmic.accent.base.into())), ..Default::default() }, - text_color: cosmic.accent.base.into(), + text_color: cosmic.accent_text_color().into(), } } } @@ -231,7 +231,7 @@ pub fn focus( let color = container.small_widget; ItemStatusAppearance { background: Some(Background::Color(color.into())), - text_color: cosmic.accent.base.into(), + text_color: cosmic.accent_text_color().into(), ..*default } } @@ -242,9 +242,7 @@ pub fn hover( default: &ItemStatusAppearance, ) -> ItemStatusAppearance { ItemStatusAppearance { - background: Some(Background::Color( - cosmic.palette.neutral_8.with_alpha(0.2).into(), - )), + background: Some(Background::Color(component.hover.with_alpha(0.2).into())), text_color: cosmic.accent.base.into(), ..*default } @@ -279,7 +277,7 @@ mod vertical { border_radius: Radius::from([rad_0[0], rad_0[1], rad_m[2], rad_m[3]]), ..Default::default() }, - text_color: cosmic.accent.base.into(), + text_color: cosmic.accent_text_color().into(), } } @@ -300,7 +298,7 @@ mod vertical { border_radius: cosmic.corner_radii.radius_m.into(), ..Default::default() }, - text_color: cosmic.accent.base.into(), + text_color: cosmic.accent_text_color().into(), } } } diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 7d088515..789969ac 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -940,7 +940,7 @@ pub fn color_button<'a, Message: Clone + 'static>( background: color.map(Background::from).or(standard.background), border_radius: cosmic.radius_xs().into(), border_width: 1.0, - border_color: cosmic.on_bg_color().into(), + border_color: cosmic.palette.neutral_8.into(), outline_width, outline_color, icon_color: None, @@ -957,7 +957,7 @@ pub fn color_button<'a, Message: Clone + 'static>( background: color.map(Background::from).or(standard.background), border_radius: cosmic.radius_xs().into(), border_width: 1.0, - border_color: cosmic.on_bg_color().into(), + border_color: cosmic.palette.neutral_8.into(), outline_width: 0.0, outline_color: Color::TRANSPARENT, icon_color: None, @@ -980,7 +980,7 @@ pub fn color_button<'a, Message: Clone + 'static>( background: color.map(Background::from).or(standard.background), border_radius: cosmic.radius_xs().into(), border_width: 1.0, - border_color: cosmic.on_bg_color().into(), + border_color: cosmic.palette.neutral_8.into(), outline_width, outline_color, icon_color: None, @@ -1003,7 +1003,7 @@ pub fn color_button<'a, Message: Clone + 'static>( background: color.map(Background::from).or(standard.background), border_radius: cosmic.radius_xs().into(), border_width: 1.0, - border_color: cosmic.on_bg_color().into(), + border_color: cosmic.palette.neutral_8.into(), outline_width, outline_color, icon_color: None, diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index 8cf890f2..67f999f7 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -262,7 +262,6 @@ pub fn menu_items< items.insert(1, widget::Space::with_width(spacing.space_xxs).into()); } - // dbg!("button with action...", action.message()); let menu_button = menu_button(items).on_press(action.message()); trees.push(MenuTree::::from(Element::from(menu_button))); @@ -296,7 +295,7 @@ pub fn menu_items< .icon() .class(theme::Svg::Custom(Rc::new(|theme| { iced_widget::svg::Style { - color: Some(theme.cosmic().accent_color().into()), + color: Some(theme.cosmic().accent_text_color().into()), } }))) .width(Length::Fixed(16.0)) diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index 8186a689..eba4641a 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -221,8 +221,8 @@ fn container_style(theme: &crate::Theme) -> iced_widget::container::Style { let cosmic_theme = &theme.cosmic(); let accent = &cosmic_theme.accent; let corners = &cosmic_theme.corner_radii; + let current_container = theme.current_container(); let border = if theme.theme_type.is_high_contrast() { - let current_container = theme.current_container(); Border { radius: corners.radius_s.into(), width: 1., @@ -237,8 +237,8 @@ fn container_style(theme: &crate::Theme) -> iced_widget::container::Style { }; iced_widget::container::Style { - icon_color: Some(accent.base.into()), - text_color: Some(cosmic_theme.palette.neutral_10.into()), + icon_color: Some(current_container.on.into()), + text_color: Some(current_container.on.into()), background: None, border, shadow: Shadow::default(), From 8ebd06bed0f6d162ae9297fd0bbf088653e6872d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 23 Jul 2025 17:42:15 -0400 Subject: [PATCH 241/556] chore: more style updates --- src/theme/style/button.rs | 2 +- src/theme/style/iced.rs | 2 +- src/theme/style/segmented_button.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index 1073fe85..0575ce67 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -118,7 +118,7 @@ pub fn appearance( Button::Link => { appearance.background = None; - appearance.icon_color = Some(cosmic.accent.base.into()); + appearance.icon_color = Some(cosmic.accent_text_color().into()); appearance.text_color = Some(cosmic.accent_text_color().into()); corner_radii = &cosmic.corner_radii.radius_0; } diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index e3da3f98..02818d81 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -766,7 +766,7 @@ impl menu::Catalog for Theme { radius: cosmic.corner_radii.radius_m.into(), ..Default::default() }, - selected_text_color: cosmic.accent.base.into(), + selected_text_color: cosmic.accent_text_color().into(), selected_background: Background::Color(cosmic.background.component.hover.into()), } } diff --git a/src/theme/style/segmented_button.rs b/src/theme/style/segmented_button.rs index ff759715..398f6fb2 100644 --- a/src/theme/style/segmented_button.rs +++ b/src/theme/style/segmented_button.rs @@ -243,7 +243,7 @@ pub fn hover( ) -> ItemStatusAppearance { ItemStatusAppearance { background: Some(Background::Color(component.hover.with_alpha(0.2).into())), - text_color: cosmic.accent.base.into(), + text_color: cosmic.accent_text_color().into(), ..*default } } From 5aa025af7dc9af4194f84ac08c7ac484b0d9d556 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 22 Jul 2025 15:45:22 -0600 Subject: [PATCH 242/556] context-menu: allow borrowed content --- src/widget/context_menu.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index b1014cf4..d0e1125a 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -18,11 +18,11 @@ use std::collections::HashSet; use std::sync::Arc; /// A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation. -pub fn context_menu( - content: impl Into> + 'static, +pub fn context_menu<'a, Message: 'static + Clone>( + content: impl Into>, // on_context: Message, context_menu: Option>>, -) -> ContextMenu<'static, Message> { +) -> ContextMenu<'a, Message> { let mut this = ContextMenu { content: content.into(), context_menu: context_menu.map(|menus| { @@ -539,10 +539,8 @@ impl Widget } } -impl<'a, Message: Clone + 'static> From> - for crate::Element<'static, Message> -{ - fn from(widget: ContextMenu<'static, Message>) -> Self { +impl<'a, Message: Clone + 'static> From> for crate::Element<'a, Message> { + fn from(widget: ContextMenu<'a, Message>) -> Self { Self::new(widget) } } From 3c13669865aadedab4ae18eb40c684e0355eed10 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 22 Jul 2025 19:33:07 -0400 Subject: [PATCH 243/556] fix: close context menu on escape press --- src/widget/context_menu.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index d0e1125a..59f78128 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -11,7 +11,7 @@ use crate::widget::menu::{ }; use derive_setters::Setters; use iced::touch::Finger; -use iced::{Event, Vector, window}; +use iced::{Event, Vector, keyboard, window}; use iced_core::widget::{Tree, Widget, tree}; use iced_core::{Length, Point, Size, event, mouse, touch}; use std::collections::HashSet; @@ -31,6 +31,7 @@ pub fn context_menu<'a, Message: 'static + Clone>( menus, )] }), + close_on_escape: true, window_id: window::Id::RESERVED, on_surface_action: None, }; @@ -51,6 +52,7 @@ pub struct ContextMenu<'a, Message> { #[setters(skip)] context_menu: Option>>, pub window_id: window::Id, + pub close_on_escape: bool, #[setters(skip)] pub(crate) on_surface_action: Option Message + Send + Sync + 'static>>, @@ -344,7 +346,31 @@ impl Widget } state.open }); + if matches!( + event, + Event::Keyboard(keyboard::Event::KeyPressed { + key: keyboard::Key::Named(keyboard::key::Named::Escape), + .. + }) + ) { + state.menu_bar_state.inner.with_data_mut(|state| { + state.menu_states.clear(); + state.active_root.clear(); + state.open = false; + #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { + if let Some(id) = state.popup_id.remove(&self.window_id) { + { + let surface_action = self.on_surface_action.as_ref().unwrap(); + shell + .publish(surface_action(crate::surface::action::destroy_popup(id))); + } + state.view_cursor = cursor; + } + } + }); + } if cursor.is_over(bounds) { let fingers_pressed = state.fingers_pressed.len(); From c40eb8761179ec680a5b0772eb73b6b199e62ddf Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 23 Jul 2025 10:22:31 -0400 Subject: [PATCH 244/556] fix(context-menu): close menu if pressed out of bounds and open --- src/widget/context_menu.rs | 103 +++++++++++-------------------------- 1 file changed, 29 insertions(+), 74 deletions(-) diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 59f78128..d9dc529a 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -346,14 +346,21 @@ impl Widget } state.open }); - if matches!( - event, + let mut was_open = false; + if matches!(event, Event::Keyboard(keyboard::Event::KeyPressed { key: keyboard::Key::Named(keyboard::key::Named::Escape), .. }) - ) { + | Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Right | mouse::Button::Left, + )) + | Event::Touch(touch::Event::FingerPressed { .. }) + | Event::Window(window::Event::Focused) + if open ) + { state.menu_bar_state.inner.with_data_mut(|state| { + was_open = true; state.menu_states.clear(); state.active_root.clear(); state.open = false; @@ -371,7 +378,8 @@ impl Widget } }); } - if cursor.is_over(bounds) { + + if !was_open && cursor.is_over(bounds) { let fingers_pressed = state.fingers_pressed.len(); match event { @@ -383,36 +391,12 @@ impl Widget state.fingers_pressed.remove(&id); } - Event::Window(window::Event::Focused) => { - #[cfg(all( - feature = "wayland", - feature = "winit", - feature = "surface-message" - ))] - if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { - state.menu_bar_state.inner.with_data_mut(|state| { - if let Some(id) = state.popup_id.remove(&self.window_id) { - state.menu_states.clear(); - state.active_root.clear(); - state.open = false; - - { - let surface_action = self.on_surface_action.as_ref().unwrap(); - shell.publish(surface_action( - crate::surface::action::destroy_popup(id), - )); - } - state.view_cursor = cursor; - } - }); - } - } - _ => (), } // Present a context menu on a right click event. - if self.context_menu.is_some() + if !was_open + && self.context_menu.is_some() && (right_button_released(&event) || (touch_lifted(&event) && fingers_pressed == 2)) { state.context_cursor = cursor.position().unwrap_or_default(); @@ -427,64 +411,35 @@ impl Widget } return event::Status::Captured; - } else if right_button_released(&event) + } else if !was_open && right_button_released(&event) || (touch_lifted(&event)) || left_button_released(&event) { - #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] - if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { - state.menu_bar_state.inner.with_data_mut(|state| { - if let Some(id) = state.popup_id.remove(&self.window_id) { - state.menu_states.clear(); - state.active_root.clear(); - state.open = false; + state.menu_bar_state.inner.with_data_mut(|state| { + was_open = true; + state.menu_states.clear(); + state.active_root.clear(); + state.open = false; - { - let surface_action = self.on_surface_action.as_ref().unwrap(); - - shell.publish(surface_action( - crate::surface::action::destroy_popup(id), - )); - } - state.view_cursor = cursor; - } - }); - } - } - } else if open { - match event { - Event::Mouse(mouse::Event::ButtonReleased( - mouse::Button::Right | mouse::Button::Left, - )) - | Event::Touch(touch::Event::FingerLifted { .. }) => { #[cfg(all( feature = "wayland", feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { - state.menu_bar_state.inner.with_data_mut(|state| { - if let Some(id) = state.popup_id.remove(&self.window_id) { - state.menu_states.clear(); - state.active_root.clear(); - state.open = false; - - { - let surface_action = self.on_surface_action.as_ref().unwrap(); - - shell.publish(surface_action( - crate::surface::action::destroy_popup(id), - )); - } - state.view_cursor = cursor; + if let Some(id) = state.popup_id.remove(&self.window_id) { + { + let surface_action = self.on_surface_action.as_ref().unwrap(); + shell.publish(surface_action( + crate::surface::action::destroy_popup(id), + )); } - }); + state.view_cursor = cursor; + } } - } - _ => (), + }); } } - self.content.as_widget_mut().on_event( &mut tree.children[0], event, From 1b988ed1e9d2b4346b32c451bac7880d28257dc2 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 24 Jul 2025 14:31:33 -0400 Subject: [PATCH 245/556] fix(theme change): make sure that all theme variables are in sync after a change --- src/app/cosmic.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 00ce3ddc..c8a8dfeb 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -690,10 +690,11 @@ impl Cosmic { let mut cmds = vec![self.app.system_theme_mode_update(&keys, &mode)]; let core = self.app.core_mut(); - let prev_is_dark = core.system_is_dark(); core.system_theme_mode = mode; let is_dark = core.system_is_dark(); - let changed = prev_is_dark != is_dark; + let changed = core.system_theme_mode.is_dark != is_dark + || core.portal_is_dark != Some(is_dark) + || core.system_theme.cosmic().is_dark != is_dark; if changed { core.theme_sub_counter += 1; let mut new_theme = if is_dark { @@ -784,10 +785,13 @@ impl Cosmic { ColorScheme::PreferLight => Some(false), }; let core = self.app.core_mut(); - let prev_is_dark = core.system_is_dark(); + core.portal_is_dark = is_dark; let is_dark = core.system_is_dark(); - let changed = prev_is_dark != is_dark; + let changed = core.system_theme_mode.is_dark != is_dark + || core.portal_is_dark != Some(is_dark) + || core.system_theme.cosmic().is_dark != is_dark; + if changed { core.theme_sub_counter += 1; let new_theme = if is_dark { From 2099dc45cb44102f96f0dd4eefb97a0c74d18125 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 24 Jul 2025 15:34:56 -0600 Subject: [PATCH 246/556] fix(header_bar): increase title portion based on maximum left or right portion --- src/widget/header_bar.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 8eec9ef4..3763ae32 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -7,7 +7,7 @@ use apply::Apply; use derive_setters::Setters; use iced::Length; use iced_core::{Vector, Widget, widget::tree}; -use std::borrow::Cow; +use std::{borrow::Cow, cmp}; #[must_use] pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { @@ -366,6 +366,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { } else { (portion, portion) }; + let title_portion = cmp::max(left_portion, right_portion) * 2; // Creates the headerbar widget. let mut widget = widget::row::with_capacity(3) // If elements exist in the start region, append them here. @@ -389,7 +390,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .into(), ) } else if !self.title.is_empty() && !self.is_condensed { - Some(self.title_widget()) + Some(self.title_widget(title_portion)) } else { None }) @@ -431,13 +432,13 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { widget.into() } - fn title_widget(&mut self) -> Element<'a, Message> { + fn title_widget(&mut self, title_portion: u16) -> Element<'a, Message> { let mut title = Cow::default(); std::mem::swap(&mut title, &mut self.title); widget::text::heading(title) .apply(widget::container) - .center(Length::Fill) + .center(Length::FillPortion(title_portion)) .into() } From 5e136f94994140548b4feee759c811284988f076 Mon Sep 17 00:00:00 2001 From: wiiznokes <78230769+wiiznokes@users.noreply.github.com> Date: Mon, 28 Jul 2025 16:33:22 +0200 Subject: [PATCH 247/556] fix!(windows): remove `desktop` dependency for the `about` feature BREAKING CHANGE: Icon must be provided as a handle instead of a string. --- Cargo.toml | 2 +- src/widget/about.rs | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 132aac8c..017d84d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ default = ["multi-window", "a11y"] # Accessibility support a11y = ["iced/a11y", "iced_accessibility"] # Enable about widget -about = ["desktop", "dep:license"] +about = ["dep:license"] # Builds support for animated images animated-image = ["dep:async-fs", "image/gif", "tokio?/io-util", "tokio?/fs"] # XXX autosize should not be used on winit windows unless dialogs diff --git a/src/widget/about.rs b/src/widget/about.rs index 47a9baa1..6590bb9d 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -1,7 +1,6 @@ use { crate::{ Element, - desktop::{IconSourceExt, fde}, iced::{Alignment, Length}, widget::{self, horizontal_space}, }, @@ -15,7 +14,7 @@ pub struct About { /// The application's name. name: Option, /// The application's icon name. - icon: Option, + icon: Option, /// The application's version. version: Option, /// Name of the application's author. @@ -138,10 +137,7 @@ pub fn about<'a, Message: Clone + 'static>( }; let application_name = about.name.as_ref().map(widget::text::title3); - let application_icon = about - .icon - .as_ref() - .map(|icon| fde::IconSource::Name(icon.clone()).as_cosmic_icon()); + let application_icon = about.icon.as_ref().map(|i| i.clone().icon()); let author = about.author.as_ref().map(widget::text::body); let version = about.version.as_ref().map(widget::button::standard); let links_section = section(&about.links, "Links"); From 05874e8ea252be0e6115c268aef18a19019842f4 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 29 Jul 2025 15:45:52 -0400 Subject: [PATCH 248/556] fix: theme updates --- cosmic-theme/src/model/theme.rs | 4 +- src/theme/style/iced.rs | 228 +++++++++++++++------------- src/theme/style/segmented_button.rs | 4 +- src/theme/style/text_input.rs | 10 +- 4 files changed, 132 insertions(+), 114 deletions(-) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index e84417e1..aad71228 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -957,7 +957,7 @@ impl ThemeBuilder { /// build the theme pub fn build(self) -> Theme { let Self { - mut palette, + palette, spacing, corner_radii, neutral_tint, @@ -1063,6 +1063,7 @@ impl ThemeBuilder { get_surface_color(bg_index, 5, &step_array, is_dark, &control_steps_array[1]) }; + let step_array = steps(container_bg, NonZeroUsize::new(100).unwrap()); let base_index: usize = color_index(container_bg, step_array.len()); let component_base = get_surface_color(base_index, 6, &step_array, is_dark, &control_steps_array[3]); @@ -1185,6 +1186,7 @@ impl ThemeBuilder { get_surface_color(bg_index, 10, &step_array, is_dark, &control_steps_array[2]) }; + let step_array = steps(container_bg, NonZeroUsize::new(100).unwrap()); let base_index = color_index(container_bg, step_array.len()); let secondary_component = get_surface_color(base_index, 3, &step_array, is_dark, &control_steps_array[4]); diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 02818d81..764c1654 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -209,7 +209,7 @@ impl iced_checkbox::Catalog for Theme { background: Background::Color(if is_checked { cosmic.accent.base.into() } else { - cosmic.background.small_widget.into() + self.current_container().small_widget.into() }), icon_color: cosmic.accent.on.into(), border: Border { @@ -229,13 +229,13 @@ impl iced_checkbox::Catalog for Theme { background: Background::Color(if is_checked { cosmic.background.component.base.into() } else { - cosmic.background.base.into() + self.current_container().small_widget.into() }), icon_color: cosmic.background.on.into(), border: Border { radius: corners.radius_xs.into(), width: if is_checked { 0.0 } else { 1.0 }, - color: cosmic.button.border.into(), + color: cosmic.palette.neutral_8.into(), }, text_color: None, }, @@ -243,7 +243,7 @@ impl iced_checkbox::Catalog for Theme { background: Background::Color(if is_checked { cosmic.success.base.into() } else { - cosmic.button.base.into() + self.current_container().small_widget.into() }), icon_color: cosmic.success.on.into(), border: Border { @@ -262,7 +262,7 @@ impl iced_checkbox::Catalog for Theme { background: Background::Color(if is_checked { cosmic.destructive.base.into() } else { - cosmic.button.base.into() + self.current_container().small_widget.into() }), icon_color: cosmic.destructive.on.into(), border: Border { @@ -271,7 +271,7 @@ impl iced_checkbox::Catalog for Theme { color: if is_checked { cosmic.destructive.base } else { - cosmic.button.border + cosmic.palette.neutral_8 } .into(), }, @@ -294,84 +294,89 @@ impl iced_checkbox::Catalog for Theme { } active } - iced_checkbox::Status::Hovered { is_checked } => match class { - Checkbox::Primary => iced_checkbox::Style { - background: Background::Color(if is_checked { - cosmic.accent.base.into() - } else { - cosmic.button.base.into() - }), - icon_color: cosmic.accent.on.into(), - border: Border { - radius: corners.radius_xs.into(), - width: if is_checked { 0.0 } else { 1.0 }, - color: if is_checked { - cosmic.accent.base + iced_checkbox::Status::Hovered { is_checked } => { + let cur_container = self.current_container().small_widget; + // TODO: this should probably be done with a custom widget instead, or the theme needs more small widget variables. + let hovered_bg = over(cosmic.palette.neutral_0.with_alpha(0.1), cur_container); + match class { + Checkbox::Primary => iced_checkbox::Style { + background: Background::Color(if is_checked { + cosmic.accent.hover_state_color().into() } else { - cosmic.button.border - } - .into(), + hovered_bg.into() + }), + icon_color: cosmic.accent.on.into(), + border: Border { + radius: corners.radius_xs.into(), + width: if is_checked { 0.0 } else { 1.0 }, + color: if is_checked { + cosmic.accent.base + } else { + cosmic.palette.neutral_8 + } + .into(), + }, + text_color: None, }, - text_color: None, - }, - Checkbox::Secondary => iced_checkbox::Style { - background: Background::Color(if is_checked { - self.current_container().base.into() - } else { - cosmic.button.base.into() - }), - icon_color: self.current_container().on.into(), - border: Border { - radius: corners.radius_xs.into(), - width: if is_checked { 0.0 } else { 1.0 }, - color: if is_checked { - self.current_container().base + Checkbox::Secondary => iced_checkbox::Style { + background: Background::Color(if is_checked { + self.current_container().component.hover.into() } else { - cosmic.button.border - } - .into(), + hovered_bg.into() + }), + icon_color: self.current_container().on.into(), + border: Border { + radius: corners.radius_xs.into(), + width: if is_checked { 0.0 } else { 1.0 }, + color: if is_checked { + self.current_container().base + } else { + cosmic.palette.neutral_8 + } + .into(), + }, + text_color: None, }, - text_color: None, - }, - Checkbox::Success => iced_checkbox::Style { - background: Background::Color(if is_checked { - cosmic.success.base.into() - } else { - cosmic.button.base.into() - }), - icon_color: cosmic.success.on.into(), - border: Border { - radius: corners.radius_xs.into(), - width: if is_checked { 0.0 } else { 1.0 }, - color: if is_checked { - cosmic.success.base + Checkbox::Success => iced_checkbox::Style { + background: Background::Color(if is_checked { + cosmic.success.hover.into() } else { - cosmic.button.border - } - .into(), + hovered_bg.into() + }), + icon_color: cosmic.success.on.into(), + border: Border { + radius: corners.radius_xs.into(), + width: if is_checked { 0.0 } else { 1.0 }, + color: if is_checked { + cosmic.success.base + } else { + cosmic.palette.neutral_8 + } + .into(), + }, + text_color: None, }, - text_color: None, - }, - Checkbox::Danger => iced_checkbox::Style { - background: Background::Color(if is_checked { - cosmic.destructive.base.into() - } else { - cosmic.button.base.into() - }), - icon_color: cosmic.destructive.on.into(), - border: Border { - radius: corners.radius_xs.into(), - width: if is_checked { 0.0 } else { 1.0 }, - color: if is_checked { - cosmic.destructive.base + Checkbox::Danger => iced_checkbox::Style { + background: Background::Color(if is_checked { + cosmic.destructive.hover.into() } else { - cosmic.button.border - } - .into(), + hovered_bg.into() + }), + icon_color: cosmic.destructive.on.into(), + border: Border { + radius: corners.radius_xs.into(), + width: if is_checked { 0.0 } else { 1.0 }, + color: if is_checked { + cosmic.destructive.base + } else { + cosmic.palette.neutral_8 + } + .into(), + }, + text_color: None, }, - text_color: None, - }, - }, + } + } } } } @@ -821,6 +826,7 @@ impl radio::Catalog for Theme { fn default<'a>() -> Self::Class<'a> {} fn style(&self, class: &Self::Class<'_>, status: radio::Status) -> radio::Style { + let cur_container = self.current_container(); let theme = self.cosmic(); match status { @@ -829,7 +835,7 @@ impl radio::Catalog for Theme { Color::from(theme.accent.base).into() } else { // TODO: this seems to be defined weirdly in FIGMA - Color::from(theme.background.small_widget).into() + Color::from(cur_container.small_widget).into() }, dot_color: theme.accent.on.into(), border_width: 1.0, @@ -840,22 +846,26 @@ impl radio::Catalog for Theme { }, text_color: None, }, - radio::Status::Hovered { is_selected } => radio::Style { - background: if is_selected { - Color::from(theme.accent.base).into() + radio::Status::Hovered { is_selected } => { + let bg = if is_selected { + theme.accent.base } else { - Color::from(theme.background.small_widget.with_alpha(0.2)).into() - }, - dot_color: theme.accent.on.into(), - border_width: 1.0, - border_color: if is_selected { - Color::from(theme.accent.base) - } else { - // TODO: this seems to be defined weirdly in FIGMA - Color::from(theme.palette.neutral_7) - }, - text_color: None, - }, + self.current_container().small_widget + }; + // TODO: this should probably be done with a custom widget instead, or the theme needs more small widget variables. + let hovered_bg = Color::from(over(theme.palette.neutral_0.with_alpha(0.1), bg)); + radio::Style { + background: hovered_bg.into(), + dot_color: theme.accent.on.into(), + border_width: 1.0, + border_color: if is_selected { + Color::from(theme.accent.base) + } else { + Color::from(theme.palette.neutral_8) + }, + text_color: None, + } + } } } } @@ -1102,8 +1112,8 @@ impl scrollable::Catalog for Theme { match status { scrollable::Status::Active => { let cosmic = self.cosmic(); - let mut neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7); - let mut neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7); + let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7); + let neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7); let mut a = scrollable::Style { container: iced_container::transparent(self), vertical_rail: scrollable::Rail { @@ -1144,11 +1154,17 @@ impl scrollable::Catalog for Theme { }, gap: None, }; + let small_widget_container = self + .current_container() + .small_widget + .clone() + .with_alpha(0.7); if matches!(class, Scrollable::Permanent) { - let neutral_3 = cosmic.palette.neutral_3.with_alpha(0.7); - a.horizontal_rail.background = Some(Background::Color(neutral_3.into())); - a.vertical_rail.background = Some(Background::Color(neutral_3.into())); + a.horizontal_rail.background = + Some(Background::Color(small_widget_container.into())); + a.vertical_rail.background = + Some(Background::Color(small_widget_container.into())); } a @@ -1156,9 +1172,8 @@ impl scrollable::Catalog for Theme { // TODO handle vertical / horizontal scrollable::Status::Hovered { .. } | scrollable::Status::Dragged { .. } => { let cosmic = self.cosmic(); - let mut neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7); - let mut neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7); - let mut neutral_3 = cosmic.palette.neutral_3; + let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7); + let neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7); // if is_mouse_over_scrollbar { // let hover_overlay = cosmic.palette.neutral_0.with_alpha(0.2); @@ -1206,8 +1221,11 @@ impl scrollable::Catalog for Theme { }; if matches!(class, Scrollable::Permanent) { - let small_widget_container = - cosmic.background.small_widget.clone().with_alpha(0.7); + let small_widget_container = self + .current_container() + .small_widget + .clone() + .with_alpha(0.7); a.horizontal_rail.background = Some(Background::Color(small_widget_container.into())); @@ -1309,7 +1327,7 @@ impl text_input::Catalog for Theme { fn style(&self, class: &Self::Class<'_>, status: text_input::Status) -> text_input::Style { let palette = self.cosmic(); - let mut bg = palette.background.small_widget.with_alpha(0.25); + let bg = self.current_container().small_widget.with_alpha(0.25); let neutral_9 = palette.palette.neutral_9; let value = neutral_9.into(); @@ -1345,7 +1363,7 @@ impl text_input::Catalog for Theme { match status { text_input::Status::Active => appearance, text_input::Status::Hovered => { - let mut bg = palette.background.small_widget.with_alpha(0.25); + let bg = self.current_container().small_widget.with_alpha(0.25); match class { TextInput::Default => text_input::Style { @@ -1374,7 +1392,7 @@ impl text_input::Catalog for Theme { } } text_input::Status::Focused => { - let mut bg = palette.background.small_widget.with_alpha(0.25); + let bg = self.current_container().small_widget.with_alpha(0.25); match class { TextInput::Default => text_input::Style { diff --git a/src/theme/style/segmented_button.rs b/src/theme/style/segmented_button.rs index 398f6fb2..4f7b4a4f 100644 --- a/src/theme/style/segmented_button.rs +++ b/src/theme/style/segmented_button.rs @@ -177,9 +177,7 @@ mod horizontal { let rad_0 = cosmic.corner_radii.radius_0; ItemStatusAppearance { - background: Some(Background::Color( - cosmic.palette.neutral_5.with_alpha(0.2).into(), - )), + background: Some(Background::Color(component.selected_state_color().into())), first: ItemAppearance { border_radius: Radius::from([rad_m[0], rad_0[1], rad_0[2], rad_m[3]]), ..Default::default() diff --git a/src/theme/style/text_input.rs b/src/theme/style/text_input.rs index c809961a..8085a48d 100644 --- a/src/theme/style/text_input.rs +++ b/src/theme/style/text_input.rs @@ -6,6 +6,7 @@ use crate::ext::ColorExt; use crate::widget::text_input::{Appearance, StyleSheet}; use iced_core::Color; +use palette::WithAlpha; #[derive(Default)] pub enum TextInput { @@ -31,8 +32,7 @@ impl StyleSheet for crate::Theme { let palette = self.cosmic(); let container = self.current_container(); - let mut background: Color = container.component.base.into(); - background.a = 0.25; + let background: Color = container.small_widget.with_alpha(0.25).into(); let corner = palette.corner_radii; let label_color = palette.palette.neutral_9; @@ -125,7 +125,7 @@ impl StyleSheet for crate::Theme { let palette = self.cosmic(); let container = self.current_container(); - let mut background: Color = container.component.base.into(); + let mut background: Color = container.small_widget.into(); background.a = 0.25; let corner = palette.corner_radii; @@ -188,7 +188,7 @@ impl StyleSheet for crate::Theme { let palette = self.cosmic(); let container = self.current_container(); - let mut background: Color = container.component.base.into(); + let mut background: Color = container.small_widget.into(); background.a = 0.25; let corner = palette.corner_radii; @@ -283,7 +283,7 @@ impl StyleSheet for crate::Theme { let palette = self.cosmic(); let container = self.current_container(); - let mut background: Color = container.component.base.into(); + let mut background: Color = container.small_widget.into(); background.a = 0.25; let corner = palette.corner_radii; From b58d864e85b3b842132e9be1b6445ad21c1f0258 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 31 Jul 2025 14:23:06 -0400 Subject: [PATCH 249/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 9312d3c2..13134181 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 9312d3c29b6308d714725a20342375a6145359d9 +Subproject commit 13134181f8d5cfeaee4fb52172e12985b06af1cf From 562e88587207368969e7bcd43bce3ccf81aee8d9 Mon Sep 17 00:00:00 2001 From: Friedrich <50049702+FriedrichGaming@users.noreply.github.com> Date: Wed, 6 Aug 2025 01:07:34 +0200 Subject: [PATCH 250/556] Make ashpd optional for async-std feature --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 017d84d2..87ad0867 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,7 @@ markdown = ["iced/markdown"] highlighter = ["iced/highlighter"] async-std = [ "dep:async-std", - "ashpd/async-std", + "ashpd?/async-std", "rfd?/async-std", "zbus?/async-io", "iced/async-std", From 8badf733833d0a14de5ab7553da5077152931321 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 12 Aug 2025 17:15:18 +0200 Subject: [PATCH 251/556] improv(segmented_button): nav bar, tab, and segmented control theme improvements --- src/theme/style/segmented_button.rs | 342 ++++++++++++---------- src/widget/nav_bar.rs | 2 +- src/widget/segmented_button/horizontal.rs | 2 + src/widget/segmented_button/style.rs | 17 +- src/widget/segmented_button/vertical.rs | 2 + src/widget/segmented_button/widget.rs | 166 +++++------ src/widget/segmented_control.rs | 2 - src/widget/tab_bar.rs | 2 - 8 files changed, 279 insertions(+), 256 deletions(-) diff --git a/src/theme/style/segmented_button.rs b/src/theme/style/segmented_button.rs index 4f7b4a4f..3f5d92db 100644 --- a/src/theme/style/segmented_button.rs +++ b/src/theme/style/segmented_button.rs @@ -5,7 +5,7 @@ use crate::widget::segmented_button::{Appearance, ItemAppearance, StyleSheet}; use crate::{theme::Theme, widget::segmented_button::ItemStatusAppearance}; -use cosmic_theme::{Component, Container}; +use iced::Border; use iced_core::{Background, border::Radius}; use palette::WithAlpha; @@ -16,6 +16,8 @@ pub enum SegmentedButton { TabBar, /// A widget for multiple choice selection. Control, + /// Navigation bar style + NavBar, /// Or implement any custom theme of your liking. Custom(Box Appearance>), } @@ -25,85 +27,54 @@ impl StyleSheet for Theme { #[allow(clippy::too_many_lines)] fn horizontal(&self, style: &Self::Style) -> Appearance { - let container = &self.current_container(); - + let cosmic = self.cosmic(); + let container = self.current_container(); match style { - SegmentedButton::TabBar => { - let cosmic = self.cosmic(); - let active = horizontal::tab_bar_active(cosmic); - let hc = cosmic.is_high_contrast; - let (border_end, border_start, border_top) = if hc { - ( - Some((1., container.component.border.into())), - Some((1., container.component.border.into())), - Some((1., container.component.border.into())), - ) - } else { - (None, None, None) - }; - Appearance { - border_radius: cosmic.corner_radii.radius_0.into(), - inactive: ItemStatusAppearance { - background: None, - first: ItemAppearance { - border_radius: cosmic.corner_radii.radius_0.into(), - border_bottom: Some((1.0, cosmic.accent.base.into())), - border_end, - border_start, - border_top, - }, - middle: ItemAppearance { - border_radius: cosmic.corner_radii.radius_0.into(), - border_bottom: Some((1.0, cosmic.accent.base.into())), - border_end, - border_start, - border_top, - }, - last: ItemAppearance { - border_radius: cosmic.corner_radii.radius_0.into(), - border_bottom: Some((1.0, cosmic.accent.base.into())), - border_end, - border_start, - border_top, - }, - text_color: container.component.on.into(), - }, - hover: hover(cosmic, &container.component, &active), - focus: focus(cosmic, container, &active), - active, - ..Default::default() - } - } SegmentedButton::Control => { - let cosmic = self.cosmic(); - let active = horizontal::selection_active(cosmic, &container.component); - let rad_m = cosmic.corner_radii.radius_m; + let rad_xl = cosmic.corner_radii.radius_xl; let rad_0 = cosmic.corner_radii.radius_0; + let active = horizontal::selection_active(cosmic, &container.component); Appearance { - background: Some(Background::Color(container.small_widget.into())), - border_radius: rad_m.into(), + background: Some(Background::Color(container.component.base.into())), + border: Border { + radius: rad_xl.into(), + ..Default::default() + }, inactive: ItemStatusAppearance { background: None, first: ItemAppearance { - border_radius: Radius::from([rad_m[0], rad_0[1], rad_0[2], rad_m[3]]), - ..Default::default() + border: Border { + radius: Radius::from([rad_xl[0], rad_0[1], rad_0[2], rad_xl[3]]), + ..Default::default() + }, }, middle: ItemAppearance { - border_radius: cosmic.corner_radii.radius_0.into(), - ..Default::default() + border: Border { + radius: cosmic.corner_radii.radius_0.into(), + ..Default::default() + }, }, last: ItemAppearance { - border_radius: Radius::from([rad_0[0], rad_m[1], rad_m[2], rad_0[3]]), - ..Default::default() + border: Border { + radius: Radius::from([rad_0[0], rad_xl[1], rad_xl[2], rad_0[3]]), + ..Default::default() + }, }, text_color: container.component.on.into(), }, - hover: hover(cosmic, &container.component, &active), - focus: focus(cosmic, container, &active), + hover: hover(cosmic, &active, 0.2), active, ..Default::default() } } + + SegmentedButton::NavBar => Appearance { + active_width: 0.0, + ..horizontal::tab_bar(cosmic, container) + }, + + SegmentedButton::TabBar => horizontal::tab_bar(cosmic, container), + SegmentedButton::Custom(func) => func(self), } } @@ -111,84 +82,126 @@ impl StyleSheet for Theme { #[allow(clippy::too_many_lines)] fn vertical(&self, style: &Self::Style) -> Appearance { let cosmic = self.cosmic(); - let rad_m = cosmic.corner_radii.radius_m; - let rad_0 = cosmic.corner_radii.radius_0; + let container = self.current_container(); match style { - SegmentedButton::TabBar => { - let container = &self.cosmic().primary; - let active = vertical::tab_bar_active(cosmic); - Appearance { - border_radius: cosmic.corner_radii.radius_0.into(), - inactive: ItemStatusAppearance { - background: None, - text_color: container.component.on.into(), - ..active - }, - hover: hover(cosmic, &container.component, &active), - focus: focus(cosmic, container, &active), - active, - ..Default::default() - } - } SegmentedButton::Control => { - let container = self.current_container(); + let rad_xl = cosmic.corner_radii.radius_xl; + let rad_0 = cosmic.corner_radii.radius_0; let active = vertical::selection_active(cosmic, &container.component); Appearance { - background: Some(Background::Color(container.small_widget.into())), - border_radius: rad_m.into(), + background: Some(Background::Color(container.component.base.into())), + border: Border { + radius: rad_xl.into(), + ..Default::default() + }, inactive: ItemStatusAppearance { background: None, first: ItemAppearance { - border_radius: Radius::from([rad_m[0], rad_m[1], rad_0[0], rad_0[0]]), - ..Default::default() + border: Border { + radius: Radius::from([rad_xl[0], rad_xl[1], rad_0[0], rad_0[0]]), + ..Default::default() + }, }, middle: ItemAppearance { - border_radius: cosmic.corner_radii.radius_0.into(), - ..Default::default() + border: Border { + radius: cosmic.corner_radii.radius_0.into(), + ..Default::default() + }, }, last: ItemAppearance { - border_radius: Radius::from([rad_0[0], rad_0[1], rad_m[2], rad_m[3]]), - ..Default::default() + border: Border { + radius: Radius::from([rad_0[0], rad_0[1], rad_xl[2], rad_xl[3]]), + ..Default::default() + }, }, text_color: container.component.on.into(), }, - hover: hover(cosmic, &container.component, &active), - focus: focus(cosmic, container, &active), + hover: hover(cosmic, &active, 0.2), active, ..Default::default() } } + + SegmentedButton::NavBar => Appearance { + active_width: 0.0, + ..vertical::tab_bar(cosmic, container) + }, + + SegmentedButton::TabBar => vertical::tab_bar(cosmic, container), + SegmentedButton::Custom(func) => func(self), } } } mod horizontal { + use super::Appearance; use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance}; - use cosmic_theme::Component; + use cosmic_theme::{Component, Container}; + use iced::Border; use iced_core::{Background, border::Radius}; use palette::WithAlpha; + pub fn tab_bar(cosmic: &cosmic_theme::Theme, container: &Container) -> Appearance { + let active = tab_bar_active(cosmic); + let hc = cosmic.is_high_contrast; + let border = if hc { + Border { + color: container.component.border.into(), + radius: cosmic.corner_radii.radius_0.into(), + width: 1.0, + } + } else { + Border::default() + }; + + Appearance { + active_width: 4.0, + border: Border { + radius: cosmic.corner_radii.radius_0.into(), + ..Default::default() + }, + inactive: ItemStatusAppearance { + background: None, + first: ItemAppearance { border }, + middle: ItemAppearance { border }, + last: ItemAppearance { border }, + text_color: container.component.on.into(), + }, + hover: super::hover(cosmic, &active, 0.3), + active, + ..Default::default() + } + } + pub fn selection_active( cosmic: &cosmic_theme::Theme, component: &Component, ) -> ItemStatusAppearance { - let rad_m = cosmic.corner_radii.radius_m; + let rad_xl = cosmic.corner_radii.radius_xl; let rad_0 = cosmic.corner_radii.radius_0; ItemStatusAppearance { - background: Some(Background::Color(component.selected_state_color().into())), + background: Some(Background::Color( + cosmic.palette.neutral_5.with_alpha(0.1).into(), + )), first: ItemAppearance { - border_radius: Radius::from([rad_m[0], rad_0[1], rad_0[2], rad_m[3]]), - ..Default::default() + border: Border { + radius: Radius::from([rad_xl[0], rad_0[1], rad_0[2], rad_xl[3]]), + ..Default::default() + }, }, middle: ItemAppearance { - border_radius: cosmic.corner_radii.radius_0.into(), - ..Default::default() + border: Border { + radius: cosmic.corner_radii.radius_0.into(), + ..Default::default() + }, }, last: ItemAppearance { - border_radius: Radius::from([rad_0[0], rad_m[1], rad_m[2], rad_0[3]]), - ..Default::default() + border: Border { + radius: Radius::from([rad_0[0], rad_xl[1], rad_xl[2], rad_0[3]]), + ..Default::default() + }, }, text_color: cosmic.accent_text_color().into(), } @@ -202,78 +215,86 @@ mod horizontal { cosmic.palette.neutral_5.with_alpha(0.2).into(), )), first: ItemAppearance { - border_radius: Radius::from([rad_s[0], rad_s[1], rad_0[2], rad_0[3]]), - border_bottom: Some((4.0, cosmic.accent.base.into())), - ..Default::default() + border: Border { + color: cosmic.accent.base.into(), + radius: Radius::from([rad_s[0], rad_s[1], rad_0[2], rad_0[3]]), + width: 0.0, + }, }, middle: ItemAppearance { - border_radius: Radius::from([rad_s[0], rad_s[1], rad_0[2], rad_0[3]]), - border_bottom: Some((4.0, cosmic.accent.base.into())), - ..Default::default() + border: Border { + color: cosmic.accent.base.into(), + radius: Radius::from([rad_s[0], rad_s[1], rad_0[2], rad_0[3]]), + width: 0.0, + }, }, last: ItemAppearance { - border_radius: Radius::from([rad_s[0], rad_s[1], rad_0[2], rad_0[3]]), - border_bottom: Some((4.0, cosmic.accent.base.into())), - ..Default::default() + border: Border { + color: cosmic.accent.base.into(), + radius: Radius::from([rad_s[0], rad_s[1], rad_0[2], rad_0[3]]), + width: 0.0, + }, }, text_color: cosmic.accent_text_color().into(), } } } -pub fn focus( - cosmic: &cosmic_theme::Theme, - container: &Container, - default: &ItemStatusAppearance, -) -> ItemStatusAppearance { - let color = container.small_widget; - ItemStatusAppearance { - background: Some(Background::Color(color.into())), - text_color: cosmic.accent_text_color().into(), - ..*default - } -} - -pub fn hover( - cosmic: &cosmic_theme::Theme, - component: &Component, - default: &ItemStatusAppearance, -) -> ItemStatusAppearance { - ItemStatusAppearance { - background: Some(Background::Color(component.hover.with_alpha(0.2).into())), - text_color: cosmic.accent_text_color().into(), - ..*default - } -} - mod vertical { + use super::Appearance; use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance}; - use cosmic_theme::Component; + use cosmic_theme::{Component, Container}; + use iced::Border; use iced_core::{Background, border::Radius}; use palette::WithAlpha; + pub fn tab_bar(cosmic: &cosmic_theme::Theme, container: &Container) -> Appearance { + let active = tab_bar_active(cosmic); + Appearance { + active_width: 4.0, + border: Border { + radius: cosmic.corner_radii.radius_0.into(), + ..Default::default() + }, + inactive: ItemStatusAppearance { + background: None, + text_color: container.component.on.into(), + ..active + }, + hover: super::hover(cosmic, &active, 0.3), + active, + ..Default::default() + } + } + pub fn selection_active( cosmic: &cosmic_theme::Theme, component: &Component, ) -> ItemStatusAppearance { let rad_0 = cosmic.corner_radii.radius_0; - let rad_m = cosmic.corner_radii.radius_m; + let rad_xl = cosmic.corner_radii.radius_xl; ItemStatusAppearance { background: Some(Background::Color( - component.selected_state_color().with_alpha(0.3).into(), + cosmic.palette.neutral_5.with_alpha(0.1).into(), )), first: ItemAppearance { - border_radius: Radius::from([rad_m[0], rad_m[1], rad_0[2], rad_0[3]]), - ..Default::default() + border: Border { + radius: Radius::from([rad_xl[0], rad_xl[1], rad_0[2], rad_0[3]]), + ..Default::default() + }, }, middle: ItemAppearance { - border_radius: cosmic.corner_radii.radius_0.into(), - ..Default::default() + border: Border { + radius: cosmic.corner_radii.radius_0.into(), + ..Default::default() + }, }, last: ItemAppearance { - border_radius: Radius::from([rad_0[0], rad_0[1], rad_m[2], rad_m[3]]), - ..Default::default() + border: Border { + radius: Radius::from([rad_0[0], rad_0[1], rad_xl[2], rad_xl[3]]), + ..Default::default() + }, }, text_color: cosmic.accent_text_color().into(), } @@ -285,18 +306,41 @@ mod vertical { cosmic.palette.neutral_5.with_alpha(0.2).into(), )), first: ItemAppearance { - border_radius: cosmic.corner_radii.radius_m.into(), - ..Default::default() + border: Border { + radius: cosmic.corner_radii.radius_m.into(), + width: 0.0, + ..Default::default() + }, }, middle: ItemAppearance { - border_radius: cosmic.corner_radii.radius_m.into(), - ..Default::default() + border: Border { + radius: cosmic.corner_radii.radius_m.into(), + width: 0.0, + ..Default::default() + }, }, last: ItemAppearance { - border_radius: cosmic.corner_radii.radius_m.into(), - ..Default::default() + border: Border { + radius: cosmic.corner_radii.radius_m.into(), + width: 0.0, + ..Default::default() + }, }, text_color: cosmic.accent_text_color().into(), } } } + +pub fn hover( + cosmic: &cosmic_theme::Theme, + default: &ItemStatusAppearance, + alpha: f32, +) -> ItemStatusAppearance { + ItemStatusAppearance { + background: Some(Background::Color( + cosmic.palette.neutral_5.with_alpha(alpha).into(), + )), + text_color: cosmic.accent_text_color().into(), + ..*default + } +} diff --git a/src/widget/nav_bar.rs b/src/widget/nav_bar.rs index 6923472a..1ae4005d 100644 --- a/src/widget/nav_bar.rs +++ b/src/widget/nav_bar.rs @@ -149,7 +149,7 @@ impl<'a, Message: Clone + 'static> From> .button_padding([space_s, space_xxs, space_s, space_xxs]) .button_spacing(space_xxs) .spacing(space_xxs) - .style(crate::theme::SegmentedButton::TabBar) + .style(crate::theme::SegmentedButton::NavBar) .apply(container) .padding(space_xxs) .apply(scrollable) diff --git a/src/widget/segmented_button/horizontal.rs b/src/widget/segmented_button/horizontal.rs index ccf4c8ae..724ded96 100644 --- a/src/widget/segmented_button/horizontal.rs +++ b/src/widget/segmented_button/horizontal.rs @@ -36,6 +36,8 @@ where Model: Selectable, SelectionMode: Default, { + const VERTICAL: bool = false; + fn variant_appearance( theme: &crate::Theme, style: &crate::theme::SegmentedButton, diff --git a/src/widget/segmented_button/style.rs b/src/widget/segmented_button/style.rs index 102b3686..f09a74a2 100644 --- a/src/widget/segmented_button/style.rs +++ b/src/widget/segmented_button/style.rs @@ -1,31 +1,24 @@ // Copyright 2022 System76 // SPDX-License-Identifier: MPL-2.0 -use iced_core::{Background, Color, border::Radius}; +use iced::Border; +use iced_core::{Background, Color}; /// Appearance of the segmented button. #[derive(Default, Clone, Copy)] pub struct Appearance { pub background: Option, - pub border_radius: Radius, - pub border_bottom: Option<(f32, Color)>, - pub border_end: Option<(f32, Color)>, - pub border_start: Option<(f32, Color)>, - pub border_top: Option<(f32, Color)>, + pub border: Border, + pub active_width: f32, pub active: ItemStatusAppearance, pub inactive: ItemStatusAppearance, pub hover: ItemStatusAppearance, - pub focus: ItemStatusAppearance, } /// Appearance of an item in the segmented button. #[derive(Default, Clone, Copy)] pub struct ItemAppearance { - pub border_radius: Radius, - pub border_bottom: Option<(f32, Color)>, - pub border_end: Option<(f32, Color)>, - pub border_start: Option<(f32, Color)>, - pub border_top: Option<(f32, Color)>, + pub border: Border, } /// Appearance of an item based on its status. diff --git a/src/widget/segmented_button/vertical.rs b/src/widget/segmented_button/vertical.rs index 3f5d5645..ce9f50fe 100644 --- a/src/widget/segmented_button/vertical.rs +++ b/src/widget/segmented_button/vertical.rs @@ -36,6 +36,8 @@ where Model: Selectable, SelectionMode: Default, { + const VERTICAL: bool = true; + fn variant_appearance( theme: &crate::Theme, style: &crate::theme::SegmentedButton, diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 1c92d0b2..620c8439 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -22,11 +22,12 @@ use iced::{ use iced_core::mouse::ScrollDelta; use iced_core::text::{LineHeight, Renderer as TextRenderer, Shaping, Wrapping}; use iced_core::widget::{self, operation, tree}; -use iced_core::{Border, Gradient, Point, Renderer as IcedRenderer, Shadow, Text}; +use iced_core::{Border, Point, Renderer as IcedRenderer, Shadow, Text}; use iced_core::{Clipboard, Layout, Shell, Widget, layout, renderer, widget::Tree}; use iced_runtime::{Action, task}; use slotmap::{Key, SecondaryMap}; use std::borrow::Cow; +use std::cell::LazyCell; use std::collections::HashSet; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; @@ -46,6 +47,8 @@ pub enum ItemBounds { /// Isolates variant-specific behaviors from [`SegmentedButton`]. pub trait SegmentedVariant { + const VERTICAL: bool; + /// Get the appearance for this variant of the widget. fn variant_appearance( theme: &crate::Theme, @@ -107,11 +110,11 @@ where /// Spacing for each indent. pub(super) indent_spacing: u16, /// Desired font for active tabs. - pub(super) font_active: Option, + pub(super) font_active: crate::font::Font, /// Desired font for hovered tabs. - pub(super) font_hovered: Option, + pub(super) font_hovered: crate::font::Font, /// Desired font for inactive tabs. - pub(super) font_inactive: Option, + pub(super) font_inactive: crate::font::Font, /// Size of the font. pub(super) font_size: f32, /// Desired width of the widget. @@ -175,9 +178,9 @@ where minimum_button_width: u16::MIN, maximum_button_width: u16::MAX, indent_spacing: 16, - font_active: None, - font_hovered: None, - font_inactive: None, + font_active: crate::font::semibold(), + font_hovered: crate::font::semibold(), + font_inactive: crate::font::default(), font_size: 14.0, height: Length::Shrink, width: Length::Fill, @@ -603,16 +606,16 @@ where for key in self.model.order.iter().copied() { if let Some(text) = self.model.text.get(key) { - let (font, button_state) = - if self.model.is_active(key) || self.button_is_focused(state, key) { - (self.font_active, 0) - } else if self.button_is_hovered(state, key) { - (self.font_hovered, 1) - } else { - (self.font_inactive, 2) - }; + let (font, button_state) = if self.button_is_focused(state, key) { + (self.font_active, 0) + } else if state.show_context.is_some() || self.button_is_hovered(state, key) { + (self.font_hovered, 1) + } else if self.model.is_active(key) { + (self.font_active, 2) + } else { + (self.font_inactive, 3) + }; - let font = font.unwrap_or_else(crate::font::default); let mut hasher = DefaultHasher::new(); text.hash(&mut hasher); font.hash(&mut hasher); @@ -1171,47 +1174,15 @@ where let bounds: Rectangle = layout.bounds(); let button_amount = self.model.items.len(); - // Modifies alpha color when `on_activate` is unset. - let apply_alpha = |mut c: Color| { - if self.on_activate.is_none() { - c.a /= 2.0; - } - - c - }; - - // Maps `apply_alpha` to background color. - let bg_with_alpha = |mut b| { - match &mut b { - Background::Color(c) => { - *c = apply_alpha(*c); - } - - Background::Gradient(g) => { - let Gradient::Linear(l) = g; - for c in &mut l.stops { - let Some(stop) = c else { - continue; - }; - stop.color = apply_alpha(stop.color); - } - } - } - b - }; - // Draw the background, if a background was defined. if let Some(background) = appearance.background { renderer.fill_quad( renderer::Quad { bounds, - border: Border { - radius: appearance.border_radius, - ..Border::default() - }, + border: appearance.border, shadow: Shadow::default(), }, - bg_with_alpha(background), + background, ); } @@ -1222,7 +1193,7 @@ where // Previous tab button let mut background_appearance = if self.on_activate.is_some() && Item::PrevButton == state.focused_item { - Some(appearance.focus) + Some(appearance.active) } else if self.on_activate.is_some() && Item::PrevButton == state.hovered { Some(appearance.hover) } else { @@ -1241,7 +1212,7 @@ where }, background_appearance .background - .map_or(Background::Color(Color::TRANSPARENT), bg_with_alpha), + .unwrap_or(Background::Color(Color::TRANSPARENT)), ); } @@ -1251,13 +1222,11 @@ where style, cursor, viewport, - apply_alpha(if state.buttons_offset == 0 { + if state.buttons_offset == 0 { appearance.inactive.text_color - } else if let Item::PrevButton = state.focused_item { - appearance.focus.text_color } else { appearance.active.text_color - }), + }, Rectangle { x: tab_bounds.x + 8.0, y: tab_bounds.y + f32::from(self.button_height) / 4.0, @@ -1272,7 +1241,7 @@ where // Next tab button background_appearance = if self.on_activate.is_some() && Item::NextButton == state.focused_item { - Some(appearance.focus) + Some(appearance.active) } else if self.on_activate.is_some() && Item::NextButton == state.hovered { Some(appearance.hover) } else { @@ -1301,13 +1270,13 @@ where style, cursor, viewport, - apply_alpha(if self.next_tab_sensitive(state) { + if self.next_tab_sensitive(state) { appearance.active.text_color } else if let Item::NextButton = state.focused_item { - appearance.focus.text_color + appearance.active.text_color } else { appearance.inactive.text_color - }), + }, Rectangle { x: tab_bounds.x + 8.0, y: tab_bounds.y + f32::from(self.button_height) / 4.0, @@ -1349,22 +1318,23 @@ where let center_y = bounds.center_y(); - let menu_open = !tree.children.is_empty() - && tree.children[0] - .state - .downcast_ref::() - .inner - .with_data(|data| data.open); + let menu_open = || { + state.show_context == Some(key) + && !tree.children.is_empty() + && tree.children[0] + .state + .downcast_ref::() + .inner + .with_data(|data| data.open) + }; let key_is_active = self.model.is_active(key); - let key_is_hovered = self.button_is_hovered(state, key); - let key_has_context_menu_open = menu_open && state.show_context == Some(key); - let status_appearance = if self.button_is_focused(state, key) { - appearance.focus + let key_is_focused = self.button_is_focused(state, key); + let key_is_hovered = LazyCell::new(|| self.button_is_hovered(state, key)); + let status_appearance = if *key_is_hovered || menu_open() { + appearance.hover } else if key_is_active { appearance.active - } else if key_is_hovered || key_has_context_menu_open { - appearance.hover } else { appearance.inactive }; @@ -1378,29 +1348,45 @@ where }; // Render the background of the button. - if status_appearance.background.is_some() { + if key_is_focused || status_appearance.background.is_some() { renderer.fill_quad( renderer::Quad { bounds, - border: Border { - radius: button_appearance.border_radius, - ..Default::default() + border: if key_is_focused { + Border { + width: 1.0, + color: appearance.active.text_color, + radius: button_appearance.border.radius, + } + } else { + button_appearance.border }, shadow: Shadow::default(), }, status_appearance .background - .map_or(Background::Color(Color::TRANSPARENT), bg_with_alpha), + .unwrap_or(Background::Color(Color::TRANSPARENT)), ); } - // Draw the bottom border defined for this button. - if let Some((width, background)) = button_appearance.border_bottom { - let mut bounds = bounds; - bounds.y = bounds.y + bounds.height - width; - bounds.height = width; - + // Draw the active hint on tabs + if appearance.active_width > 0.0 { let rad_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0; + let active_width = if key_is_active { + appearance.active_width + } else { + 1.0 + }; + let mut bounds = bounds; + + if Self::VERTICAL { + bounds.x += bounds.height - active_width; + bounds.width = active_width; + } else { + bounds.y += bounds.height - active_width; + bounds.height = active_width; + } + renderer.fill_quad( renderer::Quad { bounds, @@ -1410,7 +1396,7 @@ where }, shadow: Shadow::default(), }, - bg_with_alpha(background.into()), + appearance.active.text_color, ); } @@ -1455,7 +1441,7 @@ where style, cursor, viewport, - apply_alpha(status_appearance.text_color), + status_appearance.text_color, Rectangle { width, height: width, @@ -1470,7 +1456,7 @@ where if key_is_active { if let crate::theme::SegmentedButton::Control = self.style { let mut image_bounds = bounds; - image_bounds.y = center_y - 16.0 / 2.0; + image_bounds.y = center_y - 8.0; draw_icon::( renderer, @@ -1478,7 +1464,7 @@ where style, cursor, viewport, - apply_alpha(status_appearance.text_color), + status_appearance.text_color, Rectangle { width: 16.0, height: 16.0, @@ -1505,7 +1491,7 @@ where // Whether to show the close button on this tab. let show_close_button = - (key_is_active || !self.show_close_icon_on_hover || key_is_hovered) + (key_is_active || !self.show_close_icon_on_hover || *key_is_hovered) && self.model.is_closable(key); // Width of the icon used by the close button, which we will subtract from the text bounds. @@ -1527,7 +1513,7 @@ where renderer.fill_paragraph( state.paragraphs[key].raw(), bounds.position(), - apply_alpha(status_appearance.text_color), + status_appearance.text_color, Rectangle { x: bounds.x, width: bounds.width, @@ -1546,7 +1532,7 @@ where style, cursor, viewport, - apply_alpha(status_appearance.text_color), + status_appearance.text_color, close_button_bounds, self.close_icon.clone(), ); diff --git a/src/widget/segmented_control.rs b/src/widget/segmented_control.rs index 9dbcfc51..0c213b2c 100644 --- a/src/widget/segmented_control.rs +++ b/src/widget/segmented_control.rs @@ -30,7 +30,6 @@ where .button_padding([space_s, 0, space_s, 0]) .button_spacing(space_xxs) .style(crate::theme::SegmentedButton::Control) - .font_active(Some(crate::font::semibold())) } /// A selection of multiple choices appearing as a conjoined button. @@ -55,5 +54,4 @@ where .button_padding([space_s, 0, space_s, 0]) .button_spacing(space_xxs) .style(crate::theme::SegmentedButton::Control) - .font_active(Some(crate::font::semibold())) } diff --git a/src/widget/tab_bar.rs b/src/widget/tab_bar.rs index b3def5ca..4f4c6149 100644 --- a/src/widget/tab_bar.rs +++ b/src/widget/tab_bar.rs @@ -29,7 +29,6 @@ where .button_height(44) .button_padding([space_s, space_xs, space_s, space_xs]) .style(crate::theme::SegmentedButton::TabBar) - .font_active(Some(crate::font::semibold())) } /// A collection of tabs for developing a tabbed interface. @@ -52,5 +51,4 @@ where .button_height(44) .button_padding([space_s, space_xs, space_s, space_xs]) .style(crate::theme::SegmentedButton::TabBar) - .font_active(Some(crate::font::semibold())) } From 989fcad99eea56967b65aab137175d8a29e8a046 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 11 Aug 2025 22:40:10 -0400 Subject: [PATCH 252/556] fix(input): reset cursor and last click state on unfocus --- src/widget/text_input/input.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 06a193b9..b8c035d4 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -2654,6 +2654,8 @@ impl State { /// Unfocuses the [`TextInput`]. #[cold] pub(super) fn unfocus(&mut self) { + self.move_cursor_to_front(); + self.last_click = None; self.is_focused = None; self.dragging_state = None; self.is_pasting = None; From 6a5076ecb7dc51fc3d255e8cc865acfc9a7b9343 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 12 Aug 2025 22:20:28 +0200 Subject: [PATCH 253/556] fix(context_drawer): adjust header to avoid text wrapping --- src/widget/context_drawer/widget.rs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index 7f493dac..23afae3b 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -57,23 +57,32 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { let horizontal_padding = if max_width < 392.0 { space_s } else { space_l }; + let title = + title.map(|title| text::heading(title).width(Length::FillPortion(3)).center()); + + let close_width = if title.is_some() { + Length::FillPortion(1) + } else { + Length::Shrink + }; + let header_row = row::with_capacity(3) .width(Length::Fixed(480.0)) .align_y(Alignment::Center) - .push( - row::with_children(header_actions) - .spacing(space_xxs) - .width(Length::FillPortion(1)), - ) - .push_maybe( - title.map(|title| text::heading(title).width(Length::FillPortion(1)).center()), - ) + .push(row::with_children(header_actions).spacing(space_xxs).width( + if title.is_some() { + Length::FillPortion(1) + } else { + Length::Fill + }, + )) + .push_maybe(title) .push( button::text("Close") .trailing_icon(icon::from_name("go-next-symbolic")) .on_press(on_close) .apply(container) - .width(Length::FillPortion(1)) + .width(close_width) .align_x(Alignment::End), ); let header = column::with_capacity(2) From 4f423349a2be727d5dae21aacc86d062e702f93c Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 13 Aug 2025 11:18:58 +0200 Subject: [PATCH 254/556] fix(segmented_button): duplicate focus fix --- src/widget/segmented_button/widget.rs | 63 ++++++++++++++++++++------- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 620c8439..126c78e5 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -21,13 +21,14 @@ use iced::{ }; use iced_core::mouse::ScrollDelta; use iced_core::text::{LineHeight, Renderer as TextRenderer, Shaping, Wrapping}; +use iced_core::widget::operation::Focusable; use iced_core::widget::{self, operation, tree}; use iced_core::{Border, Point, Renderer as IcedRenderer, Shadow, Text}; use iced_core::{Clipboard, Layout, Shell, Widget, layout, renderer, widget::Tree}; use iced_runtime::{Action, task}; use slotmap::{Key, SecondaryMap}; use std::borrow::Cow; -use std::cell::LazyCell; +use std::cell::{Cell, LazyCell}; use std::collections::HashSet; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; @@ -35,6 +36,11 @@ use std::marker::PhantomData; use std::mem; use std::time::{Duration, Instant}; +thread_local! { + // Prevents two segmented buttons from being focused at the same time. + static LAST_FOCUS_UPDATE: LazyCell> = LazyCell::new(|| Cell::new(Instant::now())); +} + /// A command that focuses a segmented item stored in a widget. pub fn focus(id: Id) -> Task { task::effect(Action::Widget(Box::new(operation::focusable::focus(id.0)))) @@ -521,7 +527,9 @@ where } fn button_is_focused(&self, state: &LocalState, key: Entity) -> bool { - self.on_activate.is_some() && Item::Tab(key) == state.focused_item + state.focused.is_some() + && self.on_activate.is_some() + && Item::Tab(key) == state.focused_item } fn button_is_hovered(&self, state: &LocalState, key: Entity) -> bool { @@ -636,7 +644,7 @@ where horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: Shaping::Advanced, - wrapping: Wrapping::default(), + wrapping: Wrapping::None, line_height: self.line_height, }; @@ -654,6 +662,13 @@ where menu_roots_diff(context_menu, &mut inner.tree); }); } + + // Unfocus if another segmented control was focused. + if let Some(f) = state.focused.as_ref() { + if f.updated_at != LAST_FOCUS_UPDATE.with(|f| f.get()) { + state.unfocus(); + } + } } fn size(&self) -> Size { @@ -911,8 +926,7 @@ where if let Event::Mouse(mouse::Event::ButtonReleased(_)) | Event::Touch(touch::Event::FingerLifted { .. }) = event { - state.focused = false; - state.focused_item = Item::None; + state.unfocus(); } if let Some(on_activate) = self.on_activate.as_ref() { @@ -932,7 +946,7 @@ where if can_activate { shell.publish(on_activate(key)); - state.focused = true; + state.set_focused(); state.focused_item = Item::Tab(key); state.pressed_item = None; return event::Status::Captured; @@ -1020,7 +1034,7 @@ where if let Some(key) = activate_key { shell.publish(on_activate(key)); - state.focused = true; + state.set_focused(); state.focused_item = Item::Tab(key); return event::Status::Captured; } @@ -1030,19 +1044,18 @@ where } } } - } else if state.focused { + } else if state.is_focused() { // Unfocus on clicks outside of the boundaries of the segmented button. if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) = event { - state.focused = true; - state.focused_item = Item::None; + state.unfocus(); state.pressed_item = None; return event::Status::Ignored; } } - if state.focused { + if state.is_focused() { if let Event::Keyboard(keyboard::Event::KeyPressed { key: keyboard::Key::Named(keyboard::key::Named::Tab), modifiers, @@ -1650,6 +1663,12 @@ where } } +#[derive(Debug, Clone, Copy)] +struct Focus { + updated_at: Instant, + now: Instant, +} + /// State that is maintained by each individual widget. pub struct LocalState { /// Menu state @@ -1661,7 +1680,7 @@ pub struct LocalState { /// Whether buttons need to be collapsed to preserve minimum width pub(super) collapsed: bool, /// If the widget is focused or not. - focused: bool, + focused: Option, /// The key inside the widget that is currently focused. focused_item: Item, /// The ID of the button that is being hovered. Defaults to null. @@ -1700,18 +1719,32 @@ enum Item { Tab(Entity), } +impl LocalState { + fn set_focused(&mut self) { + let now = Instant::now(); + LAST_FOCUS_UPDATE.with(|x| x.set(now)); + + self.focused = Some(Focus { + updated_at: now, + now, + }); + } +} + impl operation::Focusable for LocalState { fn is_focused(&self) -> bool { - self.focused + self.focused.map_or(false, |f| { + f.updated_at == LAST_FOCUS_UPDATE.with(|f| f.get()) + }) } fn focus(&mut self) { - self.focused = true; + self.set_focused(); self.focused_item = Item::Set; } fn unfocus(&mut self) { - self.focused = false; + self.focused = None; self.focused_item = Item::None; self.show_context = None; } From 5434dc95d5df6d8da0fb3e7fb7c91b6c6aee3637 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 13 Aug 2025 12:13:05 +0200 Subject: [PATCH 255/556] feat(segmented_button): pressed state style --- src/theme/style/segmented_button.rs | 4 ++ src/widget/segmented_button/style.rs | 1 + src/widget/segmented_button/widget.rs | 55 ++++++++++++++++----------- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/theme/style/segmented_button.rs b/src/theme/style/segmented_button.rs index 3f5d92db..5306b3bf 100644 --- a/src/theme/style/segmented_button.rs +++ b/src/theme/style/segmented_button.rs @@ -63,6 +63,7 @@ impl StyleSheet for Theme { text_color: container.component.on.into(), }, hover: hover(cosmic, &active, 0.2), + pressed: hover(cosmic, &active, 0.15), active, ..Default::default() } @@ -117,6 +118,7 @@ impl StyleSheet for Theme { text_color: container.component.on.into(), }, hover: hover(cosmic, &active, 0.2), + pressed: hover(cosmic, &active, 0.15), active, ..Default::default() } @@ -169,6 +171,7 @@ mod horizontal { text_color: container.component.on.into(), }, hover: super::hover(cosmic, &active, 0.3), + pressed: super::hover(cosmic, &active, 0.25), active, ..Default::default() } @@ -262,6 +265,7 @@ mod vertical { ..active }, hover: super::hover(cosmic, &active, 0.3), + pressed: super::hover(cosmic, &active, 0.25), active, ..Default::default() } diff --git a/src/widget/segmented_button/style.rs b/src/widget/segmented_button/style.rs index f09a74a2..4aa856ef 100644 --- a/src/widget/segmented_button/style.rs +++ b/src/widget/segmented_button/style.rs @@ -13,6 +13,7 @@ pub struct Appearance { pub active: ItemStatusAppearance, pub inactive: ItemStatusAppearance, pub hover: ItemStatusAppearance, + pub pressed: ItemStatusAppearance, } /// Appearance of an item in the segmented button. diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 126c78e5..6701bddb 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -541,6 +541,10 @@ where .is_some_and(|id| id.data.is_some_and(|d| d == key)) } + fn button_is_pressed(&self, state: &LocalState, key: Entity) -> bool { + state.pressed_item == Some(Item::Tab(key)) + } + /// Returns the drag id of the destination. /// /// # Panics @@ -923,28 +927,15 @@ where } } - if let Event::Mouse(mouse::Event::ButtonReleased(_)) - | Event::Touch(touch::Event::FingerLifted { .. }) = event - { + if is_lifted(&event) { state.unfocus(); } if let Some(on_activate) = self.on_activate.as_ref() { - if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) = event - { + if is_pressed(&event) { state.pressed_item = Some(Item::Tab(key)); - } else if let Event::Mouse(mouse::Event::ButtonReleased( - mouse::Button::Left, - )) - | Event::Touch(touch::Event::FingerLifted { .. }) = event - { - let mut can_activate = true; - if state.pressed_item != Some(Item::Tab(key)) { - can_activate = false; - } - - if can_activate { + } else if is_lifted(&event) { + if self.button_is_pressed(state, key) { shell.publish(on_activate(key)); state.set_focused(); state.focused_item = Item::Tab(key); @@ -1046,13 +1037,13 @@ where } } else if state.is_focused() { // Unfocus on clicks outside of the boundaries of the segmented button. - if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) = event - { + if is_pressed(&event) { state.unfocus(); state.pressed_item = None; return event::Status::Ignored; } + } else if is_lifted(&event) { + state.pressed_item = None; } if state.is_focused() { @@ -1343,8 +1334,10 @@ where let key_is_active = self.model.is_active(key); let key_is_focused = self.button_is_focused(state, key); - let key_is_hovered = LazyCell::new(|| self.button_is_hovered(state, key)); - let status_appearance = if *key_is_hovered || menu_open() { + let key_is_hovered = self.button_is_hovered(state, key); + let status_appearance = if self.button_is_pressed(state, key) && key_is_hovered { + appearance.pressed + } else if key_is_hovered || menu_open() { appearance.hover } else if key_is_active { appearance.active @@ -1504,7 +1497,7 @@ where // Whether to show the close button on this tab. let show_close_button = - (key_is_active || !self.show_close_icon_on_hover || *key_is_hovered) + (key_is_active || !self.show_close_icon_on_hover || key_is_hovered) && self.model.is_closable(key); // Width of the icon used by the close button, which we will subtract from the text bounds. @@ -1856,6 +1849,22 @@ fn right_button_released(event: &Event) -> bool { ) } +fn is_pressed(event: &Event) -> bool { + matches!( + event, + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) + ) +} + +fn is_lifted(event: &Event) -> bool { + matches!( + event, + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left,)) + | Event::Touch(touch::Event::FingerLifted { .. }) + ) +} + fn touch_lifted(event: &Event) -> bool { matches!(event, Event::Touch(touch::Event::FingerLifted { .. })) } From 95ebabf149aa287dbdbf93c97df8d8bc03c57b47 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 13 Aug 2025 12:20:22 +0200 Subject: [PATCH 256/556] improv(segmented_button): hide focus state until tabbed --- src/widget/segmented_button/widget.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 6701bddb..d21f409b 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -600,6 +600,7 @@ where collapsed: Default::default(), focused: Default::default(), focused_item: Default::default(), + focused_visible: false, hovered: Default::default(), known_length: Default::default(), middle_clicked: Default::default(), @@ -1053,6 +1054,7 @@ where .. }) = event { + state.focused_visible = true; return if modifiers.shift() { self.focus_previous(state) } else { @@ -1333,7 +1335,7 @@ where }; let key_is_active = self.model.is_active(key); - let key_is_focused = self.button_is_focused(state, key); + let key_is_focused = state.focused_visible && self.button_is_focused(state, key); let key_is_hovered = self.button_is_hovered(state, key); let status_appearance = if self.button_is_pressed(state, key) && key_is_hovered { appearance.pressed @@ -1672,6 +1674,8 @@ pub struct LocalState { pub(super) buttons_offset: usize, /// Whether buttons need to be collapsed to preserve minimum width pub(super) collapsed: bool, + /// Visibility of focus state + focused_visible: bool, /// If the widget is focused or not. focused: Option, /// The key inside the widget that is currently focused. @@ -1733,12 +1737,14 @@ impl operation::Focusable for LocalState { fn focus(&mut self) { self.set_focused(); + self.focused_visible = true; self.focused_item = Item::Set; } fn unfocus(&mut self) { self.focused = None; self.focused_item = Item::None; + self.focused_visible = false; self.show_context = None; } } From 7712ec0021efdd13a56863a25cf5571fbb2a89ec Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 13 Aug 2025 20:06:06 +0200 Subject: [PATCH 257/556] fix(context_drawer): adjust fill portion when max_width < 392 --- src/widget/context_drawer/widget.rs | 38 +++++++++++++++++------------ 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index 23afae3b..e618fbcf 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -55,27 +55,35 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { .. } = crate::theme::spacing(); - let horizontal_padding = if max_width < 392.0 { space_s } else { space_l }; - - let title = - title.map(|title| text::heading(title).width(Length::FillPortion(3)).center()); - - let close_width = if title.is_some() { - Length::FillPortion(1) + let (horizontal_padding, title_portion, side_portion) = if max_width < 392.0 { + (space_s, 1, 1) } else { - Length::Shrink + (space_l, 2, 1) + }; + + let title = title.map(|title| { + text::heading(title) + .width(Length::FillPortion(title_portion)) + .center() + }); + + let (actions_width, close_width) = if title.is_some() { + ( + Length::FillPortion(side_portion), + Length::FillPortion(side_portion), + ) + } else { + (Length::Fill, Length::Shrink) }; let header_row = row::with_capacity(3) .width(Length::Fixed(480.0)) .align_y(Alignment::Center) - .push(row::with_children(header_actions).spacing(space_xxs).width( - if title.is_some() { - Length::FillPortion(1) - } else { - Length::Fill - }, - )) + .push( + row::with_children(header_actions) + .spacing(space_xxs) + .width(actions_width), + ) .push_maybe(title) .push( button::text("Close") From 8412dd593913b85618ec30e8b92a58aaa0ad6bb8 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 13 Aug 2025 21:39:29 +0200 Subject: [PATCH 258/556] fix(image_button): improve rendering of selected image buttons --- src/widget/button/widget.rs | 42 +++---------------------------------- 1 file changed, 3 insertions(+), 39 deletions(-) diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index da8612f7..3f5a1fdf 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -521,27 +521,6 @@ impl<'a, Message: 'a + Clone> Widget let c_rad = THEME.lock().unwrap().cosmic().corner_radii; - // NOTE: Workaround to round the border of the unselected, unhovered image. - if !self.selected && !is_mouse_over { - let mut bounds = bounds; - bounds.x -= 2.0; - bounds.y -= 2.0; - bounds.width += 4.0; - bounds.height += 4.0; - renderer.fill_quad( - renderer::Quad { - bounds, - border: Border { - width: 2.0, - color: crate::theme::active().current_container().base.into(), - radius: 9.0.into(), - }, - shadow: Shadow::default(), - }, - Color::TRANSPARENT, - ); - } - if self.selected { renderer.fill_quad( Quad { @@ -961,25 +940,10 @@ pub fn draw( let mut clipped_bounds = viewport_bounds.intersection(&bounds).unwrap_or_default(); clipped_bounds.height += styling.border_width; + clipped_bounds.width += 1.0; + // Finish by drawing the border above the contents. renderer.with_layer(clipped_bounds, |renderer| { - // NOTE: Workaround to round the border of the hovered/selected image. - if is_image { - renderer.fill_quad( - renderer::Quad { - bounds, - border: Border { - width: styling.border_width, - color: crate::theme::active().current_container().base.into(), - radius: 0.0.into(), - }, - shadow: Shadow::default(), - }, - Color::TRANSPARENT, - ); - } - - // Finish by drawing the border above the contents. renderer.fill_quad( renderer::Quad { bounds, @@ -992,7 +956,7 @@ pub fn draw( }, Color::TRANSPARENT, ); - }); + }) } else { draw_contents(renderer, styling); } From c10695600b070d86d12d1e775bfb7d2a6dca93c6 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 19 Aug 2025 11:13:28 +0200 Subject: [PATCH 259/556] feat(segmented_button): add FileNav style with related widget improvements --- src/theme/style/segmented_button.rs | 6 +- src/widget/segmented_button/horizontal.rs | 8 +- src/widget/segmented_button/widget.rs | 143 ++++++++++++++-------- 3 files changed, 100 insertions(+), 57 deletions(-) diff --git a/src/theme/style/segmented_button.rs b/src/theme/style/segmented_button.rs index 5306b3bf..b9863c88 100644 --- a/src/theme/style/segmented_button.rs +++ b/src/theme/style/segmented_button.rs @@ -18,6 +18,8 @@ pub enum SegmentedButton { Control, /// Navigation bar style NavBar, + /// File browser + FileNav, /// Or implement any custom theme of your liking. Custom(Box Appearance>), } @@ -69,7 +71,7 @@ impl StyleSheet for Theme { } } - SegmentedButton::NavBar => Appearance { + SegmentedButton::NavBar | SegmentedButton::FileNav => Appearance { active_width: 0.0, ..horizontal::tab_bar(cosmic, container) }, @@ -124,7 +126,7 @@ impl StyleSheet for Theme { } } - SegmentedButton::NavBar => Appearance { + SegmentedButton::NavBar | SegmentedButton::FileNav => Appearance { active_width: 0.0, ..vertical::tab_bar(cosmic, container) }, diff --git a/src/widget/segmented_button/horizontal.rs b/src/widget/segmented_button/horizontal.rs index 724ded96..966f3a7c 100644 --- a/src/widget/segmented_button/horizontal.rs +++ b/src/widget/segmented_button/horizontal.rs @@ -67,7 +67,7 @@ where / num as f32; } - let segmetned_control = matches!(self.style, crate::theme::SegmentedButton::Control); + let is_control = matches!(self.style, crate::theme::SegmentedButton::Control); Box::new( self.model @@ -93,7 +93,7 @@ where let button_bounds = ItemBounds::Button(key, layout_bounds); let mut divider = None; - if self.dividers && segmetned_control && nth + 1 < num { + if self.dividers && is_control && nth + 1 < num { divider = Some(ItemBounds::Divider( Rectangle { width: 1.0, @@ -143,7 +143,7 @@ where let max_size = limits.height(Length::Fixed(max_height)).resolve( Length::Fill, max_height, - Size::new(f32::MAX, max_height), + Size::new(limits.max().width, max_height), ); let mut visible_width = 0.0; @@ -152,7 +152,7 @@ where for (button_size, _actual_size) in &state.internal_layout { visible_width += button_size.width; - if max_size.width >= visible_width { + if max_size.width - spacing >= visible_width { state.buttons_visible += 1; } else { visible_width = max_size.width - max_height; diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index d21f409b..685d3b0e 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -619,20 +619,19 @@ where for key in self.model.order.iter().copied() { if let Some(text) = self.model.text.get(key) { - let (font, button_state) = if self.button_is_focused(state, key) { - (self.font_active, 0) + let font = if self.button_is_focused(state, key) { + self.font_active } else if state.show_context.is_some() || self.button_is_hovered(state, key) { - (self.font_hovered, 1) + self.font_hovered } else if self.model.is_active(key) { - (self.font_active, 2) + self.font_active } else { - (self.font_inactive, 3) + self.font_inactive }; let mut hasher = DefaultHasher::new(); text.hash(&mut hasher); font.hash(&mut hasher); - button_state.hash(&mut hasher); let text_hash = hasher.finish(); if let Some(prev_hash) = state.text_hashes.insert(key, text_hash) { @@ -1293,6 +1292,15 @@ where ); } + let rad_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0; + + let divider_background = Background::Color( + crate::theme::active() + .cosmic() + .primary_component_divider() + .into(), + ); + // Draw each of the items in the widget. let mut nth = 0; self.variant_bounds(state, bounds).for_each(move |item| { @@ -1337,7 +1345,7 @@ where let key_is_active = self.model.is_active(key); let key_is_focused = state.focused_visible && self.button_is_focused(state, key); let key_is_hovered = self.button_is_hovered(state, key); - let status_appearance = if self.button_is_pressed(state, key) && key_is_hovered { + let status_appearance = if self.button_is_pressed(state, key) { appearance.pressed } else if key_is_hovered || menu_open() { appearance.hover @@ -1355,11 +1363,87 @@ where status_appearance.middle }; + // Draw the active hint on tabs + if appearance.active_width > 0.0 { + let active_width = if key_is_active { + appearance.active_width + } else { + 1.0 + }; + + renderer.fill_quad( + renderer::Quad { + bounds: if Self::VERTICAL { + Rectangle { + x: bounds.x + bounds.width - active_width, + width: active_width, + ..bounds + } + } else { + Rectangle { + y: bounds.y + bounds.height - active_width, + height: active_width, + ..bounds + } + }, + border: Border { + radius: rad_0.into(), + ..Default::default() + }, + shadow: Shadow::default(), + }, + appearance.active.text_color, + ); + } + + let original_bounds = bounds; + bounds.x += f32::from(self.button_padding[0]); + bounds.width -= f32::from(self.button_padding[0]) - f32::from(self.button_padding[2]); + let mut indent_padding = 0.0; + + // Adjust bounds by indent + if let Some(indent) = self.model.indent(key) { + if indent > 0 { + let adjustment = f32::from(indent) * f32::from(self.indent_spacing); + bounds.x += adjustment; + bounds.width -= adjustment; + + // Draw indent line + if let crate::theme::SegmentedButton::FileNav = self.style { + if indent > 1 { + indent_padding = 7.0; + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x - self.indent_spacing as f32 + indent_padding, + width: 1.0, + ..bounds + }, + border: Border { + radius: rad_0.into(), + ..Default::default() + }, + shadow: Shadow::default(), + }, + divider_background, + ); + indent_padding += 4.0; + } + } + } + } + // Render the background of the button. if key_is_focused || status_appearance.background.is_some() { renderer.fill_quad( renderer::Quad { - bounds, + bounds: Rectangle { + x: bounds.x - f32::from(self.button_padding[0]) + indent_padding, + width: bounds.width + f32::from(self.button_padding[0]) + - f32::from(self.button_padding[2]) + - indent_padding, + ..bounds + }, border: if key_is_focused { Border { width: 1.0, @@ -1377,49 +1461,6 @@ where ); } - // Draw the active hint on tabs - if appearance.active_width > 0.0 { - let rad_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0; - let active_width = if key_is_active { - appearance.active_width - } else { - 1.0 - }; - let mut bounds = bounds; - - if Self::VERTICAL { - bounds.x += bounds.height - active_width; - bounds.width = active_width; - } else { - bounds.y += bounds.height - active_width; - bounds.height = active_width; - } - - renderer.fill_quad( - renderer::Quad { - bounds, - border: Border { - radius: rad_0.into(), - ..Default::default() - }, - shadow: Shadow::default(), - }, - appearance.active.text_color, - ); - } - - let original_bounds = bounds; - - bounds.x += f32::from(self.button_padding[0]); - bounds.width -= f32::from(self.button_padding[0]) - f32::from(self.button_padding[2]); - - // Adjust bounds by indent - if let Some(indent) = self.model.indent(key) { - let adjustment = f32::from(indent) * f32::from(self.indent_spacing); - bounds.x += adjustment; - bounds.width -= adjustment; - } - // Align contents of the button to the requested `button_alignment`. { let actual_width = state.internal_layout[nth].1.width; From 6e7a6343981df7d86f7ab01fe102d0b69d8e3bed Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 19 Aug 2025 16:31:19 +0200 Subject: [PATCH 260/556] fix(segmented_button): draw all indent levels --- src/widget/segmented_button/widget.rs | 34 ++++++++++++++++----------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 685d3b0e..0fd8dcd6 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -1412,21 +1412,27 @@ where if let crate::theme::SegmentedButton::FileNav = self.style { if indent > 1 { indent_padding = 7.0; - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x - self.indent_spacing as f32 + indent_padding, - width: 1.0, - ..bounds + + for level in 1..indent { + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x + - (level as f32 * self.indent_spacing as f32) + + indent_padding, + width: 1.0, + ..bounds + }, + border: Border { + radius: rad_0.into(), + ..Default::default() + }, + shadow: Shadow::default(), }, - border: Border { - radius: rad_0.into(), - ..Default::default() - }, - shadow: Shadow::default(), - }, - divider_background, - ); + divider_background, + ); + } + indent_padding += 4.0; } } From e7b7c3a1261c6a3b4d38b1c11c3fd119810236ac Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 20 Aug 2025 16:09:17 +0200 Subject: [PATCH 261/556] improv: enable dbus-config by default, but only for Linux targets --- Cargo.toml | 13 +++++++++---- src/app/cosmic.rs | 2 +- src/core.rs | 8 ++++---- src/theme/mod.rs | 3 --- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 87ad0867..c55d2c44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ rust-version = "1.85" name = "cosmic" [features] -default = ["multi-window", "a11y"] +default = ["dbus-config", "multi-window", "a11y"] # Accessibility support a11y = ["iced/a11y", "iced_accessibility"] # Enable about widget @@ -27,8 +27,8 @@ applet = [ "multi-window", ] applet-token = ["applet"] -# Use the cosmic-settings-daemon for config handling -dbus-config = ["cosmic-config/dbus", "dep:zbus", "cosmic-settings-daemon"] +# Use the cosmic-settings-daemon for config handling on Linux targets +dbus-config = [] # Debug features debug = ["iced/debug"] # Enables pipewire support in ashpd, if ashpd is enabled @@ -107,7 +107,6 @@ cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-c chrono = "0.4.40" cosmic-config = { path = "cosmic-config" } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } -cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } css-color = "0.2.8" derive_setters = "0.1.6" futures = "0.3" @@ -135,6 +134,12 @@ unicode-segmentation = "1.12" url = "2.5.4" zbus = { version = "5.7.1", default-features = false, optional = true } +# Enable DBus feature on Linux targets +[target.'cfg(target_os = "linux")'.dependencies] +cosmic-config = { path = "cosmic-config", features = ["dbus"] } +cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" } +zbus = { version = "5.7.1", default-features = false } + [target.'cfg(unix)'.dependencies] freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } freedesktop-desktop-entry = { version = "0.7.11", optional = true } diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index c8a8dfeb..9814cf70 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -101,7 +101,7 @@ where pub fn init( (mut core, flags): (Core, T::Flags), ) -> (Self, iced::Task>) { - #[cfg(feature = "dbus-config")] + #[cfg(all(feature = "dbus-config", target_os = "linux"))] { use iced_futures::futures::executor::block_on; core.settings_daemon = block_on(cosmic_config::dbus::settings_daemon_proxy()).ok(); diff --git a/src/core.rs b/src/core.rs index 4b9811ec..c82aa839 100644 --- a/src/core.rs +++ b/src/core.rs @@ -89,7 +89,7 @@ pub struct Core { #[cfg(feature = "single-instance")] pub(crate) single_instance: bool, - #[cfg(feature = "dbus-config")] + #[cfg(all(feature = "dbus-config", target_os = "linux"))] pub(crate) settings_daemon: Option>, pub(crate) main_window: Option, @@ -146,7 +146,7 @@ impl Default for Core { applet: crate::applet::Context::default(), #[cfg(feature = "single-instance")] single_instance: false, - #[cfg(feature = "dbus-config")] + #[cfg(all(feature = "dbus-config", target_os = "linux"))] settings_daemon: None, portal_is_dark: None, portal_accent: None, @@ -353,7 +353,7 @@ impl Core { &self, config_id: &'static str, ) -> iced::Subscription> { - #[cfg(feature = "dbus-config")] + #[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); } @@ -370,7 +370,7 @@ impl Core { &self, state_id: &'static str, ) -> iced::Subscription> { - #[cfg(feature = "dbus-config")] + #[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); } diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 5e335f59..9c4e7d53 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -18,9 +18,6 @@ use iced_runtime::{Appearance, DefaultStyle}; use std::sync::{Arc, Mutex}; pub use style::*; -#[cfg(feature = "dbus-config")] -use cosmic_config::dbus; - pub type CosmicColor = ::palette::rgb::Srgba; pub type CosmicComponent = cosmic_theme::Component; pub type CosmicTheme = cosmic_theme::Theme; From ba2f4b193a26663e58359a55d394446dc00501e6 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 19 Aug 2025 23:13:43 -0400 Subject: [PATCH 262/556] fix(theme): control tint colors need to be reversed for light theme --- cosmic-theme/src/model/theme.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index aad71228..7bfd41c5 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -1004,15 +1004,14 @@ impl ThemeBuilder { let text_steps_array = text_tint.map(|c| steps(c, NonZeroUsize::new(100).unwrap())); - let control_steps_array = if let Some(neutral_tint) = neutral_tint { - let mut neutral_steps_arr = steps(neutral_tint, NonZeroUsize::new(11).unwrap()); - if !is_dark { - neutral_steps_arr.reverse(); - } - neutral_steps_arr + let mut control_steps_array = if let Some(neutral_tint) = neutral_tint { + steps(neutral_tint, NonZeroUsize::new(11).unwrap()) } else { steps(palette.as_ref().neutral_2, NonZeroUsize::new(11).unwrap()) }; + if !is_dark { + control_steps_array.reverse(); + } let p_ref = palette.as_ref(); From 29f38f83a38b550ae0de2b130fde9f2c36341fab Mon Sep 17 00:00:00 2001 From: Soso <51865119+sgued@users.noreply.github.com> Date: Wed, 20 Aug 2025 17:33:55 +0200 Subject: [PATCH 263/556] fix(about): wrong icon size in about widget --- src/widget/about.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/widget/about.rs b/src/widget/about.rs index 6590bb9d..13fcea23 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -137,7 +137,13 @@ pub fn about<'a, Message: Clone + 'static>( }; let application_name = about.name.as_ref().map(widget::text::title3); - let application_icon = about.icon.as_ref().map(|i| i.clone().icon()); + 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 links_section = section(&about.links, "Links"); From 8415d77b0aa4035a7c21189d350b23f5203559b2 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 21 Aug 2025 10:51:36 -0600 Subject: [PATCH 264/556] feat(settings/section): support custom header widgets --- src/widget/settings/section.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/widget/settings/section.rs b/src/widget/settings/section.rs index bc885005..899826dc 100644 --- a/src/widget/settings/section.rs +++ b/src/widget/settings/section.rs @@ -8,10 +8,7 @@ use std::borrow::Cow; /// A section within a settings view column. #[deprecated(note = "use `settings::section().title()` instead")] pub fn view_section<'a, Message: 'static>(title: impl Into>) -> Section<'a, Message> { - Section { - title: title.into(), - children: ListColumn::default(), - } + section().title(title) } /// A section within a settings view column. @@ -22,21 +19,26 @@ pub fn section<'a, Message: 'static>() -> Section<'a, Message> { /// A section with a pre-defined list column. pub fn with_column(children: ListColumn<'_, Message>) -> Section<'_, Message> { Section { - title: Cow::Borrowed(""), + header: None, children, } } #[must_use] pub struct Section<'a, Message> { - title: Cow<'a, str>, + header: Option>, children: ListColumn<'a, Message>, } impl<'a, Message: 'static> Section<'a, Message> { /// Define an optional title for the section. pub fn title(mut self, title: impl Into>) -> Self { - self.title = title.into(); + self.header(text::heading(title.into())) + } + + /// Define an optional custom header for the section. + pub fn header(mut self, header: impl Into>) -> Self { + self.header = Some(header.into()); self } @@ -69,11 +71,7 @@ impl<'a, Message: 'static> From> for Element<'a, Message> { fn from(data: Section<'a, Message>) -> Self { column::with_capacity(2) .spacing(8) - .push_maybe(if data.title.is_empty() { - None - } else { - Some(text::heading(data.title)) - }) + .push_maybe(data.header) .push(data.children) .into() } From 2d62503fdf042a215ebb9647e8a69f2d6dbde218 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 22 Aug 2025 13:41:12 -0700 Subject: [PATCH 265/556] fix: don't error when default config for toolkit settings is not present --- src/app/cosmic.rs | 6 ++++++ src/config/mod.rs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 9814cf70..17331832 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -369,6 +369,12 @@ where .into_iter() .filter(cosmic_config::Error::is_err) { + if let cosmic_config::Error::GetKey(_, err) = &why { + if err.kind() == std::io::ErrorKind::NotFound { + // No system default config installed; don't error + continue; + } + } tracing::error!(?why, "cosmic toolkit config update error"); } diff --git a/src/config/mod.rs b/src/config/mod.rs index dedadbc2..1253ce8d 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -25,6 +25,12 @@ pub static COSMIC_TK: LazyLock> = LazyLock::new(|| { .map(|c| { CosmicTk::get_entry(&c).unwrap_or_else(|(errors, mode)| { for why in errors.into_iter().filter(cosmic_config::Error::is_err) { + if let cosmic_config::Error::GetKey(_, err) = &why { + if err.kind() == std::io::ErrorKind::NotFound { + // No system default config installed; don't error + continue; + } + } tracing::error!(?why, "CosmicTk config entry error"); } mode From 66a2632e2ee72a1e3a9888cd5638821f6af2f861 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 22 Aug 2025 14:40:03 -0700 Subject: [PATCH 266/556] fix(cosmic-config): Fixes for error printing * Use `tracing::error!` in places where `eprintln!` was used * Loop over errors and print seperately * Print errors with `Display` rather than `Debug` * Don't print errors that should be ignored - Matches https://github.com/pop-os/libcosmic/pull/949, for same reasons. With this, and the previous change, cosmic-panel no longer spams a bunch of config errors from different applets on start. --- cosmic-config/src/dbus.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/cosmic-config/src/dbus.rs b/cosmic-config/src/dbus.rs index e66d8556..f8256bc3 100644 --- a/cosmic-config/src/dbus.rs +++ b/cosmic-config/src/dbus.rs @@ -156,8 +156,16 @@ fn watcher_stream config, Err((errors, default)) => { - if !errors.is_empty() { - eprintln!("Error getting config: {config_id} {errors:?}"); + for why in &errors { + if why.is_err() { + if let crate::Error::GetKey(_, err) = &why { + if err.kind() == std::io::ErrorKind::NotFound { + // No system default config installed; don't error + continue; + } + } + tracing::error!("error getting config: {config_id} {why}"); + } } default } @@ -171,7 +179,7 @@ fn watcher_stream Date: Mon, 25 Aug 2025 11:30:40 -0400 Subject: [PATCH 267/556] chore: update libcosmic --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 13134181..ebbfd6ca 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 13134181f8d5cfeaee4fb52172e12985b06af1cf +Subproject commit ebbfd6ca6be1e2b2e73f390ef2beefd31a4d868a From 94ee4e1915d05807c041c8770d27c6e1ec41ec05 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 26 Aug 2025 15:13:15 -0400 Subject: [PATCH 268/556] theme: fix disabled button --- cosmic-theme/src/composite.rs | 14 ++++---------- cosmic-theme/src/model/derivation.rs | 4 ++-- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/cosmic-theme/src/composite.rs b/cosmic-theme/src/composite.rs index c30469b2..66d7ac92 100644 --- a/cosmic-theme/src/composite.rs +++ b/cosmic-theme/src/composite.rs @@ -4,16 +4,10 @@ use palette::Srgba; pub fn over, B: Into>(a: A, b: B) -> Srgba { let a = a.into(); let b = b.into(); - let o_a = (alpha_over(a.alpha, b.alpha)).max(0.0).min(1.0); - let o_r = (c_over(a.red, b.red, a.alpha, b.alpha, o_a)) - .max(0.0) - .min(1.0); - let o_g = (c_over(a.green, b.green, a.alpha, b.alpha, o_a)) - .max(0.0) - .min(1.0); - let o_b = (c_over(a.blue, b.blue, a.alpha, b.alpha, o_a)) - .max(0.0) - .min(1.0); + let o_a = (alpha_over(a.alpha, b.alpha)).clamp(0.0, 1.0); + let o_r = (c_over(a.red, b.red, a.alpha, b.alpha, o_a)).clamp(0.0, 1.0); + let o_g = (c_over(a.green, b.green, a.alpha, b.alpha, o_a)).clamp(0.0, 1.0); + let o_b = (c_over(a.blue, b.blue, a.alpha, b.alpha, o_a)).clamp(0.0, 1.0); Srgba::new(o_r, o_g, o_b, o_a) } diff --git a/cosmic-theme/src/model/derivation.rs b/cosmic-theme/src/model/derivation.rs index bcc4990f..2944af40 100644 --- a/cosmic-theme/src/model/derivation.rs +++ b/cosmic-theme/src/model/derivation.rs @@ -194,8 +194,8 @@ impl Component { focus: accent, divider: if is_high_contrast { on_50 } else { on_20 }, on: on_component, - disabled: over(base_50, base), - on_disabled: over(on_50, base), + disabled: base_50, + on_disabled: on_50, border, disabled_border, } From 364c0b938183af799739abd3e870be15601ca727 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 26 Aug 2025 16:00:26 -0400 Subject: [PATCH 269/556] refactor(theme): .65 opacity for disabled button text --- cosmic-theme/src/model/derivation.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cosmic-theme/src/model/derivation.rs b/cosmic-theme/src/model/derivation.rs index 2944af40..dce653e5 100644 --- a/cosmic-theme/src/model/derivation.rs +++ b/cosmic-theme/src/model/derivation.rs @@ -168,7 +168,7 @@ impl Component { base_50.alpha *= 0.5; let on_20 = on_component.with_alpha(0.2); - let on_50 = on_20.with_alpha(0.5); + let on_65 = on_20.with_alpha(0.65); let mut disabled_border = border; disabled_border.alpha *= 0.5; @@ -192,10 +192,10 @@ impl Component { }, selected_text: accent, focus: accent, - divider: if is_high_contrast { on_50 } else { on_20 }, + divider: if is_high_contrast { on_65 } else { on_20 }, on: on_component, disabled: base_50, - on_disabled: on_50, + on_disabled: on_65, border, disabled_border, } From 4d06524f2c0a2c9fc8911f8aefae0bfb2b3e13d7 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 2 Sep 2025 12:01:05 -0400 Subject: [PATCH 270/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index ebbfd6ca..567b4a09 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit ebbfd6ca6be1e2b2e73f390ef2beefd31a4d868a +Subproject commit 567b4a0973d7a1797e7581f896c1aee236142f32 From 2dd6dce0533118104052594cce250314975453bc Mon Sep 17 00:00:00 2001 From: Tony4dev <78384793+Tony4dev@users.noreply.github.com> Date: Wed, 3 Sep 2025 12:49:35 +0000 Subject: [PATCH 271/556] improv(about): support custom license URLs --- examples/about/src/main.rs | 1 + src/widget/about.rs | 14 +++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/about/src/main.rs b/examples/about/src/main.rs index 5450b47e..957433f0 100644 --- a/examples/about/src/main.rs +++ b/examples/about/src/main.rs @@ -71,6 +71,7 @@ impl cosmic::Application for App { .version("0.1.0") .author("System 76") .license("GPL-3.0-only") + //.license_url("https://www.some-custom-license-url.com") .developers([("Michael Murphy", "mmstick@system76.com")]) .links([ ("Website", "https://system76.com/cosmic"), diff --git a/src/widget/about.rs b/src/widget/about.rs index 13fcea23..aea92991 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -25,6 +25,8 @@ pub struct About { copyright: Option, /// The license name. license: Option, + /// The license url. If None spdx.org url is used. + license_url: Option, /// Artists who contributed to the application. #[setters(skip)] artists: Vec<(String, String)>, @@ -95,10 +97,12 @@ impl<'a> About { self } - fn license_url(&self) -> Option { - self.license.as_ref().and_then(|license_str| { - let license: &dyn License = license_str.parse().ok()?; - Some(format!("https://spdx.org/licenses/{}.html", license.id())) + fn get_license_url(&self) -> Option { + self.license_url.clone().or_else(|| { + self.license.as_ref().and_then(|license_str| { + let license: &dyn License = license_str.parse().ok()?; + Some(format!("https://spdx.org/licenses/{}.html", license.id())) + }) }) } } @@ -153,7 +157,7 @@ pub fn about<'a, Message: Clone + 'static>( let translators_section = section(&about.translators, "Translators"); let documenters_section = section(&about.documenters, "Documenters"); let license = about.license.as_ref().map(|license| { - let url = about.license_url(); + let url = about.get_license_url(); widget::settings::section().title("License").add( widget::button::custom( widget::row() From f5f7c14f0375453abae8f9e9dab126fc2e637757 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 3 Sep 2025 13:31:36 -0400 Subject: [PATCH 272/556] chore: update cctk --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c55d2c44..4e9bf983 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,7 +103,7 @@ ashpd = { version = "0.11.0", default-features = false, optional = true } async-fs = { version = "2.1", optional = true } async-std = { version = "1.13", optional = true } auto_enums = "0.8.7" -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "178eb0b", optional = true } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "6254f50", optional = true } chrono = "0.4.40" cosmic-config = { path = "cosmic-config" } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } From c5df9dcf889fcb425bb96c15d4fd46e08690e58b Mon Sep 17 00:00:00 2001 From: UchiWerfer <87275644+UchiWerfer@users.noreply.github.com> Date: Wed, 3 Sep 2025 17:35:37 +0000 Subject: [PATCH 273/556] fix(calendar): show button icons on non-Linux targets --- res/icons/go-next-symbolic.svg | 3 +++ res/icons/go-previous-symbolic.svg | 3 +++ src/widget/calendar.rs | 34 +++++++++++++++++++++--------- 3 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 res/icons/go-next-symbolic.svg create mode 100644 res/icons/go-previous-symbolic.svg diff --git a/res/icons/go-next-symbolic.svg b/res/icons/go-next-symbolic.svg new file mode 100644 index 00000000..3aed3717 --- /dev/null +++ b/res/icons/go-next-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/icons/go-previous-symbolic.svg b/res/icons/go-previous-symbolic.svg new file mode 100644 index 00000000..4957cffd --- /dev/null +++ b/res/icons/go-previous-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index 83b1dcfd..02b98cfb 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -7,6 +7,7 @@ use std::cmp; use crate::iced_core::{Alignment, Length, Padding}; use crate::widget::{Grid, button, column, grid, icon, row, text}; +use apply::Apply; use chrono::{Datelike, Days, Local, Months, NaiveDate, Weekday}; /// A widget that displays an interactive calendar. @@ -115,19 +116,32 @@ where Message: Clone + 'static, { fn from(this: Calendar<'a, Message>) -> Self { + macro_rules! icon { + ($name:expr, $on_press:expr) => {{ + #[cfg(target_os = "linux")] + let icon = { + icon::from_name($name) + .apply(button::icon) + }; + #[cfg(not(target_os = "linux"))] + let icon = { + icon::from_svg_bytes(include_bytes!(concat!( + "../../res/icons/", + $name, + ".svg" + ))) + .symbolic(true) + .apply(button::icon) + }; + icon.padding([0, 12]) + .on_press($on_press) + }}; + } let date = text(this.model.visible.format("%B %Y").to_string()).size(18); let month_controls = row::with_capacity(2) - .push( - button::icon(icon::from_name("go-previous-symbolic")) - .padding([0, 12]) - .on_press((this.on_prev)()), - ) - .push( - button::icon(icon::from_name("go-next-symbolic")) - .padding([0, 12]) - .on_press((this.on_next)()), - ); + .push(icon!("go-previous-symbolic", (this.on_prev)())) + .push(icon!("go-next-symbolic", (this.on_next)())); // Calender let mut calendar_grid: Grid<'_, Message> = From b72b15d71961e06bcdaed43d1f2c66113eb565b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Wed, 3 Sep 2025 20:08:49 +0200 Subject: [PATCH 274/556] chore: update dependencies --- Cargo.toml | 28 ++++++++++++++-------------- cosmic-config/Cargo.toml | 16 ++++++++-------- cosmic-config/src/lib.rs | 8 ++------ cosmic-theme/Cargo.toml | 8 ++++---- 4 files changed, 28 insertions(+), 32 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4e9bf983..a8fcdae7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,50 +99,50 @@ async-std = [ [dependencies] apply = "0.3.0" -ashpd = { version = "0.11.0", default-features = false, optional = true } +ashpd = { version = "0.12.0", default-features = false, optional = true } async-fs = { version = "2.1", optional = true } async-std = { version = "1.13", optional = true } auto_enums = "0.8.7" cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "6254f50", optional = true } -chrono = "0.4.40" +chrono = "0.4.41" cosmic-config = { path = "cosmic-config" } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } css-color = "0.2.8" -derive_setters = "0.1.6" +derive_setters = "0.1.8" futures = "0.3" -image = { version = "0.25.5", default-features = false, features = [ +image = { version = "0.25.8", default-features = false, features = [ "jpeg", "png", ] } lazy_static = "1.5.0" -libc = { version = "0.2.171", optional = true } -license = { version = "3.6.0", optional = true } +libc = { version = "0.2.175", optional = true } +license = { version = "3.7.0", optional = true } mime = { version = "0.3.17", optional = true } palette = "0.7.6" raw-window-handle = "0.6" -rfd = { version = "0.15.3", default-features = false, features = [ +rfd = { version = "0.15.4", default-features = false, features = [ "xdg-portal", ], optional = true } rustix = { version = "1.0", features = ["pipe", "process"], optional = true } serde = { version = "1.0.219", features = ["derive"] } slotmap = "1.0.7" smol = { version = "2.0.2", optional = true } -thiserror = "2.0.12" -tokio = { version = "1.44.1", optional = true } +thiserror = "2.0.16" +tokio = { version = "1.47.1", optional = true } tracing = "0.1.41" unicode-segmentation = "1.12" -url = "2.5.4" -zbus = { version = "5.7.1", default-features = false, optional = true } +url = "2.5.7" +zbus = { version = "5.10.0", default-features = false, optional = true } # Enable DBus feature on Linux targets [target.'cfg(target_os = "linux")'.dependencies] cosmic-config = { path = "cosmic-config", features = ["dbus"] } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" } -zbus = { version = "5.7.1", default-features = false } +zbus = { version = "5.10.0", default-features = false } [target.'cfg(unix)'.dependencies] freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } -freedesktop-desktop-entry = { version = "0.7.11", optional = true } +freedesktop-desktop-entry = { version = "0.7.14", optional = true } shlex = { version = "1.3.0", optional = true } [dependencies.cosmic-theme] @@ -197,7 +197,7 @@ git = "https://github.com/pop-os/cosmic-panel" optional = true [dependencies.ron] -version = "0.9" +version = "0.11" optional = true [dependencies.taffy] diff --git a/cosmic-config/Cargo.toml b/cosmic-config/Cargo.toml index a79237c8..4d7b99e1 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -11,24 +11,24 @@ subscription = ["iced_futures"] [dependencies] cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } -zbus = { version = "5.7.1", default-features = false, optional = true } +zbus = { version = "5.10.0", default-features = false, optional = true } atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" } -calloop = { version = "0.14.2", optional = true } -notify = "8.0.0" -ron = "0.9.0" +calloop = { version = "0.14.3", optional = true } +notify = "8.2.0" +ron = "0.11.0" serde = "1.0.219" cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true } iced = { path = "../iced/", default-features = false, optional = true } iced_futures = { path = "../iced/futures/", default-features = false, optional = true } -once_cell = "1.21.1" +once_cell = "1.21.3" futures-util = { version = "0.3", optional = true } dirs.workspace = true -tokio = { version = "1.44", optional = true, features = ["time"] } +tokio = { version = "1.47", optional = true, features = ["time"] } async-std = { version = "1.13", optional = true } tracing = "0.1" [target.'cfg(unix)'.dependencies] -xdg = "2.5" +xdg = "3.0" [target.'cfg(windows)'.dependencies] -known-folders = "1.2.0" +known-folders = "1.3.1" diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index e408eac5..8759a527 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -140,9 +140,7 @@ impl Config { pub fn system(name: &str, version: u64) -> Result { let path = sanitize_name(name)?.join(format!("v{version}")); #[cfg(unix)] - let system_path = xdg::BaseDirectories::with_prefix("cosmic") - .map_err(std::io::Error::from)? - .find_data_file(path); + let system_path = xdg::BaseDirectories::with_prefix("cosmic").find_data_file(path); #[cfg(windows)] let system_path = @@ -164,9 +162,7 @@ impl Config { // Search data file, which provides default (e.g. /usr/share) #[cfg(unix)] - let system_path = xdg::BaseDirectories::with_prefix("cosmic") - .map_err(std::io::Error::from)? - .find_data_file(&path); + let system_path = xdg::BaseDirectories::with_prefix("cosmic").find_data_file(&path); #[cfg(windows)] let system_path = diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index 483014f6..bb735342 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -18,15 +18,15 @@ no-default = [] palette = { version = "0.7.6", features = ["serializing"] } almost = "0.2" serde = { version = "1.0.219", features = ["derive"] } -serde_json = { version = "1.0.140", optional = true, features = [ +serde_json = { version = "1.0.143", optional = true, features = [ "preserve_order", ] } -ron = "0.9.0" +ron = "0.11.0" lazy_static = "1.5.0" -csscolorparser = { version = "0.7.0", features = ["serde"] } +csscolorparser = { version = "0.7.2", features = ["serde"] } cosmic-config = { path = "../cosmic-config/", default-features = false, features = [ "subscription", "macro", ] } dirs.workspace = true -thiserror = "2.0.12" +thiserror = "2.0.16" From ea349aca82eecd1edabee376203608f94851d8dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Wed, 3 Sep 2025 20:22:06 +0200 Subject: [PATCH 275/556] chore: use `std::syncLazyLock` Also migrates workspace members to Rust 2024. --- Cargo.toml | 1 - cosmic-config/Cargo.toml | 3 +- cosmic-config/src/dbus.rs | 5 +-- cosmic-config/src/lib.rs | 4 +-- cosmic-config/src/subscription.rs | 2 +- cosmic-theme/Cargo.toml | 3 +- cosmic-theme/src/model/cosmic_palette.rs | 17 +++++---- cosmic-theme/src/model/theme.rs | 6 ++-- cosmic-theme/src/output/gtk4_output.rs | 6 ++-- cosmic-theme/src/output/mod.rs | 2 +- cosmic-theme/src/output/vs_code.rs | 2 +- cosmic-theme/src/steps.rs | 2 +- src/theme/mod.rs | 46 +++++++++++++----------- src/widget/calendar.rs | 18 +++------- src/widget/color_picker/mod.rs | 18 +++++----- 15 files changed, 64 insertions(+), 71 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a8fcdae7..33bf7c49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,7 +114,6 @@ image = { version = "0.25.8", default-features = false, features = [ "jpeg", "png", ] } -lazy_static = "1.5.0" libc = { version = "0.2.175", optional = true } license = { version = "3.7.0", optional = true } mime = { version = "0.3.17", optional = true } diff --git a/cosmic-config/Cargo.toml b/cosmic-config/Cargo.toml index 4d7b99e1..e838f9b5 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cosmic-config" version = "0.1.0" -edition = "2021" +edition = "2024" [features] default = ["macro", "subscription"] @@ -20,7 +20,6 @@ serde = "1.0.219" cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true } iced = { path = "../iced/", default-features = false, optional = true } iced_futures = { path = "../iced/futures/", default-features = false, optional = true } -once_cell = "1.21.3" futures-util = { version = "0.3", optional = true } dirs.workspace = true tokio = { version = "1.47", optional = true, features = ["time"] } diff --git a/cosmic-config/src/dbus.rs b/cosmic-config/src/dbus.rs index f8256bc3..e9e3395c 100644 --- a/cosmic-config/src/dbus.rs +++ b/cosmic-config/src/dbus.rs @@ -4,8 +4,9 @@ use crate::{CosmicConfigEntry, Update}; use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy}; use futures_util::SinkExt; use iced_futures::{ - futures::{self, future::pending, Stream, StreamExt}, - stream, Subscription, + Subscription, + futures::{self, Stream, StreamExt, future::pending}, + stream, }; pub async fn settings_daemon_proxy() -> zbus::Result> { diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 8759a527..72b02371 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -1,10 +1,10 @@ //! Integrations for cosmic-config — the cosmic configuration system. use notify::{ - event::{EventKind, ModifyKind, RenameMode}, RecommendedWatcher, Watcher, + event::{EventKind, ModifyKind, RenameMode}, }; -use serde::{de::DeserializeOwned, Serialize}; +use serde::{Serialize, de::DeserializeOwned}; use std::{ fmt, fs, io::Write, diff --git a/cosmic-config/src/subscription.rs b/cosmic-config/src/subscription.rs index 64255954..88f8bfa2 100644 --- a/cosmic-config/src/subscription.rs +++ b/cosmic-config/src/subscription.rs @@ -62,7 +62,7 @@ async fn start_listening, output: &mut mpsc::Sender>, ) -> ConfigState { - use iced_futures::futures::{future::pending, StreamExt}; + use iced_futures::futures::{StreamExt, future::pending}; match state { ConfigState::Init(config_id, version, is_state) => { diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index bb735342..44b0df5a 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cosmic-theme" version = "0.1.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -22,7 +22,6 @@ serde_json = { version = "1.0.143", optional = true, features = [ "preserve_order", ] } ron = "0.11.0" -lazy_static = "1.5.0" csscolorparser = { version = "0.7.2", features = ["serde"] } cosmic-config = { path = "../cosmic-config/", default-features = false, features = [ "subscription", diff --git a/cosmic-theme/src/model/cosmic_palette.rs b/cosmic-theme/src/model/cosmic_palette.rs index 6a189089..3852742b 100644 --- a/cosmic-theme/src/model/cosmic_palette.rs +++ b/cosmic-theme/src/model/cosmic_palette.rs @@ -1,15 +1,14 @@ -use lazy_static::lazy_static; use palette::Srgba; use serde::{Deserialize, Serialize}; +use std::sync::LazyLock; -lazy_static! { - /// built in light palette - pub static ref LIGHT_PALETTE: CosmicPalette = - ron::from_str(include_str!("light.ron")).unwrap(); - /// built in dark palette - pub static ref DARK_PALETTE: CosmicPalette = - ron::from_str(include_str!("dark.ron")).unwrap(); -} +/// built-in light palette +pub static LIGHT_PALETTE: LazyLock = + LazyLock::new(|| ron::from_str(include_str!("light.ron")).unwrap()); + +/// built-in dark palette +pub static DARK_PALETTE: LazyLock = + LazyLock::new(|| ron::from_str(include_str!("dark.ron")).unwrap()); /// Palette type #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 7bfd41c5..d1d3ae0a 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -1,12 +1,12 @@ use crate::{ + Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, DARK_PALETTE, + LIGHT_PALETTE, NAME, Spacing, ThemeMode, composite::over, steps::{color_index, get_small_widget_color, get_surface_color, get_text, steps}, - Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, Spacing, ThemeMode, - DARK_PALETTE, LIGHT_PALETTE, NAME, }; use cosmic_config::{Config, CosmicConfigEntry}; use palette::{ - color_difference::Wcag21RelativeContrast, rgb::Rgb, IntoColor, Oklcha, Srgb, Srgba, WithAlpha, + IntoColor, Oklcha, Srgb, Srgba, WithAlpha, color_difference::Wcag21RelativeContrast, rgb::Rgb, }; use serde::{Deserialize, Serialize}; use std::num::NonZeroUsize; diff --git a/cosmic-theme/src/output/gtk4_output.rs b/cosmic-theme/src/output/gtk4_output.rs index 9d7210f0..df6aca6a 100644 --- a/cosmic-theme/src/output/gtk4_output.rs +++ b/cosmic-theme/src/output/gtk4_output.rs @@ -1,5 +1,5 @@ -use crate::{composite::over, steps::steps, Component, Theme}; -use palette::{rgb::Rgba, Darken, IntoColor, Lighten, Srgba, WithAlpha}; +use crate::{Component, Theme, composite::over, steps::steps}; +use palette::{Darken, IntoColor, Lighten, Srgba, WithAlpha, rgb::Rgba}; use std::{ fs::{self, File}, io::{self, Write}, @@ -7,7 +7,7 @@ use std::{ path::Path, }; -use super::{to_rgba, OutputError}; +use super::{OutputError, to_rgba}; impl Theme { #[must_use] diff --git a/cosmic-theme/src/output/mod.rs b/cosmic-theme/src/output/mod.rs index f2eb6b4b..832771d4 100644 --- a/cosmic-theme/src/output/mod.rs +++ b/cosmic-theme/src/output/mod.rs @@ -1,4 +1,4 @@ -use palette::{rgb::Rgba, Srgba}; +use palette::{Srgba, rgb::Rgba}; use thiserror::Error; use crate::Theme; diff --git a/cosmic-theme/src/output/vs_code.rs b/cosmic-theme/src/output/vs_code.rs index 5c770cd6..b07c82e1 100644 --- a/cosmic-theme/src/output/vs_code.rs +++ b/cosmic-theme/src/output/vs_code.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use crate::Theme; -use super::{to_hex, OutputError}; +use super::{OutputError, to_hex}; /// Represents the workbench.colorCustomizations section of a VS Code settings.json file #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/cosmic-theme/src/steps.rs b/cosmic-theme/src/steps.rs index 506b6fa8..6c0779c2 100644 --- a/cosmic-theme/src/steps.rs +++ b/cosmic-theme/src/steps.rs @@ -1,7 +1,7 @@ use std::num::NonZeroUsize; use almost::equal; -use palette::{convert::FromColorUnclamped, ClampAssign, FromColor, Lch, Oklcha, Srgb, Srgba}; +use palette::{ClampAssign, FromColor, Lch, Oklcha, Srgb, Srgba, convert::FromColorUnclamped}; /// Get an array of 100 colors with a specific hue and chroma /// over the full range of lightness. diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 9c4e7d53..f01180c1 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -15,33 +15,37 @@ use cosmic_theme::Spacing; use cosmic_theme::ThemeMode; use iced_futures::Subscription; use iced_runtime::{Appearance, DefaultStyle}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, LazyLock, Mutex}; pub use style::*; pub type CosmicColor = ::palette::rgb::Srgba; pub type CosmicComponent = cosmic_theme::Component; pub type CosmicTheme = cosmic_theme::Theme; -lazy_static::lazy_static! { - pub static ref COSMIC_DARK: CosmicTheme = CosmicTheme::dark_default(); - pub static ref COSMIC_HC_DARK: CosmicTheme = CosmicTheme::high_contrast_dark_default(); - pub static ref COSMIC_LIGHT: CosmicTheme = CosmicTheme::light_default(); - pub static ref COSMIC_HC_LIGHT: CosmicTheme = CosmicTheme::high_contrast_light_default(); - pub static ref TRANSPARENT_COMPONENT: Component = Component { - base: CosmicColor::new(0.0, 0.0, 0.0, 0.0), - hover: CosmicColor::new(0.0, 0.0, 0.0, 0.0), - pressed: CosmicColor::new(0.0, 0.0, 0.0, 0.0), - selected: CosmicColor::new(0.0, 0.0, 0.0, 0.0), - selected_text: CosmicColor::new(0.0, 0.0, 0.0, 0.0), - focus: CosmicColor::new(0.0, 0.0, 0.0, 0.0), - disabled: CosmicColor::new(0.0, 0.0, 0.0, 0.0), - on: CosmicColor::new(0.0, 0.0, 0.0, 0.0), - on_disabled: CosmicColor::new(0.0, 0.0, 0.0, 0.0), - divider: CosmicColor::new(0.0, 0.0, 0.0, 0.0), - border: CosmicColor::new(0.0, 0.0, 0.0, 0.0), - disabled_border: CosmicColor::new(0.0, 0.0, 0.0, 0.0), - }; -} +pub static COSMIC_DARK: LazyLock = LazyLock::new(|| CosmicTheme::dark_default()); + +pub static COSMIC_HC_DARK: LazyLock = + LazyLock::new(|| CosmicTheme::high_contrast_dark_default()); + +pub static COSMIC_LIGHT: LazyLock = LazyLock::new(|| CosmicTheme::light_default()); + +pub static COSMIC_HC_LIGHT: LazyLock = + 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), + hover: CosmicColor::new(0.0, 0.0, 0.0, 0.0), + pressed: CosmicColor::new(0.0, 0.0, 0.0, 0.0), + selected: CosmicColor::new(0.0, 0.0, 0.0, 0.0), + selected_text: CosmicColor::new(0.0, 0.0, 0.0, 0.0), + focus: CosmicColor::new(0.0, 0.0, 0.0, 0.0), + disabled: CosmicColor::new(0.0, 0.0, 0.0, 0.0), + on: CosmicColor::new(0.0, 0.0, 0.0, 0.0), + on_disabled: CosmicColor::new(0.0, 0.0, 0.0, 0.0), + divider: CosmicColor::new(0.0, 0.0, 0.0, 0.0), + border: CosmicColor::new(0.0, 0.0, 0.0, 0.0), + disabled_border: CosmicColor::new(0.0, 0.0, 0.0, 0.0), +}); pub(crate) static THEME: Mutex = Mutex::new(Theme { theme_type: ThemeType::Dark, diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index 02b98cfb..303a1ed9 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -119,22 +119,14 @@ where macro_rules! icon { ($name:expr, $on_press:expr) => {{ #[cfg(target_os = "linux")] - let icon = { - icon::from_name($name) - .apply(button::icon) - }; + let icon = { icon::from_name($name).apply(button::icon) }; #[cfg(not(target_os = "linux"))] let icon = { - icon::from_svg_bytes(include_bytes!(concat!( - "../../res/icons/", - $name, - ".svg" - ))) - .symbolic(true) - .apply(button::icon) + icon::from_svg_bytes(include_bytes!(concat!("../../res/icons/", $name, ".svg"))) + .symbolic(true) + .apply(button::icon) }; - icon.padding([0, 12]) - .on_press($on_press) + icon.padding([0, 12]).on_press($on_press) }}; } let date = text(this.model.visible.format("%B %Y").to_string()).size(18); diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 789969ac..a17625dc 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -6,6 +6,7 @@ use std::borrow::Cow; use std::iter; use std::rc::Rc; +use std::sync::LazyLock; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{Duration, Instant}; @@ -26,7 +27,6 @@ use iced_core::{ use iced_widget::slider::HandleShape; use iced_widget::{Row, canvas, column, horizontal_space, row, scrollable, vertical_space}; -use lazy_static::lazy_static; use palette::{FromColor, RgbHue}; use super::divider::horizontal; @@ -38,17 +38,17 @@ use super::{Icon, button, segmented_control, text, text_input, tooltip}; pub use ColorPickerModel as Model; // TODO is this going to look correct enough? -lazy_static! { - pub static ref HSV_RAINBOW: Vec = (0u16..8) - .map( - |h| iced::Color::from(palette::Srgba::from_color(palette::Hsv::new_srgb_const( +pub static HSV_RAINBOW: LazyLock> = LazyLock::new(|| { + (0u16..8) + .map(|h| { + Color::from(palette::Srgba::from_color(palette::Hsv::new_srgb_const( RgbHue::new(f32::from(h) * 360.0 / 7.0), 1.0, - 1.0 + 1.0, ))) - ) - .collect(); -} + }) + .collect() +}); const MAX_RECENT: usize = 20; From 066999586bef4a30f45de0edb872ef0dddd7adf9 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, 5 Sep 2025 18:50:25 +0200 Subject: [PATCH 276/556] feat: add i18n support for libcosmic widgets --- Cargo.toml | 7 ++++ i18n.toml | 4 +++ i18n/en/libcosmic.ftl | 11 +++++++ i18n/sr-Cyrl/libcosmic.ftl | 11 +++++++ i18n/sr-Latn/libcosmic.ftl | 11 +++++++ src/lib.rs | 2 ++ src/localize.rs | 51 +++++++++++++++++++++++++++++ src/widget/about.rs | 18 +++++----- src/widget/context_drawer/widget.rs | 10 +++--- 9 files changed, 110 insertions(+), 15 deletions(-) create mode 100644 i18n.toml create mode 100644 i18n/en/libcosmic.ftl create mode 100644 i18n/sr-Cyrl/libcosmic.ftl create mode 100644 i18n/sr-Latn/libcosmic.ftl create mode 100644 src/localize.rs diff --git a/Cargo.toml b/Cargo.toml index 33bf7c49..076fc00f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,6 +107,13 @@ cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-c chrono = "0.4.41" cosmic-config = { path = "cosmic-config" } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } +# Internationalization +i18n-embed = { version = "0.16.0", features = [ + "fluent-system", + "desktop-requester", +] } +i18n-embed-fl = "0.10" +rust-embed = "8.7.2" css-color = "0.2.8" derive_setters = "0.1.8" futures = "0.3" diff --git a/i18n.toml b/i18n.toml new file mode 100644 index 00000000..76f7c310 --- /dev/null +++ b/i18n.toml @@ -0,0 +1,4 @@ +fallback_language = "en" + +[fluent] +assets_dir = "i18n" diff --git a/i18n/en/libcosmic.ftl b/i18n/en/libcosmic.ftl new file mode 100644 index 00000000..45266a9b --- /dev/null +++ b/i18n/en/libcosmic.ftl @@ -0,0 +1,11 @@ +# Context Drawer +close = Close + +# About +license = License +links = Links +developers = Developers +designers = Designers +artists = Artists +translators = Translators +documenters = Documenters diff --git a/i18n/sr-Cyrl/libcosmic.ftl b/i18n/sr-Cyrl/libcosmic.ftl new file mode 100644 index 00000000..579392f4 --- /dev/null +++ b/i18n/sr-Cyrl/libcosmic.ftl @@ -0,0 +1,11 @@ +# Context Drawer +close = Затвори + +# About +license = Лиценца +links = Линкови +Developers = Програмери +Designers = Дизајнери +Artists = Уметници +Translators = Преводиоци +Documenters = Документатори diff --git a/i18n/sr-Latn/libcosmic.ftl b/i18n/sr-Latn/libcosmic.ftl new file mode 100644 index 00000000..9fbe9a21 --- /dev/null +++ b/i18n/sr-Latn/libcosmic.ftl @@ -0,0 +1,11 @@ +# Context Drawer +close = Zatvori + +# About +license = Licenca +links = Linkovi +developers = Programeri +designers = Dizajneri +artists = Umetnici +translators = Prevodioci +documenters = Dokumentatori diff --git a/src/lib.rs b/src/lib.rs index e8aeeedd..a180c224 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,6 +90,8 @@ pub use iced_wgpu; pub mod icon_theme; pub mod keyboard_nav; +mod localize; + #[cfg(all(target_env = "gnu", not(target_os = "windows")))] pub(crate) mod malloc; diff --git a/src/localize.rs b/src/localize.rs new file mode 100644 index 00000000..95a31655 --- /dev/null +++ b/src/localize.rs @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use i18n_embed::{ + DefaultLocalizer, LanguageLoader, Localizer, + fluent::{FluentLanguageLoader, fluent_language_loader}, +}; +use rust_embed::RustEmbed; +use std::sync::{LazyLock, OnceLock}; + +#[derive(RustEmbed)] +#[folder = "i18n/"] +struct Localizations; + +pub static LANGUAGE_LOADER: LazyLock = LazyLock::new(|| { + let loader: FluentLanguageLoader = fluent_language_loader!(); + + loader + .load_fallback_language(&Localizations) + .expect("Error while loading fallback language"); + + loader +}); + +static LOCALIZATION_INITIALIZED: OnceLock<()> = OnceLock::new(); + +#[macro_export] +macro_rules! fl { + ($message_id:literal) => {{ + $crate::localize::localize(); + i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id) + }}; + ($message_id:literal, $($args:expr),*) => {{ + $crate::localize::localize(); + i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id, $($args), *) + }}; +} + +// Get the `Localizer` to be used for localizing this library. +pub fn localizer() -> Box { + Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations)) +} + +pub fn localize() { + LOCALIZATION_INITIALIZED.get_or_init(|| { + let localizer = localizer(); + let requested_languages = i18n_embed::DesktopLanguageRequester::requested_languages(); + if let Err(error) = localizer.select(&requested_languages) { + eprintln!("Error while loading language for libcosmic {}", error); + } + }); +} diff --git a/src/widget/about.rs b/src/widget/about.rs index aea92991..f1f84106 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -1,6 +1,6 @@ use { crate::{ - Element, + Element, fl, iced::{Alignment, Length}, widget::{self, horizontal_space}, }, @@ -116,7 +116,7 @@ pub fn about<'a, Message: Clone + 'static>( space_xxs, space_m, .. } = crate::theme::spacing(); - let section = |list: &'a Vec<(String, String)>, title: &'a str| { + let section = |list: &'a Vec<(String, String)>, title: String| { (!list.is_empty()).then_some({ let items: Vec> = list.iter() @@ -150,15 +150,15 @@ pub fn about<'a, Message: Clone + 'static>( }); let author = about.author.as_ref().map(widget::text::body); let version = about.version.as_ref().map(widget::button::standard); - let links_section = section(&about.links, "Links"); - let developers_section = section(&about.developers, "Developers"); - let designers_section = section(&about.designers, "Designers"); - let artists_section = section(&about.artists, "Artists"); - let translators_section = section(&about.translators, "Translators"); - let documenters_section = section(&about.documenters, "Documenters"); + 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.get_license_url(); - widget::settings::section().title("License").add( + widget::settings::section().title(fl!("license")).add( widget::button::custom( widget::row() .push(widget::text(license)) diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index e618fbcf..b46f6017 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -1,12 +1,10 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -use std::borrow::Cow; - -use crate::widget::{LayerContainer, button, column, container, icon, row, scrollable, text}; -use crate::{Apply, Element, Renderer, Theme}; - use super::overlay::Overlay; +use crate::widget::{LayerContainer, button, column, container, icon, row, scrollable, text}; +use crate::{Apply, Element, Renderer, Theme, fl}; +use std::borrow::Cow; use iced_core::Alignment; use iced_core::event::{self, Event}; @@ -86,7 +84,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { ) .push_maybe(title) .push( - button::text("Close") + button::text(fl!("close")) .trailing_icon(icon::from_name("go-next-symbolic")) .on_press(on_close) .apply(container) From ac18f009b4fd2a20d18e2f656cd668c320ba58fd Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Sun, 7 Sep 2025 19:17:59 -0600 Subject: [PATCH 277/556] Update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 567b4a09..f89222f4 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 567b4a0973d7a1797e7581f896c1aee236142f32 +Subproject commit f89222f4fa7c11d24a97ee12a80877189427ddbe From 39a5607400452fbf27fe2c1d14c1d2dea8d51447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Tue, 9 Sep 2025 15:30:12 +0200 Subject: [PATCH 278/556] improv(icon): use correct size variant for `Named` Update`Icon::size` method to correctly handle `Named` icons by using the provided size retroactively. --- src/widget/icon/mod.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/widget/icon/mod.rs b/src/widget/icon/mod.rs index 5a90d35b..8b21b6dd 100644 --- a/src/widget/icon/mod.rs +++ b/src/widget/icon/mod.rs @@ -43,6 +43,7 @@ pub struct Icon { #[setters(skip)] handle: Handle, class: crate::theme::Svg, + #[setters(skip)] pub(super) size: u16, content_fit: ContentFit, #[setters(strip_option)] @@ -72,6 +73,22 @@ impl Icon { None } + #[must_use] + pub fn size(mut self, size: u16) -> Self { + match &self.handle.data { + // ensures correct icon size variant selection + Data::Name(named) => { + let mut new_named = named.clone(); + new_named.size = Some(size); + self.handle = new_named.handle(); + } + _ => { + self.size = size; + } + } + self + } + #[must_use] fn view<'a, Message: 'a>(self) -> Element<'a, Message> { let from_image = |handle| { From e83e43bf1e38476e79383b299668afa525bad3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:50:38 +0200 Subject: [PATCH 279/556] fix(icon): always set size Fixes an oversight in my previous commit 39a5607400452fbf27fe2c1d14c1d2dea8d51447. --- src/widget/icon/mod.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/widget/icon/mod.rs b/src/widget/icon/mod.rs index 8b21b6dd..20e8bf25 100644 --- a/src/widget/icon/mod.rs +++ b/src/widget/icon/mod.rs @@ -75,16 +75,12 @@ impl Icon { #[must_use] pub fn size(mut self, size: u16) -> Self { - match &self.handle.data { - // ensures correct icon size variant selection - Data::Name(named) => { - let mut new_named = named.clone(); - new_named.size = Some(size); - self.handle = new_named.handle(); - } - _ => { - self.size = size; - } + self.size = size; + // ensures correct icon size variant selection + if let Data::Name(named) = &self.handle.data { + let mut new_named = named.clone(); + new_named.size = Some(size); + self.handle = new_named.handle(); } self } From 31aa0bd3dfea708722ce214d44ad24265cf2112d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 10 Sep 2025 11:32:10 -0400 Subject: [PATCH 280/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index f89222f4..efcad825 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit f89222f4fa7c11d24a97ee12a80877189427ddbe +Subproject commit efcad82516588897420adc646b33ed71f7f836c3 From e568122083ebc42d11e82b95ecc2e233c00554f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Thu, 11 Sep 2025 16:37:42 +0200 Subject: [PATCH 281/556] fix(context_drawer): title alignment Something caused text alignment to break, so this gets around it by wrapping the text in a container. --- Cargo.toml | 8 ++++---- cosmic-config/Cargo.toml | 2 +- src/widget/context_drawer/widget.rs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 076fc00f..5d22afc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,7 +104,7 @@ async-fs = { version = "2.1", optional = true } async-std = { version = "1.13", optional = true } auto_enums = "0.8.7" cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "6254f50", optional = true } -chrono = "0.4.41" +chrono = "0.4.42" cosmic-config = { path = "cosmic-config" } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } # Internationalization @@ -129,7 +129,7 @@ raw-window-handle = "0.6" rfd = { version = "0.15.4", default-features = false, features = [ "xdg-portal", ], optional = true } -rustix = { version = "1.0", features = ["pipe", "process"], optional = true } +rustix = { version = "1.1", features = ["pipe", "process"], optional = true } serde = { version = "1.0.219", features = ["derive"] } slotmap = "1.0.7" smol = { version = "2.0.2", optional = true } @@ -138,13 +138,13 @@ tokio = { version = "1.47.1", optional = true } tracing = "0.1.41" unicode-segmentation = "1.12" url = "2.5.7" -zbus = { version = "5.10.0", default-features = false, optional = true } +zbus = { version = "5.11.0", default-features = false, optional = true } # Enable DBus feature on Linux targets [target.'cfg(target_os = "linux")'.dependencies] cosmic-config = { path = "cosmic-config", features = ["dbus"] } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" } -zbus = { version = "5.10.0", default-features = false } +zbus = { version = "5.11.0", default-features = false } [target.'cfg(unix)'.dependencies] freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } diff --git a/cosmic-config/Cargo.toml b/cosmic-config/Cargo.toml index e838f9b5..9b5aca07 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -11,7 +11,7 @@ subscription = ["iced_futures"] [dependencies] cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } -zbus = { version = "5.10.0", default-features = false, optional = true } +zbus = { version = "5.11.0", default-features = false, optional = true } atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" } calloop = { version = "0.14.3", optional = true } notify = "8.2.0" diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index b46f6017..c65fe082 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -61,8 +61,8 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { let title = title.map(|title| { text::heading(title) - .width(Length::FillPortion(title_portion)) - .center() + .apply(container) + .center_x(Length::FillPortion(title_portion)) }); let (actions_width, close_width) = if title.is_some() { From b9a00c6e799b80154190f11943bb65c1fc4dc58b 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, 12 Sep 2025 20:31:01 +0200 Subject: [PATCH 282/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index efcad825..d0508750 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit efcad82516588897420adc646b33ed71f7f836c3 +Subproject commit d05087507a7a0e37e26f174cfc97629c960b4383 From 978bde5720ee00b26fe4ab221a914926851bd62b Mon Sep 17 00:00:00 2001 From: Matei Pralea Date: Sun, 14 Sep 2025 10:40:06 +0300 Subject: [PATCH 283/556] i18n(ro): Add Romanian translation --- i18n/ro/libcosmic.ftl | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 i18n/ro/libcosmic.ftl diff --git a/i18n/ro/libcosmic.ftl b/i18n/ro/libcosmic.ftl new file mode 100644 index 00000000..da9f80a5 --- /dev/null +++ b/i18n/ro/libcosmic.ftl @@ -0,0 +1,11 @@ +# Context Drawer +close = Închide + +# About +license = Licență +links = Linkuri +developers = Dezvoltatori +designers = Designeri +artists = Artiști +translators = Traducători +documenters = Documentatori From 0e797b244043ee86610113d547950204258dea83 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 11 Sep 2025 01:46:03 -0400 Subject: [PATCH 284/556] improv(input): better initial handling of focus state --- src/widget/text_input/input.rs | 53 ++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index b8c035d4..3a2af337 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -591,7 +591,10 @@ where // Unfocus text input if it becomes disabled if self.on_input.is_none() && !self.manage_value { state.last_click = None; - state.is_focused = None; + state.is_focused = state.is_focused.map(|mut f| { + f.focused = false; + f + }); state.is_pasting = None; state.dragging_state = None; } @@ -628,18 +631,19 @@ where state.dirty = true; } - if self.always_active && state.is_focused.is_none() { + if self.always_active && !state.is_focused() { let now = Instant::now(); LAST_FOCUS_UPDATE.with(|x| x.set(now)); state.is_focused = Some(Focus { updated_at: now, now, + focused: true, }); } // if the previous state was at the end of the text, keep it there let old_value = Value::new(&old_value); - if state.is_focused.is_some() { + if state.is_focused() { if let cursor::State::Index(index) = state.cursor.state(&old_value) { if index == old_value.len() { state.cursor.move_to(self.value.len()); @@ -647,7 +651,7 @@ where }; } - if let Some(f) = state.is_focused.as_ref() { + if let Some(f) = state.is_focused.as_ref().filter(|f| f.focused) { if f.updated_at != LAST_FOCUS_UPDATE.with(|f| f.get()) { state.unfocus(); state.emit_unfocus = true; @@ -838,9 +842,12 @@ where if self.is_editable_variant { if let Some(ref on_edit) = self.on_toggle_edit { let state = tree.state.downcast_mut::(); - if !state.is_read_only && state.is_focused.is_none() { + if !state.is_read_only && state.is_focused.is_some_and(|f| !f.focused) { state.is_read_only = true; shell.publish((on_edit)(false)); + } else if state.is_focused() && state.is_read_only { + state.is_read_only = false; + shell.publish((on_edit)(true)); } } } @@ -1392,6 +1399,7 @@ pub fn update<'a, Message: Clone + 'static>( state.is_focused = Some(Focus { updated_at: now, now, + focused: true, }); } @@ -1520,7 +1528,7 @@ pub fn update<'a, Message: Clone + 'static>( } // Focus on click of the text input, and ensure that the input is writable. - if state.is_focused.is_none() + if !state.is_focused() && matches!(state.dragging_state, None | Some(DraggingState::Selection)) { if let Some(on_focus) = on_focus { @@ -1541,6 +1549,7 @@ pub fn update<'a, Message: Clone + 'static>( state.is_focused = Some(Focus { updated_at: now, now, + focused: true, }); } @@ -1592,8 +1601,7 @@ pub fn update<'a, Message: Clone + 'static>( .. }) => { let state = state(); - - if let Some(focus) = &mut state.is_focused { + if let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) { if state.is_read_only || (!manage_value && on_input.is_none()) { return event::Status::Ignored; }; @@ -1873,7 +1881,7 @@ pub fn update<'a, Message: Clone + 'static>( Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => { let state = state(); - if state.is_focused.is_some() { + if state.is_focused() { match key { keyboard::Key::Character(c) if "v" == c => { state.is_pasting = None; @@ -1897,7 +1905,7 @@ pub fn update<'a, Message: Clone + 'static>( Event::Window(window::Event::RedrawRequested(now)) => { let state = state(); - if let Some(focus) = &mut state.is_focused { + if let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) { focus.now = now; let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS @@ -2258,12 +2266,15 @@ pub fn draw<'a, Message>( let handling_dnd_offer = !matches!(state.dnd_offer, DndOfferState::None); #[cfg(not(feature = "wayland"))] let handling_dnd_offer = false; - let (cursor, offset) = if let Some(focus) = &state.is_focused.or_else(|| { - handling_dnd_offer.then(|| Focus { - updated_at: Instant::now(), - now: Instant::now(), - }) - }) { + 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 { + updated_at: now, + now, + focused: true, + }) + }) { match state.cursor.state(value) { cursor::State::Index(position) => { let (text_value_width, offset) = @@ -2547,6 +2558,7 @@ pub struct State { struct Focus { updated_at: Instant, now: Instant, + focused: bool, } impl State { @@ -2565,6 +2577,7 @@ impl State { Focus { updated_at: now, now, + focused: true, } }), select_on_focus, @@ -2623,7 +2636,7 @@ impl State { #[inline] #[must_use] pub fn is_focused(&self) -> bool { - self.is_focused.is_some() + self.is_focused.is_some_and(|f| f.focused) } /// Returns the [`Cursor`] of the [`TextInput`]. @@ -2642,6 +2655,7 @@ impl State { self.is_focused = Some(Focus { updated_at: now, now, + focused: true, }); if self.select_on_focus { @@ -2656,7 +2670,10 @@ impl State { pub(super) fn unfocus(&mut self) { self.move_cursor_to_front(); self.last_click = None; - self.is_focused = None; + self.is_focused = self.is_focused.map(|mut f| { + f.focused = false; + f + }); self.dragging_state = None; self.is_pasting = None; self.keyboard_modifiers = keyboard::Modifiers::default(); From c01254dd18c95f05742690f04964feef7d931192 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 16 Sep 2025 22:54:27 -0400 Subject: [PATCH 285/556] fix(menu): overlays should be used when multi-window is not active --- src/widget/menu/menu_bar.rs | 41 ++++++++++++++++++++++++++++------- src/widget/menu/menu_inner.rs | 24 ++++++++++++++++---- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 707aebdc..30c802c1 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -9,7 +9,12 @@ use super::{ }, menu_tree::MenuTree, }; -#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] +#[cfg(all( + feature = "multi-window", + feature = "wayland", + feature = "winit", + feature = "surface-message" +))] use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; use crate::{ Renderer, @@ -190,7 +195,7 @@ pub struct MenuBar { menu_roots: Vec>, style: ::Style, window_id: window::Id, - #[cfg(all(feature = "wayland", feature = "winit"))] + #[cfg(all(feature = "multi-window", feature = "wayland", feature = "winit"))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, pub(crate) on_surface_action: Option Message + Send + Sync + 'static>>, @@ -225,7 +230,7 @@ where menu_roots, style: ::Style::default(), window_id: window::Id::NONE, - #[cfg(all(feature = "wayland", feature = "winit"))] + #[cfg(all(feature = "multi-window", feature = "wayland", feature = "winit"))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), on_surface_action: None, } @@ -319,7 +324,7 @@ where self } - #[cfg(all(feature = "wayland", feature = "winit"))] + #[cfg(all(feature = "multi-window", feature = "wayland", feature = "winit"))] pub fn with_positioner( mut self, positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, @@ -351,7 +356,12 @@ where self } - #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + #[cfg(all( + feature = "multi-window", + feature = "wayland", + feature = "winit", + feature = "surface-message" + ))] #[allow(clippy::too_many_lines)] fn create_popup( &mut self, @@ -630,7 +640,12 @@ where if !create_popup { return event::Status::Ignored; } - #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + #[cfg(all( + feature = "multi-window", + feature = "wayland", + feature = "winit", + feature = "surface-message" + ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state); } @@ -638,7 +653,12 @@ where Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered) if open && view_cursor.is_over(layout.bounds()) => { - #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + #[cfg(all( + feature = "multi-window", + feature = "wayland", + feature = "winit", + feature = "surface-message" + ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state); } @@ -715,7 +735,12 @@ where _renderer: &Renderer, translation: Vector, ) -> Option> { - #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + #[cfg(all( + feature = "multi-window", + feature = "wayland", + feature = "winit", + feature = "surface-message" + ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && self.on_surface_action.is_some() && self.window_id != window::Id::NONE diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 18b4433f..6c694de7 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -4,7 +4,12 @@ use std::{borrow::Cow, sync::Arc}; use super::{menu_bar::MenuBarState, menu_tree::MenuTree}; -#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] +#[cfg(all( + feature = "multi-window", + feature = "wayland", + feature = "winit", + feature = "surface-message" +))] use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; use crate::style::menu_bar::StyleSheet; @@ -663,6 +668,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { if needs_reset { #[cfg(all( + feature = "multi-window", feature = "wayland", feature = "winit", feature = "surface-message" @@ -932,7 +938,12 @@ impl Widget event::Status { let (new_root, status) = self.on_event(event, layout, cursor, renderer, clipboard, shell); - #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + #[cfg(all( + feature = "multi-window", + feature = "wayland", + feature = "winit", + feature = "surface-message" + ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { if let Some((new_root, new_ms)) = new_root { use iced_runtime::platform_specific::wayland::popup::{ @@ -1177,7 +1188,12 @@ pub(crate) fn init_root_menu( }); } -#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] +#[cfg(all( + feature = "multi-window", + feature = "wayland", + feature = "winit", + feature = "surface-message" +))] pub(super) fn init_root_popup_menu( menu: &mut Menu<'_, Message>, renderer: &crate::Renderer, @@ -1474,7 +1490,7 @@ where .as_ref() .is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty()); - #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + #[cfg(all(feature = "multi-window", feature = "wayland", feature = "winit", feature = "surface-message"))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove { if let Some(id) = state.popup_id.remove(&menu.window_id) { state.active_root.truncate(menu.depth + 1); From 9ff208e9d7b538bc780bd2c4b13d342337780469 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 17 Sep 2025 14:31:41 -0400 Subject: [PATCH 286/556] fix: if editable input is focused by operation, emit a message --- src/widget/text_input/input.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 3a2af337..12e8e7ce 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -638,6 +638,7 @@ where updated_at: now, now, focused: true, + needs_update: false, }); } @@ -838,7 +839,7 @@ where let size = self.size.unwrap_or_else(|| renderer.default_size().0); let line_height = self.line_height; - // Disables editing of the editable variant when clicking outside of it. + // Disables editing of the editable variant when clicking outside of, or for tab focus changes. if self.is_editable_variant { if let Some(ref on_edit) = self.on_toggle_edit { let state = tree.state.downcast_mut::(); @@ -848,6 +849,11 @@ where } else if state.is_focused() && state.is_read_only { state.is_read_only = false; shell.publish((on_edit)(true)); + } else if let Some(f) = state.is_focused.as_mut().filter(|f| f.needs_update) { + // TODO do we want to just move this to on_focus or on_unfocus for all inputs? + f.needs_update = false; + state.is_read_only = true; + shell.publish((on_edit)(f.focused)); } } } @@ -1400,6 +1406,7 @@ pub fn update<'a, Message: Clone + 'static>( updated_at: now, now, focused: true, + needs_update: false, }); } @@ -1550,6 +1557,7 @@ pub fn update<'a, Message: Clone + 'static>( updated_at: now, now, focused: true, + needs_update: false, }); } @@ -2270,6 +2278,7 @@ pub fn draw<'a, Message>( state.is_focused.filter(|f| f.focused).or_else(|| { let now = Instant::now(); handling_dnd_offer.then(|| Focus { + needs_update: false, updated_at: now, now, focused: true, @@ -2559,6 +2568,7 @@ struct Focus { updated_at: Instant, now: Instant, focused: bool, + needs_update: bool, } impl State { @@ -2578,6 +2588,7 @@ impl State { updated_at: now, now, focused: true, + needs_update: false, } }), select_on_focus, @@ -2656,6 +2667,7 @@ impl State { updated_at: now, now, focused: true, + needs_update: false, }); if self.select_on_focus { @@ -2672,6 +2684,7 @@ impl State { self.last_click = None; self.is_focused = self.is_focused.map(|mut f| { f.focused = false; + f.needs_update = false; f }); self.dragging_state = None; @@ -2724,11 +2737,17 @@ impl operation::Focusable for State { #[inline] fn focus(&mut self) { Self::focus(self); + if let Some(focus) = self.is_focused.as_mut() { + focus.needs_update = true; + } } #[inline] fn unfocus(&mut self) { Self::unfocus(self); + if let Some(focus) = self.is_focused.as_mut() { + focus.needs_update = true; + } } } From 19d273ed2e2058e52a947387c948a8bce7bb39a0 Mon Sep 17 00:00:00 2001 From: jermanuts <109705802+jermanuts@users.noreply.github.com> Date: Sat, 6 Sep 2025 05:54:13 +0200 Subject: [PATCH 287/556] i18n(ar): add Arabic translation --- i18n/ar/libcosmic.ftl | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 i18n/ar/libcosmic.ftl diff --git a/i18n/ar/libcosmic.ftl b/i18n/ar/libcosmic.ftl new file mode 100644 index 00000000..4fc8582b --- /dev/null +++ b/i18n/ar/libcosmic.ftl @@ -0,0 +1,11 @@ +# Context Drawer +close = أغلق + +# About +license = الترخيص +links = الروابط +developers = المطوّرون +designers = المصمّمون +artists = الفنانون +translators = المترجمون +documenters = الموثّقون From 66df10ad89a3a37183c124bfbef254af108af5ac Mon Sep 17 00:00:00 2001 From: FurkanAdmin <47474630+FurkanAdmin@users.noreply.github.com> Date: Fri, 19 Sep 2025 01:20:39 +0300 Subject: [PATCH 288/556] i18n(tr): add translation --- i18n/tr/libcosmic.ftl | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 i18n/tr/libcosmic.ftl diff --git a/i18n/tr/libcosmic.ftl b/i18n/tr/libcosmic.ftl new file mode 100644 index 00000000..fd0f5475 --- /dev/null +++ b/i18n/tr/libcosmic.ftl @@ -0,0 +1,11 @@ +# Context Drawer +close = Kapat + +# About +license = Lisans +links = Bağlantılar +developers = Geliştiriciler +designers = Tasarımcılar +artists = Sanatçılar +translators = Çevirmenler +documenters = Belgelendiriciler From 17fa2cd29a69eb2098eb4f3bb912631ee6cf1df1 Mon Sep 17 00:00:00 2001 From: David Carvalho Date: Sun, 7 Sep 2025 22:47:59 -0300 Subject: [PATCH 289/556] i18n(pt-BR): add translations --- i18n/pt-BR/libcosmic.ftl | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 i18n/pt-BR/libcosmic.ftl diff --git a/i18n/pt-BR/libcosmic.ftl b/i18n/pt-BR/libcosmic.ftl new file mode 100644 index 00000000..febf5b2e --- /dev/null +++ b/i18n/pt-BR/libcosmic.ftl @@ -0,0 +1,11 @@ +# Context Drawer +close = Fechar + +# About +license = Licença +links = Links +developers = Desenvolvedores +designers = Designers +artists = Artistas +translators = Tradutores +documenters = Documentadores From 31fa09a92a98ccbe47c81c83b41cd20a91107aae Mon Sep 17 00:00:00 2001 From: therealmate <61843503+therealmate@users.noreply.github.com> Date: Fri, 19 Sep 2025 00:21:35 +0200 Subject: [PATCH 290/556] i18n(hu): add translation --- i18n/hu/libcosmic.ftl | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 i18n/hu/libcosmic.ftl diff --git a/i18n/hu/libcosmic.ftl b/i18n/hu/libcosmic.ftl new file mode 100644 index 00000000..ddc43e6c --- /dev/null +++ b/i18n/hu/libcosmic.ftl @@ -0,0 +1,11 @@ +# Context Drawer +close = Bezárás + +# About +license = Licenc +links = Linkek +developers = Fejlesztők +designers = Tervezők +artists = Művészek +translators = Fordítók +documenters = Dokumentálók From 4a29788199532e050a11f9c3e7fb839de9b900ad Mon Sep 17 00:00:00 2001 From: VandaLHJ Date: Mon, 15 Sep 2025 08:24:02 +0200 Subject: [PATCH 291/556] Create libcosmic.ftl PL initial translation Initial translation, i hope it's good enough. --- i18n/libcosmic.ftl | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 i18n/libcosmic.ftl diff --git a/i18n/libcosmic.ftl b/i18n/libcosmic.ftl new file mode 100644 index 00000000..f4a65aa6 --- /dev/null +++ b/i18n/libcosmic.ftl @@ -0,0 +1,11 @@ +# Context Drawer +close = Zamknij + +# About +license = Licencja +links = Linki +developers = Programiści +designers = Projektanci +artists = Artyści +translators = Tłumacze +documenters = Dokumentaliści From f1998afff91a1466a1b246956f0bdc26d10fc16f Mon Sep 17 00:00:00 2001 From: VandaLHJ Date: Fri, 19 Sep 2025 07:12:37 +0200 Subject: [PATCH 292/556] Create libcosmic.ftl PL initial translation This time in correct location --- i18n/pl/libcosmic.ftl | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 i18n/pl/libcosmic.ftl diff --git a/i18n/pl/libcosmic.ftl b/i18n/pl/libcosmic.ftl new file mode 100644 index 00000000..f4a65aa6 --- /dev/null +++ b/i18n/pl/libcosmic.ftl @@ -0,0 +1,11 @@ +# Context Drawer +close = Zamknij + +# About +license = Licencja +links = Linki +developers = Programiści +designers = Projektanci +artists = Artyści +translators = Tłumacze +documenters = Dokumentaliści From 47daaab610f0fae2d3cee72746f66e0a6b732af7 Mon Sep 17 00:00:00 2001 From: VandaLHJ Date: Fri, 19 Sep 2025 07:14:32 +0200 Subject: [PATCH 293/556] Delete i18n/libcosmic.ftl PL wrong location --- i18n/libcosmic.ftl | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 i18n/libcosmic.ftl diff --git a/i18n/libcosmic.ftl b/i18n/libcosmic.ftl deleted file mode 100644 index f4a65aa6..00000000 --- a/i18n/libcosmic.ftl +++ /dev/null @@ -1,11 +0,0 @@ -# Context Drawer -close = Zamknij - -# About -license = Licencja -links = Linki -developers = Programiści -designers = Projektanci -artists = Artyści -translators = Tłumacze -documenters = Dokumentaliści From 9ccade723a3f5d4438b16d5ad5ace927b903e794 Mon Sep 17 00:00:00 2001 From: twlvnn kraftwerk Date: Sun, 21 Sep 2025 16:00:29 +0200 Subject: [PATCH 294/556] i18n(bg): added bulgarian translation --- i18n/bg/libcosmic.ftl | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 i18n/bg/libcosmic.ftl diff --git a/i18n/bg/libcosmic.ftl b/i18n/bg/libcosmic.ftl new file mode 100644 index 00000000..2ac4d072 --- /dev/null +++ b/i18n/bg/libcosmic.ftl @@ -0,0 +1,11 @@ +# Context Drawer +close = Затваряне + +# About +license = Лиценз +links = Връзки +developers = Разработчици +designers = Дизайнери +artists = Художници +translators = Преводачи +documenters = Документатори From ad70236a5860562e8f77ef376cdb42af033dfdbf Mon Sep 17 00:00:00 2001 From: lorduskordus Date: Sat, 27 Sep 2025 17:59:56 +0200 Subject: [PATCH 295/556] i18n(cs): Add Czech translation --- i18n/cs/libcosmic.ftl | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 i18n/cs/libcosmic.ftl diff --git a/i18n/cs/libcosmic.ftl b/i18n/cs/libcosmic.ftl new file mode 100644 index 00000000..561ca9ac --- /dev/null +++ b/i18n/cs/libcosmic.ftl @@ -0,0 +1,11 @@ +# Context Drawer +close = Zavřít + +# About +license = Licence +links = Odkazy +developers = Vývojáři +designers = Designéři +artists = Grafici +translators = Překladatelé +documenters = Tvůrci dokumentace From 12014b683a97807c61809a8d31b6bc89ed6344e6 Mon Sep 17 00:00:00 2001 From: UchiWerfer Date: Sun, 28 Sep 2025 20:58:42 +0200 Subject: [PATCH 296/556] i18n(de): add German translations --- i18n/de/libcosmic.ftl | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 i18n/de/libcosmic.ftl diff --git a/i18n/de/libcosmic.ftl b/i18n/de/libcosmic.ftl new file mode 100644 index 00000000..3806cc59 --- /dev/null +++ b/i18n/de/libcosmic.ftl @@ -0,0 +1,11 @@ +# Context Drawer +close = Schließen + +# About +license = Lizenz +links = Links +developers = Entwickler*innen +designers = Designer*innen +artists = Künstler*innen +translators = Übersetzer*innen +documenters = Dokumentierer*innen From 43314e3e6af112681b97f05e2228177f790e3f76 Mon Sep 17 00:00:00 2001 From: rdsq Date: Mon, 29 Sep 2025 17:29:25 +0300 Subject: [PATCH 297/556] add eo and uk --- i18n/eo/libcosmic.ftl | 11 +++++++++++ i18n/uk/libcosmic.ftl | 11 +++++++++++ 2 files changed, 22 insertions(+) create mode 100644 i18n/eo/libcosmic.ftl create mode 100644 i18n/uk/libcosmic.ftl diff --git a/i18n/eo/libcosmic.ftl b/i18n/eo/libcosmic.ftl new file mode 100644 index 00000000..69764d88 --- /dev/null +++ b/i18n/eo/libcosmic.ftl @@ -0,0 +1,11 @@ +# Context Drawer +close = Fermi + +# About +license = Permesilo +links = Ligiloj +developers = Programistoj +designers = Grafikistoj +artists = Artistoj +translators = Tradukantoj +documenters = Dokumentantoj diff --git a/i18n/uk/libcosmic.ftl b/i18n/uk/libcosmic.ftl new file mode 100644 index 00000000..07dbaf9c --- /dev/null +++ b/i18n/uk/libcosmic.ftl @@ -0,0 +1,11 @@ +# Context Drawer +close = Закрити + +# About +license = Ліцензія +links = Лінки +developers = Розробники +designers = Дизайнери +artists = Митці +translators = Перекладачі +documenters = Документатори From 9815d4d98125b04ecb1afc28ef4b407510b45ac9 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 24 Sep 2025 15:55:34 -0400 Subject: [PATCH 298/556] feat(wayland): corner-radius protocol support --- Cargo.toml | 8 +- examples/multi-window/Cargo.toml | 2 +- iced | 2 +- src/app/cosmic.rs | 245 ++++++++++++++++++++++++++++--- src/core.rs | 16 ++ src/surface/action.rs | 85 +++++++++++ src/surface/mod.rs | 25 ++++ 7 files changed, 355 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5d22afc8..2edab9f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,7 +103,7 @@ ashpd = { version = "0.12.0", default-features = false, optional = true } async-fs = { version = "2.1", optional = true } async-std = { version = "1.13", optional = true } auto_enums = "0.8.7" -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "6254f50", optional = true } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "633beb0", optional = true } chrono = "0.4.42" cosmic-config = { path = "cosmic-config" } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } @@ -229,6 +229,6 @@ libcosmic = { path = "./" } # FIXME update winit deps where necessary to use this # [patch.crates-io] -# [patch."https://github.com/pop-os/winit.git"] -# winit = { git = "https://github.com/rust-windowing/winit.git", rev = "241b7a80bba96c91fa3901729cd5dec66abb9be4" } -# winit = { path = "../../winit" } +[patch."https://github.com/pop-os/winit.git"] +winit = { git = "https://github.com/pop-os/winit.git//", branch = "xdg-toplevel" } +# winit = { path = "../winit" } diff --git a/examples/multi-window/Cargo.toml b/examples/multi-window/Cargo.toml index 7a8e3051..168bd4ec 100644 --- a/examples/multi-window/Cargo.toml +++ b/examples/multi-window/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "multi-window", "dbus-config", "wgpu"] } +libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "multi-window", "dbus-config", "wgpu", "wayland"] } diff --git a/iced b/iced index d0508750..788be2f7 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit d05087507a7a0e37e26f174cfc97629c960b4383 +Subproject commit 788be2f7825b648ec3ce33697c6e675a7b7265ec diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 17331832..9c5a39f8 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 use std::borrow::Borrow; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::sync::Arc; use super::{Action, Application, ApplicationExt, Subscription}; @@ -92,6 +92,7 @@ pub struct Cosmic { Box Fn(&'a App) -> Element<'a, crate::Action>>, ), >, + pub tracked_windows: HashSet, } impl Cosmic @@ -139,11 +140,11 @@ where #[cfg(feature = "wayland")] crate::surface::Action::AppSubsurface(settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) - .ok() - .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else { - tracing::error!("Invalid settings for subsurface"); - return Task::none(); - }; + .ok() + .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else { + tracing::error!("Invalid settings for subsurface"); + return Task::none(); + }; if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: { let Some(settings) = std::sync::Arc::try_unwrap(settings) - .ok() - .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else { - tracing::error!("Invalid settings for subsurface"); - return Task::none(); - }; + .ok() + .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else { + tracing::error!("Invalid settings for subsurface"); + return Task::none(); + }; if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: { let Some(settings) = std::sync::Arc::try_unwrap(settings) - .ok() - .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync>>().ok()) else { - tracing::error!("Invalid settings for popup"); - return Task::none(); - }; + .ok() + .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync>>().ok()) else { + tracing::error!("Invalid settings for popup"); + return Task::none(); + }; if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: { iced_winit::commands::subsurface::destroy_subsurface(id) } + #[cfg(feature = "wayland")] + crate::surface::Action::DestroyWindow(id) => iced::window::close(id), crate::surface::Action::ResponsiveMenuBar { menu_bar, limits, @@ -241,11 +244,11 @@ where #[cfg(feature = "wayland")] crate::surface::Action::Popup(settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) - .ok() - .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync>>().ok()) else { - tracing::error!("Invalid settings for popup"); - return Task::none(); - }; + .ok() + .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync>>().ok()) else { + tracing::error!("Invalid settings for popup"); + return Task::none(); + }; if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: { + let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| { + s.downcast:: iced::window::Settings + Send + Sync>>() + .ok() + }) else { + tracing::error!("Invalid settings for AppWindow"); + return Task::none(); + }; + + if let Some(view) = view.and_then(|view| { + match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Fn(&'a T) -> Element<'a, crate::Action> + + Send + + Sync, + >>() { + Ok(v) => Some(v), + Err(err) => { + tracing::error!("Invalid view for AppWindow: {err:?}"); + None + } + } + }) { + let settings = settings(&mut self.app); + self.get_window(id, settings, *view) + } else { + let settings = settings(&mut self.app); + + self.tracked_windows.insert(id); + iced_runtime::task::oneshot(|channel| { + iced_runtime::Action::Window(iced_runtime::window::Action::Open( + id, settings, channel, + )) + }) + .discard() + } + } + #[cfg(feature = "wayland")] + crate::surface::Action::Window(id, settings, view) => { + let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| { + s.downcast:: iced::window::Settings + Send + Sync>>() + .ok() + }) else { + tracing::error!("Invalid settings for Window"); + return Task::none(); + }; + + if let Some(view) = view.and_then(|view| { + match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Element<'static, crate::Action> + Send + Sync, + >>() { + Ok(v) => Some(v), + Err(err) => { + tracing::error!("Invalid view for Window: {err:?}"); + None + } + } + }) { + let settings = settings(); + self.get_window(id, settings, Box::new(move |_| view())) + } else { + let settings = settings(); + + self.tracked_windows.insert(id); + + iced_runtime::task::oneshot(|channel| { + iced_runtime::Action::Window(iced_runtime::window::Action::Open( + id, settings, channel, + )) + }) + .discard() + } + } + crate::surface::Action::Ignore => iced::Task::none(), crate::surface::Action::Task(f) => { f().map(|sm| crate::Action::Cosmic(Action::Surface(sm))) @@ -667,6 +744,42 @@ impl Cosmic { new_theme.theme_type.prefer_dark(prefer_dark); cosmic_theme.set_theme(new_theme.theme_type); + #[cfg(feature = "wayland")] + if self.app.core().sync_window_border_radii_to_theme() { + use iced_runtime::platform_specific::wayland::CornerRadius; + use iced_winit::platform_specific::commands::corner_radius::corner_radius; + + let t = cosmic_theme.cosmic(); + + let radii = t.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); + let cur_rad = CornerRadius { + top_left: radii[0].round() as u32, + top_right: radii[1].round() as u32, + bottom_left: radii[2].round() as u32, + bottom_right: radii[3].round() as u32, + }; + + // Update radius for the main window + let main_window_id = self + .app + .core() + .main_window_id() + .unwrap_or(window::Id::RESERVED); + let mut cmds = + vec![corner_radius(main_window_id, Some(cur_rad)).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, Some(cur_rad)).discard()); + } + } + // Update radius for all tracked windows + for id in self.tracked_windows.iter() { + cmds.push(corner_radius(*id, Some(cur_rad)).discard()); + } + + return Task::batch(cmds); + } } } @@ -725,9 +838,46 @@ impl Cosmic { core.system_theme = new_theme.clone(); { let mut cosmic_theme = THEME.lock().unwrap(); + // Only apply update if the theme is set to load a system theme - if let ThemeType::System { theme: _, .. } = cosmic_theme.theme_type { + if let ThemeType::System { .. } = cosmic_theme.theme_type { cosmic_theme.set_theme(new_theme.theme_type); + #[cfg(feature = "wayland")] + if self.app.core().sync_window_border_radii_to_theme() { + use iced_runtime::platform_specific::wayland::CornerRadius; + use iced_winit::platform_specific::commands::corner_radius::corner_radius; + + let t = cosmic_theme.cosmic(); + + let radii = t.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); + let cur_rad = CornerRadius { + top_left: radii[0].round() as u32, + top_right: radii[1].round() as u32, + bottom_left: radii[2].round() as u32, + bottom_right: radii[3].round() as u32, + }; + + // Update radius for the main window + let main_window_id = self + .app + .core() + .main_window_id() + .unwrap_or(window::Id::RESERVED); + let mut cmds = + vec![corner_radius(main_window_id, Some(cur_rad)).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, Some(cur_rad)).discard()); + } + } + // Update radius for all tracked windows + for id in self.tracked_windows.iter() { + cmds.push(corner_radius(*id, Some(cur_rad)).discard()); + } + + return Task::batch(cmds); + } } } } @@ -748,6 +898,10 @@ 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); + let mut ret = if let Some(msg) = self.app.on_close_requested(id) { self.app.update(msg) } else { @@ -910,6 +1064,26 @@ 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; + use iced_winit::platform_specific::commands::corner_radius::corner_radius; + + let theme = THEME.lock().unwrap(); + let t = theme.cosmic(); + let radii = t.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); + let cur_rad = CornerRadius { + top_left: radii[0].round() as u32, + top_right: radii[1].round() as u32, + bottom_left: radii[2].round() as u32, + bottom_right: radii[3].round() as u32, + }; + return Task::batch(vec![ + corner_radius(id, Some(cur_rad)).discard(), + iced_runtime::window::run_with_handle(id, init_windowing_system), + ]); + } return iced_runtime::window::run_with_handle(id, init_windowing_system); } _ => {} @@ -925,6 +1099,7 @@ impl Cosmic { app, #[cfg(feature = "wayland")] surface_views: HashMap::new(), + tracked_windows: HashSet::new(), } } @@ -971,4 +1146,30 @@ impl Cosmic { ); get_popup(settings) } + + #[cfg(feature = "wayland")] + /// Create a window surface + pub fn get_window( + &mut self, + id: iced::window::Id, + settings: iced::window::Settings, + view: Box< + dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync, + >, + ) -> Task> { + use iced_winit::SurfaceIdWrapper; + + self.surface_views.insert( + id.clone(), + ( + None, // TODO parent for window, platform specific option maybe? + SurfaceIdWrapper::Window(id), + view, + ), + ); + iced_runtime::task::oneshot(|channel| { + iced_runtime::Action::Window(iced_runtime::window::Action::Open(id, settings, channel)) + }) + .discard() + } } diff --git a/src/core.rs b/src/core.rs index c82aa839..6069a83d 100644 --- a/src/core.rs +++ b/src/core.rs @@ -97,6 +97,9 @@ pub struct Core { pub(crate) exit_on_main_window_closed: bool, pub(crate) menu_bars: HashMap, + + #[cfg(feature = "wayland")] + pub(crate) sync_window_border_radii_to_theme: bool, } impl Default for Core { @@ -154,6 +157,8 @@ impl Default for Core { main_window: None, exit_on_main_window_closed: true, menu_bars: HashMap::new(), + #[cfg(feature = "wayland")] + sync_window_border_radii_to_theme: true } } } @@ -476,4 +481,15 @@ impl Core { crate::command::toggle_maximize(id) } + + // TODO should we emit tasks setting the corner radius or unsetting it if this is changed? + #[cfg(feature = "wayland")] + pub fn set_sync_window_border_radii_to_theme(&mut self, sync: bool) { + self.sync_window_border_radii_to_theme = sync; + } + + #[cfg(feature = "wayland")] + pub fn sync_window_border_radii_to_theme(&self) -> bool { + self.sync_window_border_radii_to_theme + } } diff --git a/src/surface/action.rs b/src/surface/action.rs index fdf2680e..25c45ce0 100644 --- a/src/surface/action.rs +++ b/src/surface/action.rs @@ -5,6 +5,7 @@ use super::Action; #[cfg(feature = "winit")] use crate::Application; +use iced::window; use std::{any::Any, sync::Arc}; /// Used to produce a destroy popup message from within a widget. @@ -20,6 +21,90 @@ pub fn destroy_subsurface(id: iced_core::window::Id) -> Action { Action::DestroySubsurface(id) } +#[cfg(feature = "wayland")] +#[must_use] +pub fn destroy_window(id: iced_core::window::Id) -> Action { + Action::DestroyWindow(id) +} + +#[cfg(all(feature = "wayland", feature = "winit"))] +#[must_use] +pub fn app_window( + settings: impl Fn(&mut App) -> window::Settings + + Send + + Sync + + 'static, + view: Option< + Box< + dyn for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action> + + Send + + Sync + + 'static, + >, + >, +) -> (window::Id, Action) { + let id = window::Id::unique(); + + let boxed: Box< + dyn Fn(&mut App) -> window::Settings + + Send + + Sync + + 'static, + > = Box::new(settings); + let boxed: Box = Box::new(boxed); + + ( + id, + Action::AppWindow( + id, + Arc::new(boxed), + view.map(|view| { + let boxed: Box = Box::new(view); + Arc::new(boxed) + }), + ) + ) +} + +/// Used to create a window message from within a widget. +#[cfg(all(feature = "wayland", feature = "winit"))] +#[must_use] +pub fn simple_window( + settings: impl Fn() -> window::Settings + + Send + + Sync + + 'static, + view: Option< + impl Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, + >, +) -> (window::Id, Action) { + let id = window::Id::unique(); + + let boxed: Box< + dyn Fn() -> window::Settings + + Send + + Sync + + 'static, + > = Box::new(settings); + let boxed: Box = Box::new(boxed); + + ( + id, + Action::Window( + id, + Arc::new(boxed), + view.map(|view| { + let boxed: Box< + dyn Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, + > = Box::new(view); + let boxed: Box = Box::new(boxed); + Arc::new(boxed) + }), + ) + ) +} + + #[cfg(all(feature = "wayland", feature = "winit"))] #[must_use] pub fn app_popup( diff --git a/src/surface/mod.rs b/src/surface/mod.rs index 3041fa54..b4ef63b6 100644 --- a/src/surface/mod.rs +++ b/src/surface/mod.rs @@ -36,6 +36,22 @@ pub enum Action { ), /// Destroy a subsurface with a view function DestroyPopup(iced::window::Id), + + /// Create a window with a view function accepting the App as a parameter + AppWindow( + iced::window::Id, + std::sync::Arc>, + Option>>, + ), + /// Create a window with a view function + Window( + iced::window::Id, + std::sync::Arc>, + Option>>, + ), + /// Destroy a window + DestroyWindow(iced::window::Id), + /// Responsive menu bar update ResponsiveMenuBar { /// Id of the menu bar @@ -80,6 +96,15 @@ impl std::fmt::Debug for Action { .field("size", size) .finish(), Self::Ignore => write!(f, "Ignore"), + Self::AppWindow(id, arg0, arg1) => { + f.debug_tuple("AppWindow").field(id).field(arg0).field(arg1).finish() + } + Self::Window(id, arg0, arg1) => { + f.debug_tuple("Window").field(id).field(arg0).field(arg1).finish() + } + Self::DestroyWindow(arg0) => { + f.debug_tuple("DestroyWindow").field(arg0).finish() + } Self::Task(_) => f.debug_tuple("Future").finish(), } } From ab41b83cd8715268ce5703ce99f64fe2e9c5dff8 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 24 Sep 2025 17:52:03 -0400 Subject: [PATCH 299/556] cargo fmt --- src/core.rs | 2 +- src/surface/action.rs | 35 +++++++++++------------------------ src/surface/mod.rs | 22 +++++++++++++--------- 3 files changed, 25 insertions(+), 34 deletions(-) diff --git a/src/core.rs b/src/core.rs index 6069a83d..2e4e0497 100644 --- a/src/core.rs +++ b/src/core.rs @@ -158,7 +158,7 @@ impl Default for Core { exit_on_main_window_closed: true, menu_bars: HashMap::new(), #[cfg(feature = "wayland")] - sync_window_border_radii_to_theme: true + sync_window_border_radii_to_theme: true, } } } diff --git a/src/surface/action.rs b/src/surface/action.rs index 25c45ce0..3a078ca3 100644 --- a/src/surface/action.rs +++ b/src/surface/action.rs @@ -30,10 +30,7 @@ pub fn destroy_window(id: iced_core::window::Id) -> Action { #[cfg(all(feature = "wayland", feature = "winit"))] #[must_use] pub fn app_window( - settings: impl Fn(&mut App) -> window::Settings - + Send - + Sync - + 'static, + settings: impl Fn(&mut App) -> window::Settings + Send + Sync + 'static, view: Option< Box< dyn for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action> @@ -45,12 +42,8 @@ pub fn app_window( ) -> (window::Id, Action) { let id = window::Id::unique(); - let boxed: Box< - dyn Fn(&mut App) -> window::Settings - + Send - + Sync - + 'static, - > = Box::new(settings); + let boxed: Box window::Settings + Send + Sync + 'static> = + Box::new(settings); let boxed: Box = Box::new(boxed); ( @@ -62,7 +55,7 @@ pub fn app_window( let boxed: Box = Box::new(view); Arc::new(boxed) }), - ) + ), ) } @@ -70,22 +63,14 @@ pub fn app_window( #[cfg(all(feature = "wayland", feature = "winit"))] #[must_use] pub fn simple_window( - settings: impl Fn() -> window::Settings - + Send - + Sync - + 'static, + settings: impl Fn() -> window::Settings + Send + Sync + 'static, view: Option< impl Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, >, ) -> (window::Id, Action) { let id = window::Id::unique(); - let boxed: Box< - dyn Fn() -> window::Settings - + Send - + Sync - + 'static, - > = Box::new(settings); + let boxed: Box window::Settings + Send + Sync + 'static> = Box::new(settings); let boxed: Box = Box::new(boxed); ( @@ -95,16 +80,18 @@ pub fn simple_window( Arc::new(boxed), view.map(|view| { let boxed: Box< - dyn Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, + dyn Fn() -> crate::Element<'static, crate::Action> + + Send + + Sync + + 'static, > = Box::new(view); let boxed: Box = Box::new(boxed); Arc::new(boxed) }), - ) + ), ) } - #[cfg(all(feature = "wayland", feature = "winit"))] #[must_use] pub fn app_popup( diff --git a/src/surface/mod.rs b/src/surface/mod.rs index b4ef63b6..4598ac7c 100644 --- a/src/surface/mod.rs +++ b/src/surface/mod.rs @@ -96,15 +96,19 @@ impl std::fmt::Debug for Action { .field("size", size) .finish(), Self::Ignore => write!(f, "Ignore"), - Self::AppWindow(id, arg0, arg1) => { - f.debug_tuple("AppWindow").field(id).field(arg0).field(arg1).finish() - } - Self::Window(id, arg0, arg1) => { - f.debug_tuple("Window").field(id).field(arg0).field(arg1).finish() - } - Self::DestroyWindow(arg0) => { - f.debug_tuple("DestroyWindow").field(arg0).finish() - } + Self::AppWindow(id, arg0, arg1) => f + .debug_tuple("AppWindow") + .field(id) + .field(arg0) + .field(arg1) + .finish(), + Self::Window(id, arg0, arg1) => f + .debug_tuple("Window") + .field(id) + .field(arg0) + .field(arg1) + .finish(), + Self::DestroyWindow(arg0) => f.debug_tuple("DestroyWindow").field(arg0).finish(), Self::Task(_) => f.debug_tuple("Future").finish(), } } From 27f591e5aa85e998a2f0dc77fd08dc4e90fcea1d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 25 Sep 2025 12:52:16 -0400 Subject: [PATCH 300/556] fix(corner-radius): fix radius from array to match iced and better respect sharp corners --- src/app/cosmic.rs | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 9c5a39f8..1d52ee0f 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -755,27 +755,31 @@ impl Cosmic { let cur_rad = CornerRadius { top_left: radii[0].round() as u32, top_right: radii[1].round() as u32, - bottom_left: radii[2].round() as u32, - bottom_right: radii[3].round() as u32, + bottom_right: radii[2].round() as u32, + bottom_left: radii[3].round() as u32, }; + let rounded = !self.app.core().window.sharp_corners; // Update radius for the main window let main_window_id = self .app .core() .main_window_id() .unwrap_or(window::Id::RESERVED); - let mut cmds = - vec![corner_radius(main_window_id, Some(cur_rad)).discard()]; + let mut cmds = vec![ + corner_radius(main_window_id, rounded.then_some(cur_rad)).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, Some(cur_rad)).discard()); + cmds.push( + corner_radius(*id, rounded.then_some(cur_rad)).discard(), + ); } } // Update radius for all tracked windows for id in self.tracked_windows.iter() { - cmds.push(corner_radius(*id, Some(cur_rad)).discard()); + cmds.push(corner_radius(*id, rounded.then_some(cur_rad)).discard()); } return Task::batch(cmds); @@ -853,9 +857,10 @@ impl Cosmic { let cur_rad = CornerRadius { top_left: radii[0].round() as u32, top_right: radii[1].round() as u32, - bottom_left: radii[2].round() as u32, - bottom_right: radii[3].round() as u32, + bottom_right: radii[2].round() as u32, + bottom_left: radii[3].round() as u32, }; + let rounded = !self.app.core().window.sharp_corners; // Update radius for the main window let main_window_id = self @@ -863,17 +868,24 @@ impl Cosmic { .core() .main_window_id() .unwrap_or(window::Id::RESERVED); - let mut cmds = - vec![corner_radius(main_window_id, Some(cur_rad)).discard()]; + let mut cmds = vec![ + corner_radius(main_window_id, rounded.then_some(cur_rad)) + .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, Some(cur_rad)).discard()); + cmds.push( + corner_radius(*id, rounded.then_some(cur_rad)) + .discard(), + ); } } // Update radius for all tracked windows for id in self.tracked_windows.iter() { - cmds.push(corner_radius(*id, Some(cur_rad)).discard()); + cmds.push( + corner_radius(*id, rounded.then_some(cur_rad)).discard(), + ); } return Task::batch(cmds); @@ -1076,11 +1088,14 @@ impl Cosmic { let cur_rad = CornerRadius { top_left: radii[0].round() as u32, top_right: radii[1].round() as u32, - bottom_left: radii[2].round() as u32, - bottom_right: radii[3].round() as u32, + bottom_right: radii[2].round() as u32, + bottom_left: radii[3].round() as u32, }; + // TODO do we need per window sharp corners? + let rounded = !self.app.core().window.sharp_corners; + return Task::batch(vec![ - corner_radius(id, Some(cur_rad)).discard(), + corner_radius(id, rounded.then_some(cur_rad)).discard(), iced_runtime::window::run_with_handle(id, init_windowing_system), ]); } From 03f07d2f1e6accd8942b35793184d6c25f953d5e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 25 Sep 2025 15:41:16 -0400 Subject: [PATCH 301/556] fix: sharp corners & window state handling --- iced | 2 +- src/app/cosmic.rs | 25 +++++++++++++++++++++++++ src/app/mod.rs | 19 ++++++++++++++----- src/theme/style/iced.rs | 18 +++++++++++++++--- src/widget/header_bar.rs | 1 + 5 files changed, 56 insertions(+), 9 deletions(-) diff --git a/iced b/iced index 788be2f7..f581f19f 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 788be2f7825b648ec3ce33697c6e675a7b7265ec +Subproject commit f581f19f897e1ccc4393c8867d4ae3ed532742b4 diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 1d52ee0f..58b73b81 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -426,6 +426,12 @@ where ) => { return Some(Action::SuggestedBounds(b)); } + #[cfg(feature = "wayland")] + wayland::Event::Window(iced::event::wayland::WindowEvent::WindowState( + s, + )) => { + return Some(Action::WindowState(id, s)); + } _ => (), } } @@ -588,6 +594,7 @@ impl Cosmic { fn cosmic_update(&mut self, message: Action) -> iced::Task> { match message { Action::WindowMaximized(id, maximized) => { + #[cfg(not(feature = "wayland"))] if self .app .core() @@ -635,6 +642,24 @@ impl Cosmic { | WindowState::TILED_BOTTOM, ); } + if self.app.core().sync_window_border_radii_to_theme() { + use iced_runtime::platform_specific::wayland::CornerRadius; + use iced_winit::platform_specific::commands::corner_radius::corner_radius; + + let theme = THEME.lock().unwrap(); + let t = theme.cosmic(); + let radii = t.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); + let cur_rad = CornerRadius { + top_left: radii[0].round() as u32, + top_right: radii[1].round() as u32, + bottom_right: radii[2].round() as u32, + bottom_left: radii[3].round() as u32, + }; + let rounded = !self.app.core().window.sharp_corners; + return Task::batch(vec![ + corner_radius(id, rounded.then_some(cur_rad)).discard(), + ]); + } } #[cfg(feature = "wayland")] diff --git a/src/app/mod.rs b/src/app/mod.rs index 1b10b68d..11053142 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -759,16 +759,25 @@ impl ApplicationExt for App { header .apply(container) .class(crate::theme::Container::custom(move |theme| { + let cosmic = theme.cosmic(); container::Style { background: Some(iced::Background::Color( - theme.cosmic().background.base.into(), + cosmic.background.base.into(), )), border: iced::Border { radius: [ - window_corner_radius[0] - 1.0, - window_corner_radius[1] - 1.0, - theme.cosmic().radius_0()[2], - theme.cosmic().radius_0()[3], + 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 + }, + cosmic.radius_0()[2], + cosmic.radius_0()[3], ] .into(), ..Default::default() diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 764c1654..1f212d13 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -395,6 +395,7 @@ pub enum Container<'a> { Dropdown, HeaderBar { focused: bool, + sharp_corners: bool, }, List, Primary, @@ -507,7 +508,10 @@ impl iced_container::Catalog for Theme { } } - Container::HeaderBar { focused } => { + Container::HeaderBar { + focused, + sharp_corners, + } => { let (icon_color, text_color) = if *focused { ( Color::from(cosmic.accent_text_color()), @@ -526,8 +530,16 @@ impl iced_container::Catalog for Theme { background: Some(iced::Background::Color(cosmic.background.base.into())), border: Border { radius: [ - window_corner_radius[0], - window_corner_radius[1], + if *sharp_corners { + cosmic.corner_radii.radius_0[0] + } else { + window_corner_radius[0] + }, + if *sharp_corners { + cosmic.corner_radii.radius_0[1] + } else { + window_corner_radius[1] + }, cosmic.corner_radii.radius_0[2], cosmic.corner_radii.radius_0[3], ] diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 3763ae32..bed7d363 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -409,6 +409,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .apply(widget::container) .class(crate::theme::Container::HeaderBar { focused: self.focused, + sharp_corners: self.maximized, }) .center_y(Length::Shrink) .apply(widget::mouse_area); From 7015b8ace423cea32c881dca670271535498c8f9 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 30 Sep 2025 08:45:57 -0400 Subject: [PATCH 302/556] chore: update iced --- Cargo.toml | 4 ++-- iced | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2edab9f0..868d69b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -229,6 +229,6 @@ libcosmic = { path = "./" } # FIXME update winit deps where necessary to use this # [patch.crates-io] -[patch."https://github.com/pop-os/winit.git"] -winit = { git = "https://github.com/pop-os/winit.git//", branch = "xdg-toplevel" } +# [patch."https://github.com/pop-os/winit.git"] +# winit = { git = "https://github.com/pop-os/winit.git//", branch = "xdg-toplevel" } # winit = { path = "../winit" } diff --git a/iced b/iced index f581f19f..12810507 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit f581f19f897e1ccc4393c8867d4ae3ed532742b4 +Subproject commit 12810507e181b52bd13f222d8810d877c6b2d0f4 From 4a71189d346e766c46f5bfaeb494a921fe0fccbd Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 30 Sep 2025 11:34:39 -0400 Subject: [PATCH 303/556] chore: update cosmic-protocols --- Cargo.toml | 2 +- iced | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 868d69b6..6ccf57d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,7 +103,7 @@ ashpd = { version = "0.12.0", default-features = false, optional = true } async-fs = { version = "2.1", optional = true } async-std = { version = "1.13", optional = true } auto_enums = "0.8.7" -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "633beb0", optional = true } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d0e95be", optional = true } chrono = "0.4.42" cosmic-config = { path = "cosmic-config" } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } diff --git a/iced b/iced index 12810507..521a04d7 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 12810507e181b52bd13f222d8810d877c6b2d0f4 +Subproject commit 521a04d7e7589fdd61b314c92155277bf350d944 From 00b4a8a9f51aff381e0083da6e7c4abf13c002be Mon Sep 17 00:00:00 2001 From: oddib Date: Tue, 30 Sep 2025 00:02:39 +0200 Subject: [PATCH 304/556] =?UTF-8?q?Added=20translation=20using=20Weblate?= =?UTF-8?q?=20(Norwegian=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- i18n/nb-NO/libcosmic.ftl | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 i18n/nb-NO/libcosmic.ftl diff --git a/i18n/nb-NO/libcosmic.ftl b/i18n/nb-NO/libcosmic.ftl new file mode 100644 index 00000000..e69de29b From 0ca7a99c9f0b130aae6cbd1ebf74fb89cd7c9ad1 Mon Sep 17 00:00:00 2001 From: oddib Date: Tue, 30 Sep 2025 00:04:42 +0200 Subject: [PATCH 305/556] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (8 of 8 strings) Translation: Pop OS/libcosmic Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/nb_NO/ --- i18n/nb-NO/libcosmic.ftl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/i18n/nb-NO/libcosmic.ftl b/i18n/nb-NO/libcosmic.ftl index e69de29b..64d4e5d1 100644 --- a/i18n/nb-NO/libcosmic.ftl +++ b/i18n/nb-NO/libcosmic.ftl @@ -0,0 +1,8 @@ +close = Lukk +license = Lisens +links = Linker +developers = Utviklere +designers = Designere +artists = Artister +translators = Oversettere +documenters = Dokumentører From f097b643b327a391fabb17efd8358ec3f4df1c1c Mon Sep 17 00:00:00 2001 From: Mattias Eriksson Date: Tue, 30 Sep 2025 07:35:30 +0200 Subject: [PATCH 306/556] Added translation using Weblate (Swedish) --- i18n/sv/libcosmic.ftl | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 i18n/sv/libcosmic.ftl diff --git a/i18n/sv/libcosmic.ftl b/i18n/sv/libcosmic.ftl new file mode 100644 index 00000000..e69de29b From 59e480a4c64fd895d8c16b84241feea4f81ce5ed Mon Sep 17 00:00:00 2001 From: Mattias Eriksson Date: Tue, 30 Sep 2025 07:43:13 +0200 Subject: [PATCH 307/556] Translated using Weblate (Swedish) Currently translated at 100.0% (8 of 8 strings) Translation: Pop OS/libcosmic Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/sv/ --- i18n/sv/libcosmic.ftl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/i18n/sv/libcosmic.ftl b/i18n/sv/libcosmic.ftl index e69de29b..75cb7fb4 100644 --- a/i18n/sv/libcosmic.ftl +++ b/i18n/sv/libcosmic.ftl @@ -0,0 +1,8 @@ +license = Licens +links = Länkar +developers = Utvecklare +designers = Designers +artists = Konstnärer +translators = Översättare +documenters = Skribenter +close = Stäng From ee84ad958f4fd28486580b0a9b5928d74d0030bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 30 Sep 2025 19:56:00 +0200 Subject: [PATCH 308/556] Added translation using Weblate (Estonian) --- i18n/et/libcosmic.ftl | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 i18n/et/libcosmic.ftl diff --git a/i18n/et/libcosmic.ftl b/i18n/et/libcosmic.ftl new file mode 100644 index 00000000..e69de29b From df9df4096345513c6bebd3ae943e943d76006f17 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 30 Sep 2025 22:04:32 +0200 Subject: [PATCH 309/556] chore(about): drop license dependency Not needed since the application can already give URLs to their license --- Cargo.toml | 3 +-- src/app/context_drawer.rs | 8 ++++---- src/widget/about.rs | 26 +++++++------------------- 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6ccf57d1..74195a21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ default = ["dbus-config", "multi-window", "a11y"] # Accessibility support a11y = ["iced/a11y", "iced_accessibility"] # Enable about widget -about = ["dep:license"] +about = [] # Builds support for animated images animated-image = ["dep:async-fs", "image/gif", "tokio?/io-util", "tokio?/fs"] # XXX autosize should not be used on winit windows unless dialogs @@ -122,7 +122,6 @@ image = { version = "0.25.8", default-features = false, features = [ "png", ] } libc = { version = "0.2.175", optional = true } -license = { version = "3.7.0", optional = true } mime = { version = "0.3.17", optional = true } palette = "0.7.6" raw-window-handle = "0.6" diff --git a/src/app/context_drawer.rs b/src/app/context_drawer.rs index 5d15d7b4..b33d2ba6 100644 --- a/src/app/context_drawer.rs +++ b/src/app/context_drawer.rs @@ -15,11 +15,11 @@ pub struct ContextDrawer<'a, Message: Clone + 'static> { } #[cfg(feature = "about")] -pub fn about( - about: &crate::widget::about::About, - on_url_press: impl Fn(String) -> Message, +pub fn about<'a, Message: Clone + 'static>( + about: &'a crate::widget::about::About, + on_url_press: impl Fn(&'a str) -> Message + 'a, on_close: Message, -) -> ContextDrawer<'_, Message> { +) -> ContextDrawer<'a, Message> { context_drawer(crate::widget::about(about, on_url_press), on_close) } diff --git a/src/widget/about.rs b/src/widget/about.rs index f1f84106..f1538d8f 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -1,10 +1,7 @@ -use { - crate::{ - Element, fl, - iced::{Alignment, Length}, - widget::{self, horizontal_space}, - }, - license::License, +use crate::{ + Element, fl, + iced::{Alignment, Length}, + widget::{self, horizontal_space}, }; #[derive(Debug, Default, Clone, derive_setters::Setters)] @@ -96,21 +93,12 @@ impl<'a> About { .collect(); self } - - fn get_license_url(&self) -> Option { - self.license_url.clone().or_else(|| { - self.license.as_ref().and_then(|license_str| { - let license: &dyn License = license_str.parse().ok()?; - Some(format!("https://spdx.org/licenses/{}.html", license.id())) - }) - }) - } } /// Constructs the widget for the about section. pub fn about<'a, Message: Clone + 'static>( about: &'a About, - on_url_press: impl Fn(String) -> Message, + on_url_press: impl Fn(&'a str) -> Message + 'a, ) -> Element<'a, Message> { let cosmic_theme::Spacing { space_xxs, space_m, .. @@ -131,7 +119,7 @@ pub fn about<'a, Message: Clone + 'static>( .align_y(Alignment::Center), ) .class(crate::theme::Button::Link) - .on_press(on_url_press(url.clone())) + .on_press(on_url_press(url)) .width(Length::Fill) .into() }) @@ -157,7 +145,7 @@ pub fn about<'a, Message: Clone + 'static>( 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.get_license_url(); + let url = about.license_url.as_deref(); widget::settings::section().title(fl!("license")).add( widget::button::custom( widget::row() From 6a0c06a3689ea8a868a629c86f5b4782a4b22aa0 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 30 Sep 2025 22:18:02 +0200 Subject: [PATCH 310/556] chore: update taffy crate to crates.io release --- Cargo.toml | 6 +----- src/widget/flex_row/layout.rs | 10 +++++----- src/widget/grid/layout.rs | 4 ++-- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 74195a21..16af9575 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -133,6 +133,7 @@ serde = { version = "1.0.219", features = ["derive"] } slotmap = "1.0.7" smol = { version = "2.0.2", optional = true } thiserror = "2.0.16" +taffy = { version = "0.9.1", features = ["grid"] } tokio = { version = "1.47.1", optional = true } tracing = "0.1.41" unicode-segmentation = "1.12" @@ -205,11 +206,6 @@ optional = true version = "0.11" optional = true -[dependencies.taffy] -git = "https://github.com/DioxusLabs/taffy" -rev = "7781c70" -features = ["grid"] - [workspace] members = [ "cosmic-config", diff --git a/src/widget/flex_row/layout.rs b/src/widget/flex_row/layout.rs index d781e4f9..720e4561 100644 --- a/src/widget/flex_row/layout.rs +++ b/src/widget/flex_row/layout.rs @@ -44,7 +44,7 @@ pub fn resolve( min_size: taffy::geometry::Size { width: length(max_size.width), - height: Dimension::Auto, + height: Dimension::auto(), }, align_items, @@ -71,7 +71,7 @@ pub fn resolve( let c_size = child_widget.size(); let (width, flex_grow, justify_self) = match c_size.width { Length::Fill | Length::FillPortion(_) => { - (Dimension::Auto, 1.0, Some(AlignItems::Stretch)) + (Dimension::auto(), 1.0, Some(AlignItems::Stretch)) } _ => (length(size.width), 0.0, None), }; @@ -82,15 +82,15 @@ pub fn resolve( min_size: taffy::geometry::Size { width: match min_item_width { Some(width) => length(size.width.min(width)), - None => Dimension::Auto, + None => Dimension::auto(), }, - height: Dimension::Auto, + height: Dimension::auto(), }, size: taffy::geometry::Size { width, height: match c_size.height { - Length::Fill | Length::FillPortion(_) => Dimension::Auto, + Length::Fill | Length::FillPortion(_) => Dimension::auto(), _ => length(size.height), }, }, diff --git a/src/widget/grid/layout.rs b/src/widget/grid/layout.rs index 6423c377..d1da68af 100644 --- a/src/widget/grid/layout.rs +++ b/src/widget/grid/layout.rs @@ -48,7 +48,7 @@ pub fn resolve( let c_size = child_widget.size(); let (width, flex_grow, justify_self) = match c_size.width { Length::Fill | Length::FillPortion(_) => { - (Dimension::Auto, 1.0, Some(AlignItems::Stretch)) + (Dimension::auto(), 1.0, Some(AlignItems::Stretch)) } _ => (length(size.width), 0.0, None), }; @@ -72,7 +72,7 @@ pub fn resolve( size: taffy::geometry::Size { width, height: match c_size.height { - Length::Fill | Length::FillPortion(_) => Dimension::Auto, + Length::Fill | Length::FillPortion(_) => Dimension::auto(), _ => length(size.height), }, }, From 432e43d258f11f24226e97abe08c110e30f9ceb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 30 Sep 2025 20:21:56 +0200 Subject: [PATCH 311/556] i18n(et): update translations from Weblate Currently translated at 87.5% (7 of 8 strings) Translation: Pop OS/libcosmic Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/et/ --- i18n/et/libcosmic.ftl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/i18n/et/libcosmic.ftl b/i18n/et/libcosmic.ftl index e69de29b..1449e0af 100644 --- a/i18n/et/libcosmic.ftl +++ b/i18n/et/libcosmic.ftl @@ -0,0 +1,7 @@ +close = Sulge +license = Litsents +links = Lingid +developers = Arendajad +artists = Kunstnikud +translators = Tõlkijad +documenters = Dokumenteerijad From 511fe02624a74007de728142368fa680217d51c3 Mon Sep 17 00:00:00 2001 From: Walter William Beckerleg Bruckman Date: Wed, 1 Oct 2025 14:45:20 +0200 Subject: [PATCH 312/556] i18n: adding translation for Spanish (Latin America) --- i18n/es-419/libcosmic.ftl | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 i18n/es-419/libcosmic.ftl diff --git a/i18n/es-419/libcosmic.ftl b/i18n/es-419/libcosmic.ftl new file mode 100644 index 00000000..e69de29b From 5092d1986148781eb7c7329de586957d61202d7a Mon Sep 17 00:00:00 2001 From: Walter William Beckerleg Bruckman Date: Wed, 1 Oct 2025 14:54:29 +0200 Subject: [PATCH 313/556] i18n(es-419): update translations from Weblate Currently translated at 100.0% (8 of 8 strings) Translation: Pop OS/libcosmic Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/es_419/ --- i18n/es-419/libcosmic.ftl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/i18n/es-419/libcosmic.ftl b/i18n/es-419/libcosmic.ftl index e69de29b..8ef988e9 100644 --- a/i18n/es-419/libcosmic.ftl +++ b/i18n/es-419/libcosmic.ftl @@ -0,0 +1,8 @@ +close = Cerrar +license = Licencia +links = Enlaces +developers = Desarrolladores +designers = Diseñadores +artists = Artistas +translators = Traductores +documenters = Documentalistas From aeafe447e3fbde3d18d4872c0b6e51a76675d597 Mon Sep 17 00:00:00 2001 From: Walter William Beckerleg Bruckman Date: Wed, 1 Oct 2025 16:28:15 +0200 Subject: [PATCH 314/556] i18n: adding translation for Chinese (Simplified Han script) --- i18n/zh-Hans/libcosmic.ftl | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 i18n/zh-Hans/libcosmic.ftl diff --git a/i18n/zh-Hans/libcosmic.ftl b/i18n/zh-Hans/libcosmic.ftl new file mode 100644 index 00000000..e69de29b From 1c83be9d1c4e62123a8b6b24425433eb0880230e Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 1 Oct 2025 21:58:53 +0200 Subject: [PATCH 315/556] i18n: translation updates from weblate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Hosted Weblate Co-authored-by: Hugo Carvalho Co-authored-by: Languages add-on Co-authored-by: Priit Jõerüüt Co-authored-by: Walter William Beckerleg Bruckman Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/es_419/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/et/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/pt/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/zh_Hans/ Translation: Pop OS/libcosmic --- i18n/af/libcosmic.ftl | 0 i18n/be/libcosmic.ftl | 0 i18n/ca/libcosmic.ftl | 0 i18n/da/libcosmic.ftl | 0 i18n/el/libcosmic.ftl | 0 i18n/en-GB/libcosmic.ftl | 0 i18n/es-MX/libcosmic.ftl | 0 i18n/es/libcosmic.ftl | 0 i18n/fa/libcosmic.ftl | 0 i18n/fi/libcosmic.ftl | 0 i18n/fr/libcosmic.ftl | 0 i18n/fy/libcosmic.ftl | 0 i18n/ga/libcosmic.ftl | 0 i18n/gd/libcosmic.ftl | 0 i18n/he/libcosmic.ftl | 0 i18n/hi/libcosmic.ftl | 0 i18n/hr/libcosmic.ftl | 0 i18n/id/libcosmic.ftl | 0 i18n/ie/libcosmic.ftl | 0 i18n/it/libcosmic.ftl | 0 i18n/ja/libcosmic.ftl | 0 i18n/jv/libcosmic.ftl | 0 i18n/kn/libcosmic.ftl | 0 i18n/ko/libcosmic.ftl | 0 i18n/li/libcosmic.ftl | 0 i18n/lt/libcosmic.ftl | 0 i18n/nl/libcosmic.ftl | 0 i18n/pt/libcosmic.ftl | 8 ++++++++ i18n/ru/libcosmic.ftl | 0 i18n/sk/libcosmic.ftl | 0 i18n/sr/libcosmic.ftl | 0 i18n/ta/libcosmic.ftl | 0 i18n/th/libcosmic.ftl | 0 i18n/vi/libcosmic.ftl | 0 i18n/zh-Hans/libcosmic.ftl | 6 ++++++ i18n/zh-Hant/libcosmic.ftl | 0 36 files changed, 14 insertions(+) create mode 100644 i18n/af/libcosmic.ftl create mode 100644 i18n/be/libcosmic.ftl create mode 100644 i18n/ca/libcosmic.ftl create mode 100644 i18n/da/libcosmic.ftl create mode 100644 i18n/el/libcosmic.ftl create mode 100644 i18n/en-GB/libcosmic.ftl create mode 100644 i18n/es-MX/libcosmic.ftl create mode 100644 i18n/es/libcosmic.ftl create mode 100644 i18n/fa/libcosmic.ftl create mode 100644 i18n/fi/libcosmic.ftl create mode 100644 i18n/fr/libcosmic.ftl create mode 100644 i18n/fy/libcosmic.ftl create mode 100644 i18n/ga/libcosmic.ftl create mode 100644 i18n/gd/libcosmic.ftl create mode 100644 i18n/he/libcosmic.ftl create mode 100644 i18n/hi/libcosmic.ftl create mode 100644 i18n/hr/libcosmic.ftl create mode 100644 i18n/id/libcosmic.ftl create mode 100644 i18n/ie/libcosmic.ftl create mode 100644 i18n/it/libcosmic.ftl create mode 100644 i18n/ja/libcosmic.ftl create mode 100644 i18n/jv/libcosmic.ftl create mode 100644 i18n/kn/libcosmic.ftl create mode 100644 i18n/ko/libcosmic.ftl create mode 100644 i18n/li/libcosmic.ftl create mode 100644 i18n/lt/libcosmic.ftl create mode 100644 i18n/nl/libcosmic.ftl create mode 100644 i18n/pt/libcosmic.ftl create mode 100644 i18n/ru/libcosmic.ftl create mode 100644 i18n/sk/libcosmic.ftl create mode 100644 i18n/sr/libcosmic.ftl create mode 100644 i18n/ta/libcosmic.ftl create mode 100644 i18n/th/libcosmic.ftl create mode 100644 i18n/vi/libcosmic.ftl create mode 100644 i18n/zh-Hant/libcosmic.ftl diff --git a/i18n/af/libcosmic.ftl b/i18n/af/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/be/libcosmic.ftl b/i18n/be/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/ca/libcosmic.ftl b/i18n/ca/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/da/libcosmic.ftl b/i18n/da/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/el/libcosmic.ftl b/i18n/el/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/en-GB/libcosmic.ftl b/i18n/en-GB/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/es-MX/libcosmic.ftl b/i18n/es-MX/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/es/libcosmic.ftl b/i18n/es/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/fa/libcosmic.ftl b/i18n/fa/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/fi/libcosmic.ftl b/i18n/fi/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/fr/libcosmic.ftl b/i18n/fr/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/fy/libcosmic.ftl b/i18n/fy/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/ga/libcosmic.ftl b/i18n/ga/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/gd/libcosmic.ftl b/i18n/gd/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/he/libcosmic.ftl b/i18n/he/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/hi/libcosmic.ftl b/i18n/hi/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/hr/libcosmic.ftl b/i18n/hr/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/id/libcosmic.ftl b/i18n/id/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/ie/libcosmic.ftl b/i18n/ie/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/it/libcosmic.ftl b/i18n/it/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/ja/libcosmic.ftl b/i18n/ja/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/jv/libcosmic.ftl b/i18n/jv/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/kn/libcosmic.ftl b/i18n/kn/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/ko/libcosmic.ftl b/i18n/ko/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/li/libcosmic.ftl b/i18n/li/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/lt/libcosmic.ftl b/i18n/lt/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/nl/libcosmic.ftl b/i18n/nl/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/pt/libcosmic.ftl b/i18n/pt/libcosmic.ftl new file mode 100644 index 00000000..e1786efb --- /dev/null +++ b/i18n/pt/libcosmic.ftl @@ -0,0 +1,8 @@ +close = Fechar +license = Licença +links = Ligações +developers = Programadores +designers = Designers +artists = Artistas +translators = Tradutores +documenters = Documentadores diff --git a/i18n/ru/libcosmic.ftl b/i18n/ru/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/sk/libcosmic.ftl b/i18n/sk/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/sr/libcosmic.ftl b/i18n/sr/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/ta/libcosmic.ftl b/i18n/ta/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/th/libcosmic.ftl b/i18n/th/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/vi/libcosmic.ftl b/i18n/vi/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/zh-Hans/libcosmic.ftl b/i18n/zh-Hans/libcosmic.ftl index e69de29b..e7c83e5c 100644 --- a/i18n/zh-Hans/libcosmic.ftl +++ b/i18n/zh-Hans/libcosmic.ftl @@ -0,0 +1,6 @@ +close = 关闭 +license = 许可证 +links = 链接 +developers = 开发者 +designers = 设计师 +translators = 译者 diff --git a/i18n/zh-Hant/libcosmic.ftl b/i18n/zh-Hant/libcosmic.ftl new file mode 100644 index 00000000..e69de29b From cc8e5ebdeaeb15b58f109398b15b0c0dd682788d Mon Sep 17 00:00:00 2001 From: grant-wilson Date: Wed, 1 Oct 2025 21:30:55 -0400 Subject: [PATCH 316/556] Fix typo in README dependencies section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 595e0d3b..ac8e60aa 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A platform toolkit based on iced for creating applets and applications for the C ## Dependencies -While libcosmic is written entirely in Rust, some of its dependencies may require shared system library headers to be installed. On Pop!_OS, the following dependencies are all that's necessary compile a typical COSMIC project: +While libcosmic is written entirely in Rust, some of its dependencies may require shared system library headers to be installed. On Pop!_OS, the following dependencies are all that's necessary to compile a typical COSMIC project: ```sh sudo apt install cargo cmake just libexpat1-dev libfontconfig-dev libfreetype-dev libxkbcommon-dev pkgconf From 6c5b799b343f877c310d9f9bf23ea3f282bdf1a2 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Thu, 2 Oct 2025 17:05:31 +0200 Subject: [PATCH 317/556] 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: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ga/ Translation: Pop OS/libcosmic --- i18n/ga/libcosmic.ftl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/i18n/ga/libcosmic.ftl b/i18n/ga/libcosmic.ftl index e69de29b..61557ccd 100644 --- a/i18n/ga/libcosmic.ftl +++ b/i18n/ga/libcosmic.ftl @@ -0,0 +1,8 @@ +close = Dún +license = Ceadúnas +links = Naisc +developers = Forbróirí +designers = Dearthóirí +artists = Ealaíontóirí +translators = Aistritheoirí +documenters = Doiciméadóirí From 0059fe182bfc4f6f5e1dd0e611cc700a4fa45835 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 3 Oct 2025 12:09:55 -0400 Subject: [PATCH 318/556] 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 319/556] 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 320/556] 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 321/556] 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 322/556] 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 323/556] 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 324/556] 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 325/556] 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), } From d40e9fa4e49397b0c5846b1c243b0b297df5fa36 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 7 Oct 2025 15:55:31 -0400 Subject: [PATCH 326/556] fix: support NotShowIn --- src/desktop.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/desktop.rs b/src/desktop.rs index 687fa6c4..c9b50704 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -61,9 +61,14 @@ pub fn load_applications<'a>( .filter_map(move |p| fde::DesktopEntry::from_path(p, Some(locales)).ok()) .filter(move |de| { (include_no_display || !de.no_display()) - && !only_show_in.zip(de.only_show_in()).is_some_and( + && only_show_in.zip(de.only_show_in()).is_none_or( |(xdg_current_desktop, only_show_in)| { - !only_show_in.contains(&xdg_current_desktop) + only_show_in.contains(&xdg_current_desktop) + }, + ) + && only_show_in.zip(de.not_show_in()).is_none_or( + |(xdg_current_desktop, not_show_in)| { + !not_show_in.contains(&xdg_current_desktop) }, ) }) @@ -94,6 +99,11 @@ pub fn load_applications_for_app_ids<'a>( ) { return false; } + if only_show_in.zip(de.not_show_in()).is_some_and( + |(xdg_current_desktop, not_show_in)| not_show_in.contains(&xdg_current_desktop), + ) { + return false; + } // Search by ID first app_ids From 2dda96c07f5c020bc69711d8bda7be3084c3d4e2 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Tue, 7 Oct 2025 23:30:29 +0200 Subject: [PATCH 327/556] i18n: translation update from Hosted Weblate (#1008) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * i18n: translation updates from weblate Co-authored-by: Aindriú Mac Giolla Eoin Co-authored-by: Aliaksandr Truš Co-authored-by: Fedorov Alexei Co-authored-by: Guðmundur Erlingsson Co-authored-by: Hosted Weblate Co-authored-by: Ignacio Viggiani 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: adding translation for Norwegian Nynorsk --------- Co-authored-by: Aindriú Mac Giolla Eoin Co-authored-by: Aliaksandr Truš Co-authored-by: Fedorov Alexei Co-authored-by: Guðmundur Erlingsson Co-authored-by: Ignacio Viggiani Co-authored-by: Priit Jõerüüt Co-authored-by: Yago Raña Gayoso Co-authored-by: mikenu Co-authored-by: oddib Co-authored-by: Jeremy Soller --- i18n/es/libcosmic.ftl | 1 + i18n/is/libcosmic.ftl | 0 i18n/nn/libcosmic.ftl | 0 3 files changed, 1 insertion(+) create mode 100644 i18n/is/libcosmic.ftl create mode 100644 i18n/nn/libcosmic.ftl diff --git a/i18n/es/libcosmic.ftl b/i18n/es/libcosmic.ftl index 6d30b5ad..3e6e337d 100644 --- a/i18n/es/libcosmic.ftl +++ b/i18n/es/libcosmic.ftl @@ -5,3 +5,4 @@ designers = Diseñadores artists = Artistas translators = Traductores documenters = Documentadores +close = Cerrar diff --git a/i18n/is/libcosmic.ftl b/i18n/is/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/nn/libcosmic.ftl b/i18n/nn/libcosmic.ftl new file mode 100644 index 00000000..e69de29b From f17cd2928a37a09bfce70f4ef1d775eebe138cf0 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 8 Oct 2025 16:45:59 -0400 Subject: [PATCH 328/556] fix: forward events to trailing element regardless of cursor position --- src/widget/text_input/input.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index fb889138..949f2040 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -876,21 +876,19 @@ where // Enable custom buttons defined on the trailing icon position to be handled. if !self.is_editable_variant { if let Some(trailing_layout) = trailing_icon_layout { - if cursor_position.is_over(trailing_layout.bounds()) { - let res = trailing_icon.as_widget_mut().on_event( - tree, - event.clone(), - trailing_layout, - cursor_position, - renderer, - clipboard, - shell, - viewport, - ); + let res = trailing_icon.as_widget_mut().on_event( + tree, + event.clone(), + trailing_layout, + cursor_position, + renderer, + clipboard, + shell, + viewport, + ); - if res == event::Status::Captured { - return res; - } + if res == event::Status::Captured { + return res; } } } From a929829521b229aa6426e14ecc1ba4c047f809e1 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 8 Oct 2025 19:04:52 -0400 Subject: [PATCH 329/556] fix(color picker): avoid 0 in color picker slider value --- src/widget/color_picker/mod.rs | 377 +++++++++++++-------------------- 1 file changed, 152 insertions(+), 225 deletions(-) diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 536531a4..87e7a4d3 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -289,237 +289,164 @@ where copy_to_clipboard_label: T, copied_to_clipboard_label: T, ) -> ColorPicker<'a, Message> { + fn rail_backgrounds(hue: f32) -> (Background, Background) { + let pivot = hue * 7.0 / 360.; + + let low_end = pivot.floor() as usize; + let high_start = pivot.ceil() as usize; + let pivot_color = palette::Hsv::new_srgb(RgbHue::new(hue), 1.0, 1.0); + let low_range = HSV_RAINBOW[0..=low_end] + .iter() + .enumerate() + .map(|(i, color)| ColorStop { + color: *color, + offset: i as f32 / pivot.max(0.0001), + }) + .chain(iter::once(ColorStop { + color: iced::Color::from(palette::Srgba::from_color(pivot_color)), + offset: 1., + })) + .collect::>(); + let high_range = iter::once(ColorStop { + color: iced::Color::from(palette::Srgba::from_color(pivot_color)), + offset: 0., + }) + .chain( + HSV_RAINBOW[high_start..] + .iter() + .enumerate() + .map(|(i, color)| ColorStop { + color: *color, + offset: (i as f32 + (1. - pivot.fract())) / (7. - pivot).max(0.0001), + }), + ) + .collect::>(); + ( + Background::Gradient(iced::Gradient::Linear( + Linear::new(Radians(90.0)).add_stops(low_range), + )), + Background::Gradient(iced::Gradient::Linear( + Linear::new(Radians(90.0)).add_stops(high_range), + )), + ) + } + let on_update = self.on_update; let spacing = THEME.lock().unwrap().cosmic().spacing; - let mut inner = - column![ - // segmented buttons - segmented_control::horizontal(self.model) - .on_activate(Box::new(move |e| on_update( - ColorPickerUpdate::ActivateSegmented(e) - ))) - .minimum_button_width(0) - .width(self.width), - // canvas with gradient for the current color - // still needs the canvas and the handle to be drawn on it - container(vertical_space().height(self.height)) - .width(self.width) - .height(self.height), - slider( - 0.0..=359.99, - self.active_color.hue.into_positive_degrees(), - move |v| { - let mut new = self.active_color; - new.hue = v.into(); - on_update(ColorPickerUpdate::ActiveColor(new)) - } + + let mut inner = column![ + // segmented buttons + segmented_control::horizontal(self.model) + .on_activate(Box::new(move |e| on_update( + ColorPickerUpdate::ActivateSegmented(e) + ))) + .minimum_button_width(0) + .width(self.width), + // canvas with gradient for the current color + // still needs the canvas and the handle to be drawn on it + container(vertical_space().height(self.height)) + .width(self.width) + .height(self.height), + slider( + 0.001..=359.99, + self.active_color.hue.into_positive_degrees(), + move |v| { + let mut new = self.active_color; + new.hue = v.into(); + on_update(ColorPickerUpdate::ActiveColor(new)) + } + ) + .on_release(on_update(ColorPickerUpdate::ActionFinished)) + .class(Slider::Custom { + active: Rc::new(move |t| { + let cosmic = t.cosmic(); + let mut a = + slider::Catalog::style(t, &Slider::default(), slider::Status::Active); + let hue = self.active_color.hue.into_positive_degrees(); + a.rail.backgrounds = rail_backgrounds(hue); + a.rail.width = 8.0; + a.handle.background = Color::TRANSPARENT.into(); + a.handle.shape = HandleShape::Circle { radius: 8.0 }; + a.handle.border_color = cosmic.palette.neutral_10.into(); + a.handle.border_width = 4.0; + a + }), + hovered: Rc::new(move |t| { + let cosmic = t.cosmic(); + let mut a = + slider::Catalog::style(t, &Slider::default(), slider::Status::Active); + let hue = self.active_color.hue.into_positive_degrees(); + a.rail.backgrounds = rail_backgrounds(hue); + a.rail.width = 8.0; + a.handle.background = Color::TRANSPARENT.into(); + a.handle.shape = HandleShape::Circle { radius: 8.0 }; + a.handle.border_color = cosmic.palette.neutral_10.into(); + a.handle.border_width = 4.0; + a + }), + dragging: Rc::new(move |t| { + let cosmic = t.cosmic(); + let mut a = + slider::Catalog::style(t, &Slider::default(), slider::Status::Active); + let hue = self.active_color.hue.into_positive_degrees(); + a.rail.backgrounds = rail_backgrounds(hue); + a.rail.width = 8.0; + a.handle.background = Color::TRANSPARENT.into(); + a.handle.shape = HandleShape::Circle { radius: 8.0 }; + a.handle.border_color = cosmic.palette.neutral_10.into(); + a.handle.border_width = 4.0; + a + }), + }) + .width(self.width), + text_input("", self.input_color) + .on_input(move |s| on_update(ColorPickerUpdate::Input(s))) + .on_paste(move |s| on_update(ColorPickerUpdate::Input(s))) + .on_submit(move |_| on_update(ColorPickerUpdate::AppliedColor)) + .leading_icon( + color_button( + None, + Some(Color::from(palette::Srgb::from_color(self.active_color))), + Length::FillPortion(12) + ) + .into() ) - .on_release(on_update(ColorPickerUpdate::ActionFinished)) - .class(Slider::Custom { - active: Rc::new(move |t| { - let cosmic = t.cosmic(); - let mut a = - slider::Catalog::style(t, &Slider::default(), slider::Status::Active); + // TODO copy paste input contents + .trailing_icon({ + let button = button::custom(crate::widget::icon( + from_name("edit-copy-symbolic").size(spacing.space_s).into(), + )) + .on_press(on_update(ColorPickerUpdate::Copied(Instant::now()))) + .class(Button::Text); - let hue = self.active_color.hue.into_positive_degrees(); - let pivot = hue * 7.0 / 360.; - - let low_end = pivot.floor() as usize; - let high_start = pivot.ceil() as usize; - let pivot_color = palette::Hsv::new_srgb(RgbHue::new(hue), 1.0, 1.0); - let low_range = HSV_RAINBOW[0..=low_end] - .iter() - .enumerate() - .map(|(i, color)| ColorStop { - color: *color, - offset: i as f32 / pivot.max(0.0001), - }) - .chain(iter::once(ColorStop { - color: iced::Color::from(palette::Srgba::from_color(pivot_color)), - offset: 1., - })) - .collect::>(); - let high_range = - iter::once(ColorStop { - color: iced::Color::from(palette::Srgba::from_color(pivot_color)), - offset: 0., - }) - .chain(HSV_RAINBOW[high_start..].iter().enumerate().map( - |(i, color)| ColorStop { - color: *color, - offset: (i as f32 + (1. - pivot.fract())) - / (7. - pivot).max(0.0001), - }, - )) - .collect::>(); - a.rail.backgrounds = ( - Background::Gradient(iced::Gradient::Linear( - Linear::new(Radians(90.0)).add_stops(low_range), - )), - Background::Gradient(iced::Gradient::Linear( - Linear::new(Radians(90.0)).add_stops(high_range), - )), - ); - - a.rail.width = 8.0; - a.handle.background = Color::TRANSPARENT.into(); - a.handle.shape = HandleShape::Circle { radius: 8.0 }; - a.handle.border_color = cosmic.palette.neutral_10.into(); - a.handle.border_width = 4.0; - a - }), - hovered: Rc::new(move |t| { - let cosmic = t.cosmic(); - let mut a = - slider::Catalog::style(t, &Slider::default(), slider::Status::Active); - let hue = self.active_color.hue.into_positive_degrees(); - let pivot = hue * 7.0 / 360.; - - let low_end = pivot.floor() as usize; - let high_start = pivot.ceil() as usize; - let pivot_color = palette::Hsv::new_srgb(RgbHue::new(hue), 1.0, 1.0); - let low_range = HSV_RAINBOW[0..=low_end] - .iter() - .enumerate() - .map(|(i, color)| ColorStop { - color: *color, - offset: i as f32 / pivot.max(0.0001), - }) - .chain(iter::once(ColorStop { - color: iced::Color::from(palette::Srgba::from_color(pivot_color)), - offset: 1., - })) - .collect::>(); - let high_range = - iter::once(ColorStop { - color: iced::Color::from(palette::Srgba::from_color(pivot_color)), - offset: 0., - }) - .chain(HSV_RAINBOW[high_start..].iter().enumerate().map( - |(i, color)| ColorStop { - color: *color, - offset: (i as f32 + (1. - pivot.fract())) - / (7. - pivot).max(0.0001), - }, - )) - .collect::>(); - a.rail.backgrounds = ( - Background::Gradient(iced::Gradient::Linear( - Linear::new(Radians(90.0)).add_stops(low_range), - )), - Background::Gradient(iced::Gradient::Linear( - Linear::new(Radians(90.0)).add_stops(high_range), - )), - ); - a.rail.width = 8.0; - a.handle.background = Color::TRANSPARENT.into(); - a.handle.shape = HandleShape::Circle { radius: 8.0 }; - a.handle.border_color = cosmic.palette.neutral_10.into(); - a.handle.border_width = 4.0; - a - }), - dragging: Rc::new(move |t| { - let cosmic = t.cosmic(); - let mut a = - slider::Catalog::style(t, &Slider::default(), slider::Status::Active); - let hue = self.active_color.hue.into_positive_degrees(); - let pivot = hue * 7.0 / 360.; - - let low_end = pivot.floor() as usize; - let high_start = pivot.ceil() as usize; - let pivot_color = palette::Hsv::new_srgb(RgbHue::new(hue), 1.0, 1.0); - let low_range = HSV_RAINBOW[0..=low_end] - .iter() - .enumerate() - .map(|(i, color)| ColorStop { - color: *color, - offset: i as f32 / pivot.max(0.0001), - }) - .chain(iter::once(ColorStop { - color: iced::Color::from(palette::Srgba::from_color(pivot_color)), - offset: 1., - })) - .collect::>(); - let high_range = - iter::once(ColorStop { - color: iced::Color::from(palette::Srgba::from_color(pivot_color)), - offset: 0., - }) - .chain(HSV_RAINBOW[high_start..].iter().enumerate().map( - |(i, color)| ColorStop { - color: *color, - offset: (i as f32 + (1. - pivot.fract())) - / (7. - pivot).max(0.0001), - }, - )) - .collect::>(); - a.rail.backgrounds = ( - Background::Gradient(iced::Gradient::Linear( - Linear::new(Radians(90.0)).add_stops(low_range), - )), - Background::Gradient(iced::Gradient::Linear( - Linear::new(Radians(90.0)).add_stops(high_range), - )), - ); - a.rail.width = 8.0; - a.handle.background = Color::TRANSPARENT.into(); - a.handle.shape = HandleShape::Circle { radius: 8.0 }; - a.handle.border_color = cosmic.palette.neutral_10.into(); - a.handle.border_width = 4.0; - a - }), + match self.copied_at.take() { + Some(t) if Instant::now().duration_since(t) > Duration::from_secs(2) => { + button.into() + } + Some(_) => tooltip( + button, + text(copied_to_clipboard_label), + iced_widget::tooltip::Position::Bottom, + ) + .into(), + None => tooltip( + button, + text(copy_to_clipboard_label), + iced_widget::tooltip::Position::Bottom, + ) + .into(), + } }) .width(self.width), - text_input("", self.input_color) - .on_input(move |s| on_update(ColorPickerUpdate::Input(s))) - .on_paste(move |s| on_update(ColorPickerUpdate::Input(s))) - .on_submit(move |_| on_update(ColorPickerUpdate::AppliedColor)) - .leading_icon( - color_button( - None, - Some(Color::from(palette::Srgb::from_color(self.active_color))), - Length::FillPortion(12) - ) - .into() - ) - // TODO copy paste input contents - .trailing_icon({ - let button = button::custom(crate::widget::icon( - from_name("edit-copy-symbolic").size(spacing.space_s).into(), - )) - .on_press(on_update(ColorPickerUpdate::Copied(Instant::now()))) - .class(Button::Text); - - match self.copied_at.take() { - Some(t) - if Instant::now().duration_since(t) > Duration::from_secs(2) => - { - button.into() - } - Some(_) => tooltip( - button, - text(copied_to_clipboard_label), - iced_widget::tooltip::Position::Bottom, - ) - .into(), - None => tooltip( - button, - text(copy_to_clipboard_label), - iced_widget::tooltip::Position::Bottom, - ) - .into(), - } - }) - .width(self.width), - ] - // Should we ensure the side padding is at least half the width of the handle? - .padding([ - spacing.space_none, - spacing.space_s, - spacing.space_s, - spacing.space_s, - ]) - .spacing(spacing.space_s); + ] + // Should we ensure the side padding is at least half the width of the handle? + .padding([ + spacing.space_none, + spacing.space_s, + spacing.space_s, + spacing.space_s, + ]) + .spacing(spacing.space_s); if !self.recent_colors.is_empty() { inner = inner.push(horizontal::light().width(self.width)); From 804250af64e941aa273687d6fc75bd91ef18e9bf Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Thu, 9 Oct 2025 00:07:32 +0200 Subject: [PATCH 330/556] i18n: translation updates from weblate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Feike Donia Co-authored-by: Guðmundur Erlingsson Co-authored-by: Hosted Weblate Co-authored-by: Stepan Denysenko Co-authored-by: oddib Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/is/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/nl/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/nn/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/uk/ Translation: Pop OS/libcosmic --- i18n/is/libcosmic.ftl | 8 ++++++++ i18n/nl/libcosmic.ftl | 1 + i18n/nn/libcosmic.ftl | 2 ++ i18n/uk/libcosmic.ftl | 3 +-- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/i18n/is/libcosmic.ftl b/i18n/is/libcosmic.ftl index e69de29b..391eaf08 100644 --- a/i18n/is/libcosmic.ftl +++ b/i18n/is/libcosmic.ftl @@ -0,0 +1,8 @@ +close = Loka +license = Notandaleyfi +links = Tenglar +developers = Forritarar +designers = Hönnuðir +artists = Listafólk +translators = Þýðendur +documenters = Skjölunarhöfundar diff --git a/i18n/nl/libcosmic.ftl b/i18n/nl/libcosmic.ftl index e69de29b..b0aafeba 100644 --- a/i18n/nl/libcosmic.ftl +++ b/i18n/nl/libcosmic.ftl @@ -0,0 +1 @@ +close = Sluiten diff --git a/i18n/nn/libcosmic.ftl b/i18n/nn/libcosmic.ftl index e69de29b..ffa3faf5 100644 --- a/i18n/nn/libcosmic.ftl +++ b/i18n/nn/libcosmic.ftl @@ -0,0 +1,2 @@ +close = Lukk +license = Lisens diff --git a/i18n/uk/libcosmic.ftl b/i18n/uk/libcosmic.ftl index 07dbaf9c..396dab36 100644 --- a/i18n/uk/libcosmic.ftl +++ b/i18n/uk/libcosmic.ftl @@ -1,9 +1,8 @@ # Context Drawer close = Закрити - # About license = Ліцензія -links = Лінки +links = Посилання developers = Розробники designers = Дизайнери artists = Митці From d82e6a167c2e79f6c615ef596d5ec3ddbb71d6b1 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 9 Oct 2025 12:08:57 -0700 Subject: [PATCH 331/556] Update `iced` Update iced with https://github.com/pop-os/iced/pull/244. --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 521a04d7..8cbf2b70 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 521a04d7e7589fdd61b314c92155277bf350d944 +Subproject commit 8cbf2b70ad229a4bc5b7055e3d0a9eef265bd10d From 483fb2cdd103e44d3d6cfc7522455f683789b87e Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Fri, 10 Oct 2025 01:07:35 +0200 Subject: [PATCH 332/556] i18n: translation updates from weblate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Feike Donia Co-authored-by: Guðmundur Erlingsson Co-authored-by: Hosted Weblate Co-authored-by: Stepan Denysenko Co-authored-by: Ziad El-sayed Co-authored-by: oddib Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ar/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/is/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/nl/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/nn/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/uk/ Translation: Pop OS/libcosmic --- i18n/ar/libcosmic.ftl | 7 +++---- i18n/uk/libcosmic.ftl | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/i18n/ar/libcosmic.ftl b/i18n/ar/libcosmic.ftl index 4fc8582b..ad86e64e 100644 --- a/i18n/ar/libcosmic.ftl +++ b/i18n/ar/libcosmic.ftl @@ -1,11 +1,10 @@ # Context Drawer close = أغلق - # About license = الترخيص links = الروابط -developers = المطوّرون -designers = المصمّمون +developers = المطورون +designers = المصممون artists = الفنانون translators = المترجمون -documenters = الموثّقون +documenters = الموثقون diff --git a/i18n/uk/libcosmic.ftl b/i18n/uk/libcosmic.ftl index 396dab36..cfdc14b8 100644 --- a/i18n/uk/libcosmic.ftl +++ b/i18n/uk/libcosmic.ftl @@ -5,6 +5,6 @@ license = Ліцензія links = Посилання developers = Розробники designers = Дизайнери -artists = Митці +artists = Художники translators = Перекладачі documenters = Документатори From cd3e9c1493644b5dffc34beb4c6b202fa3db7bc4 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 13 Oct 2025 22:07:31 +0200 Subject: [PATCH 333/556] i18n: translation updates from weblate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Aleksandar Anžel <44969003+AAnzel@users.noreply.github.com> Co-authored-by: Hosted Weblate Co-authored-by: sicKat Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/it/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/sr_Cyrl/ Translation: Pop OS/libcosmic --- i18n/it/libcosmic.ftl | 8 ++++++++ i18n/sr-Cyrl/libcosmic.ftl | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/i18n/it/libcosmic.ftl b/i18n/it/libcosmic.ftl index e69de29b..a551a716 100644 --- a/i18n/it/libcosmic.ftl +++ b/i18n/it/libcosmic.ftl @@ -0,0 +1,8 @@ +close = Chiudi +license = Licenza +links = Link +developers = Sviluppatori +designers = Designer +artists = Artisti +translators = Traduttori +documenters = Documentatori diff --git a/i18n/sr-Cyrl/libcosmic.ftl b/i18n/sr-Cyrl/libcosmic.ftl index 30ed82d3..ce6afb28 100644 --- a/i18n/sr-Cyrl/libcosmic.ftl +++ b/i18n/sr-Cyrl/libcosmic.ftl @@ -3,3 +3,8 @@ close = Затвори # About license = Лиценца links = Линкови +developers = Програмер +designers = Дизајнери +artists = Уметници +translators = Преводиоци +documenters = Произвођачи документације From f44d82a7e83af15270a9ca3beb832f4799699337 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 14 Oct 2025 16:28:43 +0200 Subject: [PATCH 334/556] fix(spin_buttton): change text style to body --- src/widget/spin_button.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index eba4641a..a93f2ee4 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -178,7 +178,7 @@ where let decrement_button = make_button!(spin_button, "list-remove-symbolic", decrement); let increment_button = make_button!(spin_button, "list-add-symbolic", increment); - let label = text::title4(spin_button.label) + let label = text::body(spin_button.label) .apply(container) .center_x(Length::Fixed(48.0)) .align_y(Alignment::Center); @@ -201,7 +201,7 @@ where let decrement_button = make_button!(spin_button, "list-remove-symbolic", decrement); let increment_button = make_button!(spin_button, "list-add-symbolic", increment); - let label = text::title4(spin_button.label) + let label = text::body(spin_button.label) .apply(container) .center_x(Length::Fixed(48.0)) .align_y(Alignment::Center); From 76c1897d4d9a637c8aa4016483bf05fec5f10ebd Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 17 Oct 2025 08:59:39 -0700 Subject: [PATCH 335/556] Update `iced` for `input_zone` change https://github.com/pop-os/iced/pull/241 --- iced | 2 +- src/applet/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/iced b/iced index 8cbf2b70..7bb364e0 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 8cbf2b70ad229a4bc5b7055e3d0a9eef265bd10d +Subproject commit 7bb364e01d6cd6c07703416828006ab497a082e6 diff --git a/src/applet/mod.rs b/src/applet/mod.rs index ded92cf6..6dfaeef6 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -252,10 +252,10 @@ impl Context { parent: parent_id.unwrap_or(window::Id::RESERVED), id: window_id, grab: false, - input_zone: Some(Rectangle::new( + input_zone: Some(vec![Rectangle::new( iced::Point::new(-1000., -1000.), iced::Size::default(), - )), + )]), positioner: SctkPositioner { size: None, size_limits: Limits::NONE.min_width(1.).min_height(1.), From 529eeebaebb34890ce9ca54b5ee8bc4f5bbc0bd5 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 20 Oct 2025 11:47:37 -0400 Subject: [PATCH 336/556] fix: avoid focus effects if already focused --- src/widget/text_input/input.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 949f2040..4c9fa0f9 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -656,7 +656,7 @@ where if index == old_value.len() { state.cursor.move_to(self.value.len()); } - }; + } } if let Some(f) = state.is_focused.as_ref().filter(|f| f.focused) { @@ -2687,6 +2687,7 @@ impl State { pub fn focus(&mut self) { let now = Instant::now(); LAST_FOCUS_UPDATE.with(|x| x.set(now)); + let was_focused = self.is_focused.is_some_and(|f| f.focused); self.is_read_only = false; self.is_focused = Some(Focus { updated_at: now, @@ -2695,6 +2696,9 @@ impl State { needs_update: false, }); + if was_focused { + return; + } if self.select_on_focus { self.select_all() } else { From f2e965c76cddf3bac183e35f2c7b91874b5f2628 Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Mon, 20 Oct 2025 12:00:32 -0700 Subject: [PATCH 337/556] fix: dialog body overflows --- src/widget/dialog.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/widget/dialog.rs b/src/widget/dialog.rs index 50bf4f1e..ecc6ef05 100644 --- a/src/widget/dialog.rs +++ b/src/widget/dialog.rs @@ -125,7 +125,9 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes content_col = content_col .push(widget::vertical_space().height(Length::Fixed(space_xxs.into()))); } - content_col = content_col.push(widget::text::body(body)); + content_col = content_col.push( + widget::container(widget::scrollable(widget::text::body(body))).max_height(300.), + ); should_space = true; } for control in dialog.controls { From 2e87bd7c41a4067d6464d085705c6efa48456c83 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 21 Oct 2025 13:03:38 -0400 Subject: [PATCH 338/556] fix(segmented_button): ensure modifier state exact match for tab --- src/widget/segmented_button/widget.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 3cbe12f9..0e725132 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -1054,10 +1054,12 @@ where }) = event { state.focused_visible = true; - return if modifiers.shift() { + return if modifiers == keyboard::Modifiers::SHIFT { self.focus_previous(state) - } else { + } else if modifiers.is_empty() { self.focus_next(state) + } else { + event::Status::Ignored }; } From 840ef21e4de2a3678c25621cf2ae07e643b63c60 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 21 Oct 2025 21:28:21 -0400 Subject: [PATCH 339/556] fix(dnd_destination): Don't capture leave events --- src/widget/dnd_destination.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index 121648ab..ccc0fb18 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -359,7 +359,7 @@ impl Widget } return event::Status::Captured; } - Event::Dnd(DndEvent::Offer(id, OfferEvent::Leave)) => { + Event::Dnd(DndEvent::Offer(_, OfferEvent::Leave)) => { if let Some(msg) = state.on_leave(self.on_leave.as_ref().map(std::convert::AsRef::as_ref)) { @@ -380,7 +380,7 @@ impl Widget viewport, ); } - return event::Status::Captured; + return event::Status::Ignored; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Motion { x, y })) if id == Some(my_id) => { if let Some(msg) = state.on_motion( @@ -412,13 +412,13 @@ impl Widget } return event::Status::Captured; } - Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) => { + Event::Dnd(DndEvent::Offer(_, OfferEvent::LeaveDestination)) => { if let Some(msg) = state.on_leave(self.on_leave.as_ref().map(std::convert::AsRef::as_ref)) { shell.publish(msg); } - return event::Status::Captured; + return event::Status::Ignored; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Drop)) if id == Some(my_id) => { if let Some(msg) = 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 340/556] 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 } From 1d6a43486eaa5ad15bc9627d0a31f0620181fe9d Mon Sep 17 00:00:00 2001 From: Cheong Lau <234708519+Cheong-Lau@users.noreply.github.com> Date: Sat, 11 Oct 2025 16:24:38 +1000 Subject: [PATCH 341/556] remove redundant `clone`s, use `mul_add` on `f32`s --- src/widget/context_drawer/overlay.rs | 2 +- src/widget/dropdown/menu/mod.rs | 7 +-- src/widget/dropdown/multi/menu.rs | 15 +++--- src/widget/dropdown/multi/widget.rs | 66 ++++++++++++++------------- src/widget/flex_row/layout.rs | 2 +- src/widget/grid/layout.rs | 2 +- src/widget/id_container.rs | 2 +- src/widget/menu/flex.rs | 28 ++++++------ src/widget/menu/menu_inner.rs | 2 +- src/widget/responsive_container.rs | 2 +- src/widget/segmented_button/widget.rs | 4 +- src/widget/toaster/widget.rs | 6 +-- 12 files changed, 69 insertions(+), 69 deletions(-) diff --git a/src/widget/context_drawer/overlay.rs b/src/widget/context_drawer/overlay.rs index d9cc88ab..4f72e113 100644 --- a/src/widget/context_drawer/overlay.rs +++ b/src/widget/context_drawer/overlay.rs @@ -33,7 +33,7 @@ where .layout(self.tree, renderer, &limits); let node_size = node.size(); - node.clone().move_to(Point { + node.move_to(Point { x: if bounds.width > node_size.width - 8.0 { bounds.width - node_size.width - 8.0 } else { diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index 021fcc60..1d42d01f 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -204,7 +204,7 @@ impl<'a, Message: Clone + 'a> Overlay<'a, Message> { .with_data_mut(|tree| tree.diff(&mut container as &mut dyn Widget<_, _, _>)); Self { - state: state.tree.clone(), + state: state.tree, container, width, target_height, @@ -234,10 +234,11 @@ impl<'a, Message: Clone + 'a> Overlay<'a, Message> { .state .with_data_mut(|tree| self.container.layout(tree, renderer, &limits)); - node.clone().move_to(if space_below > space_above { + let node_size = node.size(); + node.move_to(if space_below > space_above { self.position + Vector::new(0.0, self.target_height) } else { - self.position - Vector::new(0.0, node.size().height) + self.position - Vector::new(0.0, node_size.height) }) } diff --git a/src/widget/dropdown/multi/menu.rs b/src/widget/dropdown/multi/menu.rs index 10b0d8d4..0035829f 100644 --- a/src/widget/dropdown/multi/menu.rs +++ b/src/widget/dropdown/multi/menu.rs @@ -199,15 +199,14 @@ impl iced_core::Overlay for Ove ) .width(self.width); - let mut node = self.container.layout(self.state, renderer, &limits); + let node = self.container.layout(self.state, renderer, &limits); - node = node.clone().move_to(if space_below > space_above { + let node_size = node.size(); + node.move_to(if space_below > space_above { position + Vector::new(0.0, self.target_height) } else { - position - Vector::new(0.0, node.size().height) - }); - - node + position - Vector::new(0.0, node_size.height) + }) } fn on_event( @@ -513,7 +512,7 @@ where OptionElement::Option((option, item)) => { let (color, font) = if self.selected_option.as_ref() == Some(&item) { let item_x = bounds.x + appearance.border_width; - let item_width = bounds.width - appearance.border_width * 2.0; + let item_width = appearance.border_width.mul_add(-2.0, bounds.width); bounds = Rectangle { x: item_x, @@ -551,7 +550,7 @@ where (appearance.selected_text_color, crate::font::semibold()) } else if self.hovered_option.as_ref() == Some(item) { let item_x = bounds.x + appearance.border_width; - let item_width = bounds.width - appearance.border_width * 2.0; + let item_width = appearance.border_width.mul_add(-2.0, bounds.width); bounds = Rectangle { x: item_x, diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index 9c183292..79b1a6b7 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -432,44 +432,46 @@ pub fn overlay<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static }; let mut desc_count = 0; - selections - .elements() - .map(|element| match element { - super::menu::OptionElement::Description(desc) => { - let paragraph = if state.descriptions.len() > desc_count { - &mut state.descriptions[desc_count] - } else { - state.descriptions.push(crate::Plain::default()); - state.descriptions.last_mut().unwrap() - }; - desc_count += 1; - measure(desc.as_ref(), paragraph, description_line_height) - } + padding.horizontal().mul_add( + 2.0, + selections + .elements() + .map(|element| match element { + super::menu::OptionElement::Description(desc) => { + let paragraph = if state.descriptions.len() > desc_count { + &mut state.descriptions[desc_count] + } else { + state.descriptions.push(crate::Plain::default()); + state.descriptions.last_mut().unwrap() + }; + desc_count += 1; + measure(desc.as_ref(), paragraph, description_line_height) + } - super::menu::OptionElement::Option((option, item)) => { - let selection_index = state.selections.iter().position(|(i, _)| i == item); + super::menu::OptionElement::Option((option, item)) => { + let selection_index = + state.selections.iter().position(|(i, _)| i == item); - let selection_index = match selection_index { - Some(index) => index, - None => { - state - .selections - .push((item.clone(), crate::Plain::default())); - state.selections.len() - 1 - } - }; + let selection_index = match selection_index { + Some(index) => index, + None => { + state + .selections + .push((item.clone(), crate::Plain::default())); + state.selections.len() - 1 + } + }; - let paragraph = &mut state.selections[selection_index].1; + let paragraph = &mut state.selections[selection_index].1; - measure(option.as_ref(), paragraph, text_line_height) - } + measure(option.as_ref(), paragraph, text_line_height) + } - super::menu::OptionElement::Separator => 1.0, - }) - .fold(0.0, |next, current| current.max(next)) - + gap + super::menu::OptionElement::Separator => 1.0, + }) + .fold(0.0, |next, current| current.max(next)), + ) + gap + 16.0 - + (padding.horizontal() * 2.0) }) .padding(padding) .text_size(text_size); diff --git a/src/widget/flex_row/layout.rs b/src/widget/flex_row/layout.rs index 720e4561..744b607d 100644 --- a/src/widget/flex_row/layout.rs +++ b/src/widget/flex_row/layout.rs @@ -156,7 +156,7 @@ pub fn resolve( _ => (), } - *node = node.clone().move_to(Point { + node.move_to_mut(Point { x: leaf_layout.location.x, y: leaf_layout.location.y, }); diff --git a/src/widget/grid/layout.rs b/src/widget/grid/layout.rs index d1da68af..a7e42759 100644 --- a/src/widget/grid/layout.rs +++ b/src/widget/grid/layout.rs @@ -187,7 +187,7 @@ pub fn resolve( _ => (), } - *node = node.clone().move_to(Point { + node.move_to_mut(Point { x: leaf_layout.location.x, y: leaf_layout.location.y, }) diff --git a/src/widget/id_container.rs b/src/widget/id_container.rs index 7f6bc97e..3d468b20 100644 --- a/src/widget/id_container.rs +++ b/src/widget/id_container.rs @@ -112,7 +112,7 @@ where ) -> event::Status { self.content.as_widget_mut().on_event( &mut tree.children[0], - event.clone(), + event, layout .children() .next() diff --git a/src/widget/menu/flex.rs b/src/widget/menu/flex.rs index 5eaf3d94..8eb08d4e 100644 --- a/src/widget/menu/flex.rs +++ b/src/widget/menu/flex.rs @@ -200,16 +200,16 @@ where let (x, y) = axis.pack(main, pad.1); - let node_ = node.clone().move_to(Point::new(x, y)); + node.move_to_mut(Point::new(x, y)); - let node_ = match axis { - Axis::Horizontal => node_.align(Alignment::Start, align_items, Size::new(0.0, cross)), - Axis::Vertical => node_.align(align_items, Alignment::Start, Size::new(cross, 0.0)), + match axis { + Axis::Horizontal => { + node.align_mut(Alignment::Start, align_items, Size::new(0.0, cross)) + } + Axis::Vertical => node.align_mut(align_items, Alignment::Start, Size::new(cross, 0.0)), }; - let size = node_.bounds().size(); - - *node = node_; + let size = node.bounds().size(); main += axis.main(size); } @@ -367,16 +367,16 @@ pub fn resolve_wrapper<'a, Message>( let (x, y) = axis.pack(main, pad.1); - let node_ = node.clone().move_to(Point::new(x, y)); + node.move_to_mut(Point::new(x, y)); - let node_ = match axis { - Axis::Horizontal => node_.align(Alignment::Start, align_items, Size::new(0.0, cross)), - Axis::Vertical => node_.align(align_items, Alignment::Start, Size::new(cross, 0.0)), + match axis { + Axis::Horizontal => { + node.align_mut(Alignment::Start, align_items, Size::new(0.0, cross)) + } + Axis::Vertical => node.align_mut(align_items, Alignment::Start, Size::new(cross, 0.0)), }; - let size = node_.bounds().size(); - - *node = node_; + let size = node.bounds().size(); main += axis.main(size); } diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index c88a7570..c455cd13 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -370,7 +370,7 @@ impl MenuState { let limits = Limits::new(Size::ZERO, self.menu_bounds.child_sizes[index]); let parent_offset = children_bounds.position() - Point::ORIGIN; let node = menu_tree.item.layout(tree, renderer, &limits); - node.clone().move_to(Point::new( + node.move_to(Point::new( parent_offset.x, parent_offset.y + position + self.scroll_offset, )) diff --git a/src/widget/responsive_container.rs b/src/widget/responsive_container.rs index 92bedef1..fbc2df9e 100644 --- a/src/widget/responsive_container.rs +++ b/src/widget/responsive_container.rs @@ -168,7 +168,7 @@ where self.content.as_widget_mut().on_event( &mut tree.children[0], - event.clone(), + event, layout .children() .next() diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 5dd8e7c7..bb05aa9d 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -1419,8 +1419,8 @@ where renderer.fill_quad( renderer::Quad { bounds: Rectangle { - x: bounds.x - - (level as f32 * self.indent_spacing as f32) + x: (level as f32) + .mul_add(-(self.indent_spacing as f32), bounds.x) + indent_padding, width: 1.0, ..bounds diff --git a/src/widget/toaster/widget.rs b/src/widget/toaster/widget.rs index f6324e15..52604592 100644 --- a/src/widget/toaster/widget.rs +++ b/src/widget/toaster/widget.rs @@ -199,7 +199,7 @@ where fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node { let limits = Limits::new(Size::ZERO, bounds); - let mut node = self + let node = self .element .as_widget() .layout(self.state, renderer, &limits); @@ -211,9 +211,7 @@ where bounds.height - (node.size().height + offset), ); - node.move_to_mut(position); - - node + node.move_to(position) } fn draw( From e49a30104bb169e08f44913b39a13496374390be Mon Sep 17 00:00:00 2001 From: UchiWerfer Date: Tue, 7 Oct 2025 23:29:29 +0200 Subject: [PATCH 342/556] added localization for month and weekday to calendar-widget --- i18n/en/libcosmic.ftl | 21 +++++++++++++++++++++ src/widget/calendar.rs | 42 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/i18n/en/libcosmic.ftl b/i18n/en/libcosmic.ftl index 45266a9b..119ac38e 100644 --- a/i18n/en/libcosmic.ftl +++ b/i18n/en/libcosmic.ftl @@ -9,3 +9,24 @@ designers = Designers artists = Artists translators = Translators documenters = Documenters + +# Calendar +january = January { $year } +february = February { $year } +march = March { $year } +april = April { $year } +may = May { $year } +june = June { $year } +july = July { $year } +august = August { $year } +september = September { $year } +october = October { $year } +november = November { $year } +december = December { $year } +monday = Mon +tuesday = Tue +wednesday = Wed +thursday = Thu +friday = Fri +saturday = Sat +sunday = Sun diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index 8531ab3d..134e84f2 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -8,7 +8,8 @@ use std::cmp; use crate::iced_core::{Alignment, Length, Padding}; use crate::widget::{Grid, button, column, grid, icon, row, text}; use apply::Apply; -use chrono::{Datelike, Days, Local, Months, NaiveDate, Weekday}; +use chrono::{Datelike, Days, Local, Month, Months, NaiveDate, Weekday}; +use crate::fl; /// A widget that displays an interactive calendar. pub fn calendar( @@ -129,7 +130,42 @@ where icon.padding([0, 12]).on_press($on_press) }}; } - let date = text(this.model.visible.format("%B %Y").to_string()).size(18); + macro_rules! translate_month { + ($month:expr, $year:expr) => {{ + match $month { + chrono::Month::January => fl!("january", year=$year), + chrono::Month::February => fl!("february", year=$year), + chrono::Month::March => fl!("march", year=$year), + chrono::Month::April => fl!("april", year=$year), + chrono::Month::May => fl!("may", year=$year), + chrono::Month::June => fl!("june", year=$year), + chrono::Month::July => fl!("july", year=$year), + chrono::Month::August => fl!("august", year=$year), + chrono::Month::September => fl!("september", year=$year), + chrono::Month::October => fl!("october", year=$year), + chrono::Month::November => fl!("november", year=$year), + chrono::Month::December => fl!("december", year=$year) + } + }} + } + macro_rules! translate_weekday { + ($weekday:expr) => {{ + match $weekday { + Weekday::Mon => fl!("monday"), + Weekday::Tue => fl!("tuesday"), + Weekday::Wed => fl!("wednesday"), + Weekday::Thu => fl!("thursday"), + Weekday::Fri => fl!("friday"), + Weekday::Sat => fl!("saturday"), + Weekday::Sun => fl!("sunday") + } + }} + } + + let date = text(translate_month!( + Month::try_from(this.model.visible.month() as u8) + .expect("Previously valid month is suddenly invalid"), + this.model.visible.year())).size(18); let month_controls = row::with_capacity(2) .push(icon!("go-previous-symbolic", (this.on_prev)())) @@ -142,7 +178,7 @@ where let mut first_day_of_week = this.first_day_of_week; for _ in 0..7 { calendar_grid = calendar_grid.push( - text(first_day_of_week.to_string()) + text(translate_weekday!(first_day_of_week)) .size(12) .width(Length::Fixed(36.0)) .align_x(Alignment::Center), From 380042396bf9b19df5a1ae25613b71609e725868 Mon Sep 17 00:00:00 2001 From: UchiWerfer Date: Tue, 7 Oct 2025 23:30:29 +0200 Subject: [PATCH 343/556] added German translations to the localization of the calendar-widget --- i18n/de/libcosmic.ftl | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/i18n/de/libcosmic.ftl b/i18n/de/libcosmic.ftl index 3806cc59..7d8dfe93 100644 --- a/i18n/de/libcosmic.ftl +++ b/i18n/de/libcosmic.ftl @@ -9,3 +9,24 @@ designers = Designer*innen artists = Künstler*innen translators = Übersetzer*innen documenters = Dokumentierer*innen + +# Calendar +january = Januar { $year } +february = Februar { $year } +march = März { $year } +april = April { $year } +may = Mai { $year } +june = Juni { $year } +july = Juli { $year } +august = August { $year } +september = September { $year } +october = Oktober { $year } +november = November { $year } +december = Dezember { $year } +monday = Mo +tuesday = Di +wednesday = Mi +thursday = Do +friday = Fr +saturday = Sa +sunday = So From 6204784f202c19b675c5a0c8a56865e3f1d9bd69 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 23 Oct 2025 17:12:06 -0400 Subject: [PATCH 344/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 7bb364e0..cfe5f4b1 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 7bb364e01d6cd6c07703416828006ab497a082e6 +Subproject commit cfe5f4b1a4413b9c94c10832093b2a9e0de5eece From 0c6c85429e313c538d7fe3cd25a49ef818293a6e Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:24:02 -0400 Subject: [PATCH 345/556] chore: update iced (#1029) --- iced | 2 +- src/widget/calendar.rs | 36 +++++++++++++++++++----------------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/iced b/iced index cfe5f4b1..783d764c 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit cfe5f4b1a4413b9c94c10832093b2a9e0de5eece +Subproject commit 783d764cabd6eee020eeae3b50a0d4727a721056 diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index 134e84f2..7f9ac0ad 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -5,11 +5,11 @@ use std::cmp; +use crate::fl; use crate::iced_core::{Alignment, Length, Padding}; use crate::widget::{Grid, button, column, grid, icon, row, text}; use apply::Apply; use chrono::{Datelike, Days, Local, Month, Months, NaiveDate, Weekday}; -use crate::fl; /// A widget that displays an interactive calendar. pub fn calendar( @@ -133,20 +133,20 @@ where macro_rules! translate_month { ($month:expr, $year:expr) => {{ match $month { - chrono::Month::January => fl!("january", year=$year), - chrono::Month::February => fl!("february", year=$year), - chrono::Month::March => fl!("march", year=$year), - chrono::Month::April => fl!("april", year=$year), - chrono::Month::May => fl!("may", year=$year), - chrono::Month::June => fl!("june", year=$year), - chrono::Month::July => fl!("july", year=$year), - chrono::Month::August => fl!("august", year=$year), - chrono::Month::September => fl!("september", year=$year), - chrono::Month::October => fl!("october", year=$year), - chrono::Month::November => fl!("november", year=$year), - chrono::Month::December => fl!("december", year=$year) + chrono::Month::January => fl!("january", year = $year), + chrono::Month::February => fl!("february", year = $year), + chrono::Month::March => fl!("march", year = $year), + chrono::Month::April => fl!("april", year = $year), + chrono::Month::May => fl!("may", year = $year), + chrono::Month::June => fl!("june", year = $year), + chrono::Month::July => fl!("july", year = $year), + chrono::Month::August => fl!("august", year = $year), + chrono::Month::September => fl!("september", year = $year), + chrono::Month::October => fl!("october", year = $year), + chrono::Month::November => fl!("november", year = $year), + chrono::Month::December => fl!("december", year = $year), } - }} + }}; } macro_rules! translate_weekday { ($weekday:expr) => {{ @@ -157,15 +157,17 @@ where Weekday::Thu => fl!("thursday"), Weekday::Fri => fl!("friday"), Weekday::Sat => fl!("saturday"), - Weekday::Sun => fl!("sunday") + Weekday::Sun => fl!("sunday"), } - }} + }}; } let date = text(translate_month!( Month::try_from(this.model.visible.month() as u8) .expect("Previously valid month is suddenly invalid"), - this.model.visible.year())).size(18); + this.model.visible.year() + )) + .size(18); let month_controls = row::with_capacity(2) .push(icon!("go-previous-symbolic", (this.on_prev)())) From a1b64dde3e445f67d5b3c7845c4b5b2b80b4fd4e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 27 Oct 2025 12:41:18 -0400 Subject: [PATCH 346/556] fix(input): handle ctrl shortcuts with caps lock --- src/widget/text_input/input.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 958673ef..7dd92e12 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -1629,22 +1629,27 @@ pub fn update<'a, Message: Clone + 'static>( key, text, physical_key, + modifiers, .. }) => { let state = state(); + state.keyboard_modifiers = modifiers; + if let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) { if state.is_read_only || (!manage_value && on_input.is_none()) { return event::Status::Ignored; }; - let modifiers = state.keyboard_modifiers; focus.updated_at = Instant::now(); LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at)); // Check if Ctrl+A/C/V/X was pressed. - if state.keyboard_modifiers.command() { + if state.keyboard_modifiers == keyboard::Modifiers::COMMAND + || state.keyboard_modifiers + == keyboard::Modifiers::COMMAND | keyboard::Modifiers::CAPS_LOCK + { match key.as_ref() { - keyboard::Key::Character("c") => { + keyboard::Key::Character("c") | keyboard::Key::Character("C") => { if !is_secure { if let Some((start, end)) = state.cursor.selection(value) { clipboard.write( @@ -1656,7 +1661,7 @@ pub fn update<'a, Message: Clone + 'static>( } // XXX if we want to allow cutting of secure text, we need to // update the cache and decide which value to cut - keyboard::Key::Character("x") => { + keyboard::Key::Character("x") | keyboard::Key::Character("X") => { if !is_secure { if let Some((start, end)) = state.cursor.selection(value) { clipboard.write( @@ -1675,7 +1680,7 @@ pub fn update<'a, Message: Clone + 'static>( } } } - keyboard::Key::Character("v") => { + keyboard::Key::Character("v") | keyboard::Key::Character("V") => { let content = if let Some(content) = state.is_pasting.take() { content } else { @@ -1719,7 +1724,7 @@ pub fn update<'a, Message: Clone + 'static>( return event::Status::Captured; } - keyboard::Key::Character("a") => { + keyboard::Key::Character("a") | keyboard::Key::Character("A") => { state.cursor.select_all(value); return event::Status::Captured; } From 8e1d06e7da79de027f37793bd7e15f23a5c4838b Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 29 Oct 2025 12:04:48 +0100 Subject: [PATCH 347/556] i18n: translation updates from weblate Co-authored-by: Hosted Weblate Co-authored-by: Mattias Eriksson Co-authored-by: Sachin Chaudhary Co-authored-by: VandaL Co-authored-by: lorduskordus Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/cs/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/pl/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/sv/ Translation: Pop OS/libcosmic --- i18n/cs/libcosmic.ftl | 20 +++++++++++++++++++- i18n/gu/libcosmic.ftl | 0 i18n/pl/libcosmic.ftl | 20 +++++++++++++++++++- i18n/sv/libcosmic.ftl | 19 +++++++++++++++++++ 4 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 i18n/gu/libcosmic.ftl diff --git a/i18n/cs/libcosmic.ftl b/i18n/cs/libcosmic.ftl index 561ca9ac..8f2ef348 100644 --- a/i18n/cs/libcosmic.ftl +++ b/i18n/cs/libcosmic.ftl @@ -1,6 +1,5 @@ # Context Drawer close = Zavřít - # About license = Licence links = Odkazy @@ -9,3 +8,22 @@ designers = Designéři artists = Grafici translators = Překladatelé documenters = Tvůrci dokumentace +sunday = Ne +january = Leden { $year } +february = Únor { $year } +march = Březen { $year } +april = Duben { $year } +may = Květen { $year } +june = Červen { $year } +july = Červenec { $year } +august = Srpen { $year } +september = Září { $year } +october = Říjen { $year } +november = Listopad { $year } +december = Prosinec { $year } +monday = Po +tuesday = Út +wednesday = St +thursday = Čt +friday = Pá +saturday = So diff --git a/i18n/gu/libcosmic.ftl b/i18n/gu/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/pl/libcosmic.ftl b/i18n/pl/libcosmic.ftl index f4a65aa6..4bbfd67f 100644 --- a/i18n/pl/libcosmic.ftl +++ b/i18n/pl/libcosmic.ftl @@ -1,6 +1,5 @@ # Context Drawer close = Zamknij - # About license = Licencja links = Linki @@ -9,3 +8,22 @@ designers = Projektanci artists = Artyści translators = Tłumacze documenters = Dokumentaliści +january = Styczeń { $year } +february = Luty { $year } +march = Marzec { $year } +april = Kwiecień { $year } +may = Maj { $year } +june = Czerwiec { $year } +july = Lipiec { $year } +august = Sierpień { $year } +september = Wrzesień { $year } +october = Październik { $year } +november = Listopad { $year } +december = Grudzień { $year } +monday = Pon +tuesday = Wto +wednesday = Śro +thursday = Czw +friday = Pią +saturday = Sob +sunday = Nie diff --git a/i18n/sv/libcosmic.ftl b/i18n/sv/libcosmic.ftl index 75cb7fb4..f0c647a1 100644 --- a/i18n/sv/libcosmic.ftl +++ b/i18n/sv/libcosmic.ftl @@ -6,3 +6,22 @@ artists = Konstnärer translators = Översättare documenters = Skribenter close = Stäng +january = Januari { $year } +february = Februari { $year } +march = Mars { $year } +april = April { $year } +may = Maj { $year } +june = Juni { $year } +july = Juli { $year } +august = Augusti { $year } +september = September { $year } +october = Oktober { $year } +november = November { $year } +december = December { $year } +monday = Mån +tuesday = Tis +wednesday = Ons +thursday = Tor +friday = Fre +saturday = Lör +sunday = Sön From b110b9ca3f7da5871224237ff5479a89cfc5e0cb Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Thu, 30 Oct 2025 15:21:26 +0100 Subject: [PATCH 348/556] i18n: translation updates from weblate (#1033) Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/hi/ Translation: Pop OS/libcosmic Co-authored-by: Kartik Nayak --- i18n/hi/libcosmic.ftl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/i18n/hi/libcosmic.ftl b/i18n/hi/libcosmic.ftl index e69de29b..ef2b0efa 100644 --- a/i18n/hi/libcosmic.ftl +++ b/i18n/hi/libcosmic.ftl @@ -0,0 +1,5 @@ +close = बंद करें +license = लाइसेंस +links = लिंक +developers = डेवलपर्स +designers = डिज़ाइनर From 2299b46862f61a8fdbdd6eeacac8005ad1a86fd3 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Thu, 30 Oct 2025 17:35:27 +0100 Subject: [PATCH 349/556] i18n: translation updates from weblate (#1034) Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/hi/ Translation: Pop OS/libcosmic Co-authored-by: Kartik Nayak --- i18n/hi/libcosmic.ftl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/i18n/hi/libcosmic.ftl b/i18n/hi/libcosmic.ftl index ef2b0efa..8603e773 100644 --- a/i18n/hi/libcosmic.ftl +++ b/i18n/hi/libcosmic.ftl @@ -3,3 +3,10 @@ license = लाइसेंस links = लिंक developers = डेवलपर्स designers = डिज़ाइनर +february = फ़रवरी { $year } +documenters = दस्तावेज़ बनाने वाले +april = अप्रैल { $year } +translators = अनुवादक +artists = कलाकार +march = मार्च { $year } +january = जनवरी { $year } From b6c6d1cb7b364f8859a8140da916e0d66e605fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Mon, 3 Nov 2025 19:16:20 +0100 Subject: [PATCH 350/556] improv(context_drawer): move title out of header row This moves the title below the header row containing actions and the close button, allowing more room for the title and actions. Also makes actions an `Element` instead of a `Vec`, providing more flexibility for developers. --- src/app/context_drawer.rs | 31 ++++----- src/app/mod.rs | 4 +- src/widget/context_drawer/mod.rs | 15 ++--- src/widget/context_drawer/widget.rs | 101 +++++++++++----------------- 4 files changed, 60 insertions(+), 91 deletions(-) diff --git a/src/app/context_drawer.rs b/src/app/context_drawer.rs index b33d2ba6..ac9d5673 100644 --- a/src/app/context_drawer.rs +++ b/src/app/context_drawer.rs @@ -7,7 +7,7 @@ use crate::Element; pub struct ContextDrawer<'a, Message: Clone + 'static> { pub title: Option>, - pub header_actions: Vec>, + pub actions: Option>, pub header: Option>, pub content: Element<'a, Message>, pub footer: Option>, @@ -29,29 +29,28 @@ pub fn context_drawer<'a, Message: Clone + 'static>( ) -> ContextDrawer<'a, Message> { ContextDrawer { title: None, + actions: None, + header: None, content: content.into(), - header_actions: vec![], footer: None, on_close, - header: None, } } impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { - /// Set a context drawer header title + /// Set a context drawer title pub fn title(mut self, title: impl Into>) -> Self { self.title = Some(title.into()); self } - /// App-specific actions at the start of the context drawer header - pub fn header_actions( - mut self, - header_actions: impl IntoIterator>, - ) -> Self { - self.header_actions = header_actions.into_iter().collect(); + + /// App-specific actions at the top-left corner of the context drawer + pub fn actions(mut self, actions: impl Into>) -> Self { + self.actions = Some(actions.into()); self } - /// Non-scrolling elements placed below the context drawer title row + + /// Elements placed above the context drawer scrollable pub fn header(mut self, header: impl Into>) -> Self { self.header = Some(header.into()); self @@ -64,20 +63,16 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { } pub fn map( - mut self, + self, on_message: fn(Message) -> Out, ) -> ContextDrawer<'a, Out> { ContextDrawer { title: self.title, - content: self.content.map(on_message), + actions: self.actions.map(|el| el.map(on_message)), header: self.header.map(|el| el.map(on_message)), + content: self.content.map(on_message), footer: self.footer.map(|el| el.map(on_message)), on_close: on_message(self.on_close), - header_actions: self - .header_actions - .into_iter() - .map(|el| el.map(on_message)) - .collect(), } } } diff --git a/src/app/mod.rs b/src/app/mod.rs index eaf0bae6..090698df 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -603,7 +603,7 @@ impl ApplicationExt for App { widgets.push( crate::widget::context_drawer( context.title, - context.header_actions, + context.actions, context.header, context.footer, context.on_close, @@ -640,7 +640,7 @@ impl ApplicationExt for App { widgets.push( crate::widget::ContextDrawer::new_inner( context.title, - context.header_actions, + context.actions, context.header, context.footer, context.content, diff --git a/src/widget/context_drawer/mod.rs b/src/widget/context_drawer/mod.rs index f1621220..107c1ff5 100644 --- a/src/widget/context_drawer/mod.rs +++ b/src/widget/context_drawer/mod.rs @@ -15,9 +15,9 @@ use crate::Element; /// An overlayed widget that attaches a toggleable context drawer to the view. pub fn context_drawer<'a, Message: Clone + 'static, Content, Drawer>( title: Option>, - header_actions: Vec>, - header_opt: Option>, - footer_opt: Option>, + actions: Option>, + header: Option>, + footer: Option>, on_close: Message, content: Content, drawer: Drawer, @@ -28,13 +28,6 @@ where Drawer: Into>, { ContextDrawer::new( - title, - header_actions, - header_opt, - footer_opt, - content, - drawer, - on_close, - max_width, + title, actions, header, footer, content, drawer, on_close, max_width, ) } diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index c65fe082..cb4b7f94 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 use super::overlay::Overlay; -use crate::widget::{LayerContainer, button, column, container, icon, row, scrollable, text}; +use crate::widget::{self, LayerContainer, button, column, container, icon, row, scrollable, text}; use crate::{Apply, Element, Renderer, Theme, fl}; use std::borrow::Cow; @@ -25,9 +25,9 @@ pub struct ContextDrawer<'a, Message> { impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { pub fn new_inner( title: Option>, - header_actions: Vec>, - header_opt: Option>, - footer_opt: Option>, + actions: Option>, + header: Option>, + footer: Option>, drawer: Drawer, on_close: Message, max_width: f32, @@ -38,7 +38,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { #[inline(never)] fn inner<'a, Message: Clone + 'static>( title: Option>, - header_actions: Vec>, + actions_opt: Option>, header_opt: Option>, footer_opt: Option>, drawer: Element<'a, Message>, @@ -53,68 +53,57 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { .. } = crate::theme::spacing(); - let (horizontal_padding, title_portion, side_portion) = if max_width < 392.0 { - (space_s, 1, 1) - } else { - (space_l, 2, 1) - }; + let horizontal_padding = if max_width < 392.0 { space_s } else { space_l }; let title = title.map(|title| { - text::heading(title) + text::title4(title) .apply(container) - .center_x(Length::FillPortion(title_portion)) + .padding([if actions_opt.is_some() { space_m } else { 0 }, 0, 0, 0]) + .width(Length::Fill) }); - - let (actions_width, close_width) = if title.is_some() { - ( - Length::FillPortion(side_portion), - Length::FillPortion(side_portion), - ) + let actions = if let Some(actions) = actions_opt { + actions + .apply(container) + .width(Length::Fill) + .apply(Element::from) } else { - (Length::Fill, Length::Shrink) + widget::horizontal_space().apply(Element::from) }; - let header_row = row::with_capacity(3) - .width(Length::Fixed(480.0)) + let header_row = row::with_capacity(2) .align_y(Alignment::Center) - .push( - row::with_children(header_actions) - .spacing(space_xxs) - .width(actions_width), - ) - .push_maybe(title) + .push(actions) .push( button::text(fl!("close")) .trailing_icon(icon::from_name("go-next-symbolic")) - .on_press(on_close) - .apply(container) - .width(close_width) - .align_x(Alignment::End), + .on_press(on_close), ); - let header = column::with_capacity(2) - .width(Length::Fixed(480.0)) + let header_element = + header_opt.map(|el| el.apply(container).padding([space_m, 0, 0, 0])); + + let header = column::with_capacity(3) .align_x(Alignment::Center) - .spacing(space_m) .padding([space_m, horizontal_padding]) .push(header_row) - .push_maybe(header_opt); + .push_maybe(title) + .push_maybe(header_element); let footer = footer_opt.map(|element| { container(element) - .width(Length::Fixed(480.0)) .align_y(Alignment::Center) .padding([space_xxs, horizontal_padding]) }); let pane = column::with_capacity(3) .push(header) .push( - scrollable(container(drawer).padding([ - 0, - horizontal_padding, - if footer.is_some() { 0 } else { space_l }, - horizontal_padding, - ])) - .height(Length::Fill) - .width(Length::Shrink), + container(drawer) + .padding([ + 0, + horizontal_padding, + if footer.is_some() { 0 } else { space_l }, + horizontal_padding, + ]) + .apply(scrollable) + .height(Length::Fill), ) .push_maybe(footer); @@ -136,9 +125,9 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { inner( title, - header_actions, - header_opt, - footer_opt, + actions, + header, + footer, drawer.into(), on_close, max_width, @@ -148,9 +137,9 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { /// Creates an empty [`ContextDrawer`]. pub fn new( title: Option>, - header_actions: Vec>, - header_opt: Option>, - footer_opt: Option>, + actions: Option>, + header: Option>, + footer: Option>, content: Content, drawer: Drawer, on_close: Message, @@ -160,15 +149,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { Content: Into>, Drawer: Into>, { - let drawer = Self::new_inner( - title, - header_actions, - header_opt, - footer_opt, - drawer, - on_close, - max_width, - ); + let drawer = Self::new_inner(title, actions, header, footer, drawer, on_close, max_width); ContextDrawer { id: None, @@ -188,7 +169,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { /// Map the message type of the context drawer to another #[inline] pub fn map( - mut self, + self, on_message: fn(Message) -> Out, ) -> ContextDrawer<'a, Out> { ContextDrawer { From d2f7fdea6d24e70b54e017e89973b8a5a44b4e54 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 3 Nov 2025 15:51:20 +0100 Subject: [PATCH 351/556] i18n: translation updates from weblate Co-authored-by: Anonymous Co-authored-by: Guilherme Aiolfi Co-authored-by: Hosted Weblate Co-authored-by: Kartik Nayak Co-authored-by: Torsten Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/de/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/hi/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/pt_BR/ Translation: Pop OS/libcosmic --- i18n/de/libcosmic.ftl | 2 -- i18n/pt-BR/libcosmic.ftl | 20 +++++++++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/i18n/de/libcosmic.ftl b/i18n/de/libcosmic.ftl index 7d8dfe93..2ef7b765 100644 --- a/i18n/de/libcosmic.ftl +++ b/i18n/de/libcosmic.ftl @@ -1,6 +1,5 @@ # Context Drawer close = Schließen - # About license = Lizenz links = Links @@ -9,7 +8,6 @@ designers = Designer*innen artists = Künstler*innen translators = Übersetzer*innen documenters = Dokumentierer*innen - # Calendar january = Januar { $year } february = Februar { $year } diff --git a/i18n/pt-BR/libcosmic.ftl b/i18n/pt-BR/libcosmic.ftl index febf5b2e..f02828bf 100644 --- a/i18n/pt-BR/libcosmic.ftl +++ b/i18n/pt-BR/libcosmic.ftl @@ -1,6 +1,5 @@ # Context Drawer close = Fechar - # About license = Licença links = Links @@ -9,3 +8,22 @@ designers = Designers artists = Artistas translators = Tradutores documenters = Documentadores +january = Janeiro { $year } +february = Fevereiro { $year } +march = Março { $year } +april = Abril { $year } +may = Maio { $year } +june = Junho { $year } +july = Julho { $year } +august = Agosto { $year } +september = Setembro { $year } +october = Outubro { $year } +november = Novembro { $year } +december = Dezembro { $year } +monday = Seg +tuesday = Ter +wednesday = Qua +thursday = Qui +friday = Sex +saturday = Sáb +sunday = Dom From 37ae722320a9750ff67808928d794538393c8556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Thu, 6 Nov 2025 01:12:52 +0100 Subject: [PATCH 352/556] fix(context_drawer): match to designs --- src/widget/context_drawer/widget.rs | 40 ++++++++++++----------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index cb4b7f94..5366832f 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -55,38 +55,32 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { let horizontal_padding = if max_width < 392.0 { space_s } else { space_l }; - let title = title.map(|title| { - text::title4(title) - .apply(container) - .padding([if actions_opt.is_some() { space_m } else { 0 }, 0, 0, 0]) - .width(Length::Fill) - }); - let actions = if let Some(actions) = actions_opt { - actions + let (actions_slot, column_title) = if let Some(actions) = actions_opt { + let actions = actions .apply(container) .width(Length::Fill) - .apply(Element::from) + .apply(Element::from); + let title = title.map(|title| text::title4(title).width(Length::Fill)); + (actions, title) } else { - widget::horizontal_space().apply(Element::from) + let title = title + .map(|title| text::title4(title).width(Length::Fill).apply(Element::from)) + .unwrap_or_else(|| widget::horizontal_space().apply(Element::from)); + (title, None) }; - let header_row = row::with_capacity(2) - .align_y(Alignment::Center) - .push(actions) - .push( - button::text(fl!("close")) - .trailing_icon(icon::from_name("go-next-symbolic")) - .on_press(on_close), - ); - let header_element = - header_opt.map(|el| el.apply(container).padding([space_m, 0, 0, 0])); - + let header_row = row::with_capacity(2).push(actions_slot).push( + button::text(fl!("close")) + .trailing_icon(icon::from_name("go-next-symbolic")) + .on_press(on_close), + ); let header = column::with_capacity(3) .align_x(Alignment::Center) .padding([space_m, horizontal_padding]) + .spacing(space_m) .push(header_row) - .push_maybe(title) - .push_maybe(header_element); + .push_maybe(column_title) + .push_maybe(header_opt); let footer = footer_opt.map(|element| { container(element) .align_y(Alignment::Center) From 6439507aa2d8d7e6a89c0fc016895dc0ab9252d4 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 6 Nov 2025 07:57:03 +0100 Subject: [PATCH 353/556] fix(icon): default to prefer_svg if symbolic --- src/widget/icon/named.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/widget/icon/named.rs b/src/widget/icon/named.rs index da5c4677..e1c53500 100644 --- a/src/widget/icon/named.rs +++ b/src/widget/icon/named.rs @@ -41,13 +41,14 @@ pub struct Named { impl Named { pub fn new(name: impl Into>) -> Self { let name = name.into(); + let symbolic = name.ends_with("-symbolic"); Self { - symbolic: name.ends_with("-symbolic"), + symbolic, name, fallback: Some(IconFallback::Default), size: None, scale: None, - prefer_svg: false, + prefer_svg: symbolic, } } From bb6f6e9ac8685051eb443cadcaf49e5d4a1f6410 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 10 Nov 2025 10:28:39 -0800 Subject: [PATCH 354/556] improv(cosmic-config): Remove unneeded trait bounds for subscriptions It looks like these functions where previously implemented in a different way that required these traits, but now it uses `Subscription::run_with_id`, the `id` only needs to be `Hash + 'static`. --- cosmic-config/src/subscription.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cosmic-config/src/subscription.rs b/cosmic-config/src/subscription.rs index 32f48849..45e021fe 100644 --- a/cosmic-config/src/subscription.rs +++ b/cosmic-config/src/subscription.rs @@ -18,7 +18,7 @@ pub enum ConfigUpdate { #[cold] pub fn config_subscription< - I: 'static + Copy + Send + Sync + Hash, + I: 'static + Hash, T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry, >( id: I, @@ -30,7 +30,7 @@ pub fn config_subscription< #[cold] pub fn config_state_subscription< - I: 'static + Copy + Send + Sync + Hash, + I: 'static + Hash, T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry, >( id: I, From bc744bd4e3287776d95e250db135a854ac49f056 Mon Sep 17 00:00:00 2001 From: Cheong Lau <234708519+Cheong-Lau@users.noreply.github.com> Date: Tue, 11 Nov 2025 16:18:38 +0000 Subject: [PATCH 355/556] fix(segmented_button): use less restrictive `FnOnce` for builder method over `Fn` --- src/widget/segmented_button/model/builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/segmented_button/model/builder.rs b/src/widget/segmented_button/model/builder.rs index d8070aa4..7e17f706 100644 --- a/src/widget/segmented_button/model/builder.rs +++ b/src/widget/segmented_button/model/builder.rs @@ -25,7 +25,7 @@ where #[must_use] pub fn insert( mut self, - builder: impl Fn(BuilderEntity) -> BuilderEntity, + builder: impl FnOnce(BuilderEntity) -> BuilderEntity, ) -> Self { let id = self.0.insert().id(); builder(BuilderEntity { model: self, id }).model From 2c93a4094fe331726ad0f7b80ae00a28f856543b Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 11 Nov 2025 16:51:41 +0100 Subject: [PATCH 356/556] i18n: translation updates from weblate Co-authored-by: Anonymous Co-authored-by: Feike Donia Co-authored-by: Hosted Weblate Co-authored-by: Yelysei Co-authored-by: twlvnn kraftwerk Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/bg/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/uk/ Translation: Pop OS/libcosmic --- i18n/bg/libcosmic.ftl | 20 +++++++++++++++++++- i18n/frk/libcosmic.ftl | 0 i18n/uk/libcosmic.ftl | 19 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 i18n/frk/libcosmic.ftl diff --git a/i18n/bg/libcosmic.ftl b/i18n/bg/libcosmic.ftl index 2ac4d072..ab5ffb56 100644 --- a/i18n/bg/libcosmic.ftl +++ b/i18n/bg/libcosmic.ftl @@ -1,6 +1,5 @@ # Context Drawer close = Затваряне - # About license = Лиценз links = Връзки @@ -9,3 +8,22 @@ designers = Дизайнери artists = Художници translators = Преводачи documenters = Документатори +january = Януари { $year } +february = Февруари { $year } +march = Март { $year } +april = Април { $year } +may = Май { $year } +june = Юни { $year } +july = Юли { $year } +august = Август { $year } +september = Септември { $year } +october = Октомври { $year } +november = Ноември { $year } +december = Декември { $year } +monday = Пн +tuesday = Вт +wednesday = Ср +thursday = Чт +friday = Пт +saturday = Сб +sunday = Нд diff --git a/i18n/frk/libcosmic.ftl b/i18n/frk/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/uk/libcosmic.ftl b/i18n/uk/libcosmic.ftl index cfdc14b8..73278ae4 100644 --- a/i18n/uk/libcosmic.ftl +++ b/i18n/uk/libcosmic.ftl @@ -8,3 +8,22 @@ designers = Дизайнери artists = Художники translators = Перекладачі documenters = Документатори +february = Лютий { $year } +november = Листопад { $year } +friday = Пт +tuesday = Вт +may = Травень { $year } +wednesday = Ср +april = Квітень { $year } +monday = Пн +december = Грудень { $year } +sunday = Нд +march = Березень { $year } +june = Червень { $year } +saturday = Сб +august = Серпень { $year } +july = Липень { $year } +thursday = Чт +september = Вересень { $year } +october = Жовтень { $year } +january = Січень { $year } From 2296e8e94dee2b8b2c2d658c72b96eccf37d566e Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Tue, 11 Nov 2025 15:04:09 -0500 Subject: [PATCH 357/556] feat(applets): configurable applet overlap and padding increases --- Cargo.toml | 1 + src/applet/column.rs | 508 +++++++++++++++++++++++++++++++++ src/applet/mod.rs | 139 +++++++-- src/applet/row.rs | 498 ++++++++++++++++++++++++++++++++ src/widget/color_picker/mod.rs | 50 ++-- 5 files changed, 1135 insertions(+), 61 deletions(-) create mode 100644 src/applet/column.rs create mode 100644 src/applet/row.rs diff --git a/Cargo.toml b/Cargo.toml index 16af9575..1f6dfe3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -200,6 +200,7 @@ optional = true [dependencies.cosmic-panel-config] git = "https://github.com/pop-os/cosmic-panel" +# path = "../cosmic-panel/cosmic-panel-config" optional = true [dependencies.ron] diff --git a/src/applet/column.rs b/src/applet/column.rs new file mode 100644 index 00000000..8fa2fa9f --- /dev/null +++ b/src/applet/column.rs @@ -0,0 +1,508 @@ +//! Distribute content vertically. +use crate::iced; +use iced::core::alignment::{self, Alignment}; +use iced::core::event::{self, Event}; +use iced::core::layout; +use iced::core::mouse; +use iced::core::overlay; +use iced::core::renderer; +use iced::core::widget::{Operation, Tree}; +use iced::core::{ + Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, + widget, +}; + +/// A container that distributes its contents vertically. +/// +/// # Example +/// ```no_run +/// # mod iced { pub mod widget { pub use iced_widget::*; } } +/// # pub type State = (); +/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; +/// use iced::widget::{button, column}; +/// +/// #[derive(Debug, Clone)] +/// enum Message { +/// // ... +/// } +/// +/// fn view(state: &State) -> Element<'_, Message> { +/// column![ +/// "I am on top!", +/// button("I am in the center!"), +/// "I am below.", +/// ].into() +/// } +/// ``` +#[allow(missing_debug_implementations)] +#[must_use] +pub struct Column<'a, Message, Theme = iced::Theme, Renderer = iced::Renderer> { + spacing: f32, + padding: Padding, + width: Length, + height: Length, + max_width: f32, + align: Alignment, + clip: bool, + children: Vec>, +} + +impl<'a, Message, Theme, Renderer> Column<'a, Message, Theme, Renderer> +where + Renderer: iced::core::Renderer, +{ + /// Creates an empty [`Column`]. + pub fn new() -> Self { + Self::from_vec(Vec::new()) + } + + /// Creates a [`Column`] with the given capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self::from_vec(Vec::with_capacity(capacity)) + } + + /// Creates a [`Column`] with the given elements. + pub fn with_children( + children: impl IntoIterator>, + ) -> Self { + let iterator = children.into_iter(); + + Self::with_capacity(iterator.size_hint().0).extend(iterator) + } + + /// Creates a [`Column`] from an already allocated [`Vec`]. + /// + /// Keep in mind that the [`Column`] will not inspect the [`Vec`], which means + /// it won't automatically adapt to the sizing strategy of its contents. + /// + /// If any of the children have a [`Length::Fill`] strategy, you will need to + /// call [`Column::width`] or [`Column::height`] accordingly. + pub fn from_vec(children: Vec>) -> Self { + Self { + spacing: 0.0, + padding: Padding::ZERO, + width: Length::Shrink, + height: Length::Shrink, + max_width: f32::INFINITY, + align: Alignment::Start, + clip: false, + children, + } + } + + /// Sets the vertical spacing _between_ elements. + /// + /// Custom margins per element do not exist in iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, amount: impl Into) -> Self { + self.spacing = amount.into().0; + self + } + + /// Sets the [`Padding`] of the [`Column`]. + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + /// Sets the width of the [`Column`]. + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Sets the height of the [`Column`]. + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } + + /// Sets the maximum width of the [`Column`]. + pub fn max_width(mut self, max_width: impl Into) -> Self { + self.max_width = max_width.into().0; + self + } + + /// Sets the horizontal alignment of the contents of the [`Column`] . + pub fn align_x(mut self, align: impl Into) -> Self { + self.align = Alignment::from(align.into()); + self + } + + /// Sets whether the contents of the [`Column`] should be clipped on + /// overflow. + pub fn clip(mut self, clip: bool) -> Self { + self.clip = clip; + self + } + + /// Adds an element to the [`Column`]. + pub fn push(mut self, child: impl Into>) -> Self { + let child = child.into(); + let child_size = child.as_widget().size_hint(); + + self.width = self.width.enclose(child_size.width); + self.height = self.height.enclose(child_size.height); + + self.children.push(child); + self + } + + /// Adds an element to the [`Column`], if `Some`. + #[must_use] + pub fn push_maybe( + self, + child: Option>>, + ) -> Self { + if let Some(child) = child { + self.push(child) + } else { + self + } + } + + /// Extends the [`Column`] with the given children. + pub fn extend( + self, + children: impl IntoIterator>, + ) -> Self { + children.into_iter().fold(self, Self::push) + } +} + +impl Default for Column<'_, Message, Renderer> +where + Renderer: iced::core::Renderer, +{ + fn default() -> Self { + Self::new() + } +} + +impl<'a, Message, Theme, Renderer: iced::core::Renderer> + FromIterator> for Column<'a, Message, Theme, Renderer> +{ + fn from_iter>>(iter: T) -> Self { + Self::with_children(iter) + } +} + +impl Widget + for Column<'_, Message, Theme, Renderer> +where + Renderer: iced::core::Renderer, +{ + fn children(&self) -> Vec { + self.children.iter().map(Tree::new).collect() + } + + fn state(&self) -> widget::tree::State { + widget::tree::State::new(State::default()) + } + + fn tag(&self) -> widget::tree::Tag { + widget::tree::Tag::of::() + } + + fn diff(&mut self, tree: &mut Tree) { + tree.diff_children(self.children.as_mut_slice()); + } + + fn size(&self) -> Size { + Size { + width: self.width, + height: self.height, + } + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.max_width(self.max_width); + + layout::flex::resolve( + layout::flex::Axis::Vertical, + renderer, + &limits, + self.width, + self.height, + self.padding, + self.spacing, + self.align, + &self.children, + &mut tree.children, + ) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn Operation, + ) { + operation.container(None, layout.bounds(), &mut |operation| { + self.children + .iter() + .zip(&mut tree.children) + .zip(layout.children()) + .for_each(|((child, state), c_layout)| { + child.as_widget().operate( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + operation, + ); + }); + }); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) -> event::Status { + let my_state = tree.state.downcast_mut::(); + + if let Some(hovered) = my_state.hovered { + let child_layout = layout.children().nth(hovered); + if let Some(child_layout) = child_layout + && cursor.is_over(child_layout.bounds()) + { + // if mouse event, we can skip checking other children + if let Event::Mouse(e) = &event { + if !matches!( + e, + mouse::Event::CursorLeft | mouse::Event::ButtonReleased { .. } + ) { + return self.children[hovered].as_widget_mut().on_event( + &mut tree.children[hovered], + event, + child_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } + } else if let Event::Touch(t) = &event { + if !matches!( + t, + iced::core::touch::Event::FingerLifted { .. } + | iced::core::touch::Event::FingerLost { .. } + ) { + return self.children[hovered].as_widget_mut().on_event( + &mut tree.children[hovered], + event, + child_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } + } + } else { + my_state.hovered = None; + } + } + + self.children + .iter_mut() + .enumerate() + .zip(&mut tree.children) + .zip(layout.children()) + .map(|(((i, child), state), c_layout)| { + let mut cursor_virtual = cursor; + if matches!( + event, + Event::Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered) + | Event::Touch( + iced_core::touch::Event::FingerMoved { .. } + | iced_core::touch::Event::FingerPressed { .. } + ) + ) && cursor.is_over(c_layout.bounds()) + { + my_state.hovered = Some(i); + return child.as_widget_mut().on_event( + state, + event.clone(), + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor_virtual, + renderer, + clipboard, + shell, + viewport, + ); + } else if my_state.hovered.is_some_and(|h| i != h) { + cursor_virtual = mouse::Cursor::Unavailable; + } + + child.as_widget_mut().on_event( + state, + event.clone(), + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor_virtual, + renderer, + clipboard, + shell, + viewport, + ) + }) + .fold(event::Status::Ignored, event::Status::merge) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.children + .iter() + .zip(&tree.children) + .zip(layout.children()) + .map(|((child, state), c_layout)| { + child.as_widget().mouse_interaction( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, + renderer, + ) + }) + .max() + .unwrap_or_default() + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + if let Some(clipped_viewport) = layout.bounds().intersection(viewport) { + let my_state = tree.state.downcast_ref::(); + + let viewport = if self.clip { + &clipped_viewport + } else { + viewport + }; + + for (i, ((child, state), c_layout)) in self + .children + .iter() + .zip(&tree.children) + .zip(layout.children()) + .filter(|(_, layout)| layout.bounds().intersects(viewport)) + .enumerate() + { + child.as_widget().draw( + state, + renderer, + theme, + style, + c_layout.with_virtual_offset(layout.virtual_offset()), + if my_state.hovered.is_some_and(|h| i == h) { + cursor + } else { + mouse::Cursor::Unavailable + }, + viewport, + ); + } + } + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + translation: Vector, + ) -> Option> { + overlay::from_children(&mut self.children, tree, layout, renderer, translation) + } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: Layout<'_>, + state: &Tree, + cursor: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + use iced_accessibility::A11yTree; + A11yTree::join( + self.children + .iter() + .zip(layout.children()) + .zip(state.children.iter()) + .map(|((c, c_layout), state)| { + c.as_widget().a11y_nodes( + c_layout.with_virtual_offset(layout.virtual_offset()), + state, + cursor, + ) + }), + ) + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut iced::core::clipboard::DndDestinationRectangles, + ) { + for ((e, c_layout), state) in self + .children + .iter() + .zip(layout.children()) + .zip(state.children.iter()) + { + e.as_widget().drag_destinations( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + dnd_rectangles, + ); + } + } +} + +impl<'a, Message, Theme, Renderer> From> + for Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: 'a, + Renderer: iced::core::Renderer + 'a, +{ + fn from(column: Column<'a, Message, Theme, Renderer>) -> Self { + Self::new(column) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub struct State { + hovered: Option, +} diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 659b7e92..0ab18817 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -1,13 +1,14 @@ #[cfg(feature = "applet-token")] pub mod token; +use crate::app::cosmic; use crate::{ Application, Element, Renderer, app::iced_settings, cctk::sctk, iced::{ self, Color, Length, Limits, Rectangle, - alignment::{Horizontal, Vertical}, + alignment::{Alignment, Horizontal, Vertical}, widget::Container, window, }, @@ -16,18 +17,24 @@ use crate::{ widget::{ self, autosize::{self, Autosize, autosize}, - layer_container, + column::Column, + horizontal_space, layer_container, + row::Row, + vertical_space, }, }; pub use cosmic_panel_config; use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize}; use iced_core::{Padding, Shadow}; +use iced_widget::Text; use iced_widget::runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner}; use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity}; use std::{borrow::Cow, num::NonZeroU32, rc::Rc, sync::LazyLock, time::Duration}; use tracing::info; -use crate::app::cosmic; +pub mod column; +pub mod row; + static AUTOSIZE_ID: LazyLock = LazyLock::new(|| iced::id::Id::new("cosmic-applet-autosize")); static AUTOSIZE_MAIN_ID: LazyLock = @@ -46,6 +53,8 @@ pub struct Context { /// Includes the configured size of the window. /// This can be used by apples to handle overflow themselves. pub suggested_bounds: Option, + /// Ratio of overlap for applet padding. + pub padding_overlap: f32, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -104,6 +113,10 @@ impl Default for Context { .unwrap_or(CosmicPanelBackground::ThemeDefault), output_name: std::env::var("COSMIC_PANEL_OUTPUT").unwrap_or_default(), panel_type: PanelType::from(std::env::var("COSMIC_PANEL_NAME").unwrap_or_default()), + padding_overlap: str::parse( + &std::env::var("COSMIC_PANEL_PADDING_OVERLAP").unwrap_or_default(), + ) + .unwrap_or(0.0), suggested_bounds: None, } } @@ -124,13 +137,19 @@ impl Context { #[must_use] pub fn suggested_window_size(&self) -> (NonZeroU32, NonZeroU32) { let suggested = self.suggested_size(true); - let applet_padding = self.suggested_padding(true); + let (applet_padding_major_axis, applet_padding_minor_axis) = self.suggested_padding(true); + let (horizontal_padding, vertical_padding) = if self.is_horizontal() { + (applet_padding_major_axis, applet_padding_minor_axis) + } else { + (applet_padding_minor_axis, applet_padding_major_axis) + }; + let configured_width = self .suggested_bounds .as_ref() .and_then(|c| NonZeroU32::new(c.width as u32)) // TODO: should this be physical size instead of logical? .unwrap_or_else(|| { - NonZeroU32::new(suggested.0 as u32 + applet_padding as u32 * 2).unwrap() + NonZeroU32::new(suggested.0 as u32 + horizontal_padding as u32 * 2).unwrap() }); let configured_height = self @@ -138,17 +157,20 @@ impl Context { .as_ref() .and_then(|c| NonZeroU32::new(c.height as u32)) .unwrap_or_else(|| { - NonZeroU32::new(suggested.1 as u32 + applet_padding as u32 * 2).unwrap() + NonZeroU32::new(suggested.1 as u32 + vertical_padding as u32 * 2).unwrap() }); info!("{configured_height:?}"); (configured_width, configured_height) } #[must_use] - pub fn suggested_padding(&self, is_symbolic: bool) -> u16 { + pub fn suggested_padding(&self, is_symbolic: bool) -> (u16, u16) { match &self.size { - Size::PanelSize(size) => size.get_applet_padding(is_symbolic), - Size::Hardcoded(_) => 8, + Size::PanelSize(size) => ( + size.get_applet_shrinkable_padding(is_symbolic), + size.get_applet_padding(is_symbolic), + ), + Size::Hardcoded(_) => (12, 8), } } @@ -160,9 +182,15 @@ impl Context { #[allow(clippy::cast_precision_loss)] pub fn window_settings(&self) -> crate::app::Settings { let (width, height) = self.suggested_size(true); - let applet_padding = self.suggested_padding(true); - let width = f32::from(width) + applet_padding as f32 * 2.; - let height = f32::from(height) + applet_padding as f32 * 2.; + let (applet_padding_major_axis, applet_padding_minor_axis) = self.suggested_padding(true); + let (horizontal_padding, vertical_padding) = if self.is_horizontal() { + (applet_padding_major_axis, applet_padding_minor_axis) + } else { + (applet_padding_minor_axis, applet_padding_major_axis) + }; + + let width = f32::from(width) + horizontal_padding as f32 * 2.; + let height = f32::from(height) + vertical_padding as f32 * 2.; let mut settings = crate::app::Settings::default() .size(iced_core::Size::new(width, height)) .size_limits(Limits::NONE.min_height(height).min_width(width)) @@ -187,28 +215,70 @@ impl Context { icon: widget::icon::Handle, ) -> crate::widget::Button<'a, Message> { let suggested = self.suggested_size(icon.symbolic); - let applet_padding = self.suggested_padding(icon.symbolic); - + let (applet_padding_major_axis, applet_padding_minor_axis) = self.suggested_padding(true); + let (horizontal_padding, vertical_padding) = if self.is_horizontal() { + (applet_padding_major_axis, applet_padding_minor_axis) + } else { + (applet_padding_minor_axis, applet_padding_major_axis) + }; let symbolic = icon.symbolic; + let icon = widget::icon(icon) + .class(if symbolic { + theme::Svg::Custom(Rc::new(|theme| crate::iced_widget::svg::Style { + color: Some(theme.cosmic().background.on.into()), + })) + } else { + theme::Svg::default() + }) + .width(Length::Fixed(suggested.0 as f32)) + .height(Length::Fixed(suggested.1 as f32)); + self.button_from_element(icon, symbolic) + } + pub fn button_from_element<'a, Message: Clone + 'static>( + &self, + content: impl Into>, + use_symbolic_size: bool, + ) -> crate::widget::Button<'a, Message> { + let suggested = self.suggested_size(use_symbolic_size); + let (applet_padding_major_axis, applet_padding_minor_axis) = self.suggested_padding(true); + let (horizontal_padding, vertical_padding) = if self.is_horizontal() { + (applet_padding_major_axis, applet_padding_minor_axis) + } else { + (applet_padding_minor_axis, applet_padding_major_axis) + }; + + crate::widget::button::custom(layer_container(content).center(Length::Fill)) + .width(Length::Fixed((suggested.0 + 2 * horizontal_padding) as f32)) + .height(Length::Fixed((suggested.1 + 2 * vertical_padding) as f32)) + .class(Button::AppletIcon) + } + + pub fn text_button<'a, Message: Clone + 'static>( + &self, + text: impl Into>, + message: Message, + ) -> crate::widget::Button<'a, Message> { + let text = text.into(); + let suggested = self.suggested_size(true); + + let (applet_padding_major_axis, applet_padding_minor_axis) = self.suggested_padding(true); + let (horizontal_padding, vertical_padding) = if self.is_horizontal() { + (applet_padding_major_axis, applet_padding_minor_axis) + } else { + (applet_padding_minor_axis, applet_padding_major_axis) + }; crate::widget::button::custom( layer_container( - widget::icon(icon) - .class(if symbolic { - theme::Svg::Custom(Rc::new(|theme| crate::iced_widget::svg::Style { - color: Some(theme.cosmic().background.on.into()), - })) - } else { - theme::Svg::default() - }) - .width(Length::Fixed(suggested.0 as f32)) - .height(Length::Fixed(suggested.1 as f32)), + Text::from(text) + .height(Length::Fill) + .align_y(Alignment::Center), ) - .center(Length::Fill), + .center_y(Length::Fixed(f32::from(suggested.1 + 2 * vertical_padding))), ) - .width(Length::Fixed((suggested.0 + 2 * applet_padding) as f32)) - .height(Length::Fixed((suggested.1 + 2 * applet_padding) as f32)) - .class(Button::AppletIcon) + .on_press_down(message) + .padding([0, horizontal_padding]) + .class(crate::theme::Button::AppletIcon) } pub fn icon_button<'a, Message: Clone + 'static>( @@ -345,7 +415,12 @@ impl Context { height_padding: Option, ) -> SctkPopupSettings { let (width, height) = self.suggested_size(true); - let applet_padding = self.suggested_padding(true); + let (applet_padding_major_axis, applet_padding_minor_axis) = self.suggested_padding(true); + let (horizontal_padding, vertical_padding) = if self.is_horizontal() { + (applet_padding_major_axis, applet_padding_minor_axis) + } else { + (applet_padding_minor_axis, applet_padding_major_axis) + }; let pixel_offset = 4; let (offset, anchor, gravity) = match self.anchor { PanelAnchor::Left => ((pixel_offset, 0), Anchor::Right, Gravity::Right), @@ -364,8 +439,10 @@ impl Context { anchor_rect: Rectangle { x: 0, y: 0, - width: width_padding.unwrap_or(applet_padding as i32) * 2 + i32::from(width), - height: height_padding.unwrap_or(applet_padding as i32) * 2 + i32::from(height), + width: width_padding.unwrap_or(horizontal_padding as i32) * 2 + + i32::from(width), + height: height_padding.unwrap_or(vertical_padding as i32) * 2 + + i32::from(height), }, reactive: true, constraint_adjustment: 15, // slide_y, slide_x, flip_x, flip_y diff --git a/src/applet/row.rs b/src/applet/row.rs new file mode 100644 index 00000000..b5cf851f --- /dev/null +++ b/src/applet/row.rs @@ -0,0 +1,498 @@ +//! Distribute content horizontally. +use crate::iced; +use iced::core::alignment::{self, Alignment}; +use iced::core::event::{self, Event}; +use iced::core::layout::{self, Layout}; +use iced::core::mouse; +use iced::core::overlay; +use iced::core::renderer; +use iced::core::widget::{Operation, Tree}; +use iced::core::{ + Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, widget, +}; +use iced::touch; + +/// A container that distributes its contents horizontally. +/// +/// # Example +/// ```no_run +/// # mod iced { pub mod widget { pub use iced_widget::*; } } +/// # pub type State = (); +/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; +/// use iced::widget::{button, row}; +/// +/// #[derive(Debug, Clone)] +/// enum Message { +/// // ... +/// } +/// +/// fn view(state: &State) -> Element<'_, Message> { +/// row![ +/// "I am to the left!", +/// button("I am in the middle!"), +/// "I am to the right!", +/// ].into() +/// } +/// ``` +#[allow(missing_debug_implementations)] +#[must_use] +pub struct Row<'a, Message, Theme = iced::Theme, Renderer = iced::Renderer> { + spacing: f32, + padding: Padding, + width: Length, + height: Length, + align: Alignment, + clip: bool, + children: Vec>, +} + +impl<'a, Message, Theme, Renderer> Row<'a, Message, Theme, Renderer> +where + Renderer: iced::core::Renderer, +{ + /// Creates an empty [`Row`]. + pub fn new() -> Self { + Self::from_vec(Vec::new()) + } + + /// Creates a [`Row`] with the given capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self::from_vec(Vec::with_capacity(capacity)) + } + + /// Creates a [`Row`] with the given elements. + pub fn with_children( + children: impl IntoIterator>, + ) -> Self { + let iterator = children.into_iter(); + + Self::with_capacity(iterator.size_hint().0).extend(iterator) + } + + /// Creates a [`Row`] from an already allocated [`Vec`]. + /// + /// Keep in mind that the [`Row`] will not inspect the [`Vec`], which means + /// it won't automatically adapt to the sizing strategy of its contents. + /// + /// If any of the children have a [`Length::Fill`] strategy, you will need to + /// call [`Row::width`] or [`Row::height`] accordingly. + pub fn from_vec(children: Vec>) -> Self { + Self { + spacing: 0.0, + padding: Padding::ZERO, + width: Length::Shrink, + height: Length::Shrink, + align: Alignment::Start, + clip: false, + children, + } + } + + /// Sets the horizontal spacing _between_ elements. + /// + /// Custom margins per element do not exist in iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, amount: impl Into) -> Self { + self.spacing = amount.into().0; + self + } + + /// Sets the [`Padding`] of the [`Row`]. + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + /// Sets the width of the [`Row`]. + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Sets the height of the [`Row`]. + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } + + /// Sets the vertical alignment of the contents of the [`Row`] . + pub fn align_y(mut self, align: impl Into) -> Self { + self.align = Alignment::from(align.into()); + self + } + + /// Sets whether the contents of the [`Row`] should be clipped on + /// overflow. + pub fn clip(mut self, clip: bool) -> Self { + self.clip = clip; + self + } + + /// Adds an [`Element`] to the [`Row`]. + pub fn push(mut self, child: impl Into>) -> Self { + let child = child.into(); + let child_size = child.as_widget().size_hint(); + + self.width = self.width.enclose(child_size.width); + self.height = self.height.enclose(child_size.height); + + self.children.push(child); + self + } + + /// Adds an element to the [`Row`], if `Some`. + pub fn push_maybe( + self, + child: Option>>, + ) -> Self { + if let Some(child) = child { + self.push(child) + } else { + self + } + } + + /// Extends the [`Row`] with the given children. + pub fn extend( + self, + children: impl IntoIterator>, + ) -> Self { + children.into_iter().fold(self, Self::push) + } +} + +impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer> +where + Renderer: iced::core::Renderer, +{ + fn default() -> Self { + Self::new() + } +} + +impl<'a, Message, Theme, Renderer: iced::core::Renderer> + FromIterator> for Row<'a, Message, Theme, Renderer> +{ + fn from_iter>>(iter: T) -> Self { + Self::with_children(iter) + } +} + +impl Widget + for Row<'_, Message, Theme, Renderer> +where + Renderer: iced::core::Renderer, +{ + fn children(&self) -> Vec { + self.children.iter().map(Tree::new).collect() + } + + fn state(&self) -> widget::tree::State { + widget::tree::State::new(State::default()) + } + + fn tag(&self) -> widget::tree::Tag { + widget::tree::Tag::of::() + } + + fn diff(&mut self, tree: &mut Tree) { + tree.diff_children(&mut self.children); + } + + fn size(&self) -> Size { + Size { + width: self.width, + height: self.height, + } + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + layout::flex::resolve( + layout::flex::Axis::Horizontal, + renderer, + limits, + self.width, + self.height, + self.padding, + self.spacing, + self.align, + &self.children, + &mut tree.children, + ) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn Operation, + ) { + operation.container(None, layout.bounds(), &mut |operation| { + self.children + .iter() + .zip(&mut tree.children) + .zip(layout.children()) + .for_each(|((child, state), c_layout)| { + child.as_widget().operate( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + operation, + ); + }); + }); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) -> event::Status { + let my_state = tree.state.downcast_mut::(); + + if let Some(hovered) = my_state.hovered { + let child_layout = layout.children().nth(hovered); + if let Some(child_layout) = child_layout + && cursor.is_over(child_layout.bounds()) + { + // if mouse event, we can skip checking other children + if let Event::Mouse(e) = &event { + if !matches!( + e, + mouse::Event::CursorLeft | mouse::Event::ButtonReleased { .. } + ) { + return self.children[hovered].as_widget_mut().on_event( + &mut tree.children[hovered], + event, + child_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } + } else if let Event::Touch(t) = &event { + if !matches!( + t, + iced::core::touch::Event::FingerLifted { .. } + | iced::core::touch::Event::FingerLost { .. } + ) { + return self.children[hovered].as_widget_mut().on_event( + &mut tree.children[hovered], + event, + child_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } + } + } else { + my_state.hovered = None; + } + } + + self.children + .iter_mut() + .enumerate() + .zip(&mut tree.children) + .zip(layout.children()) + .map(|(((i, child), state), c_layout)| { + let mut cursor_virtual = cursor; + + if matches!( + event, + Event::Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered) + | Event::Touch( + iced_core::touch::Event::FingerMoved { .. } + | iced_core::touch::Event::FingerPressed { .. } + ) + ) && cursor.is_over(c_layout.bounds()) + { + my_state.hovered = Some(i); + return child.as_widget_mut().on_event( + state, + event.clone(), + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor_virtual, + renderer, + clipboard, + shell, + viewport, + ); + } else if my_state.hovered.is_some_and(|h| i != h) { + cursor_virtual = mouse::Cursor::Unavailable; + } + + child.as_widget_mut().on_event( + state, + event.clone(), + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor_virtual, + renderer, + clipboard, + shell, + viewport, + ) + }) + .fold(event::Status::Ignored, event::Status::merge) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.children + .iter() + .zip(&tree.children) + .zip(layout.children()) + .map(|((child, state), c_layout)| { + child.as_widget().mouse_interaction( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, + renderer, + ) + }) + .max() + .unwrap_or_default() + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + if let Some(clipped_viewport) = layout.bounds().intersection(viewport) { + let my_state = tree.state.downcast_ref::(); + + let viewport = if self.clip { + &clipped_viewport + } else { + viewport + }; + + for (i, ((child, state), c_layout)) in self + .children + .iter() + .zip(&tree.children) + .zip(layout.children()) + .filter(|(_, layout)| layout.bounds().intersects(viewport)) + .enumerate() + { + child.as_widget().draw( + state, + renderer, + theme, + style, + c_layout.with_virtual_offset(layout.virtual_offset()), + if my_state.hovered.is_some_and(|h| i == h) { + cursor + } else { + mouse::Cursor::Unavailable + }, + viewport, + ); + } + } + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + translation: Vector, + ) -> Option> { + overlay::from_children(&mut self.children, tree, layout, renderer, translation) + } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: Layout<'_>, + state: &Tree, + cursor: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + use iced_accessibility::A11yTree; + A11yTree::join( + self.children + .iter() + .zip(layout.children()) + .zip(state.children.iter()) + .map(|((c, c_layout), state)| { + c.as_widget().a11y_nodes( + c_layout.with_virtual_offset(layout.virtual_offset()), + state, + cursor, + ) + }), + ) + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut iced::core::clipboard::DndDestinationRectangles, + ) { + for ((e, c_layout), state) in self + .children + .iter() + .zip(layout.children()) + .zip(state.children.iter()) + { + e.as_widget().drag_destinations( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + dnd_rectangles, + ); + } + } +} + +impl<'a, Message, Theme, Renderer> From> + for Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: 'a, + Renderer: iced::core::Renderer + 'a, +{ + fn from(row: Row<'a, Message, Theme, Renderer>) -> Self { + Self::new(row) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub struct State { + hovered: Option, +} diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 8dba2e1a..40a4a940 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -50,6 +50,24 @@ pub static HSV_RAINBOW: LazyLock> = LazyLock::new(|| { .collect() }); +fn hsv_rainbow(low_hue: f32, high_hue: f32) -> Vec { + let mut colors = Vec::new(); + let steps: u8 = 7; + let step_size = (high_hue - low_hue) / f32::from(steps); + for i in 0..=steps { + let hue = low_hue + step_size * f32::from(i); + colors.push(ColorStop { + color: Color::from(palette::Srgba::from_color(palette::Hsv::new_srgb_const( + RgbHue::new(hue), + 1.0, + 1.0, + ))), + offset: f32::from(i) / f32::from(steps), + }); + } + colors +} + const MAX_RECENT: usize = 20; #[derive(Debug, Clone)] @@ -290,37 +308,9 @@ where copied_to_clipboard_label: T, ) -> ColorPicker<'a, Message> { fn rail_backgrounds(hue: f32) -> (Background, Background) { - let pivot = hue * 7.0 / 360.; + let low_range = hsv_rainbow(0., hue); + let high_range = hsv_rainbow(hue, 360.); - let low_end = pivot.floor() as usize; - let high_start = pivot.ceil() as usize; - let pivot_color = palette::Hsv::new_srgb(RgbHue::new(hue), 1.0, 1.0); - let low_range = HSV_RAINBOW[0..=low_end] - .iter() - .enumerate() - .map(|(i, color)| ColorStop { - color: *color, - offset: i as f32 / pivot.max(0.0001), - }) - .chain(iter::once(ColorStop { - color: iced::Color::from(palette::Srgba::from_color(pivot_color)), - offset: 1., - })) - .collect::>(); - let high_range = iter::once(ColorStop { - color: iced::Color::from(palette::Srgba::from_color(pivot_color)), - offset: 0., - }) - .chain( - HSV_RAINBOW[high_start..] - .iter() - .enumerate() - .map(|(i, color)| ColorStop { - color: *color, - offset: (i as f32 + (1. - pivot.fract())) / (7. - pivot).max(0.0001), - }), - ) - .collect::>(); ( Background::Gradient(iced::Gradient::Linear( Linear::new(Radians(90.0)).add_stops(low_range), From 690f1d331d7fbe732837f615688f1e1ce1df81b4 Mon Sep 17 00:00:00 2001 From: Stephan Buys Date: Thu, 13 Nov 2025 17:02:12 +0200 Subject: [PATCH 358/556] feat(desktop): add DesktopEntryCache and unit tests for known problematic entries --- Cargo.toml | 9 +- src/desktop.rs | 816 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 816 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1f6dfe3d..94b53a64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -219,12 +219,7 @@ exclude = ["iced"] [workspace.dependencies] dirs = "6.0.0" +[dev-dependencies] +tempfile = "3.13.0" -[patch."https://github.com/pop-os/libcosmic"] -libcosmic = { path = "./" } -# FIXME update winit deps where necessary to use this -# [patch.crates-io] -# [patch."https://github.com/pop-os/winit.git"] -# winit = { git = "https://github.com/pop-os/winit.git//", branch = "xdg-toplevel" } -# winit = { path = "../winit" } diff --git a/src/desktop.rs b/src/desktop.rs index c9b50704..82242460 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -2,9 +2,9 @@ pub use freedesktop_desktop_entry as fde; #[cfg(not(windows))] pub use mime::Mime; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; #[cfg(not(windows))] -use std::{borrow::Cow, ffi::OsStr}; +use std::{borrow::Cow, collections::HashSet, ffi::OsStr}; pub trait IconSourceExt { fn as_cosmic_icon(&self) -> crate::widget::icon::Icon; @@ -51,6 +51,557 @@ pub struct DesktopEntryData { pub terminal: bool, } +#[cfg(not(windows))] +#[derive(Debug, Clone)] +pub struct DesktopEntryCache { + locales: Vec, + entries: Vec, +} + +#[cfg(not(windows))] +impl DesktopEntryCache { + pub fn new(locales: Vec) -> Self { + Self { + locales, + entries: Vec::new(), + } + } + + pub fn from_entries(locales: Vec, entries: Vec) -> Self { + Self { locales, entries } + } + + pub fn ensure_loaded(&mut self) { + if self.entries.is_empty() { + self.refresh(); + } + } + + pub fn refresh(&mut self) { + self.entries = fde::Iter::new(fde::default_paths()) + .filter_map(|p| fde::DesktopEntry::from_path(p, Some(&self.locales)).ok()) + .collect(); + } + + pub fn insert(&mut self, entry: fde::DesktopEntry) { + if self + .entries + .iter() + .any(|existing| existing.id() == entry.id()) + { + return; + } + + self.entries.push(entry); + } + + pub fn locales(&self) -> &[String] { + &self.locales + } + + pub fn entries(&self) -> &[fde::DesktopEntry] { + &self.entries + } + + pub fn entries_mut(&mut self) -> &mut [fde::DesktopEntry] { + &mut self.entries + } +} + +#[cfg(not(windows))] +impl Default for DesktopEntryCache { + fn default() -> Self { + Self::new(Vec::new()) + } +} + +#[cfg(not(windows))] +#[derive(Debug, Clone)] +pub struct DesktopLookupContext<'a> { + pub app_id: Cow<'a, str>, + pub identifier: Option>, + pub title: Option>, +} + +#[cfg(not(windows))] +impl<'a> DesktopLookupContext<'a> { + pub fn new(app_id: impl Into>) -> Self { + Self { + app_id: app_id.into(), + identifier: None, + title: None, + } + } + + pub fn with_identifier(mut self, identifier: impl Into>) -> Self { + self.identifier = Some(identifier.into()); + self + } + + pub fn with_title(mut self, title: impl Into>) -> Self { + self.title = Some(title.into()); + self + } +} + +#[cfg(not(windows))] +#[derive(Debug, Clone)] +pub struct DesktopResolveOptions { + pub include_no_display: bool, + pub xdg_current_desktop: Option, +} + +#[cfg(not(windows))] +impl Default for DesktopResolveOptions { + fn default() -> Self { + Self { + include_no_display: false, + xdg_current_desktop: std::env::var("XDG_CURRENT_DESKTOP").ok(), + } + } +} + +#[cfg(not(windows))] +/// Resolve a DesktopEntry for a running toplevel, applying heuristics over +/// app_id, identifier, and title. Includes Proton/Wine handling: Proton can +/// open games as `steam_app_X` (often `steam_app_default`), and Wine windows +/// may use an `.exe` app_id. In those cases we match the localized name +/// against the toplevel title and, for Proton default, restrict matches to +/// entries with `Game` in Categories. +pub fn resolve_desktop_entry( + cache: &mut DesktopEntryCache, + context: &DesktopLookupContext<'_>, + options: &DesktopResolveOptions, +) -> fde::DesktopEntry { + let app_id = fde::unicase::Ascii::new(context.app_id.as_ref()); + + if let Some(entry) = fde::find_app_by_id(cache.entries(), app_id) { + return entry.clone(); + } + + cache.refresh(); + if let Some(entry) = fde::find_app_by_id(cache.entries(), app_id) { + return entry.clone(); + } + + let candidate_ids = candidate_desktop_ids(context); + + if let Some(entry) = try_match_cached(cache.entries(), &candidate_ids) { + return entry; + } + + if let Some(entry) = load_entry_via_app_ids( + cache, + &candidate_ids, + options.include_no_display, + options.xdg_current_desktop.as_deref(), + ) { + cache.insert(entry.clone()); + return entry; + } + + if let Some(entry) = match_startup_wm_class(cache.entries(), context) { + return entry; + } + + // Chromium/CRX heuristic: scan exec/wmclass/icon for a CRX id match. + if let Some(entry) = match_crx_id(cache.entries(), context) { + return entry; + } + + if let Some(entry) = match_exec_basename(cache.entries(), &candidate_ids) { + return entry; + } + + if let Some(entry) = proton_or_wine_fallback(cache, context) { + cache.insert(entry.clone()); + entry + } else { + let fallback = fallback_entry(context); + cache.insert(fallback.clone()); + fallback + } +} + +#[cfg(not(windows))] +fn try_match_cached( + entries: &[fde::DesktopEntry], + candidate_ids: &[String], +) -> Option { + candidate_ids.iter().find_map(|candidate| { + fde::find_app_by_id(entries, fde::unicase::Ascii::new(candidate.as_str())).cloned() + }) +} + +#[cfg(not(windows))] +fn load_entry_via_app_ids( + cache: &DesktopEntryCache, + candidate_ids: &[String], + include_no_display: bool, + xdg_current_desktop: Option<&str>, +) -> Option { + if candidate_ids.is_empty() { + return None; + } + + let candidate_refs: Vec<&str> = candidate_ids.iter().map(String::as_str).collect(); + let locales = cache.locales().to_vec(); + let iter_locales = locales.clone(); + + let desktop_iter = fde::Iter::new(fde::default_paths()) + .filter_map(move |path| fde::DesktopEntry::from_path(path, Some(&iter_locales)).ok()); + + let app_iter = load_applications_for_app_ids( + desktop_iter, + &locales, + candidate_refs, + false, + include_no_display, + xdg_current_desktop, + ); + + let locales_for_load = cache.locales().to_vec(); + for app in app_iter { + if let Some(path) = app.path { + if let Ok(entry) = fde::DesktopEntry::from_path(path, Some(&locales_for_load)) { + return Some(entry); + } + } + } + + None +} + +#[cfg(not(windows))] +fn match_startup_wm_class( + entries: &[fde::DesktopEntry], + context: &DesktopLookupContext<'_>, +) -> Option { + let mut candidates = Vec::new(); + candidates.push(context.app_id.as_ref()); + if let Some(identifier) = context.identifier.as_deref() { + candidates.push(identifier); + } + if let Some(title) = context.title.as_deref() { + candidates.push(title); + } + + for entry in entries { + let Some(wm_class) = entry.startup_wm_class() else { + continue; + }; + + if candidates + .iter() + .any(|candidate| candidate.eq_ignore_ascii_case(wm_class)) + { + return Some(entry.clone()); + } + } + + None +} + +#[cfg(not(windows))] +fn is_crx_id(candidate: &str) -> bool { + is_crx_bytes(candidate.as_bytes()) +} + +#[cfg(not(windows))] +fn is_crx_bytes(bytes: &[u8]) -> bool { + bytes.len() == 32 && bytes.iter().all(|b| matches!(b, b'a'..=b'p')) +} + +#[cfg(not(windows))] +pub fn extract_crx_id(value: &str) -> Option { + if let Some(rest) = value.strip_prefix("chrome-") { + if let Some(first) = rest.split(&['-', '_'][..]).next() { + if is_crx_id(first) { + return Some(first.to_string()); + } + } + } + if let Some(rest) = value.strip_prefix("crx_") { + let token = rest + .split(|c: char| !c.is_ascii_lowercase()) + .next() + .unwrap_or(rest); + if is_crx_id(token) { + return Some(token.to_string()); + } + } + if is_crx_id(value) { + return Some(value.to_string()); + } + + for window in value.as_bytes().windows(32) { + if is_crx_bytes(window) { + // SAFETY: `is_crx_bytes` guarantees the window is ASCII. + let slice = std::str::from_utf8(window).expect("ASCII window"); + return Some(slice.to_string()); + } + } + + None +} + +#[cfg(not(windows))] +fn match_crx_id( + entries: &[fde::DesktopEntry], + context: &DesktopLookupContext<'_>, +) -> Option { + let crx = extract_crx_id(context.app_id.as_ref()) + .or_else(|| context.identifier.as_deref().and_then(extract_crx_id))?; + + for entry in entries { + if let Some(exec) = entry.exec() { + if exec.contains(&format!("--app-id={}", crx)) { + return Some(entry.clone()); + } + } + if let Some(wm) = entry.startup_wm_class() { + if wm.eq_ignore_ascii_case(&format!("crx_{}", crx)) { + return Some(entry.clone()); + } + } + if let Some(icon) = entry.icon() { + if icon.contains(&crx) { + return Some(entry.clone()); + } + } + } + + None +} + +#[cfg(not(windows))] +fn match_exec_basename( + entries: &[fde::DesktopEntry], + candidate_ids: &[String], +) -> Option { + fn normalize_candidate(candidate: &str) -> String { + candidate + .trim_matches(|c: char| c == '"' || c == '\'') + .to_ascii_lowercase() + } + + let mut normalized: Vec = candidate_ids + .iter() + .map(|c| normalize_candidate(c)) + .collect(); + normalized.retain(|c| !c.is_empty()); + + for entry in entries { + let Some(exec) = entry.exec() else { + continue; + }; + + let command = exec + .split_whitespace() + .next() + .map(|token| token.trim_matches(|c: char| c == '"' || c == '\'')) + .filter(|token| !token.is_empty()); + + let Some(command) = command else { + continue; + }; + + let command = Path::new(command); + let basename = command + .file_stem() + .or_else(|| command.file_name()) + .and_then(|s| s.to_str()); + + let Some(basename) = basename else { + continue; + }; + + let basename_lower = basename.to_ascii_lowercase(); + + if normalized + .iter() + .any(|candidate| candidate == &basename_lower) + { + return Some(entry.clone()); + } + } + + None +} + +#[cfg(not(windows))] +fn fallback_entry(context: &DesktopLookupContext<'_>) -> fde::DesktopEntry { + let mut entry = fde::DesktopEntry { + appid: context.app_id.to_string(), + groups: Default::default(), + path: Default::default(), + ubuntu_gettext_domain: None, + }; + + let name = context + .title + .as_ref() + .map(|title| title.to_string()) + .unwrap_or_else(|| context.app_id.to_string()); + entry.add_desktop_entry("Name".to_string(), name); + entry +} + +#[cfg(not(windows))] +// proton opens games as steam_app_X, where X is either the steam appid or +// "default". Games with a steam appid can have a desktop entry generated +// elsewhere; this specifically handles non-steam games opened under Proton. +// In addition, try to match WINE entries whose app_id is the full name of +// the executable (including `.exe`). +fn proton_or_wine_fallback( + cache: &DesktopEntryCache, + context: &DesktopLookupContext<'_>, +) -> Option { + let app_id = context.app_id.as_ref(); + let is_proton_game = app_id == "steam_app_default"; + let is_wine_entry = app_id.ends_with(".exe"); + + if !is_proton_game && !is_wine_entry { + return None; + } + + let title = context.title.as_deref()?; + + for entry in cache.entries() { + let localized_name_matches = entry + .name(cache.locales()) + .is_some_and(|name| name == title); + + if !localized_name_matches { + continue; + } + + if is_proton_game && !entry.categories().unwrap_or_default().contains(&"Game") { + continue; + } + + return Some(entry.clone()); + } + + None +} + +#[cfg(not(windows))] +fn candidate_desktop_ids(context: &DesktopLookupContext<'_>) -> Vec { + const SUFFIXES: &[&str] = &[".desktop", ".Desktop", ".DESKTOP"]; + let mut ordered = Vec::new(); + let mut seen = HashSet::new(); + + fn push_candidate(seen: &mut HashSet, ordered: &mut Vec, candidate: &str) { + let trimmed = candidate.trim(); + if trimmed.is_empty() { + return; + } + + let key = trimmed.to_ascii_lowercase(); + if seen.insert(key) { + ordered.push(trimmed.to_string()); + } + } + + fn add_variants( + seen: &mut HashSet, + ordered: &mut Vec, + value: Option<&str>, + suffixes: &[&str], + ) { + let Some(value) = value else { + return; + }; + + let stripped_quotes = value.trim_matches(|c: char| c == '"' || c == '\''); + let trimmed = stripped_quotes.trim(); + if trimmed.is_empty() { + return; + } + + push_candidate(seen, ordered, trimmed); + if stripped_quotes != trimmed { + push_candidate(seen, ordered, stripped_quotes.trim()); + } + + for suffix in suffixes { + if trimmed.ends_with(suffix) { + let cut = &trimmed[..trimmed.len() - suffix.len()]; + push_candidate(seen, ordered, cut); + } + } + + if trimmed.contains('.') { + if let Some(last) = trimmed.rsplit('.').next() { + if last.len() >= 2 { + push_candidate(seen, ordered, last); + } + } + } + + if trimmed.contains('-') { + push_candidate(seen, ordered, &trimmed.replace('-', "_")); + } + if trimmed.contains('_') { + push_candidate(seen, ordered, &trimmed.replace('_', "-")); + } + + for token in trimmed.split(|c: char| matches!(c, '.' | '-' | '_' | '@' | ' ')) { + if token.len() >= 2 && token != trimmed { + push_candidate(seen, ordered, token); + } + } + } + + add_variants( + &mut seen, + &mut ordered, + Some(context.app_id.as_ref()), + SUFFIXES, + ); + add_variants( + &mut seen, + &mut ordered, + context.identifier.as_deref(), + SUFFIXES, + ); + add_variants(&mut seen, &mut ordered, context.title.as_deref(), &[]); + + // Chromium/Chrome PWA heuristics: favorites may store a short id like + // "chrome--Default" while the actual desktop id is + // "org.chromium.Chromium.flextop.chrome--Default" (Flatpak Chromium) + // or sometimes "org.chromium.Chromium.chrome--Default". Expand those + // candidates so we can match cached entries. + if let Some(app_id) = Some(context.app_id.as_ref()) { + if let Some(rest) = app_id.strip_prefix("chrome-") { + if rest.ends_with("-Default") { + let crx = rest.trim_end_matches("-Default"); + let variants = [ + format!("org.chromium.Chromium.flextop.chrome-{}-Default", crx), + format!("org.chromium.Chromium.chrome-{}-Default", crx), + ]; + for v in variants { + push_candidate(&mut seen, &mut ordered, &v); + } + } + } + if let Some(rest) = app_id.strip_prefix("crx_") { + // Older identifiers may be crx_; expand similarly + let crx = rest; + let variants = [ + format!("org.chromium.Chromium.flextop.chrome-{}-Default", crx), + format!("org.chromium.Chromium.chrome-{}-Default", crx), + ]; + for v in variants { + push_candidate(&mut seen, &mut ordered, &v); + } + } + } + + ordered +} + #[cfg(not(windows))] pub fn load_applications<'a>( locales: &'a [String], @@ -315,3 +866,264 @@ trait SystemdManger { aux: &[(String, Vec<(String, zbus::zvariant::OwnedValue)>)], ) -> zbus::Result; } + +#[cfg(all(test, not(windows)))] +mod tests { + use super::*; + use std::{env, fs, path::Path, path::PathBuf}; + use tempfile::tempdir; + + struct EnvVarGuard { + key: &'static str, + original: Option, + } + + impl EnvVarGuard { + fn set(key: &'static str, value: &Path) -> Self { + let original = env::var(key).ok(); + std::env::set_var(key, value); + Self { key, original } + } + } + + impl Drop for EnvVarGuard { + fn drop(&mut self) { + if let Some(ref original) = self.original { + std::env::set_var(self.key, original); + } else { + std::env::remove_var(self.key); + } + } + } + + fn load_entry(file_name: &str, contents: &str, locales: &[String]) -> fde::DesktopEntry { + let temp = tempdir().expect("tempdir"); + let path = temp.path().join(file_name); + fs::write(&path, contents).expect("write desktop file"); + let entry = fde::DesktopEntry::from_path(path, Some(locales)).expect("load desktop file"); + // Ensure directory stays alive until after parsing + temp.close().expect("close tempdir"); + entry + } + + #[test] + fn candidate_generation_covers_common_variants() { + let ctx = DesktopLookupContext::new("com.example.App.desktop") + .with_identifier("com-example-App") + .with_title("Example App"); + let candidates = candidate_desktop_ids(&ctx); + + assert_eq!(candidates.first().unwrap(), "com.example.App.desktop"); + assert!(candidates.contains(&"com.example.App".to_string())); + assert!(candidates.contains(&"com-example-App".to_string())); + assert!(candidates.contains(&"com_example_App".to_string())); + assert!(candidates.contains(&"Example App".to_string())); + assert!(candidates.contains(&"Example".to_string())); + assert!(candidates.contains(&"App".to_string())); + } + + #[test] + fn startup_wm_class_matching_detects_flatpak_chrome_apps() { + let temp = tempdir().expect("tempdir"); + let apps_dir = temp.path().join("applications"); + fs::create_dir_all(&apps_dir).expect("create applications dir"); + + let desktop_contents = "\ +[Desktop Entry] +Version=1.0 +Type=Application +Name=Proton Mail +Exec=chromium --app-id=jnpecgipniidlgicjocehkhajgdnjekh +Icon=chrome-jnpecgipniidlgicjocehkhajgdnjekh-Default +StartupWMClass=crx_jnpecgipniidlgicjocehkhajgdnjekh +"; + let desktop_path = apps_dir.join( + "org.chromium.Chromium.flextop.chrome-jnpecgipniidlgicjocehkhajgdnjekh-Default.desktop", + ); + fs::write(desktop_path, desktop_contents).expect("write desktop file"); + + let _guard = EnvVarGuard::set("XDG_DATA_HOME", temp.path()); + + let locales = vec!["en_US.UTF-8".to_string()]; + let mut cache = DesktopEntryCache::new(locales.clone()); + cache.refresh(); + + let ctx = DesktopLookupContext::new("crx_jnpecgipniidlgicjocehkhajgdnjekh"); + let resolved = resolve_desktop_entry(&mut cache, &ctx, &DesktopResolveOptions::default()); + + assert_eq!( + resolved.id(), + "org.chromium.Chromium.flextop.chrome-jnpecgipniidlgicjocehkhajgdnjekh-Default" + ); + } + + #[test] + fn exec_basename_matching_handles_vmware() { + let temp = tempdir().expect("tempdir"); + let apps_dir = temp.path().join("applications"); + fs::create_dir_all(&apps_dir).expect("create applications dir"); + + let desktop_contents = "\ +[Desktop Entry]\n\ +Version=1.0\n\ +Type=Application\n\ +Name=VMware Workstation\n\ +Exec=/usr/bin/vmware %U\n\ +Icon=vmware-workstation\n\ +"; + let desktop_path = apps_dir.join("vmware-workstation.desktop"); + fs::write(desktop_path, desktop_contents).expect("write desktop file"); + + let _guard = EnvVarGuard::set("XDG_DATA_HOME", temp.path()); + + let locales = vec!["en_US.UTF-8".to_string()]; + let mut cache = DesktopEntryCache::new(locales.clone()); + cache.refresh(); + + let ctx = DesktopLookupContext::new("vmware").with_title("Library — VMware Workstation"); + + let resolved = resolve_desktop_entry(&mut cache, &ctx, &DesktopResolveOptions::default()); + + assert_eq!(resolved.id(), "vmware-workstation.desktop"); + } + + #[test] + fn proton_fallback_prefers_game_entries() { + let locales = vec!["en_US.UTF-8".to_string()]; + let entry = load_entry( + "proton.desktop", + "[Desktop Entry]\nType=Application\nName=Proton Game\nCategories=Game;Utility;\nExec=proton-game\n", + &locales, + ); + let cache = DesktopEntryCache::from_entries(locales.clone(), vec![entry]); + let ctx = DesktopLookupContext::new("steam_app_default").with_title("Proton Game"); + + let resolved = proton_or_wine_fallback(&cache, &ctx).expect("expected proton match"); + let name = resolved + .name(&locales) + .expect("name available") + .into_owned(); + + assert_eq!(name, "Proton Game"); + } + + #[test] + fn proton_fallback_skips_non_games() { + let locales = vec!["en_US.UTF-8".to_string()]; + let entry = load_entry( + "tool.desktop", + "[Desktop Entry]\nType=Application\nName=Proton Tool\nCategories=Utility;\nExec=proton-tool\n", + &locales, + ); + let cache = DesktopEntryCache::from_entries(locales, vec![entry]); + let ctx = DesktopLookupContext::new("steam_app_default").with_title("Proton Tool"); + + assert!(proton_or_wine_fallback(&cache, &ctx).is_none()); + } + + #[test] + fn wine_fallback_matches_executable_titles() { + let locales = vec!["en_US.UTF-8".to_string()]; + let entry = load_entry( + "wine.desktop", + "[Desktop Entry]\nType=Application\nName=Wine Game\nExec=wine-game\n", + &locales, + ); + let cache = DesktopEntryCache::from_entries(locales.clone(), vec![entry]); + let ctx = DesktopLookupContext::new("WINEGAME.EXE").with_title("Wine Game"); + + let resolved = proton_or_wine_fallback(&cache, &ctx).expect("expected wine match"); + let name = resolved + .name(&locales) + .expect("name available") + .into_owned(); + assert_eq!(name, "Wine Game"); + } + + #[test] + fn fallback_entry_uses_title_when_available() { + let ctx = DesktopLookupContext::new("unknown-app").with_title("Unknown App"); + let entry = fallback_entry(&ctx); + + assert_eq!(entry.id(), "unknown-app"); + assert_eq!( + entry.name(&["en_US".to_string()]), + Some(Cow::Owned("Unknown App".to_string())) + ); + } + + #[test] + fn desktop_entry_data_prefers_localized_name() { + let locales = vec!["fr".to_string(), "en_US".to_string()]; + let entry = load_entry( + "localized.desktop", + "[Desktop Entry]\nType=Application\nName=Default\nName[fr]=Localisé\nExec=localized\n", + &locales, + ); + let data = DesktopEntryData::from_desktop_entry(&locales, entry); + + assert_eq!(data.name, "Localisé"); + } + + #[test] + fn crx_id_extraction_variants() { + let id = "cadlkienfkclaiaibeoongdcgmdikeeg"; // 32 chars a..p + assert_eq!( + super::extract_crx_id(&format!("chrome-{}-Default", id)), + Some(id.to_string()) + ); + assert_eq!( + super::extract_crx_id(&format!("crx_{}", id)), + Some(id.to_string()) + ); + assert_eq!(super::extract_crx_id(id), Some(id.to_string())); + // Embedded + let embedded = format!("org.chromium.Chromium.flextop.chrome-{}-Default", id); + assert_eq!(super::extract_crx_id(&embedded), Some(id.to_string())); + } + + #[test] + fn crx_matcher_by_exec_and_wmclass() { + use std::fs; + let id = "cadlkienfkclaiaibeoongdcgmdikeeg"; + let temp = tempdir().expect("tempdir"); + let apps_dir = temp.path().join("applications"); + fs::create_dir_all(&apps_dir).expect("create applications dir"); + let desktop_contents = format!( + "[Desktop Entry]\nType=Application\nName=ChatGPT\nExec=chromium --app-id={} --profile-directory=Default\nStartupWMClass=crx_{}\nIcon=chrome-{}-Default\n", + id, id, id + ); + let desktop_path = apps_dir.join( + "org.chromium.Chromium.flextop.chrome-cadlkienfkclaiaibeoongdcgmdikeeg-Default.desktop", + ); + fs::write(&desktop_path, desktop_contents).expect("write desktop file"); + + let _guard = EnvVarGuard::set("XDG_DATA_HOME", temp.path()); + let locales = vec!["en_US.UTF-8".to_string()]; + let mut cache = DesktopEntryCache::new(locales.clone()); + cache.refresh(); + + let short_id = format!("chrome-{}-Default", id); + let ctx = DesktopLookupContext::new(short_id); + let resolved = resolve_desktop_entry(&mut cache, &ctx, &DesktopResolveOptions::default()); + assert!(resolved.icon().is_some()); + assert!(resolved.exec().is_some()); + assert_eq!(resolved.startup_wm_class(), Some(&format!("crx_{}", id))); + } + + #[test] + fn crx_extraction_handles_utf8_prefixes() { + let id = "cadlkienfkclaiaibeoongdcgmdikeeg"; + let prefixed = format!("å{}", id); + assert_eq!(super::extract_crx_id(&prefixed), Some(id.to_string())); + } + + #[test] + fn crx_extraction_ignores_non_ascii_sequences() { + let id = "cadlkienfkclaiaibeoongdcgmdikeeg"; + let embedded = format!("{id}æøå"); + + assert_eq!(super::extract_crx_id(&embedded), Some(id.to_string())); + assert_eq!(super::extract_crx_id("æøå"), None); + } +} From d6b3720e1f161f064a586f4317422e78cbb60214 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Thu, 13 Nov 2025 17:51:26 +0100 Subject: [PATCH 359/556] i18n: translation updates from weblate Co-authored-by: Anonymous Co-authored-by: Hosted Weblate Co-authored-by: therealmate Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/hu/ Translation: Pop OS/libcosmic --- i18n/hu/libcosmic.ftl | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/i18n/hu/libcosmic.ftl b/i18n/hu/libcosmic.ftl index ddc43e6c..583fbe5c 100644 --- a/i18n/hu/libcosmic.ftl +++ b/i18n/hu/libcosmic.ftl @@ -1,6 +1,5 @@ # Context Drawer close = Bezárás - # About license = Licenc links = Linkek @@ -9,3 +8,22 @@ designers = Tervezők artists = Művészek translators = Fordítók documenters = Dokumentálók +january = { $year } január +february = { $year } február +march = { $year } március +april = { $year } április +may = { $year } május +june = { $year } június +july = { $year } július +august = { $year } augusztus +september = { $year } szeptember +october = { $year } október +november = { $year } november +december = { $year } december +monday = H +tuesday = K +wednesday = Sze +thursday = Cs +friday = P +saturday = Szo +sunday = V From 96a51be3e4e0ae2de4af223f3dda75f321973a3e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 14 Nov 2025 11:46:21 -0500 Subject: [PATCH 360/556] chore: update iced image improvements --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 783d764c..16b1f1f3 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 783d764cabd6eee020eeae3b50a0d4727a721056 +Subproject commit 16b1f1f3a2c1ed09e5830724e84bbe5ad6909216 From 16d095b2cdf3696718b1da87a83d8679fbee01a0 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 14 Nov 2025 15:00:01 -0500 Subject: [PATCH 361/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 16b1f1f3..b788625a 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 16b1f1f3a2c1ed09e5830724e84bbe5ad6909216 +Subproject commit b788625a353593daea8ef64e9fec58f199ae08d8 From 85284773554d6fcf4f5e9676eb04deb0da472328 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 17 Nov 2025 21:51:23 +0100 Subject: [PATCH 362/556] i18n: translation updates from weblate Co-authored-by: Feike Donia Co-authored-by: GerardWassink Co-authored-by: Hosted Weblate Co-authored-by: Julien Brouillard Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/fr/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/nl/ Translation: Pop OS/libcosmic --- i18n/fr/libcosmic.ftl | 27 +++++++++++++++++++++++++++ i18n/nl/libcosmic.ftl | 20 ++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/i18n/fr/libcosmic.ftl b/i18n/fr/libcosmic.ftl index e69de29b..43e2d6f7 100644 --- a/i18n/fr/libcosmic.ftl +++ b/i18n/fr/libcosmic.ftl @@ -0,0 +1,27 @@ +close = Fermer +documenters = Rédacteurs +translators = Traducteurs +artists = Artistes +license = Licence +links = Liens +developers = Développeurs +january = Janvier { $year } +february = Février { $year } +april = Avril { $year } +march = Mars { $year } +november = Novembre { $year } +friday = Ven +tuesday = Mar +may = Mai { $year } +wednesday = Mer +monday = Lun +december = Décembre { $year } +sunday = Dim +june = Juin { $year } +saturday = Sam +august = Août { $year } +july = Juillet { $year } +thursday = Jeu +september = Septembre { $year } +october = Octobre { $year } +designers = Designers diff --git a/i18n/nl/libcosmic.ftl b/i18n/nl/libcosmic.ftl index b0aafeba..75fc8cdf 100644 --- a/i18n/nl/libcosmic.ftl +++ b/i18n/nl/libcosmic.ftl @@ -1 +1,21 @@ close = Sluiten +license = Licentie +january = Januari { $year } +february = Februari { $year } +march = Maart { $year } +april = April { $year } +may = Mei { $year } +june = Juni { $year } +july = Juli { $year } +august = Augustus { $year } +september = September { $year } +october = Oktober { $year } +november = November { $year } +december = December { $year } +monday = Ma +tuesday = Di +wednesday = Woe +thursday = Do +friday = Vrij +saturday = Za +sunday = Zon From 47cc6dbdbf35b214bf8bf1deaf6b523ea0fe7f93 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 18 Nov 2025 10:36:11 -0500 Subject: [PATCH 363/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index b788625a..d2949f2d 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit b788625a353593daea8ef64e9fec58f199ae08d8 +Subproject commit d2949f2dbbb60c65a48e941e89c36563a5cde3a6 From 7eecbe30d78b1d4f959429ea233b294900af4eed Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 18 Nov 2025 18:35:27 +0100 Subject: [PATCH 364/556] feat(dropdown): add `Id` support with custom `close`, `open` operations --- src/widget/dropdown/mod.rs | 14 +- src/widget/dropdown/operation.rs | 72 ++++++++++ src/widget/dropdown/widget.rs | 238 ++++++++++++++++++++----------- 3 files changed, 236 insertions(+), 88 deletions(-) create mode 100644 src/widget/dropdown/operation.rs diff --git a/src/widget/dropdown/mod.rs b/src/widget/dropdown/mod.rs index bcb37af8..fa4184c4 100644 --- a/src/widget/dropdown/mod.rs +++ b/src/widget/dropdown/mod.rs @@ -7,15 +7,17 @@ use std::borrow::Cow; pub mod menu; -use iced_core::window; pub use menu::Menu; pub mod multi; +pub mod operation; mod widget; pub use widget::*; use crate::surface; +pub use iced_core::widget::Id; +use iced_core::window; /// Displays a list of options in a popover menu on select. pub fn dropdown< @@ -53,3 +55,13 @@ pub fn popup_dropdown< dropdown } + +/// Produces a [`Task`] that closes the [`Dropdown`]. +pub fn close(id: Id) -> iced_runtime::Task { + iced_runtime::task::effect(iced_runtime::Action::Widget(Box::new(operation::close(id)))) +} + +/// Produces a [`Task`] that opens the [`Dropdown`]. +pub fn open(id: Id) -> iced_runtime::Task { + iced_runtime::task::effect(iced_runtime::Action::Widget(Box::new(operation::open(id)))) +} diff --git a/src/widget/dropdown/operation.rs b/src/widget/dropdown/operation.rs new file mode 100644 index 00000000..8cea4566 --- /dev/null +++ b/src/widget/dropdown/operation.rs @@ -0,0 +1,72 @@ +// Copyright 2025 System76 +// SPDX-License-Identifier: MPL-2.0 AND MIT +//! Operate on dropdown widgets. + +use super::State; +use iced::Rectangle; +use iced_core::widget::{Id, Operation}; + +pub trait Dropdown { + fn close(&mut self); + fn open(&mut self); +} + +/// Produces a [`Task`] that closes a [`Dropdown`] popup. +pub fn close(id: Id) -> impl Operation { + struct Close(Id); + + impl Operation for Close { + fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) { + if id.map_or(true, |id| id != &self.0) { + return; + } + + let Some(state) = state.downcast_mut::() else { + return; + }; + + state.close(); + } + + fn container( + &mut self, + _id: Option<&Id>, + _bounds: Rectangle, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + } + + Close(id) +} + +/// Produces a [`Task`] that opens a [`Dropdown`] popup. +pub fn open(id: Id) -> impl Operation { + struct Open(Id); + + impl Operation for Open { + fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) { + if id.map_or(true, |id| id != &self.0) { + return; + } + + let Some(state) = state.downcast_mut::() else { + return; + }; + + state.open(); + } + + fn container( + &mut self, + _id: Option<&Id>, + _bounds: Rectangle, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + } + + Open(id) +} diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index d196215d..47df9b89 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -2,6 +2,7 @@ // Copyright 2019 Héctor Ramón, Iced contributors // SPDX-License-Identifier: MPL-2.0 AND MIT +use super::Id; use super::menu::{self, Menu}; use crate::widget::icon::{self, Handle}; use crate::{Element, surface}; @@ -18,19 +19,21 @@ use iced_widget::pick_list::{self, Catalog}; use std::borrow::Cow; use std::ffi::OsStr; use std::hash::{DefaultHasher, Hash, Hasher}; -use std::marker::PhantomData; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, LazyLock, Mutex}; pub type DropdownView = Arc Element<'static, Message> + Send + Sync>; static AUTOSIZE_ID: LazyLock = LazyLock::new(|| crate::widget::Id::new("cosmic-applet-autosize")); + /// A widget for selecting a single value from a list of selections. #[derive(Setters)] pub struct Dropdown<'a, S: AsRef + Send + Sync + Clone + 'static, Message, AppMessage> where [S]: std::borrow::ToOwned, { + #[setters(skip)] + id: Option, #[setters(skip)] on_selected: Arc Message + Send + Sync>, #[setters(skip)] @@ -78,6 +81,7 @@ where on_selected: impl Fn(usize) -> Message + 'static + Send + Sync, ) -> Self { Self { + id: None, on_selected: Arc::new(on_selected), selections, icons: Cow::Borrowed(&[]), @@ -100,12 +104,13 @@ where /// Handle dropdown requests for popup creation. /// Intended to be used with [`crate::app::message::get_popup`] pub fn with_popup( - mut self, + self, parent_id: window::Id, on_surface_action: impl Fn(surface::Action) -> Message + Send + Sync + 'static, action_map: impl Fn(Message) -> NewAppMessage + Send + Sync + 'static, ) -> Dropdown<'a, S, Message, NewAppMessage> { let Self { + id, on_selected, selections, icons, @@ -121,6 +126,7 @@ where } = self; Dropdown::<'a, S, Message, NewAppMessage> { + id, on_selected, selections, icons, @@ -138,6 +144,11 @@ where } } + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } + #[cfg(all(feature = "winit", feature = "wayland"))] pub fn with_positioner( mut self, @@ -299,6 +310,17 @@ where ); } + fn operate( + &self, + tree: &mut Tree, + _layout: Layout<'_>, + _renderer: &crate::Renderer, + operation: &mut dyn iced_core::widget::Operation, + ) { + let state = tree.state.downcast_mut::(); + operation.custom(state, self.id.as_ref()); + } + fn overlay<'b>( &'b mut self, tree: &'b mut Tree, @@ -364,6 +386,8 @@ pub struct State { menu: menu::State, keyboard_modifiers: keyboard::Modifiers, is_open: Arc, + close_operation: bool, + open_operation: bool, hovered_option: Arc>>, hashes: Vec, selections: Vec, @@ -389,6 +413,8 @@ impl State { selections: Vec::new(), hashes: Vec::new(), popup_id: window::Id::unique(), + close_operation: false, + open_operation: false, } } } @@ -399,6 +425,16 @@ impl Default for State { } } +impl super::operation::Dropdown for State { + fn close(&mut self) { + self.close_operation = true; + } + + fn open(&mut self) { + self.open_operation = true; + } +} + /// Computes the layout of a [`Dropdown`]. #[allow(clippy::too_many_arguments)] pub fn layout( @@ -484,10 +520,121 @@ pub fn update< font: Option, selected_option: Option, ) -> event::Status { + let state = state(); + + let open = |shell: &mut Shell<'_, Message>, + state: &mut State, + on_selected: Arc Message + Send + Sync + 'static>| { + state.is_open.store(true, Ordering::Relaxed); + let mut hovered_guard = state.hovered_option.lock().unwrap(); + *hovered_guard = selected; + let id = window::Id::unique(); + state.popup_id = id; + #[cfg(all(feature = "winit", feature = "wayland"))] + if let Some(((on_surface_action, parent), action_map)) = on_surface_action + .as_ref() + .zip(_window_id) + .zip(action_map.clone()) + { + use iced_runtime::platform_specific::wayland::popup::{ + SctkPopupSettings, SctkPositioner, + }; + let bounds = layout.bounds(); + let anchor_rect = Rectangle { + x: bounds.x as i32, + y: bounds.y as i32, + width: bounds.width as i32, + height: bounds.height as i32, + }; + let icon_width = if icons.is_empty() { 0.0 } else { 24.0 }; + let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 { + selection_paragraph.min_width().round() + }; + let pad_width = padding.horizontal().mul_add(2.0, 16.0); + + let selections_width = selections + .iter() + .zip(state.selections.iter_mut()) + .map(|(label, selection)| measure(label.as_ref(), selection.raw())) + .fold(0.0, |next, current| current.max(next)); + + let icons: Cow<'static, [Handle]> = Cow::Owned(icons.to_vec()); + let selections: Cow<'static, [S]> = Cow::Owned(selections.to_vec()); + let state = state.clone(); + let on_close = surface::action::destroy_popup(id); + let on_surface_action_clone = on_surface_action.clone(); + let translation = layout.virtual_offset(); + let get_popup_action = surface::action::simple_popup::( + move || { + SctkPopupSettings { + parent, + id, + input_zone: None, + positioner: SctkPositioner { + size: Some((selections_width as u32 + gap as u32 + pad_width as u32 + icon_width as u32, 10)), + anchor_rect, + // TODO: left or right alignment based on direction? + anchor: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::BottomLeft, + gravity: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight, + reactive: true, + offset: ((-padding.left - translation.x) as i32, -translation.y as i32), + constraint_adjustment: 9, + ..Default::default() + }, + parent_size: None, + grab: true, + close_with_children: true, + } + }, + Some(Box::new(move || { + let action_map = action_map.clone(); + let on_selected = on_selected.clone(); + let e: Element<'static, crate::Action> = + Element::from(menu_widget( + bounds, + &state, + gap, + padding, + text_size.unwrap_or(14.0), + selections.clone(), + icons.clone(), + selected_option, + Arc::new(move |i| on_selected.clone()(i)), + Some(on_surface_action_clone(on_close.clone())), + )) + .map(move |m| crate::Action::App(action_map.clone()(m))); + e + })), + ); + shell.publish(on_surface_action(get_popup_action)); + } + }; + + let is_open = state.is_open.load(Ordering::Relaxed); + let refresh = state.close_operation && state.open_operation; + + if state.close_operation { + state.close_operation = false; + state.is_open.store(false, Ordering::SeqCst); + if is_open { + #[cfg(all(feature = "winit", feature = "wayland"))] + if let Some(ref on_close) = on_surface_action { + shell.publish(on_close(surface::action::destroy_popup(state.popup_id))); + } + } + } + + if state.open_operation { + state.open_operation = false; + state.is_open.store(true, Ordering::SeqCst); + if (refresh && is_open) || (!refresh && !is_open) { + open(shell, state, on_selected.clone()); + } + } + match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { - let state = state(); let is_open = state.is_open.load(Ordering::Relaxed); if is_open { // Event wasn't processed by overlay, so cursor was clicked either outside it's @@ -499,87 +646,7 @@ pub fn update< } event::Status::Captured } else if cursor.is_over(layout.bounds()) { - state.is_open.store(true, Ordering::Relaxed); - let mut hovered_guard = state.hovered_option.lock().unwrap(); - *hovered_guard = selected; - let id = window::Id::unique(); - state.popup_id = id; - #[cfg(all(feature = "winit", feature = "wayland"))] - if let Some(((on_surface_action, parent), action_map)) = - on_surface_action.zip(_window_id).zip(action_map) - { - use iced_runtime::platform_specific::wayland::popup::{ - SctkPopupSettings, SctkPositioner, - }; - let bounds = layout.bounds(); - let anchor_rect = Rectangle { - x: bounds.x as i32, - y: bounds.y as i32, - width: bounds.width as i32, - height: bounds.height as i32, - }; - let icon_width = if icons.is_empty() { 0.0 } else { 24.0 }; - let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 { - selection_paragraph.min_width().round() - }; - let pad_width = padding.horizontal().mul_add(2.0, 16.0); - - let selections_width = selections - .iter() - .zip(state.selections.iter_mut()) - .map(|(label, selection)| measure(label.as_ref(), selection.raw())) - .fold(0.0, |next, current| current.max(next)); - - let icons: Cow<'static, [Handle]> = Cow::Owned(icons.to_vec()); - let selections: Cow<'static, [S]> = Cow::Owned(selections.to_vec()); - let state = state.clone(); - let on_close = surface::action::destroy_popup(id); - let on_surface_action_clone = on_surface_action.clone(); - let translation = layout.virtual_offset(); - let get_popup_action = surface::action::simple_popup::( - move || { - SctkPopupSettings { - parent, - id, - input_zone: None, - positioner: SctkPositioner { - size: Some((selections_width as u32 + gap as u32 + pad_width as u32 + icon_width as u32, 10)), - anchor_rect, - // TODO: left or right alignment based on direction? - anchor: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::BottomLeft, - gravity: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight, - reactive: true, - offset: ((-padding.left - translation.x) as i32, -translation.y as i32), - constraint_adjustment: 9, - ..Default::default() - }, - parent_size: None, - grab: true, - close_with_children: true, - } - }, - Some(Box::new(move || { - let action_map = action_map.clone(); - let on_selected = on_selected.clone(); - let e: Element<'static, crate::Action> = - Element::from(menu_widget( - bounds, - &state, - gap, - padding, - text_size.unwrap_or(14.0), - selections.clone(), - icons.clone(), - selected_option, - Arc::new(move |i| on_selected.clone()(i)), - Some(on_surface_action_clone(on_close.clone())), - )) - .map(move |m| crate::Action::App(action_map.clone()(m))); - e - })), - ); - shell.publish(on_surface_action(get_popup_action)); - } + open(shell, state, on_selected); event::Status::Captured } else { event::Status::Ignored @@ -588,7 +655,6 @@ pub fn update< Event::Mouse(mouse::Event::WheelScrolled { delta: mouse::ScrollDelta::Lines { .. }, }) => { - let state = state(); let is_open = state.is_open.load(Ordering::Relaxed); if state.keyboard_modifiers.command() && cursor.is_over(layout.bounds()) && !is_open { @@ -604,8 +670,6 @@ pub fn update< } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - let state = state(); - state.keyboard_modifiers = *modifiers; event::Status::Ignored From fc85fcac3e1d1b7a05982e7396a8bc23ff4d0143 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 18 Nov 2025 18:47:24 +0100 Subject: [PATCH 365/556] fix(dropdown): refresh popup when selections change --- src/widget/dropdown/widget.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 47df9b89..a5612ccd 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -178,6 +178,8 @@ where fn diff(&mut self, tree: &mut Tree) { let state = tree.state.downcast_mut::(); + let mut selections_changed = state.selections.len() != self.selections.len(); + state .selections .resize_with(self.selections.len(), crate::Plain::default); @@ -192,6 +194,7 @@ where continue; } + selections_changed = true; state.hashes[i] = text_hash; state.selections[i].update(Text { content: selection.as_ref(), @@ -206,6 +209,11 @@ where wrapping: text::Wrapping::default(), }); } + + if state.is_open.load(Ordering::SeqCst) && selections_changed { + state.close_operation = true; + state.open_operation = true; + } } fn size(&self) -> Size { From 709044891ee04c6ca62ff3d1087ab0e4ebb59bb4 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 19 Nov 2025 10:39:23 -0500 Subject: [PATCH 366/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index d2949f2d..c9cd78e0 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit d2949f2dbbb60c65a48e941e89c36563a5cde3a6 +Subproject commit c9cd78e030d5b228f190af28d908e1fbcf8737ce From 7f321cb0a3b5ec53f6a6d33320e4f5d0f737959c Mon Sep 17 00:00:00 2001 From: Stephan Buys Date: Wed, 19 Nov 2025 15:44:31 +0200 Subject: [PATCH 367/556] segmented button: support tab drag + drop --- Cargo.toml | 2 +- src/widget/dnd_destination.rs | 144 ++++- src/widget/segmented_button/mod.rs | 13 + src/widget/segmented_button/model/mod.rs | 71 +++ src/widget/segmented_button/widget.rs | 753 ++++++++++++++++++++++- 5 files changed, 950 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 94b53a64..430af23d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -122,6 +122,7 @@ image = { version = "0.25.8", default-features = false, features = [ "png", ] } libc = { version = "0.2.175", optional = true } +log = "0.4" mime = { version = "0.3.17", optional = true } palette = "0.7.6" raw-window-handle = "0.6" @@ -222,4 +223,3 @@ dirs = "6.0.0" [dev-dependencies] tempfile = "3.13.0" - diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index ccc0fb18..c943d2c7 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -39,6 +39,7 @@ pub fn dnd_destination_for_data<'a, T: AllowedMimeTypes, Message: 'static>( } static DRAG_ID_COUNTER: AtomicU64 = AtomicU64::new(0); +const DND_DEST_LOG_TARGET: &str = "libcosmic::widget::dnd_destination"; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct DragId(pub u128); @@ -75,6 +76,12 @@ pub struct DndDestination<'a, Message> { } impl<'a, Message: 'static> DndDestination<'a, Message> { + fn mime_matches(&self, offered: &[String]) -> bool { + self.mime_types.is_empty() + || offered + .iter() + .any(|mime| self.mime_types.iter().any(|allowed| allowed == mime)) + } pub fn new(child: impl Into>, mimes: Vec>) -> Self { Self { id: Id::unique(), @@ -324,6 +331,12 @@ impl Widget let my_id = self.get_drag_id(); + log::trace!( + target: DND_DEST_LOG_TARGET, + "dnd_destination id={:?}: event {:?}", + self.drag_id.unwrap_or_default(), + event + ); match event { Event::Dnd(DndEvent::Offer( id, @@ -331,6 +344,18 @@ impl Widget x, y, mime_types, .. }, )) if id == Some(my_id) => { + if !self.mime_matches(&mime_types) { + log::trace!( + target: DND_DEST_LOG_TARGET, + "offer enter id={my_id:?} ignored (mimes={mime_types:?} not in {:?})", + self.mime_types + ); + return event::Status::Ignored; + } + log::trace!( + target: DND_DEST_LOG_TARGET, + "offer enter id={my_id:?} coords=({x},{y}) mimes={mime_types:?}" + ); if let Some(msg) = state.on_enter( x, y, @@ -360,6 +385,11 @@ impl Widget return event::Status::Captured; } Event::Dnd(DndEvent::Offer(_, OfferEvent::Leave)) => { + log::trace!( + target: DND_DEST_LOG_TARGET, + "offer leave id={:?}", + my_id + ); if let Some(msg) = state.on_leave(self.on_leave.as_ref().map(std::convert::AsRef::as_ref)) { @@ -383,6 +413,10 @@ impl Widget return event::Status::Ignored; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Motion { x, y })) if id == Some(my_id) => { + log::trace!( + target: DND_DEST_LOG_TARGET, + "offer motion id={my_id:?} coords=({x},{y})" + ); if let Some(msg) = state.on_motion( x, y, @@ -413,6 +447,11 @@ impl Widget return event::Status::Captured; } Event::Dnd(DndEvent::Offer(_, OfferEvent::LeaveDestination)) => { + log::trace!( + target: DND_DEST_LOG_TARGET, + "offer leave-destination id={:?}", + my_id + ); if let Some(msg) = state.on_leave(self.on_leave.as_ref().map(std::convert::AsRef::as_ref)) { @@ -421,6 +460,10 @@ impl Widget return event::Status::Ignored; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Drop)) if id == Some(my_id) => { + log::trace!( + target: DND_DEST_LOG_TARGET, + "offer drop id={my_id:?}" + ); if let Some(msg) = state.on_drop(self.on_drop.as_ref().map(std::convert::AsRef::as_ref)) { @@ -431,6 +474,10 @@ impl Widget Event::Dnd(DndEvent::Offer(id, OfferEvent::SelectedAction(action))) if id == Some(my_id) => { + log::trace!( + target: DND_DEST_LOG_TARGET, + "offer selected-action id={my_id:?} action={action:?}" + ); if let Some(msg) = state.on_action_selected( action, self.on_action_selected @@ -444,6 +491,11 @@ impl Widget Event::Dnd(DndEvent::Offer(id, OfferEvent::Data { data, mime_type })) if id == Some(my_id) => { + log::trace!( + target: DND_DEST_LOG_TARGET, + "offer data id={my_id:?} mime={mime_type:?} bytes={}", + data.len() + ); if let (Some(msg), ret) = state.on_data_received( mime_type, data, @@ -521,6 +573,16 @@ impl Widget ) { let bounds = layout.bounds(); let my_id = self.get_drag_id(); + log::trace!( + target: DND_DEST_LOG_TARGET, + "register destination id={:?} bounds=({:.2},{:.2},{:.2},{:.2}) mimes={:?}", + my_id, + bounds.x, + bounds.y, + bounds.width, + bounds.height, + self.mime_types + ); let my_dest = DndDestinationRectangle { id: my_id, rectangle: dnd::Rectangle { @@ -535,12 +597,14 @@ impl Widget }; dnd_rectangles.push(my_dest); - self.container.as_widget().drag_destinations( - &state.children[0], - layout, - renderer, - dnd_rectangles, - ); + if let Some(child_layout) = layout.children().next() { + self.container.as_widget().drag_destinations( + &state.children[0], + child_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + dnd_rectangles, + ); + } } fn id(&self) -> Option { @@ -696,3 +760,71 @@ impl<'a, Message: 'static> From> for Element<'a, Mes Element::new(wrapper) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Clone, Copy, Debug, PartialEq)] + enum TestMsg { + Data, + Finished, + } + + #[test] + fn data_before_drop_invokes_data_handler_only() { + let mut state: State<()> = State::new(); + assert!(state.drag_offer.is_none()); + state.on_enter::( + 4.0, + 2.0, + vec!["text/plain".into()], + Option:: TestMsg>::None, + (), + ); + let (message, status) = state.on_data_received( + "text/plain".into(), + vec![1], + Some(|mime, data| { + assert_eq!(mime, "text/plain"); + assert_eq!(data, vec![1]); + TestMsg::Data + }), + Option:: TestMsg>::None, + ); + assert!(matches!(message, Some(TestMsg::Data))); + assert_eq!(status, event::Status::Captured); + assert!(state.drag_offer.is_some()); + } + + #[test] + fn finish_only_emits_after_drop() { + let mut state: State<()> = State::new(); + state.on_enter::( + 5.0, + -1.0, + vec![], + Option:: TestMsg>::None, + (), + ); + state.on_action_selected::(DndAction::Move, Option:: TestMsg>::None); + state.on_drop::(Option:: TestMsg>::None); + + let (message, status) = state.on_data_received( + "application/x-test".into(), + vec![7], + Option:: TestMsg>::None, + Some(|mime, data, action, x, y| { + assert_eq!(mime, "application/x-test"); + assert_eq!(data, vec![7]); + assert_eq!(action, DndAction::Move); + assert_eq!(x, 5.0); + assert_eq!(y, -1.0); + TestMsg::Finished + }), + ); + assert!(matches!(message, Some(TestMsg::Finished))); + assert_eq!(status, event::Status::Captured); + assert!(state.drag_offer.is_none()); + } +} diff --git a/src/widget/segmented_button/mod.rs b/src/widget/segmented_button/mod.rs index e609d70b..81c71be8 100644 --- a/src/widget/segmented_button/mod.rs +++ b/src/widget/segmented_button/mod.rs @@ -88,6 +88,19 @@ pub use self::style::{Appearance, ItemAppearance, ItemStatusAppearance, StyleShe pub use self::vertical::{VerticalSegmentedButton, vertical}; pub use self::widget::{Id, SegmentedButton, SegmentedVariant, focus}; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum InsertPosition { + Before, + After, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ReorderEvent { + pub dragged: Entity, + pub target: Entity, + pub position: InsertPosition, +} + /// Associates extra data with an external secondary map. /// /// The secondary map internally uses a `Vec`, so should only be used for data that diff --git a/src/widget/segmented_button/model/mod.rs b/src/widget/segmented_button/model/mod.rs index 6b5a8a64..e0dd8c54 100644 --- a/src/widget/segmented_button/model/mod.rs +++ b/src/widget/segmented_button/model/mod.rs @@ -11,6 +11,7 @@ mod selection; pub use self::selection::{MultiSelect, Selectable, SingleSelect}; use crate::widget::Icon; +use crate::widget::segmented_button::InsertPosition; use slotmap::{SecondaryMap, SlotMap}; use std::any::{Any, TypeId}; use std::borrow::Cow; @@ -410,6 +411,36 @@ where true } + /// Reorder `dragged` relative to `target` based on the provided position. + /// + /// Returns `true` if the model changed, or `false` if the move was invalid. + pub fn reorder(&mut self, dragged: Entity, target: Entity, position: InsertPosition) -> bool { + if !self.contains_item(dragged) || !self.contains_item(target) || dragged == target { + return false; + } + + let len = self.iter().count(); + let target_pos = self.position(target).map(|pos| pos as usize).unwrap_or(len); + let from_pos = self + .position(dragged) + .map(|pos| pos as usize) + .unwrap_or(target_pos); + let mut insert_pos = match position { + InsertPosition::Before => target_pos, + InsertPosition::After => target_pos.saturating_add(1), + }; + if from_pos < insert_pos { + insert_pos = insert_pos.saturating_sub(1); + } + if len > 0 { + insert_pos = insert_pos.min(len.saturating_sub(1)); + } + + self.position_set(dragged, insert_pos as u16); + self.activate(dragged); + true + } + /// Removes an item from the model. /// /// The generation of the slot for the ID will be incremented, so this ID will no @@ -469,3 +500,43 @@ where self.text.remove(id) } } + +#[cfg(test)] +mod tests { + use super::*; + + fn sample_model() -> (Model, Vec) { + let mut ids = Vec::new(); + let model = Model::builder() + .insert(|b| b.text("Tab1").with_id(|id| ids.push(id))) + .insert(|b| b.text("Tab2").with_id(|id| ids.push(id))) + .insert(|b| b.text("Tab3").with_id(|id| ids.push(id))) + .insert(|b| b.text("Tab4").with_id(|id| ids.push(id))) + .build(); + (model, ids) + } + + fn order_of(model: &Model) -> Vec { + model.iter().collect() + } + + #[test] + fn reorder_inserts_before_target() { + let (mut model, ids) = sample_model(); + assert!(model.reorder(ids[3], ids[1], InsertPosition::Before)); + assert_eq!(order_of(&model), vec![ids[0], ids[3], ids[1], ids[2]]); + } + + #[test] + fn reorder_inserts_after_target() { + let (mut model, ids) = sample_model(); + assert!(model.reorder(ids[0], ids[2], InsertPosition::After)); + assert_eq!(order_of(&model), vec![ids[1], ids[2], ids[0], ids[3]]); + } + + #[test] + fn reorder_rejects_invalid_entities() { + let (mut model, ids) = sample_model(); + assert!(!model.reorder(ids[0], ids[0], InsertPosition::After)); + } +} diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index bb05aa9d..e852a2eb 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 use super::model::{Entity, Model, Selectable}; +use super::{InsertPosition, ReorderEvent}; use crate::iced_core::id::Internal; use crate::theme::{SegmentedButton as Style, THEME}; use crate::widget::dnd_destination::DragId; @@ -12,7 +13,9 @@ use crate::widget::menu::{ use crate::widget::{Icon, icon}; use crate::{Element, Renderer}; use derive_setters::Setters; -use iced::clipboard::dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent}; +use iced::clipboard::dnd::{ + self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent, SourceEvent, +}; use iced::clipboard::mime::AllowedMimeTypes; use iced::touch::Finger; use iced::{ @@ -41,6 +44,8 @@ thread_local! { static LAST_FOCUS_UPDATE: LazyCell> = LazyCell::new(|| Cell::new(Instant::now())); } +const TAB_REORDER_LOG_TARGET: &str = "libcosmic::widget::tab_reorder"; + /// A command that focuses a segmented item stored in a widget. pub fn focus(id: Id) -> Task { task::effect(Action::Widget(Box::new(operation::focusable::focus(id.0)))) @@ -51,6 +56,27 @@ pub enum ItemBounds { Divider(Rectangle, bool), } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum DropSide { + Before, + After, +} + +impl From for InsertPosition { + fn from(side: DropSide) -> Self { + match side { + DropSide::Before => InsertPosition::Before, + DropSide::After => InsertPosition::After, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct DropHint { + entity: Entity, + side: DropSide, +} + /// Isolates variant-specific behaviors from [`SegmentedButton`]. pub trait SegmentedVariant { const VERTICAL: bool; @@ -157,6 +183,12 @@ where #[setters(strip_option)] pub(super) drag_id: Option, #[setters(skip)] + pub(super) tab_drag: Option>, + #[setters(skip)] + pub(super) on_drop_hint: Option) -> Message + 'static>>, + #[setters(skip)] + pub(super) on_reorder: Option Message + 'static>>, + #[setters(skip)] /// Defines the implementation of this struct variant: PhantomData, } @@ -204,6 +236,9 @@ where mimes: Vec::new(), variant: PhantomData, drag_id: None, + tab_drag: None, + on_drop_hint: None, + on_reorder: None, } } @@ -261,6 +296,77 @@ where self } + /// Enable drag-and-drop support for tabs using the provided payload builder. + pub fn enable_tab_drag( + mut self, + payload: impl Fn(Entity) -> Option<(String, Vec)> + 'static, + ) -> Self { + self.tab_drag = Some(TabDragSource::new(payload)); + self + } + + /// Receive drop hint updates during drag-and-drop. + pub fn on_drop_hint( + mut self, + callback: impl Fn(Option<(Entity, bool)>) -> Message + 'static, + ) -> Self { + self.on_drop_hint = Some(Box::new(callback)); + self + } + + /// Emit a message when a tab drag is dropped inside this widget. + pub fn on_reorder(mut self, callback: impl Fn(ReorderEvent) -> Message + 'static) -> Self { + self.on_reorder = Some(Box::new(callback)); + self + } + + /// Set the pointer distance threshold before a drag is started. + pub fn tab_drag_threshold(mut self, threshold: f32) -> Self { + if let Some(tab_drag) = self.tab_drag.as_mut() { + tab_drag.threshold = threshold.max(1.0); + } + self + } + + fn reorder_event_for_drop(&self, state: &LocalState, target: Entity) -> Option { + let dragged = state.dragging_tab?; + if dragged == target + || !self.model.contains_item(dragged) + || !self.model.contains_item(target) + { + return None; + } + let position = state + .drop_hint + .filter(|hint| hint.entity == target) + .map(|hint| InsertPosition::from(hint.side)) + .unwrap_or_else(|| self.default_insert_position(dragged, target)); + Some(ReorderEvent { + dragged, + target, + position, + }) + } + + fn default_insert_position(&self, dragged: Entity, target: Entity) -> InsertPosition { + let len = self.model.len(); + let target_pos = self + .model + .position(target) + .map(|pos| pos as usize) + .unwrap_or(len); + let from_pos = self + .model + .position(dragged) + .map(|pos| pos as usize) + .unwrap_or(target_pos); + if from_pos < target_pos { + InsertPosition::After + } else { + InsertPosition::Before + } + } + /// Check if an item is enabled. fn is_enabled(&self, key: Entity) -> bool { self.model.items.get(key).is_some_and(|item| item.enabled) @@ -545,6 +651,101 @@ where state.pressed_item == Some(Item::Tab(key)) } + fn emit_drop_hint(&self, shell: &mut Shell<'_, Message>, hint: Option) { + if let Some(on_hint) = self.on_drop_hint.as_ref() { + let mapped = hint.map(|hint| (hint.entity, matches!(hint.side, DropSide::After))); + shell.publish(on_hint(mapped)); + } + } + + fn drop_hint_for_position( + &self, + state: &LocalState, + bounds: Rectangle, + cursor: Point, + ) -> Option { + let dragging = state.dragging_tab?; + + self.variant_bounds(state, bounds) + .filter_map(|item| match item { + ItemBounds::Button(entity, rect) if rect.contains(cursor) => Some((entity, rect)), + _ => None, + }) + .find_map(|(entity, rect)| { + let before = if Self::VERTICAL { + cursor.y < rect.center_y() + } else { + cursor.x < rect.center_x() + }; + Some(DropHint { + entity, + side: if before { + DropSide::Before + } else { + DropSide::After + }, + }) + }) + } + + fn start_tab_drag( + &self, + state: &mut LocalState, + entity: Entity, + bounds: Rectangle, + cursor: Point, + clipboard: &mut dyn Clipboard, + ) -> bool { + let Some(tab_drag) = self.tab_drag.as_ref() else { + return false; + }; + + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "start_tab_drag requested entity={:?} cursor=({:.2},{:.2}) bounds=({:.2},{:.2},{:.2},{:.2}) threshold={}", + entity, + cursor.x, + cursor.y, + bounds.x, + bounds.y, + bounds.width, + bounds.height, + tab_drag.threshold + ); + + let Some((mime, data)) = (tab_drag.payload)(entity) else { + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "start_tab_drag aborted entity={:?}: payload builder returned None", + entity + ); + return false; + }; + + let data_len = data.len(); + let mime_label = mime.clone(); + + iced_core::clipboard::start_dnd::( + clipboard, + false, + Some(iced_core::clipboard::DndSource::Widget(self.id.0.clone())), + None, + Box::new(SimpleDragData::new(mime, data)), + DndAction::Move, + ); + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "tab drag started entity={:?} mime={} bytes={}", + entity, + mime_label, + data_len + ); + state.dragging_tab = Some(entity); + state.tab_drag_candidate = None; + state.pressed_item = None; + true + } + /// Returns the drag id of the destination. /// /// # Panics @@ -611,6 +812,9 @@ where dnd_state: Default::default(), fingers_pressed: Default::default(), pressed_item: None, + tab_drag_candidate: None, + dragging_tab: None, + drop_hint: None, }) } @@ -701,7 +905,7 @@ where layout: Layout<'_>, cursor_position: mouse::Cursor, _renderer: &Renderer, - _clipboard: &mut dyn Clipboard, + clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &iced::Rectangle, ) -> event::Status { @@ -717,7 +921,26 @@ where .drag_offer .as_ref() .map(|dnd_state| dnd_state.data); + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "segmented button {:?} received DnD event: {:?} entity={entity:?}", + my_id, + e + ); match e { + DndEvent::Source(SourceEvent::Cancelled | SourceEvent::Finished) => { + if state.dragging_tab.take().is_some() { + state.tab_drag_candidate = None; + state.drop_hint = None; + self.emit_drop_hint(shell, state.drop_hint); + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "tab drag source finished id={:?}", + my_id + ); + return event::Status::Captured; + } + } DndEvent::Offer( id, OfferEvent::Enter { @@ -732,6 +955,16 @@ where }) .find(|(_key, bounds)| bounds.contains(Point::new(*x as f32, *y as f32))) .map(|(key, _)| key); + state.drop_hint = self.drop_hint_for_position( + state, + bounds, + Point::new(*x as f32, *y as f32), + ); + self.emit_drop_hint(shell, state.drop_hint); + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "offer enter id={my_id:?} entity={entity:?} @ ({x},{y}) mimes={mime_types:?}" + ); let on_dnd_enter = self.on_dnd_enter @@ -750,15 +983,28 @@ where ); } DndEvent::Offer(id, OfferEvent::LeaveDestination) if Some(my_id) != *id => {} - DndEvent::Offer(id, OfferEvent::Leave | OfferEvent::LeaveDestination) => { + DndEvent::Offer(id, OfferEvent::Leave | OfferEvent::LeaveDestination) + if Some(my_id) == *id => + { + state.drop_hint = None; + self.emit_drop_hint(shell, state.drop_hint); if let Some(Some(entity)) = entity { if let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() { shell.publish(on_dnd_leave(entity)); } } + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "offer leave id={my_id:?} entity={entity:?}" + ); _ = state.dnd_state.on_leave::(None); } + DndEvent::Offer(_, OfferEvent::Leave | OfferEvent::LeaveDestination) => {} DndEvent::Offer(id, OfferEvent::Motion { x, y }) if Some(my_id) == *id => { + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "offer motion id={my_id:?} cursor=({x},{y}) current_entity={entity:?}" + ); let new = self .variant_bounds(state, bounds) .filter_map(|item| match item { @@ -775,6 +1021,12 @@ where None:: Message>, Some(new_entity), ); + state.drop_hint = self.drop_hint_for_position( + state, + bounds, + Point::new(*x as f32, *y as f32), + ); + self.emit_drop_hint(shell, state.drop_hint); if Some(Some(new_entity)) != entity { let prev_action = state .dnd_state @@ -792,6 +1044,12 @@ where } } } else if entity.is_some() { + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "offer motion leaving id={my_id:?}" + ); + state.drop_hint = None; + self.emit_drop_hint(shell, state.drop_hint); state.dnd_state.on_motion::( *x, *y, @@ -807,32 +1065,81 @@ where } } DndEvent::Offer(id, OfferEvent::Drop) if Some(my_id) == *id => { + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "offer drop id={my_id:?} entity={entity:?}" + ); _ = state .dnd_state .on_drop::(None:: Message>); } DndEvent::Offer(id, OfferEvent::SelectedAction(action)) if Some(my_id) == *id => { if state.dnd_state.drag_offer.is_some() { + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "offer selected action id={my_id:?} action={action:?} entity={entity:?}" + ); _ = state .dnd_state .on_action_selected::(*action, None:: Message>); } } DndEvent::Offer(id, OfferEvent::Data { data, mime_type }) if Some(my_id) == *id => { - if let Some(Some(entity)) = entity { + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "offer data id={my_id:?} entity={entity:?} mime={mime_type:?}" + ); + let drop_entity = entity + .flatten() + .or_else(|| state.drop_hint.map(|hint| hint.entity)); + let allow_reorder = state + .dnd_state + .drag_offer + .as_ref() + .is_some_and(|offer| offer.selected_action.contains(DndAction::Move)); + let pending_reorder = if allow_reorder && self.on_reorder.is_some() { + drop_entity.and_then(|target| self.reorder_event_for_drop(state, target)) + } else { + None + }; + if let Some(entity) = drop_entity { let on_drop = self.on_dnd_drop.as_ref(); let on_drop = on_drop.map(|on_drop| { |mime, data, action, _, _| on_drop(entity, data, mime, action) }); - if let (Some(msg), ret) = state.dnd_state.on_data_received( + let (maybe_msg, ret) = state.dnd_state.on_data_received( mem::take(mime_type), mem::take(data), None:: Message>, on_drop, - ) { + ); + if let Some(msg) = maybe_msg { + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "publishing drop message entity={entity:?}" + ); shell.publish(msg); - return ret; + } + state.drop_hint = None; + self.emit_drop_hint(shell, state.drop_hint); + if let Some(event) = pending_reorder { + if let Some(on_reorder) = self.on_reorder.as_ref() { + shell.publish(on_reorder(event)); + } + } + return ret; + } else { + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "data received without entity id={my_id:?}" + ); + state.drop_hint = None; + self.emit_drop_hint(shell, state.drop_hint); + if let Some(event) = pending_reorder { + if let Some(on_reorder) = self.on_reorder.as_ref() { + shell.publish(on_reorder(event)); + } } } } @@ -897,12 +1204,16 @@ where // Record that the mouse is hovering over this button. state.hovered = Item::Tab(key); + let close_button_bounds = + close_bounds(bounds, f32::from(self.close_icon.size)); + let over_close_button = self.model.items[key].closable + && cursor_position.is_over(close_button_bounds); + // If marked as closable, show a close icon. if self.model.items[key].closable { // Emit close message if the close button is pressed. if let Some(on_close) = self.on_close.as_ref() { - if cursor_position - .is_over(close_bounds(bounds, f32::from(self.close_icon.size))) + if over_close_button && (left_button_released(&event) || (touch_lifted(&event) && fingers_pressed == 1)) { @@ -927,6 +1238,36 @@ where } } + if self.tab_drag.is_some() + && matches!( + event, + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + ) + && !over_close_button + { + if let Some(position) = cursor_position.position() { + state.tab_drag_candidate = Some(TabDragCandidate { + entity: key, + bounds, + origin: position, + }); + if let Some(tab_drag) = self.tab_drag.as_ref() { + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "tab drag candidate entity={:?} origin=({:.2},{:.2}) bounds=({:.2},{:.2},{:.2},{:.2}) threshold={}", + key, + position.x, + position.y, + bounds.x, + bounds.y, + bounds.width, + bounds.height, + tab_drag.threshold + ); + } + } + } + if is_lifted(&event) { state.unfocus(); } @@ -1046,6 +1387,42 @@ where state.pressed_item = None; } + if let (Some(tab_drag), Some(candidate)) = + (self.tab_drag.as_ref(), state.tab_drag_candidate) + { + if let Event::Mouse(mouse::Event::CursorMoved { .. }) = event { + if let Some(position) = cursor_position.position() { + if position.distance(candidate.origin) >= tab_drag.threshold { + if let Some(candidate) = state.tab_drag_candidate.take() { + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "tab drag threshold met entity={:?} distance={:.2} threshold={}", + candidate.entity, + position.distance(candidate.origin), + tab_drag.threshold + ); + if self.start_tab_drag( + state, + candidate.entity, + candidate.bounds, + position, + clipboard, + ) { + return event::Status::Captured; + } + } + } + } + } + } + + if matches!( + event, + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + ) { + state.tab_drag_candidate = None; + } + if state.is_focused() { if let Event::Keyboard(keyboard::Event::KeyPressed { key: keyboard::Key::Named(keyboard::key::Named::Tab), @@ -1120,6 +1497,7 @@ where ) { let state = tree.state.downcast_mut::(); operation.focusable(state, Some(&self.id.0)); + operation.custom(state, Some(&self.id.0)); if let Item::Set = state.focused_item { if self.prev_tab_sensitive(state) { @@ -1180,6 +1558,12 @@ where let appearance = Self::variant_appearance(theme, &self.style); let bounds: Rectangle = layout.bounds(); let button_amount = self.model.items.len(); + let show_drop_hint = state.dragging_tab.is_some(); + let drop_hint = if show_drop_hint { + state.drop_hint + } else { + None + }; // Draw the background, if a background was defined. if let Some(background) = appearance.background { @@ -1305,6 +1689,8 @@ where // Draw each of the items in the widget. let mut nth = 0; + let drop_hint_marker = drop_hint; + let show_drop_hint_marker = show_drop_hint; self.variant_bounds(state, bounds).for_each(move |item| { let (key, mut bounds) = match item { // Draw a button @@ -1332,8 +1718,27 @@ where } }; + let original_bounds = bounds; let center_y = bounds.center_y(); + if show_drop_hint_marker { + if matches!( + drop_hint_marker, + Some(DropHint { + entity, + side: DropSide::Before + }) if entity == key + ) { + draw_drop_indicator( + renderer, + original_bounds, + DropSide::Before, + Self::VERTICAL, + appearance.active.text_color, + ); + } + } + let menu_open = || { state.show_context == Some(key) && !tree.children.is_empty() @@ -1398,7 +1803,6 @@ where ); } - let original_bounds = bounds; bounds.x += f32::from(self.button_padding[0]); bounds.width -= f32::from(self.button_padding[0]) - f32::from(self.button_padding[2]); let mut indent_padding = 0.0; @@ -1595,6 +1999,24 @@ where ); } + if show_drop_hint_marker { + if matches!( + drop_hint_marker, + Some(DropHint { + entity, + side: DropSide::After + }) if entity == key + ) { + draw_drop_indicator( + renderer, + original_bounds, + DropSide::After, + Self::VERTICAL, + appearance.active.text_color, + ); + } + } + nth += 1; }); } @@ -1658,27 +2080,68 @@ where fn drag_destinations( &self, - _state: &Tree, + tree: &Tree, layout: Layout<'_>, _renderer: &Renderer, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { - let bounds = layout.bounds(); - + let local_state = tree.state.downcast_ref::(); let my_id = self.get_drag_id(); - let dnd_rect = DndDestinationRectangle { - id: my_id, - rectangle: dnd::Rectangle { - x: f64::from(bounds.x), - y: f64::from(bounds.y), - width: f64::from(bounds.width), - height: f64::from(bounds.height), - }, - mime_types: self.mimes.clone().into_iter().map(Cow::Owned).collect(), - actions: DndAction::Copy | DndAction::Move, - preferred: DndAction::Move, - }; - dnd_rectangles.push(dnd_rect); + let mut pushed = false; + + for item in self.variant_bounds(local_state, layout.bounds()) { + if let ItemBounds::Button(_entity, rect) = item { + pushed = true; + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "register drag destination id={:?} bounds=({:.2},{:.2},{:.2},{:.2}) mimes={:?}", + my_id, + rect.x, + rect.y, + rect.width, + rect.height, + self.mimes + ); + dnd_rectangles.push(DndDestinationRectangle { + id: my_id, + rectangle: dnd::Rectangle { + x: f64::from(rect.x), + y: f64::from(rect.y), + width: f64::from(rect.width), + height: f64::from(rect.height), + }, + mime_types: self.mimes.clone().into_iter().map(Cow::Owned).collect(), + actions: DndAction::Copy | DndAction::Move, + preferred: DndAction::Move, + }); + } + } + + if !pushed { + let bounds = layout.bounds(); + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "register drag destination id={:?} bounds=({:.2},{:.2},{:.2},{:.2}) mimes={:?}", + my_id, + bounds.x, + bounds.y, + bounds.width, + bounds.height, + self.mimes + ); + dnd_rectangles.push(DndDestinationRectangle { + id: my_id, + rectangle: dnd::Rectangle { + x: f64::from(bounds.x), + y: f64::from(bounds.y), + width: f64::from(bounds.width), + height: f64::from(bounds.height), + }, + mime_types: self.mimes.clone().into_iter().map(Cow::Owned).collect(), + actions: DndAction::Copy | DndAction::Move, + preferred: DndAction::Move, + }); + } } } @@ -1700,6 +2163,54 @@ where } } +struct TabDragSource { + payload: Box Option<(String, Vec)>>, + threshold: f32, + _marker: PhantomData, +} + +impl TabDragSource { + fn new(payload: impl Fn(Entity) -> Option<(String, Vec)> + 'static) -> Self { + Self { + payload: Box::new(payload), + threshold: 8.0, + _marker: PhantomData, + } + } +} + +struct SimpleDragData { + mime: String, + bytes: Vec, +} + +impl SimpleDragData { + fn new(mime: String, bytes: Vec) -> Self { + Self { mime, bytes } + } +} + +impl iced::clipboard::mime::AsMimeTypes for SimpleDragData { + fn available(&self) -> Cow<'static, [String]> { + Cow::Owned(vec![self.mime.clone()]) + } + + fn as_bytes(&self, mime_type: &str) -> Option> { + if mime_type == self.mime { + Some(Cow::Owned(self.bytes.clone())) + } else { + None + } + } +} + +#[derive(Clone, Copy)] +struct TabDragCandidate { + entity: Entity, + bounds: Rectangle, + origin: Point, +} + #[derive(Debug, Clone, Copy)] struct Focus { updated_at: Instant, @@ -1746,6 +2257,12 @@ pub struct LocalState { fingers_pressed: HashSet, /// The currently pressed item pressed_item: Option, + /// Pending tab drag candidate data + tab_drag_candidate: Option, + /// Currently dragging tab entity + dragging_tab: Option, + /// Current drop hint for drag-and-drop indicator + drop_hint: Option, } #[derive(Debug, Default, PartialEq)] @@ -1770,6 +2287,143 @@ impl LocalState { } } +#[cfg(test)] +mod tests { + use super::*; + use crate::widget::segmented_button::{self, Appearance as SegAppearance}; + use iced::Size; + use slotmap::SecondaryMap; + use std::collections::HashSet; + + #[derive(Clone, Debug)] + enum TestMessage {} + + struct TestVariant; + + impl SegmentedVariant + for SegmentedButton<'_, TestVariant, SelectionMode, Message> + where + Model: Selectable, + SelectionMode: Default, + { + const VERTICAL: bool = false; + + fn variant_appearance( + _theme: &crate::Theme, + _style: &crate::theme::SegmentedButton, + ) -> SegAppearance { + SegAppearance::default() + } + + fn variant_bounds<'b>( + &'b self, + _state: &'b LocalState, + bounds: Rectangle, + ) -> Box + 'b> { + let len = self.model.order.len(); + if len == 0 { + return Box::new(std::iter::empty()); + } + let width = bounds.width / len as f32; + Box::new( + self.model + .order + .iter() + .copied() + .enumerate() + .map(move |(idx, entity)| { + let rect = Rectangle { + x: bounds.x + (idx as f32) * width, + y: bounds.y, + width, + height: bounds.height, + }; + ItemBounds::Button(entity, rect) + }), + ) + } + + fn variant_layout( + &self, + _state: &mut LocalState, + _renderer: &crate::Renderer, + _limits: &layout::Limits, + ) -> Size { + Size::ZERO + } + } + + fn sample_model() -> ( + segmented_button::SingleSelectModel, + Vec, + ) { + let mut entities = Vec::new(); + let model = segmented_button::Model::builder() + .insert(|b| b.text("One").with_id(|id| entities.push(id))) + .insert(|b| b.text("Two").with_id(|id| entities.push(id))) + .insert(|b| b.text("Three").with_id(|id| entities.push(id))) + .build(); + (model, entities) + } + + fn test_state(dragging: segmented_button::Entity, len: usize) -> LocalState { + let mut state = LocalState { + menu_state: MenuBarState::default(), + paragraphs: SecondaryMap::new(), + text_hashes: SecondaryMap::new(), + buttons_visible: 0, + buttons_offset: 0, + collapsed: false, + focused: None, + focused_item: Item::default(), + focused_visible: false, + hovered: Item::default(), + known_length: 0, + middle_clicked: None, + internal_layout: Vec::new(), + context_cursor: Point::ORIGIN, + show_context: None, + wheel_timestamp: None, + dnd_state: crate::widget::dnd_destination::State::>::new(), + fingers_pressed: HashSet::new(), + pressed_item: None, + tab_drag_candidate: None, + dragging_tab: Some(dragging), + drop_hint: None, + }; + state.buttons_visible = len; + state.known_length = len; + state + } + + #[test] + fn drop_hint_reports_before_and_after() { + let (model, ids) = sample_model(); + let button = + SegmentedButton::::new( + &model, + ); + let state = test_state(ids[0], model.order.len()); + let bounds = Rectangle { + x: 0.0, + y: 0.0, + width: 300.0, + height: 30.0, + }; + let before = button + .drop_hint_for_position(&state, bounds, Point::new(10.0, 15.0)) + .expect("hint"); + assert_eq!(before.entity, ids[0]); + assert!(matches!(before.side, DropSide::Before)); + + let after = button + .drop_hint_for_position(&state, bounds, Point::new(290.0, 15.0)) + .expect("hint"); + assert_eq!(after.entity, ids[2]); + assert!(matches!(after.side, DropSide::After)); + } +} + impl operation::Focusable for LocalState { fn is_focused(&self) -> bool { self.focused @@ -1882,6 +2536,53 @@ fn draw_icon( ); } +fn draw_drop_indicator( + renderer: &mut Renderer, + bounds: Rectangle, + side: DropSide, + vertical: bool, + color: Color, +) { + let thickness = 4.0; + let quad_bounds = if vertical { + let y = match side { + DropSide::Before => bounds.y - thickness / 2.0, + DropSide::After => bounds.y + bounds.height - thickness / 2.0, + }; + + Rectangle { + x: bounds.x, + y, + width: bounds.width, + height: thickness, + } + } else { + let x = match side { + DropSide::Before => bounds.x - thickness / 2.0, + DropSide::After => bounds.x + bounds.width - thickness / 2.0, + }; + + Rectangle { + x, + y: bounds.y, + width: thickness, + height: bounds.height, + } + }; + + renderer.fill_quad( + renderer::Quad { + bounds: quad_bounds, + border: Border { + radius: 2.0.into(), + ..Default::default() + }, + shadow: Shadow::default(), + }, + Background::Color(color), + ); +} + fn left_button_released(event: &Event) -> bool { matches!( event, From ce0868582b3cc09d52de917fef91f50d439fb2a9 Mon Sep 17 00:00:00 2001 From: Stephan Buys Date: Thu, 20 Nov 2025 11:23:49 +0200 Subject: [PATCH 368/556] tests: fix env guard and pipe read for tab dnd --- src/desktop.rs | 11 +++++++---- src/process.rs | 30 ++++++++++++++++++++---------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/desktop.rs b/src/desktop.rs index 82242460..01698af5 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -881,7 +881,9 @@ mod tests { impl EnvVarGuard { fn set(key: &'static str, value: &Path) -> Self { let original = env::var(key).ok(); - std::env::set_var(key, value); + // std::env::{set_var, remove_var} are unsafe on newer toolchains; + // we limit scope here to the test helper that toggles a single key. + unsafe { std::env::set_var(key, value) }; Self { key, original } } } @@ -889,9 +891,9 @@ mod tests { impl Drop for EnvVarGuard { fn drop(&mut self) { if let Some(ref original) = self.original { - std::env::set_var(self.key, original); + unsafe { std::env::set_var(self.key, original) }; } else { - std::env::remove_var(self.key); + unsafe { std::env::remove_var(self.key) }; } } } @@ -1108,7 +1110,8 @@ Icon=vmware-workstation\n\ let resolved = resolve_desktop_entry(&mut cache, &ctx, &DesktopResolveOptions::default()); assert!(resolved.icon().is_some()); assert!(resolved.exec().is_some()); - assert_eq!(resolved.startup_wm_class(), Some(&format!("crx_{}", id))); + let expected = format!("crx_{}", id); + assert_eq!(resolved.startup_wm_class(), Some(expected.as_str())); } #[test] diff --git a/src/process.rs b/src/process.rs index 1ad048dc..2b6c4e0e 100644 --- a/src/process.rs +++ b/src/process.rs @@ -9,18 +9,28 @@ use std::process::{Command, Stdio, exit}; #[cfg(feature = "tokio")] use tokio::io::AsyncReadExt; -#[cfg(feature = "tokio")] async fn read_from_pipe(read: OwnedFd) -> Option { - let mut read = tokio::net::unix::pipe::Receiver::from_owned_fd(read).unwrap(); - read.read_u32().await.ok() -} + #[cfg(feature = "tokio")] + { + let mut read = tokio::net::unix::pipe::Receiver::from_owned_fd(read).unwrap(); + return read.read_u32().await.ok(); + } -#[cfg(all(feature = "smol", not(feature = "tokio")))] -async fn read_from_pipe(read: OwnedFd) -> Option { - let mut read = smol::Async::new(std::fs::File::from(read)).unwrap(); - let mut bytes = [0; 4]; - read.read_exact(&mut bytes).await.ok()?; - Some(u32::from_be_bytes(bytes)) + #[cfg(all(feature = "smol", not(feature = "tokio")))] + { + let mut read = smol::Async::new(std::fs::File::from(read)).unwrap(); + let mut bytes = [0; 4]; + read.read_exact(&mut bytes).await.ok()?; + return Some(u32::from_be_bytes(bytes)); + } + + #[cfg(not(any(feature = "tokio", feature = "smol")))] + { + use rustix::fd::AsFd; + let mut bytes = [0u8; 4]; + rustix::io::read(&read, &mut bytes).ok()?; + return Some(u32::from_be_bytes(bytes)); + } } /// Performs a double fork with setsid to spawn and detach a command. From 639326fcc31a95a0c7a9a5bb56f1ed4d53530f26 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 11 Nov 2025 23:02:57 +0100 Subject: [PATCH 369/556] feat(icon): optimize & bundle icons with crabtime for non-unix platforms --- .gitmodules | 3 ++ Cargo.toml | 7 ++- cosmic-icons | 1 + res/icons/close-menu-symbolic.svg | 4 -- res/icons/go-next-symbolic.svg | 3 -- res/icons/go-previous-symbolic.svg | 3 -- res/icons/list-add-symbolic.svg | 3 -- res/icons/list-remove-symbolic.svg | 3 -- res/icons/navbar-closed-symbolic.svg | 10 ----- res/icons/navbar-open-symbolic.svg | 8 ---- res/icons/open-menu-symbolic.svg | 3 -- res/icons/window-close-symbolic.svg | 3 -- res/icons/window-maximize-symbolic.svg | 4 -- res/icons/window-minimize-symbolic.svg | 3 -- res/icons/window-restore-symbolic.svg | 4 -- src/widget/button/icon.rs | 4 -- src/widget/button/text.rs | 22 ++++----- src/widget/dropdown/multi/widget.rs | 4 -- src/widget/dropdown/widget.rs | 4 -- src/widget/header_bar.rs | 13 ------ src/widget/icon/bundle.rs | 62 ++++++++++++++++++++++++++ src/widget/icon/handle.rs | 6 +-- src/widget/icon/mod.rs | 55 ++--------------------- src/widget/icon/named.rs | 16 ++++++- src/widget/nav_bar_toggle.rs | 12 ++--- src/widget/spin_button.rs | 48 ++++++++++---------- src/widget/warning.rs | 9 ---- 27 files changed, 128 insertions(+), 189 deletions(-) create mode 160000 cosmic-icons delete mode 100644 res/icons/close-menu-symbolic.svg delete mode 100644 res/icons/go-next-symbolic.svg delete mode 100644 res/icons/go-previous-symbolic.svg delete mode 100644 res/icons/list-add-symbolic.svg delete mode 100644 res/icons/list-remove-symbolic.svg delete mode 100644 res/icons/navbar-closed-symbolic.svg delete mode 100644 res/icons/navbar-open-symbolic.svg delete mode 100644 res/icons/open-menu-symbolic.svg delete mode 100644 res/icons/window-close-symbolic.svg delete mode 100644 res/icons/window-maximize-symbolic.svg delete mode 100644 res/icons/window-minimize-symbolic.svg delete mode 100644 res/icons/window-restore-symbolic.svg create mode 100644 src/widget/icon/bundle.rs diff --git a/.gitmodules b/.gitmodules index 367f7f22..fdaf8abe 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,6 @@ path = iced url = https://github.com/pop-os/iced.git branch = master +[submodule "icon-theme"] + path = cosmic-icons + url = https://github.com/pop-os/cosmic-icons diff --git a/Cargo.toml b/Cargo.toml index 430af23d..4d742126 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,6 +107,8 @@ cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-c chrono = "0.4.42" cosmic-config = { path = "cosmic-config" } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } +# Compile-time generation of code +crabtime = "1.1.4" # Internationalization i18n-embed = { version = "0.16.0", features = [ "fluent-system", @@ -152,6 +154,10 @@ freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://githu freedesktop-desktop-entry = { version = "0.7.14", optional = true } shlex = { version = "1.3.0", optional = true } +[target.'cfg(not(unix))'.dependencies] +# Used to embed bundled icons for non-unix platforms. +phf = { version = "0.13.1", features = ["macros"] } + [dependencies.cosmic-theme] path = "cosmic-theme" @@ -222,4 +228,3 @@ dirs = "6.0.0" [dev-dependencies] tempfile = "3.13.0" - diff --git a/cosmic-icons b/cosmic-icons new file mode 160000 index 00000000..70b07582 --- /dev/null +++ b/cosmic-icons @@ -0,0 +1 @@ +Subproject commit 70b07582e24ec2114672256b9657ca80670bca8a diff --git a/res/icons/close-menu-symbolic.svg b/res/icons/close-menu-symbolic.svg deleted file mode 100644 index caf00d31..00000000 --- a/res/icons/close-menu-symbolic.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/res/icons/go-next-symbolic.svg b/res/icons/go-next-symbolic.svg deleted file mode 100644 index 3aed3717..00000000 --- a/res/icons/go-next-symbolic.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/res/icons/go-previous-symbolic.svg b/res/icons/go-previous-symbolic.svg deleted file mode 100644 index 4957cffd..00000000 --- a/res/icons/go-previous-symbolic.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/res/icons/list-add-symbolic.svg b/res/icons/list-add-symbolic.svg deleted file mode 100644 index 59b2fb03..00000000 --- a/res/icons/list-add-symbolic.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/res/icons/list-remove-symbolic.svg b/res/icons/list-remove-symbolic.svg deleted file mode 100644 index 5b9ded7c..00000000 --- a/res/icons/list-remove-symbolic.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/res/icons/navbar-closed-symbolic.svg b/res/icons/navbar-closed-symbolic.svg deleted file mode 100644 index 46f35e16..00000000 --- a/res/icons/navbar-closed-symbolic.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/res/icons/navbar-open-symbolic.svg b/res/icons/navbar-open-symbolic.svg deleted file mode 100644 index c1f32161..00000000 --- a/res/icons/navbar-open-symbolic.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/res/icons/open-menu-symbolic.svg b/res/icons/open-menu-symbolic.svg deleted file mode 100644 index efae2a2f..00000000 --- a/res/icons/open-menu-symbolic.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/res/icons/window-close-symbolic.svg b/res/icons/window-close-symbolic.svg deleted file mode 100644 index 25336395..00000000 --- a/res/icons/window-close-symbolic.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/res/icons/window-maximize-symbolic.svg b/res/icons/window-maximize-symbolic.svg deleted file mode 100644 index ef66334e..00000000 --- a/res/icons/window-maximize-symbolic.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/res/icons/window-minimize-symbolic.svg b/res/icons/window-minimize-symbolic.svg deleted file mode 100644 index fdcf99b4..00000000 --- a/res/icons/window-minimize-symbolic.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/res/icons/window-restore-symbolic.svg b/res/icons/window-restore-symbolic.svg deleted file mode 100644 index bcb506f5..00000000 --- a/res/icons/window-restore-symbolic.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/widget/button/icon.rs b/src/widget/button/icon.rs index 0bb3c84d..754bc433 100644 --- a/src/widget/button/icon.rs +++ b/src/widget/button/icon.rs @@ -132,10 +132,6 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes fn from(mut builder: Button<'a, Message>) -> Element<'a, Message> { let mut content = Vec::with_capacity(2); - if let icon::Data::Name(ref mut named) = builder.variant.handle.data { - named.size = Some(builder.icon_size); - } - content.push( crate::widget::icon(builder.variant.handle.clone()) .size(builder.icon_size) diff --git a/src/widget/button/text.rs b/src/widget/button/text.rs index e5dea9f3..3f58c932 100644 --- a/src/widget/button/text.rs +++ b/src/widget/button/text.rs @@ -91,21 +91,15 @@ impl Button<'_, Message> { impl<'a, Message: Clone + 'static> From> for Element<'a, Message> { fn from(mut builder: Button<'a, Message>) -> Element<'a, Message> { - let trailing_icon = builder.variant.trailing_icon.map(|mut i| { - if let icon::Data::Name(ref mut named) = i.data { - named.size = Some(builder.icon_size); - } + let trailing_icon = builder + .variant + .trailing_icon + .map(crate::widget::icon::Handle::icon); - i.icon() - }); - - let leading_icon = builder.variant.leading_icon.map(|mut i| { - if let icon::Data::Name(ref mut named) = i.data { - named.size = Some(builder.icon_size); - } - - i.icon() - }); + let leading_icon = builder + .variant + .leading_icon + .map(crate::widget::icon::Handle::icon); let label: Option> = (!builder.label.is_empty()).then(|| { let font = crate::font::Font { diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index 79b1a6b7..458cf5e6 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -230,10 +230,6 @@ impl State { pub fn new() -> Self { Self { icon: match icon::from_name("pan-down-symbolic").size(16).handle().data { - icon::Data::Name(named) => named - .path() - .filter(|path| path.extension().is_some_and(|ext| ext == OsStr::new("svg"))) - .map(iced_core::svg::Handle::from_path), icon::Data::Svg(handle) => Some(handle), icon::Data::Image(_) => None, }, diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index a5612ccd..d4a9bc87 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -407,10 +407,6 @@ impl State { pub fn new() -> Self { Self { icon: match icon::from_name("pan-down-symbolic").size(16).handle().data { - icon::Data::Name(named) => named - .path() - .filter(|path| path.extension().is_some_and(|ext| ext == OsStr::new("svg"))) - .map(iced_core::svg::Handle::from_path), icon::Data::Svg(handle) => Some(handle), icon::Data::Image(_) => None, }, diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 01a8d559..d500bde3 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -449,25 +449,12 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { fn window_controls(&mut self) -> Element<'a, Message> { macro_rules! icon { ($name:expr, $size:expr, $on_press:expr) => {{ - #[cfg(target_os = "linux")] let icon = { widget::icon::from_name($name) .apply(widget::button::icon) .padding(8) }; - #[cfg(not(target_os = "linux"))] - let icon = { - widget::icon::from_svg_bytes(include_bytes!(concat!( - "../../res/icons/", - $name, - ".svg" - ))) - .symbolic(true) - .apply(widget::button::icon) - .padding(8) - }; - icon.class(crate::theme::Button::HeaderBar) .selected(self.focused) .icon_size($size) diff --git a/src/widget/icon/bundle.rs b/src/widget/icon/bundle.rs new file mode 100644 index 00000000..0e1fdc16 --- /dev/null +++ b/src/widget/icon/bundle.rs @@ -0,0 +1,62 @@ +// Copyright 2025 System76 +// SPDX-License-Identifier: MPL-2.0 + +//! Embedded icons for platforms which do not support icon themes yet. + +/// Icon bundling is not enabled on unix platforms. +pub fn get(icon_name: &str) -> Option { + None +} + +#[cfg(not(unix))] +/// Get a bundled icon on non-unix platforms. +pub fn get(icon_name: &str) -> Option { + ICONS + .get(icon_name) + .map(|bytes| super::Data::Svg(crate::iced::widget::svg::Handle::from_memory(*bytes))) +} + +#[cfg(not(unix))] +#[crabtime::expression] +fn comptime_icon_bundler() -> String { + let manifest_dir = std::path::Path::new(crabtime::WORKSPACE_PATH); + let icon_paths = [ + "cosmic-icons/freedesktop/scalable", + "cosmic-icons/extra/scalable", + ]; + + let key_value_assignments = icon_paths + .into_iter() + .map(|path| manifest_dir.join(path)) + .inspect(|icon_path| assert!(icon_path.exists(), "path = {icon_path:?}")) + .map(|icon_path| std::fs::read_dir(icon_path).unwrap()) + .flat_map(|dir| { + dir.flat_map(|entry| entry.unwrap().path().read_dir().unwrap()) + .map(|entry| { + let entry = entry.unwrap(); + let path = entry.path().canonicalize().unwrap(); + let file_name = path.file_stem().unwrap().to_str().unwrap().to_owned(); + let path = path.into_os_string().into_string().unwrap(); + (file_name, path) + }) + }) + .fold( + std::collections::BTreeMap::new(), + |mut set, (name, path)| { + set.insert(name, path); + set + }, + ) + .into_iter() + .fold(String::new(), |mut output, (name, path)| { + output.push_str(&format!(" \"{name}\" => include_bytes!(\"{path}\"),\n")); + output + }); + + ["phf::phf_map!(\n", &key_value_assignments, ")"].concat() +} + +#[cfg(not(unix))] +static ICONS: phf::Map<&'static str, &'static [u8]> = { + comptime_icon_bundler! {} +}; diff --git a/src/widget/icon/handle.rs b/src/widget/icon/handle.rs index 1fa2d85f..a4ddd364 100644 --- a/src/widget/icon/handle.rs +++ b/src/widget/icon/handle.rs @@ -1,7 +1,7 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -use super::{Icon, Named}; +use super::Icon; use crate::widget::{image, svg}; use std::borrow::Cow; use std::ffi::OsStr; @@ -26,7 +26,7 @@ impl Handle { #[must_use] #[derive(Clone, Debug, Hash)] pub enum Data { - Name(Named), + // Name(Named), Image(image::Handle), Svg(svg::Handle), } @@ -94,7 +94,7 @@ pub fn from_raster_pixels( /// Create a SVG handle from memory. pub fn from_svg_bytes(bytes: impl Into>) -> Handle { Handle { - symbolic: false, + symbolic: true, data: Data::Svg(svg::Handle::from_memory(bytes)), } } diff --git a/src/widget/icon/mod.rs b/src/widget/icon/mod.rs index 20e8bf25..6c6a9f08 100644 --- a/src/widget/icon/mod.rs +++ b/src/widget/icon/mod.rs @@ -3,8 +3,8 @@ //! Lazily-generated SVG icon widget for Iced. +mod bundle; mod named; -use std::ffi::OsStr; use std::sync::Arc; pub use named::{IconFallback, Named}; @@ -58,14 +58,6 @@ impl Icon { #[must_use] pub fn into_svg_handle(self) -> Option { match self.handle.data { - Data::Name(named) => { - if let Some(path) = named.path() { - if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) { - return Some(iced_core::svg::Handle::from_path(path)); - } - } - } - Data::Image(_) => (), Data::Svg(handle) => return Some(handle), } @@ -76,12 +68,6 @@ impl Icon { #[must_use] pub fn size(mut self, size: u16) -> Self { self.size = size; - // ensures correct icon size variant selection - if let Data::Name(named) = &self.handle.data { - let mut new_named = named.clone(); - new_named.size = Some(size); - self.handle = new_named.handle(); - } self } @@ -120,19 +106,6 @@ impl Icon { }; match self.handle.data { - Data::Name(named) => { - if let Some(path) = named.path() { - if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) { - from_svg(iced_core::svg::Handle::from_path(path)) - } else { - from_image(iced_core::image::Handle::from_path(path)) - } - } else { - let bytes: &'static [u8] = &[]; - from_svg(iced_core::svg::Handle::from_memory(bytes)) - } - } - Data::Image(handle) => from_image(handle), Data::Svg(handle) => from_svg(handle), } @@ -147,32 +120,14 @@ impl<'a, Message: 'a> From for Element<'a, Message> { /// Draw an icon in the given bounds via the runtime's renderer. pub fn draw(renderer: &mut crate::Renderer, handle: &Handle, icon_bounds: Rectangle) { - enum IcedHandle { - Svg(iced_core::svg::Handle), - Image(iced_core::image::Handle), - } - - let iced_handle = match handle.clone().data { - Data::Name(named) => named.path().map(|path| { - if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) { - IcedHandle::Svg(iced_core::svg::Handle::from_path(path)) - } else { - IcedHandle::Image(iced_core::image::Handle::from_path(path)) - } - }), - - Data::Image(handle) => Some(IcedHandle::Image(handle)), - Data::Svg(handle) => Some(IcedHandle::Svg(handle)), - }; - - match iced_handle { - Some(IcedHandle::Svg(handle)) => iced_core::svg::Renderer::draw_svg( + match handle.clone().data { + Data::Svg(handle) => iced_core::svg::Renderer::draw_svg( renderer, iced_core::svg::Svg::new(handle), icon_bounds, ), - Some(IcedHandle::Image(handle)) => { + Data::Image(handle) => { iced_core::image::Renderer::draw_image( renderer, handle, @@ -183,7 +138,5 @@ pub fn draw(renderer: &mut crate::Renderer, handle: &Handle, icon_bounds: Rectan [0.0; 4], ); } - - None => {} } } diff --git a/src/widget/icon/named.rs b/src/widget/icon/named.rs index e1c53500..8405e080 100644 --- a/src/widget/icon/named.rs +++ b/src/widget/icon/named.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 use super::{Handle, Icon}; -use std::{borrow::Cow, path::PathBuf, sync::Arc}; +use std::{borrow::Cow, ffi::OsStr, path::PathBuf, sync::Arc}; #[derive(Debug, Clone, Default, Hash)] /// Fallback icon to use if the icon was not found. @@ -116,9 +116,21 @@ impl Named { #[inline] pub fn handle(self) -> Handle { + let name = self.name.clone(); Handle { symbolic: self.symbolic, - data: super::Data::Name(self), + data: if let Some(path) = self.path() { + if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) { + super::Data::Svg(iced_core::svg::Handle::from_path(path)) + } else { + super::Data::Image(iced_core::image::Handle::from_path(path)) + } + } else { + super::bundle::get(&name).unwrap_or_else(|| { + let bytes: &'static [u8] = &[]; + super::Data::Svg(iced_core::svg::Handle::from_memory(bytes)) + }) + }, } } diff --git a/src/widget/nav_bar_toggle.rs b/src/widget/nav_bar_toggle.rs index 23495e3b..b0849dd2 100644 --- a/src/widget/nav_bar_toggle.rs +++ b/src/widget/nav_bar_toggle.rs @@ -28,18 +28,12 @@ pub const fn nav_bar_toggle() -> NavBarToggle { impl From> for Element<'_, Message> { fn from(nav_bar_toggle: NavBarToggle) -> Self { let icon = if nav_bar_toggle.active { - widget::icon::from_svg_bytes( - &include_bytes!("../../res/icons/navbar-open-symbolic.svg")[..], - ) - .symbolic(true) + "navbar-open-symbolic" } else { - widget::icon::from_svg_bytes( - &include_bytes!("../../res/icons/navbar-closed-symbolic.svg")[..], - ) - .symbolic(true) + "navbar-closed-symbolic" }; - widget::button::icon(icon) + widget::button::icon(widget::icon::from_name(icon)) .padding([8, 16]) .on_press_maybe(nav_bar_toggle.on_toggle) .selected(nav_bar_toggle.selected) diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index a93f2ee4..6f4a4de2 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -115,7 +115,7 @@ where } } -fn increment(value: T, step: T, min: T, max: T) -> T +fn increment(value: T, step: T, _min: T, max: T) -> T where T: Copy + Sub + Add + PartialOrd, { @@ -126,7 +126,7 @@ where } } -fn decrement(value: T, step: T, min: T, max: T) -> T +fn decrement(value: T, step: T, min: T, _max: T) -> T where T: Copy + Sub + Add + PartialOrd, { @@ -149,25 +149,25 @@ where } } } -macro_rules! make_button { - ($spin_button:expr, $icon:expr, $operation:expr) => {{ - #[cfg(target_os = "linux")] - let button = icon::from_name($icon); - #[cfg(not(target_os = "linux"))] - let button = - icon::from_svg_bytes(include_bytes!(concat!["../../res/icons/", $icon, ".svg"])) - .symbolic(true); - - button - .apply(button::icon) - .on_press(($spin_button.on_press)($operation( - $spin_button.value, - $spin_button.step, - $spin_button.min, - $spin_button.max, - ))) - }}; +fn make_button<'a, T, Message>( + spin_button: &SpinButton<'a, T, Message>, + icon: &'static str, + operation: fn(T, T, T, T) -> T, +) -> Element<'a, Message> +where + Message: Clone + 'static, + T: Copy + Sub + Add + PartialOrd, +{ + icon::from_name(icon) + .apply(button::icon) + .on_press((spin_button.on_press)(operation( + spin_button.value, + spin_button.step, + spin_button.min, + spin_button.max, + ))) + .into() } fn horizontal_variant(spin_button: SpinButton<'_, T, Message>) -> Element<'_, Message> @@ -175,8 +175,8 @@ where Message: Clone + 'static, T: Copy + Sub + Add + PartialOrd, { - let decrement_button = make_button!(spin_button, "list-remove-symbolic", decrement); - let increment_button = make_button!(spin_button, "list-add-symbolic", increment); + let decrement_button = make_button(&spin_button, "list-remove-symbolic", decrement); + let increment_button = make_button(&spin_button, "list-add-symbolic", increment); let label = text::body(spin_button.label) .apply(container) @@ -198,8 +198,8 @@ where Message: Clone + 'static, T: Copy + Sub + Add + PartialOrd, { - let decrement_button = make_button!(spin_button, "list-remove-symbolic", decrement); - let increment_button = make_button!(spin_button, "list-add-symbolic", increment); + let decrement_button = make_button(&spin_button, "list-remove-symbolic", decrement); + let increment_button = make_button(&spin_button, "list-add-symbolic", increment); let label = text::body(spin_button.label) .apply(container) diff --git a/src/widget/warning.rs b/src/widget/warning.rs index 3e3a1ad4..942ffb8b 100644 --- a/src/widget/warning.rs +++ b/src/widget/warning.rs @@ -33,20 +33,11 @@ impl<'a, Message: 'static + Clone> Warning<'a, Message> { pub fn into_widget(self) -> widget::Container<'a, Message, crate::Theme, Renderer> { let label = widget::container(crate::widget::text(self.message)).width(Length::Fill); - #[cfg(target_os = "linux")] let close_button = icon::from_name("window-close-symbolic") .size(16) .apply(widget::button::icon) .on_press_maybe(self.on_close); - #[cfg(not(target_os = "linux"))] - let close_button = - icon::from_svg_bytes(include_bytes!("../../res/icons/window-close-symbolic.svg")) - .symbolic(true) - .apply(widget::button::icon) - .icon_size(16) - .on_press_maybe(self.on_close); - widget::row::with_capacity(2) .push(label) .push(close_button) From 62f661e077a11090f35ff8ace026e4387ab8c85e Mon Sep 17 00:00:00 2001 From: Kyle Scheuing Date: Wed, 26 Nov 2025 01:25:27 -0500 Subject: [PATCH 370/556] fix: compile errors on windows calendar.rs had some left over icon! macro_rules macros referencing now deleted files. bundle::get was defined twice on non-unix platforms. A known remaining issue is that projects using libcosmic need to have cosmic-icons in their project root, since the crabtime macro uses crabtime::WORKSPACE_PATH rather than the path to wherever cargo puts libcosmic's git submodule. See: 639326fcc31a95a0c7a9a5bb56f1ed4d53530f26 --- src/widget/calendar.rs | 27 ++++++++++++--------------- src/widget/icon/bundle.rs | 1 + 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index 7f9ac0ad..2e21ebfc 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -117,19 +117,6 @@ where Message: Clone + 'static, { fn from(this: Calendar<'a, Message>) -> Self { - macro_rules! icon { - ($name:expr, $on_press:expr) => {{ - #[cfg(target_os = "linux")] - let icon = { icon::from_name($name).apply(button::icon) }; - #[cfg(not(target_os = "linux"))] - let icon = { - icon::from_svg_bytes(include_bytes!(concat!("../../res/icons/", $name, ".svg"))) - .symbolic(true) - .apply(button::icon) - }; - icon.padding([0, 12]).on_press($on_press) - }}; - } macro_rules! translate_month { ($month:expr, $year:expr) => {{ match $month { @@ -170,8 +157,18 @@ where .size(18); let month_controls = row::with_capacity(2) - .push(icon!("go-previous-symbolic", (this.on_prev)())) - .push(icon!("go-next-symbolic", (this.on_next)())); + .push( + icon::from_name("go-previous-symbolic") + .apply(button::icon) + .padding([0, 12]) + .on_press((this.on_prev)()), + ) + .push( + icon::from_name("go-next-symbolic") + .apply(button::icon) + .padding([0, 12]) + .on_press((this.on_next)()), + ); // Calender let mut calendar_grid: Grid<'_, Message> = diff --git a/src/widget/icon/bundle.rs b/src/widget/icon/bundle.rs index 0e1fdc16..bdb74c04 100644 --- a/src/widget/icon/bundle.rs +++ b/src/widget/icon/bundle.rs @@ -4,6 +4,7 @@ //! Embedded icons for platforms which do not support icon themes yet. /// Icon bundling is not enabled on unix platforms. +#[cfg(unix)] pub fn get(icon_name: &str) -> Option { None } From 882481e518f786a92a77cd24e7f8669e63441108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Tue, 18 Nov 2025 13:29:56 +0100 Subject: [PATCH 371/556] fix(popover): match popup styling to designs --- src/widget/popover.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/widget/popover.rs b/src/widget/popover.rs index ddc31455..26120b75 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -14,18 +14,20 @@ use iced_core::{ Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, }; -pub use iced_widget::container::{Catalog, Style}; - pub fn popover<'a, Message, Renderer>( content: impl Into>, -) -> Popover<'a, Message, Renderer> { +) -> Popover<'a, Message, Renderer> +where + Renderer: iced_core::Renderer + 'a, + Message: 'a, +{ Popover::new(content) } #[derive(Clone, Copy, Debug, Default)] pub enum Position { - #[default] Center, + #[default] Bottom, Point(Point), } @@ -40,7 +42,11 @@ pub struct Popover<'a, Message, Renderer> { on_close: Option, } -impl<'a, Message, Renderer> Popover<'a, Message, Renderer> { +impl<'a, Message, Renderer> Popover<'a, Message, Renderer> +where + Renderer: iced_core::Renderer + 'a, + Message: 'a, +{ pub fn new(content: impl Into>) -> Self { Self { content: content.into(), @@ -67,7 +73,12 @@ impl<'a, Message, Renderer> Popover<'a, Message, Renderer> { #[inline] pub fn popup(mut self, popup: impl Into>) -> Self { - self.popup = Some(popup.into()); + self.popup = Some( + iced_widget::container(popup) + .padding(crate::theme::spacing().space_xxs) + .class(crate::style::Container::Dropdown) + .into(), + ); self } From 14cbebbadc015d4fd3d7d702e1f77d4b879ffc5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Tue, 2 Dec 2025 17:32:08 +0100 Subject: [PATCH 372/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index c9cd78e0..10db38f9 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit c9cd78e030d5b228f190af28d908e1fbcf8737ce +Subproject commit 10db38f982001a714bd94e99a082368762b378ee From 18182e5f97c989b93e50a6f425073232f217692f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Tue, 2 Dec 2025 18:00:56 +0100 Subject: [PATCH 373/556] fix(popover): set default position to `Bottom` I didn't see this part in my previous PR (sorry!). --- src/widget/popover.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/popover.rs b/src/widget/popover.rs index 26120b75..4cea3ebf 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -52,7 +52,7 @@ where content: content.into(), modal: false, popup: None, - position: Position::Center, + position: Position::Bottom, on_close: None, } } From 80875d596219c5ca354340b861d0274e8fa68e64 Mon Sep 17 00:00:00 2001 From: Kyle Scheuing Date: Thu, 4 Dec 2025 11:31:47 -0500 Subject: [PATCH 374/556] fix: compiling on windows requires cosmic-icons in project root * fix: compiling on windows requires cosmic-icons in project root crabtime provides crabtime::WORKSPACE_PATH to refer to the CARGO_MANIFEST_DIR of the top level crate being built, which means when building libcosmic directly, crabtime::WORKSPACE_PATH will work, but when building it as a dependency of another crate, crabtime::WORKSPACE_PATH will no longer refer to the path to libcosmic. I don't think there's a good workaround, since when in the context of crabtime, CARGO_MANIFEST_DIR refers to the path to the crate generated by crabtime rather than to libcosmic. This replaces crabtime with a simple build.rs script that generates a file in OUT_DIR. * fix: do not generate icon bundle for unix targets --------- Co-authored-by: Michael Aaron Murphy --- Cargo.toml | 2 -- build.rs | 56 +++++++++++++++++++++++++++++++++++++++ src/widget/icon/bundle.rs | 44 +----------------------------- 3 files changed, 57 insertions(+), 45 deletions(-) create mode 100644 build.rs diff --git a/Cargo.toml b/Cargo.toml index 4d742126..927444e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,8 +107,6 @@ cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-c chrono = "0.4.42" cosmic-config = { path = "cosmic-config" } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } -# Compile-time generation of code -crabtime = "1.1.4" # Internationalization i18n-embed = { version = "0.16.0", features = [ "fluent-system", diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..7ba035d4 --- /dev/null +++ b/build.rs @@ -0,0 +1,56 @@ +fn main() { + println!("cargo::rerun-if-changed=build.rs"); + + #[cfg(not(unix))] + generate_bundled_icons(); +} + +#[cfg(not(unix))] +fn generate_bundled_icons() { + println!("cargo::rerun-if-changed=cosmic-icons"); + + let manifest_dir = std::path::Path::new(std::env!("CARGO_MANIFEST_DIR")); + let icon_paths = [ + "cosmic-icons/freedesktop/scalable", + "cosmic-icons/extra/scalable", + ]; + + let key_value_assignments = icon_paths + .into_iter() + .map(|path| manifest_dir.join(path)) + .inspect(|icon_path| assert!(icon_path.exists(), "path = {icon_path:?}")) + .map(|icon_path| std::fs::read_dir(icon_path).unwrap()) + .flat_map(|dir| { + dir.flat_map(|entry| entry.unwrap().path().read_dir().unwrap()) + .map(|entry| { + let entry = entry.unwrap(); + let path = entry.path().canonicalize().unwrap(); + let file_name = path.file_stem().unwrap().to_str().unwrap().to_owned(); + let path = path.into_os_string().into_string().unwrap(); + (file_name, path) + }) + }) + .fold( + std::collections::BTreeMap::new(), + |mut set, (name, path)| { + set.insert(name, path); + set + }, + ) + .into_iter() + .fold(String::new(), |mut output, (name, path)| { + output.push_str(&format!(" \"{name}\" => include_bytes!(\"{path}\"),\n")); + output + }); + + let code = [ + "static ICONS: phf::Map<&'static str, &'static [u8]> = phf::phf_map!(\n", + &key_value_assignments, + ");", + ] + .concat(); + + let out_dir = std::env::var_os("OUT_DIR").unwrap(); + let out_file = std::path::Path::new(&out_dir).join("bundled_icons.rs"); + std::fs::write(&out_file, &code).unwrap(); +} diff --git a/src/widget/icon/bundle.rs b/src/widget/icon/bundle.rs index bdb74c04..9d0877d0 100644 --- a/src/widget/icon/bundle.rs +++ b/src/widget/icon/bundle.rs @@ -18,46 +18,4 @@ pub fn get(icon_name: &str) -> Option { } #[cfg(not(unix))] -#[crabtime::expression] -fn comptime_icon_bundler() -> String { - let manifest_dir = std::path::Path::new(crabtime::WORKSPACE_PATH); - let icon_paths = [ - "cosmic-icons/freedesktop/scalable", - "cosmic-icons/extra/scalable", - ]; - - let key_value_assignments = icon_paths - .into_iter() - .map(|path| manifest_dir.join(path)) - .inspect(|icon_path| assert!(icon_path.exists(), "path = {icon_path:?}")) - .map(|icon_path| std::fs::read_dir(icon_path).unwrap()) - .flat_map(|dir| { - dir.flat_map(|entry| entry.unwrap().path().read_dir().unwrap()) - .map(|entry| { - let entry = entry.unwrap(); - let path = entry.path().canonicalize().unwrap(); - let file_name = path.file_stem().unwrap().to_str().unwrap().to_owned(); - let path = path.into_os_string().into_string().unwrap(); - (file_name, path) - }) - }) - .fold( - std::collections::BTreeMap::new(), - |mut set, (name, path)| { - set.insert(name, path); - set - }, - ) - .into_iter() - .fold(String::new(), |mut output, (name, path)| { - output.push_str(&format!(" \"{name}\" => include_bytes!(\"{path}\"),\n")); - output - }); - - ["phf::phf_map!(\n", &key_value_assignments, ")"].concat() -} - -#[cfg(not(unix))] -static ICONS: phf::Map<&'static str, &'static [u8]> = { - comptime_icon_bundler! {} -}; +include!(concat!(env!("OUT_DIR"), "/bundled_icons.rs")); From 54934a961fc39d43bd22e7246f18a75b5935f37e Mon Sep 17 00:00:00 2001 From: Kyle Scheuing Date: Thu, 4 Dec 2025 13:21:34 -0500 Subject: [PATCH 375/556] fix: cross compiling for windows from linux #[cfg(not(unix))] applies to the host machine (since that's where the build script is running) rather than the compilation target. Instead, environment variables are available to provide the information relevant to the build target at the build script's runtime. --- build.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/build.rs b/build.rs index 7ba035d4..a8f1a4cc 100644 --- a/build.rs +++ b/build.rs @@ -1,11 +1,13 @@ +use std::env; + fn main() { println!("cargo::rerun-if-changed=build.rs"); - #[cfg(not(unix))] - generate_bundled_icons(); + if env::var_os("CARGO_CFG_UNIX").is_none() { + generate_bundled_icons(); + } } -#[cfg(not(unix))] fn generate_bundled_icons() { println!("cargo::rerun-if-changed=cosmic-icons"); From c2b7d7847a34079213dcc9af421ffb16dc7f77e3 Mon Sep 17 00:00:00 2001 From: Frederic Laing Date: Tue, 2 Dec 2025 17:21:28 +0100 Subject: [PATCH 376/556] feat: add Flatpak sandbox support for config paths Implement get_config_dir() and get_state_dir() helper functions that detect Flatpak sandboxing via FLATPAK_ID and use HOST_XDG_CONFIG_HOME/HOST_XDG_STATE_HOME environment variables or fallback to HOME-based paths. This allows libcosmic apps running in Flatpak sandboxes to properly read system-wide COSMIC configuration (themes, corner radii, etc.) from the host. --- cosmic-config/src/lib.rs | 48 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 5f424cc3..c8eda064 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -6,12 +6,54 @@ use notify::{ }; use serde::{Serialize, de::DeserializeOwned}; use std::{ - fmt, fs, + env, fmt, fs, io::Write, path::{Path, PathBuf}, sync::Mutex, }; +/// Get the config directory, with Flatpak sandbox support. +/// In Flatpak, HOST_XDG_CONFIG_HOME points to the real user config directory, +/// allowing sandboxed apps to read host config files. +fn get_config_dir() -> Option { + // Check if we're running in Flatpak + if let Some(flatpak_id) = env::var_os("FLATPAK_ID") { + tracing::debug!("Running in Flatpak: {:?}", flatpak_id); + // Try HOST_XDG_CONFIG_HOME first (requires --filesystem=xdg-config permission) + if let Some(host_config) = env::var_os("HOST_XDG_CONFIG_HOME") { + tracing::debug!("Using HOST_XDG_CONFIG_HOME: {:?}", host_config); + return Some(PathBuf::from(host_config)); + } + // Fallback: try to construct from HOME (which points to real home in Flatpak) + if let Some(home) = env::var_os("HOME") { + let config_path = PathBuf::from(&home).join(".config"); + tracing::debug!("Using HOME fallback for config: {:?}", config_path); + return Some(config_path); + } + tracing::warn!("Flatpak detected but no config directory found"); + } + // Not in Flatpak or no host config available, use standard dirs + let config_dir = dirs::config_dir(); + tracing::debug!("Using standard config dir: {:?}", config_dir); + config_dir +} + +/// Get the state directory, with Flatpak sandbox support. +fn get_state_dir() -> Option { + // Check if we're running in Flatpak + if env::var_os("FLATPAK_ID").is_some() { + // Try HOST_XDG_STATE_HOME first + if let Some(host_state) = env::var_os("HOST_XDG_STATE_HOME") { + return Some(PathBuf::from(host_state)); + } + // Fallback: try to construct from HOME + if let Some(home) = env::var_os("HOME") { + return Some(PathBuf::from(home).join(".local").join("state")); + } + } + dirs::state_dir() +} + #[cfg(feature = "subscription")] mod subscription; #[cfg(feature = "subscription")] @@ -170,7 +212,7 @@ impl Config { .map(|x| x.join("COSMIC").join(&path)); // Get libcosmic user configuration directory - let mut user_path = dirs::config_dir().ok_or(Error::NoConfigDirectory)?; + let mut user_path = get_config_dir().ok_or(Error::NoConfigDirectory)?; user_path.push("cosmic"); user_path.push(path); @@ -212,7 +254,7 @@ impl Config { let path = sanitize_name(name)?.join(format!("v{}", version)); // Get libcosmic user state directory - let mut user_path = dirs::state_dir().ok_or(Error::NoConfigDirectory)?; + let mut user_path = get_state_dir().ok_or(Error::NoConfigDirectory)?; user_path.push("cosmic"); user_path.push(path); // Create new state directory if not found. From 45fd683bc949e82d40005d8d80dcd8a422821342 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 5 Dec 2025 16:42:29 +0100 Subject: [PATCH 377/556] examples(about): update and fix compile --- examples/about/Cargo.toml | 3 -- examples/about/src/main.rs | 80 ++++++++++++++++---------------------- 2 files changed, 33 insertions(+), 50 deletions(-) diff --git a/examples/about/Cargo.toml b/examples/about/Cargo.toml index cf067095..0f598535 100644 --- a/examples/about/Cargo.toml +++ b/examples/about/Cargo.toml @@ -4,9 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] -tracing = "0.1.41" -tracing-subscriber = "0.3.19" -tracing-log = "0.2.0" open = "5.3.2" [dependencies.libcosmic] diff --git a/examples/about/src/main.rs b/examples/about/src/main.rs index 957433f0..50f25da4 100644 --- a/examples/about/src/main.rs +++ b/examples/about/src/main.rs @@ -5,17 +5,14 @@ use cosmic::app::context_drawer::{self, ContextDrawer}; use cosmic::app::{Core, Settings, Task}; -use cosmic::iced::widget::column; -use cosmic::iced_core::Size; +use cosmic::executor; +use cosmic::iced::{alignment, Length, Size}; +use cosmic::prelude::*; use cosmic::widget::{self, about::About, nav_bar}; -use cosmic::{executor, iced, ApplicationExt, Element}; /// Runs application with these settings #[rustfmt::skip] fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - let _ = tracing_log::LogTracer::init(); - let settings = Settings::default() .size(Size::new(1024., 768.)); @@ -67,12 +64,12 @@ impl cosmic::Application for App { let about = About::default() .name("About Demo") - .icon(Self::APP_ID) + .icon(widget::icon::from_name(Self::APP_ID)) .version("0.1.0") - .author("System 76") + .author("System76") .license("GPL-3.0-only") - //.license_url("https://www.some-custom-license-url.com") - .developers([("Michael Murphy", "mmstick@system76.com")]) + .license_url("https://choosealicense.com/licenses/gpl-3.0/") + .developers([("Michael Murphy", "info@system76.com")]) .links([ ("Website", "https://system76.com/cosmic"), ("Repository", "https://github.com/pop-os/libcosmic"), @@ -86,7 +83,11 @@ impl cosmic::Application for App { show_about: false, }; - let command = app.update_title(); + app.set_header_title("COSMIC About Example".into()); + let command = app.set_window_title( + "COSMIC About Example".into(), + app.core.main_window_id().unwrap(), + ); (app, command) } @@ -99,12 +100,17 @@ impl cosmic::Application for App { /// Called when a navigation item is selected. fn on_nav_select(&mut self, id: nav_bar::Id) -> Task { self.nav_model.activate(id); - self.update_title() + Task::none() } - fn context_drawer(&self) -> Option> { - self.show_about - .then(|| context_drawer::about(&self.about, Message::Open, Message::ToggleAbout)) + fn context_drawer(&self) -> Option> { + self.show_about.then(|| { + context_drawer::about( + &self.about, + |url| Message::Open(url.to_owned()), + Message::ToggleAbout, + ) + }) } /// Handle application events here. @@ -116,47 +122,27 @@ impl cosmic::Application for App { } Message::Open(url) => match open::that_detached(url) { Ok(_) => (), - Err(err) => tracing::error!("Failed to open URL: {err}"), + Err(err) => eprintln!("Failed to open URL: {err}"), }, } Task::none() } /// Creates a view after each update. - fn view(&self) -> Element { + fn view(&self) -> Element<'_, Self::Message> { + let show_about_button = widget::button::text("Show about").on_press(Message::ToggleAbout); let centered = cosmic::widget::container( - column![widget::button::text("Show about").on_press(Message::ToggleAbout)] - .width(iced::Length::Fill) - .height(iced::Length::Shrink) - .align_x(iced::alignment::Horizontal::Center), + widget::column() + .push(show_about_button) + .width(Length::Fill) + .height(Length::Shrink) + .align_x(alignment::Horizontal::Center), ) - .width(iced::Length::Fill) - .height(iced::Length::Shrink) - .align_x(iced::alignment::Horizontal::Center) - .align_y(iced::alignment::Vertical::Center); + .width(Length::Fill) + .height(Length::Shrink) + .align_x(alignment::Horizontal::Center) + .align_y(alignment::Vertical::Center); Element::from(centered) } } - -impl App -where - Self: cosmic::Application, -{ - fn active_page_title(&mut self) -> &str { - self.nav_model - .text(self.nav_model.active()) - .unwrap_or("Unknown Page") - } - - fn update_title(&mut self) -> Task { - let header_title = self.active_page_title().to_owned(); - let window_title = format!("{header_title} — COSMIC AppDemo"); - self.set_header_title(header_title); - if let Some(id) = self.core.main_window_id() { - self.set_window_title(window_title, id) - } else { - Task::none() - } - } -} From 866da0f94b9111174f0460ba3c395d1fe055a216 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 5 Dec 2025 16:44:39 +0100 Subject: [PATCH 378/556] revert: "fix(popover): set default position to `Bottom`" Causes popups to be misplaced in applications that required the previous behavior. This reverts commit 18182e5f97c989b93e50a6f425073232f217692f. --- src/widget/popover.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/popover.rs b/src/widget/popover.rs index 4cea3ebf..26120b75 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -52,7 +52,7 @@ where content: content.into(), modal: false, popup: None, - position: Position::Bottom, + position: Position::Center, on_close: None, } } From e13ab241510ddc2e9ddfc112786426328b3124de Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 5 Dec 2025 16:46:00 +0100 Subject: [PATCH 379/556] revert: "fix(popover): match popup styling to designs" Some application popovers required the previous behavior This reverts commit 882481e518f786a92a77cd24e7f8669e63441108. --- src/widget/popover.rs | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/widget/popover.rs b/src/widget/popover.rs index 26120b75..ddc31455 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -14,20 +14,18 @@ use iced_core::{ Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, }; +pub use iced_widget::container::{Catalog, Style}; + pub fn popover<'a, Message, Renderer>( content: impl Into>, -) -> Popover<'a, Message, Renderer> -where - Renderer: iced_core::Renderer + 'a, - Message: 'a, -{ +) -> Popover<'a, Message, Renderer> { Popover::new(content) } #[derive(Clone, Copy, Debug, Default)] pub enum Position { - Center, #[default] + Center, Bottom, Point(Point), } @@ -42,11 +40,7 @@ pub struct Popover<'a, Message, Renderer> { on_close: Option, } -impl<'a, Message, Renderer> Popover<'a, Message, Renderer> -where - Renderer: iced_core::Renderer + 'a, - Message: 'a, -{ +impl<'a, Message, Renderer> Popover<'a, Message, Renderer> { pub fn new(content: impl Into>) -> Self { Self { content: content.into(), @@ -73,12 +67,7 @@ where #[inline] pub fn popup(mut self, popup: impl Into>) -> Self { - self.popup = Some( - iced_widget::container(popup) - .padding(crate::theme::spacing().space_xxs) - .class(crate::style::Container::Dropdown) - .into(), - ); + self.popup = Some(popup.into()); self } From 8a9cd0da326e638a78149439a71753ea16c31540 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sun, 30 Nov 2025 02:53:48 +0100 Subject: [PATCH 380/556] i18n: translation updates from weblate Co-authored-by: CYAXXX Co-authored-by: Hosted Weblate --- i18n/kmr/libcosmic.ftl | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 i18n/kmr/libcosmic.ftl diff --git a/i18n/kmr/libcosmic.ftl b/i18n/kmr/libcosmic.ftl new file mode 100644 index 00000000..e69de29b From 2ffd1f32f404ac7f9fcb8fafeb309ce43d3fd8c3 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 5 Dec 2025 17:05:20 +0100 Subject: [PATCH 381/556] examples(application): update and fix compile --- examples/application/src/main.rs | 92 +++++++++++++++----------------- 1 file changed, 42 insertions(+), 50 deletions(-) diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index c70a9d30..45805579 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -3,25 +3,14 @@ //! Application API example +use cosmic::app::Settings; +use cosmic::iced::{Alignment, Length, Size}; +use cosmic::widget::menu::{self, KeyBind}; +use cosmic::widget::nav_bar; +use cosmic::{executor, iced, prelude::*, widget, Core}; use std::collections::HashMap; use std::sync::LazyLock; -use cosmic::app::{Core, Settings, Task}; -use cosmic::iced::alignment::{Horizontal, Vertical}; -use cosmic::iced::widget::column; -use cosmic::iced::Length; -use cosmic::iced_core::Size; -use cosmic::widget::icon::{from_name, Handle}; -use cosmic::widget::menu::KeyBind; -use cosmic::widget::{button, text}; -use cosmic::widget::{ - container, - menu::menu_button, - menu::{self, action::MenuAction}, - nav_bar, responsive, -}; -use cosmic::{executor, iced, ApplicationExt, Element}; - static MENU_ID: LazyLock = LazyLock::new(|| iced::id::Id::new("menu_id")); #[derive(Clone, Copy)] @@ -50,7 +39,7 @@ pub enum Action { Hi3, } -impl MenuAction for Action { +impl widget::menu::Action for Action { type Message = Message; fn message(&self) -> Message { @@ -129,7 +118,7 @@ impl cosmic::Application for App { } /// Creates the application, and optionally emits task on initialize. - fn init(core: Core, input: Self::Flags) -> (Self, Task) { + fn init(core: Core, input: Self::Flags) -> (Self, cosmic::app::Task) { let mut nav_model = nav_bar::Model::default(); for (title, content) in input { @@ -158,13 +147,13 @@ impl cosmic::Application for App { } /// Called when a navigation item is selected. - fn on_nav_select(&mut self, id: nav_bar::Id) -> Task { + fn on_nav_select(&mut self, id: nav_bar::Id) -> cosmic::app::Task { self.nav_model.activate(id); self.update_title() } /// Handle application events here. - fn update(&mut self, message: Self::Message) -> Task { + fn update(&mut self, message: Self::Message) -> cosmic::app::Task { match message { Message::Input1(v) => { self.input_1 = v; @@ -195,46 +184,49 @@ impl cosmic::Application for App { } /// Creates a view after each update. - fn view(&self) -> Element { + fn view(&self) -> Element<'_, Self::Message> { let page_content = self .nav_model .active_data::() .map_or("No page selected", String::as_str); - let text = cosmic::widget::text(page_content); - - let centered = cosmic::widget::container( - column![ - text, - cosmic::widget::text_input::text_input("", &self.input_1) - .on_input(Message::Input1) - .on_clear(Message::Ignore), - cosmic::widget::text_input::secure_input( - "", - &self.input_1, - Some(Message::ToggleHide), - self.hidden + let centered = widget::container( + widget::column() + .push(widget::text::body(page_content)) + .push( + widget::text_input::text_input("", &self.input_1) + .on_input(Message::Input1) + .on_clear(Message::Ignore), ) - .on_input(Message::Input1), - cosmic::widget::text_input::text_input("", &self.input_1).on_input(Message::Input1), - cosmic::widget::text_input::search_input("", &self.input_2) - .on_input(Message::Input2) - .on_clear(Message::Ignore), - ] - .spacing(cosmic::theme::spacing().space_s) - .width(iced::Length::Fill) - .height(iced::Length::Shrink) - .align_x(iced::Alignment::Center), + .push( + widget::text_input::secure_input( + "", + &self.input_1, + Some(Message::ToggleHide), + self.hidden, + ) + .on_input(Message::Input1), + ) + .push(widget::text_input::text_input("", &self.input_2).on_input(Message::Input2)) + .push( + widget::text_input::search_input("", &self.input_2) + .on_input(Message::Input2) + .on_clear(Message::Ignore), + ) + .spacing(cosmic::theme::spacing().space_s) + .width(Length::Fill) + .height(Length::Shrink) + .align_x(Alignment::Center), ) - .width(iced::Length::Fill) - .height(iced::Length::Shrink) - .align_x(iced::Alignment::Center) - .align_y(iced::Alignment::Center); + .width(Length::Fill) + .height(Length::Shrink) + .align_x(Alignment::Center) + .align_y(Alignment::Center); Element::from(centered) } - fn header_start(&self) -> Vec> { + fn header_start(&self) -> Vec> { vec![cosmic::widget::responsive_menu_bar().into_element( self.core(), &self.keybinds, @@ -322,7 +314,7 @@ where .unwrap_or("Unknown Page") } - fn update_title(&mut self) -> Task { + fn update_title(&mut self) -> cosmic::app::Task { let header_title = self.active_page_title().to_owned(); let window_title = format!("{header_title} — COSMIC AppDemo"); self.set_header_title(header_title); From 6793950bbc6039eb59cfd0ee92a95919fb636e81 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 5 Dec 2025 17:16:35 +0100 Subject: [PATCH 382/556] fix(icon): from_svg_bytes should not default to symbolic --- src/widget/icon/handle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/icon/handle.rs b/src/widget/icon/handle.rs index a4ddd364..7e0bab02 100644 --- a/src/widget/icon/handle.rs +++ b/src/widget/icon/handle.rs @@ -94,7 +94,7 @@ pub fn from_raster_pixels( /// Create a SVG handle from memory. pub fn from_svg_bytes(bytes: impl Into>) -> Handle { Handle { - symbolic: true, + symbolic: false, data: Data::Svg(svg::Handle::from_memory(bytes)), } } From cdf4eafc9ecb53693000f8011362238ef9c3f769 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 5 Dec 2025 17:18:26 +0100 Subject: [PATCH 383/556] fix(segmented_button): set icon to symbolic --- src/widget/segmented_button/widget.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index e852a2eb..72bc7580 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -1935,6 +1935,7 @@ where match crate::widget::common::object_select().data() { crate::iced_core::svg::Data::Bytes(bytes) => { crate::widget::icon::from_svg_bytes(bytes.as_ref()) + .symbolic(true) } crate::iced_core::svg::Data::Path(path) => { crate::widget::icon::from_path(path.clone()) From f39ad728c9f84394d9a8ac8d4543b9c1c2aec8a2 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 5 Dec 2025 17:29:11 +0100 Subject: [PATCH 384/556] examples(calendar): update and fix compile --- examples/calendar/src/main.rs | 4 ++-- src/widget/calendar.rs | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/calendar/src/main.rs b/examples/calendar/src/main.rs index 47549a70..fec7b543 100644 --- a/examples/calendar/src/main.rs +++ b/examples/calendar/src/main.rs @@ -84,7 +84,7 @@ impl cosmic::Application for App { } /// Creates a view after each update. - fn view(&self) -> Element { + fn view(&self) -> Element<'_, Self::Message> { let mut content = cosmic::widget::column().spacing(12); let calendar = cosmic::widget::calendar( @@ -111,7 +111,7 @@ impl App where Self: cosmic::Application, { - fn update_title(&mut self) -> Task { + fn update_title(&mut self) -> cosmic::app::Task { self.set_header_title(String::from("Calendar Demo")); self.set_window_title(String::from("Calendar Demo")) } diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index 2e21ebfc..7ee06204 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -10,6 +10,7 @@ use crate::iced_core::{Alignment, Length, Padding}; use crate::widget::{Grid, button, column, grid, icon, row, text}; use apply::Apply; use chrono::{Datelike, Days, Local, Month, Months, NaiveDate, Weekday}; +use iced::alignment::Vertical; /// A widget that displays an interactive calendar. pub fn calendar( @@ -156,17 +157,17 @@ where )) .size(18); + let day = text::body(translate_weekday!(this.model.visible.weekday())); + let month_controls = row::with_capacity(2) .push( icon::from_name("go-previous-symbolic") .apply(button::icon) - .padding([0, 12]) .on_press((this.on_prev)()), ) .push( icon::from_name("go-next-symbolic") .apply(button::icon) - .padding([0, 12]) .on_press((this.on_next)()), ); @@ -216,10 +217,11 @@ where let content_list = column::with_children([ row::with_children([ - date.into(), + column().push(date).push(day).into(), crate::widget::Space::with_width(Length::Fill).into(), month_controls.into(), ]) + .align_y(Vertical::Center) .padding([12, 20]) .into(), calendar_grid.into(), From 05c66088422b7a03e54ac1f96ff50339cc7776cf Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 5 Dec 2025 17:59:42 +0100 Subject: [PATCH 385/556] examples: fix libcosmic features, warnings, etc. --- examples/about/Cargo.toml | 3 --- examples/application/Cargo.toml | 3 --- examples/calendar/Cargo.toml | 1 - examples/calendar/src/main.rs | 5 ++++- examples/config/src/main.rs | 2 +- examples/context-menu/Cargo.toml | 3 +-- examples/context-menu/src/main.rs | 4 +--- examples/image-button/Cargo.toml | 3 +-- examples/image-button/src/main.rs | 7 +++++-- examples/menu/Cargo.toml | 3 +-- examples/menu/src/main.rs | 4 ++-- examples/multi-window/Cargo.toml | 2 +- examples/multi-window/src/window.rs | 12 ++++++------ examples/nav-context/Cargo.toml | 3 +-- examples/nav-context/src/main.rs | 2 +- examples/open-dialog/Cargo.toml | 3 +-- examples/open-dialog/src/main.rs | 10 +++++++--- examples/spin-button/Cargo.toml | 2 +- examples/spin-button/src/main.rs | 2 +- examples/table-view/Cargo.toml | 3 +-- examples/table-view/src/main.rs | 2 +- examples/text-input/Cargo.toml | 3 +-- examples/text-input/src/main.rs | 4 ++-- 23 files changed, 40 insertions(+), 46 deletions(-) diff --git a/examples/about/Cargo.toml b/examples/about/Cargo.toml index 0f598535..d2642cd6 100644 --- a/examples/about/Cargo.toml +++ b/examples/about/Cargo.toml @@ -8,18 +8,15 @@ open = "5.3.2" [dependencies.libcosmic] path = "../../" -default-features = false features = [ "debug", "winit", "tokio", "xdg-portal", - "dbus-config", "desktop", "a11y", "wayland", "wgpu", "single-instance", - "multi-window", "about", ] diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index e5ae2f30..28c13117 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -14,16 +14,13 @@ tracing-log = "0.2.0" [dependencies.libcosmic] path = "../../" -default-features = false features = [ "debug", "winit", "tokio", "xdg-portal", - "dbus-config", "a11y", "wgpu", "single-instance", - "multi-window", "surface-message", ] diff --git a/examples/calendar/Cargo.toml b/examples/calendar/Cargo.toml index 18bc6b49..9ffb838c 100644 --- a/examples/calendar/Cargo.toml +++ b/examples/calendar/Cargo.toml @@ -10,5 +10,4 @@ chrono = "0.4.40" [dependencies.libcosmic] path = "../../" -default-features = false features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/calendar/src/main.rs b/examples/calendar/src/main.rs index fec7b543..589bc1ff 100644 --- a/examples/calendar/src/main.rs +++ b/examples/calendar/src/main.rs @@ -113,6 +113,9 @@ where { fn update_title(&mut self) -> cosmic::app::Task { self.set_header_title(String::from("Calendar Demo")); - self.set_window_title(String::from("Calendar Demo")) + self.set_window_title( + String::from("Calendar Demo"), + self.core.main_window_id().unwrap(), + ) } } diff --git a/examples/config/src/main.rs b/examples/config/src/main.rs index f606e15c..f6fb5c0d 100644 --- a/examples/config/src/main.rs +++ b/examples/config/src/main.rs @@ -4,7 +4,7 @@ use cosmic_config::{Config, ConfigGet, ConfigSet}; fn test_config(config: Config) { - let watcher = config + let _watcher = config .watch(|config, keys| { println!("Changed: {:?}", keys); for key in keys.iter() { diff --git a/examples/context-menu/Cargo.toml b/examples/context-menu/Cargo.toml index 9a24a1c8..45cbf78a 100644 --- a/examples/context-menu/Cargo.toml +++ b/examples/context-menu/Cargo.toml @@ -10,13 +10,12 @@ tracing-log = "0.2.0" [dependencies.libcosmic] path = "../../" -default-features = false features = [ "debug", "winit", + "wgpu", "tokio", "xdg-portal", - "multi-window", "surface-message", "wayland", ] diff --git a/examples/context-menu/src/main.rs b/examples/context-menu/src/main.rs index c744f963..db66ba1b 100644 --- a/examples/context-menu/src/main.rs +++ b/examples/context-menu/src/main.rs @@ -37,7 +37,6 @@ pub enum Message { pub struct App { core: Core, button_label: String, - show_context: bool, hide_content: bool, } @@ -69,7 +68,6 @@ impl cosmic::Application for App { core, button_label: String::from("Right click me"), hide_content: false, - show_context: false, }; app.set_header_title("COSMIC Context Menu Demo".into()); @@ -102,7 +100,7 @@ impl cosmic::Application for App { } /// Creates a view after each update. - fn view(&self) -> Element { + fn view(&self) -> Element<'_, Self::Message> { let widget = cosmic::widget::context_menu( cosmic::widget::button::text(self.button_label.to_string()).on_press(Message::Clicked), self.context_menu(), diff --git a/examples/image-button/Cargo.toml b/examples/image-button/Cargo.toml index 110be619..cf61955a 100644 --- a/examples/image-button/Cargo.toml +++ b/examples/image-button/Cargo.toml @@ -9,5 +9,4 @@ tracing-subscriber = "0.3.19" [dependencies.libcosmic] path = "../../" -default-features = false -features = ["debug", "winit", "tokio"] +features = ["debug", "winit", "wgpu", "tokio"] diff --git a/examples/image-button/src/main.rs b/examples/image-button/src/main.rs index 34d907e7..0ac906ca 100644 --- a/examples/image-button/src/main.rs +++ b/examples/image-button/src/main.rs @@ -79,7 +79,7 @@ impl cosmic::Application for App { } /// Creates a view after each update. - fn view(&self) -> Element { + fn view(&self) -> Element<'_, Self::Message> { let mut content = cosmic::widget::column().spacing(12); for (id, image) in self.images.iter().enumerate() { @@ -108,6 +108,9 @@ where { fn update_title(&mut self) -> Task { self.set_header_title(String::from("Image Button Demo")); - self.set_window_title(String::from("Image Button Demo")) + self.set_window_title( + String::from("Image Button Demo"), + self.core.main_window_id().unwrap(), + ) } } diff --git a/examples/menu/Cargo.toml b/examples/menu/Cargo.toml index c83a216d..dcab1ef5 100644 --- a/examples/menu/Cargo.toml +++ b/examples/menu/Cargo.toml @@ -10,5 +10,4 @@ tracing-log = "0.2.0" [dependencies.libcosmic] path = "../../" -default-features = false -features = ["debug", "winit", "tokio", "xdg-portal", "multi-window"] +features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs index 7037a62c..8b5a1cb7 100644 --- a/examples/menu/src/main.rs +++ b/examples/menu/src/main.rs @@ -110,7 +110,7 @@ impl cosmic::Application for App { (app, Task::none()) } - fn header_start(&self) -> Vec> { + fn header_start(&self) -> Vec> { vec![menu_bar(&self.config, &self.key_binds)] } @@ -137,7 +137,7 @@ impl cosmic::Application for App { } /// Creates a view after each update. - fn view(&self) -> Element { + fn view(&self) -> Element<'_, Self::Message> { let text = if self.config.hide_content { cosmic::widget::text("") } else { diff --git a/examples/multi-window/Cargo.toml b/examples/multi-window/Cargo.toml index 168bd4ec..0b5440f8 100644 --- a/examples/multi-window/Cargo.toml +++ b/examples/multi-window/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "multi-window", "dbus-config", "wgpu", "wayland"] } +libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "wgpu", "wayland"] } diff --git a/examples/multi-window/src/window.rs b/examples/multi-window/src/window.rs index 96d166d4..74ab5386 100644 --- a/examples/multi-window/src/window.rs +++ b/examples/multi-window/src/window.rs @@ -2,11 +2,11 @@ use std::collections::HashMap; use cosmic::{ app::Core, - iced::{self, event, window}, + iced::{self, event, window, Subscription}, iced_core::{id, Alignment, Length, Point}, iced_widget::{column, container, scrollable, text}, + prelude::*, widget::{button, header_bar}, - ApplicationExt, Task, }; #[derive(Debug, Clone, PartialEq)] @@ -57,7 +57,7 @@ impl cosmic::Application for MultiWindow { (windows, cosmic::app::Task::none()) } - fn subscription(&self) -> cosmic::iced_futures::Subscription { + fn subscription(&self) -> Subscription { event::listen_with(|event, _, id| { if let iced::Event::Window(window_event) = event { match window_event { @@ -74,7 +74,7 @@ impl cosmic::Application for MultiWindow { }) } - fn update(&mut self, message: Self::Message) -> iced::Task> { + fn update(&mut self, message: Self::Message) -> Task> { match message { Message::CloseWindow(id) => window::close(id), Message::WindowClosed(id) => { @@ -119,7 +119,7 @@ impl cosmic::Application for MultiWindow { } } - fn view_window(&self, id: window::Id) -> cosmic::prelude::Element { + fn view_window(&self, id: window::Id) -> Element<'_, Self::Message> { let w = self.windows.get(&id).unwrap(); let input_id = w.input_id.clone(); @@ -152,7 +152,7 @@ impl cosmic::Application for MultiWindow { } } - fn view(&self) -> cosmic::prelude::Element { + fn view(&self) -> Element<'_, Self::Message> { self.view_window(self.core.main_window_id().unwrap()) } } diff --git a/examples/nav-context/Cargo.toml b/examples/nav-context/Cargo.toml index a1b95413..93dbe3e9 100644 --- a/examples/nav-context/Cargo.toml +++ b/examples/nav-context/Cargo.toml @@ -10,5 +10,4 @@ tracing-log = "0.2.0" [dependencies.libcosmic] path = "../../" -default-features = false -features = ["debug", "winit", "tokio", "xdg-portal", "multi-window"] +features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/nav-context/src/main.rs b/examples/nav-context/src/main.rs index be458171..fdfb90f9 100644 --- a/examples/nav-context/src/main.rs +++ b/examples/nav-context/src/main.rs @@ -172,7 +172,7 @@ impl cosmic::Application for App { } /// Creates a view after each update. - fn view(&self) -> Element { + fn view(&self) -> Element<'_, Self::Message> { let page_content = self .nav_model .active_data::() diff --git a/examples/open-dialog/Cargo.toml b/examples/open-dialog/Cargo.toml index 3fa07d42..2a734da0 100644 --- a/examples/open-dialog/Cargo.toml +++ b/examples/open-dialog/Cargo.toml @@ -16,6 +16,5 @@ tracing-subscriber = "0.3.19" url = "2.5.4" [dependencies.libcosmic] -features = ["debug", "winit", "multi-window", "wayland", "tokio"] +features = ["debug", "winit", "wgpu", "wayland", "tokio"] path = "../../" -default-features = false diff --git a/examples/open-dialog/src/main.rs b/examples/open-dialog/src/main.rs index 0edac466..10e46315 100644 --- a/examples/open-dialog/src/main.rs +++ b/examples/open-dialog/src/main.rs @@ -82,7 +82,7 @@ impl cosmic::Application for App { (app, cmd) } - fn header_end(&self) -> Vec> { + fn header_end(&self) -> Vec> { // Places a button the header to create open dialogs. vec![button::suggested("Open").on_press(Message::OpenFile).into()] } @@ -186,13 +186,17 @@ impl cosmic::Application for App { Message::CloseError => { self.error_status = None; } - Message::Surface(surface) => {} + Message::Surface(action) => { + return cosmic::task::message(cosmic::Action::Cosmic( + cosmic::app::Action::Surface(action), + )); + } } Task::none() } - fn view(&self) -> Element { + fn view(&self) -> Element<'_, Self::Message> { let mut content = Vec::new(); if let Some(error) = self.error_status.as_deref() { diff --git a/examples/spin-button/Cargo.toml b/examples/spin-button/Cargo.toml index 3088a313..a522050b 100644 --- a/examples/spin-button/Cargo.toml +++ b/examples/spin-button/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" fraction = "0.15.3" [dependencies.libcosmic] -features = ["debug", "multi-window", "wayland", "winit", "desktop", "tokio"] +features = ["debug", "wgpu", "winit", "desktop", "tokio"] path = "../.." default-features = false diff --git a/examples/spin-button/src/main.rs b/examples/spin-button/src/main.rs index 310c5107..47db4dce 100644 --- a/examples/spin-button/src/main.rs +++ b/examples/spin-button/src/main.rs @@ -130,7 +130,7 @@ impl Application for SpinButtonExamplApp { Task::none() } - fn view(&self) -> Element { + fn view(&'_ self) -> Element<'_, Self::Message> { let space_xs = cosmic::theme::spacing().space_xs; let vert_spinner_row = iced::widget::row![ diff --git a/examples/table-view/Cargo.toml b/examples/table-view/Cargo.toml index ba3bd88e..41669cb8 100644 --- a/examples/table-view/Cargo.toml +++ b/examples/table-view/Cargo.toml @@ -10,6 +10,5 @@ tracing-log = "0.2.0" chrono = "*" [dependencies.libcosmic] -features = ["debug", "multi-window", "wayland", "winit", "desktop", "tokio"] +features = ["debug", "wgpu", "winit", "desktop", "tokio"] path = "../.." -default-features = false diff --git a/examples/table-view/src/main.rs b/examples/table-view/src/main.rs index 6bd773bc..bbd9cf5b 100644 --- a/examples/table-view/src/main.rs +++ b/examples/table-view/src/main.rs @@ -204,7 +204,7 @@ impl cosmic::Application for App { } /// Creates a view after each update. - fn view(&self) -> Element { + fn view(&self) -> Element<'_, Self::Message> { cosmic::widget::responsive(|size| { if size.width < 600.0 { widget::compact_table(&self.table_model) diff --git a/examples/text-input/Cargo.toml b/examples/text-input/Cargo.toml index 1cc35d1d..fb1bdf28 100644 --- a/examples/text-input/Cargo.toml +++ b/examples/text-input/Cargo.toml @@ -10,5 +10,4 @@ tracing-log = "0.2.0" [dependencies.libcosmic] path = "../../" -default-features = false -features = ["debug", "winit", "tokio", "xdg-portal"] +features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"] diff --git a/examples/text-input/src/main.rs b/examples/text-input/src/main.rs index 573b9dc1..ea99666c 100644 --- a/examples/text-input/src/main.rs +++ b/examples/text-input/src/main.rs @@ -87,7 +87,7 @@ impl cosmic::Application for App { } /// Creates a view after each update. - fn view(&self) -> Element { + fn view(&self) -> Element<'_, Self::Message> { let editable = cosmic::widget::editable_input( "Input text here", &self.input, @@ -118,6 +118,6 @@ where fn update_title(&mut self) -> Task { let window_title = format!("COSMIC TextInputs Demo"); self.set_header_title(window_title.clone()); - self.set_window_title(window_title) + self.set_window_title(window_title, self.core.main_window_id().unwrap()) } } From 2f0b3334914e4ab1b0f3df821eeadd7ad700566f Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 13 Oct 2025 13:59:45 -0700 Subject: [PATCH 386/556] Add helper for accumulating scroll into discrete delta This converts `ScrollDelta::Pixels` and `ScrollDelta::Lines` into integer values, accumulating partial scrolls until a full integer is reached. It also has a configurable rate-limit, so discrete integer events can occur at a certain maximum frequency. This may need tuning for different use cases, though I haven't tried using it for things other than changing workspaces so far. --- src/lib.rs | 2 + src/scroll.rs | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 src/scroll.rs diff --git a/src/lib.rs b/src/lib.rs index a180c224..7e61730b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,6 +108,8 @@ pub mod task; pub mod theme; +pub mod scroll; + #[doc(inline)] pub use theme::{Theme, style}; diff --git a/src/scroll.rs b/src/scroll.rs new file mode 100644 index 00000000..b6d42378 --- /dev/null +++ b/src/scroll.rs @@ -0,0 +1,112 @@ +use iced::Task; +use iced::mouse::ScrollDelta; +use std::time::{Duration, Instant}; + +// Number of scroll pixels before changing workspace +const SCROLL_PIXELS: f32 = 24.0; + +// Timeout for scroll accumulation; older partial scroll is dropped +const SCROLL_TIMEOUT: Duration = Duration::from_millis(100); + +/// A scroll delta with discrete integer deltas +#[derive(Debug, Default, Clone, Copy)] +pub struct DiscreteScrollDelta { + pub x: isize, + pub y: isize, +} + +/// Helper for accumulating and converting pixel/line scrolls into and integer +/// delta between discrete options. +#[derive(Debug, Default)] +pub struct DiscreteScrollState { + x: Scroll, + y: Scroll, + rate_limit: Option, +} + +impl DiscreteScrollState { + /// Set a rate limit. If set, a call to `update()` will only not produce + /// values other than 1, -1, or 0 and a non-zero return value will not + /// occur more frequently than this duration. + pub fn rate_limit(mut self, rate_limit: Option) -> Self { + self.rate_limit = rate_limit; + self + } + + /// Reset, clearing any acculuated scroll events that haven't been + /// converted to discrete events yet. + pub fn reset(&mut self) { + self.x.reset(); + self.y.reset(); + } + + /// Accumulate delta with a timer + pub fn update(&mut self, delta: ScrollDelta) -> DiscreteScrollDelta { + let (x, y) = match delta { + ScrollDelta::Pixels { x, y } => (x / SCROLL_PIXELS, y / SCROLL_PIXELS), + ScrollDelta::Lines { x, y } => (x, y), + }; + + DiscreteScrollDelta { + x: self.x.update(x, self.rate_limit), + y: self.y.update(y, self.rate_limit), + } + } +} + +/// Scroll over a single axis +#[derive(Debug, Default)] +struct Scroll { + scroll: Option<(f32, Instant)>, + last_discrete: Option, +} + +impl Scroll { + fn reset(&mut self) { + *self = Default::default(); + } + + fn update(&mut self, delta: f32, rate_limit: Option) -> isize { + if delta == 0. { + // If delta is 0, scroll is on other axis; clear accumulated scroll + self.reset(); + 0 + } else { + let previous_scroll = if let Some((scroll, last_scroll_time)) = self.scroll { + if last_scroll_time.elapsed() > SCROLL_TIMEOUT { + 0. + } else { + scroll + } + } else { + 0. + }; + + let scroll = previous_scroll + delta; + + if self + .last_discrete + .is_some_and(|time| time.elapsed() < rate_limit.unwrap_or(Duration::ZERO)) + { + // If rate limit is hit, continute accumulating, but don't return + // a discrete event yet. + self.scroll = Some((scroll, Instant::now())); + 0 + } else { + // Return integer part of scroll, and keep remainder + self.scroll = Some((scroll.fract(), Instant::now())); + let mut discrete = scroll.trunc() as isize; + if discrete != 0 { + self.last_discrete = Some(Instant::now()); + } + if rate_limit.is_some() { + // If we are rate limiting, don't return multiple discrete events + // at once; drop extras. + discrete.signum() + } else { + discrete + } + } + } + } +} From 3b8ad45950f5d23c8550e18e628f6e70b7089d89 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 9 Dec 2025 15:00:24 +0100 Subject: [PATCH 387/556] i18n: translation updates from weblate Co-authored-by: Hosted Weblate Co-authored-by: jonnysemon Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ar/ Translation: Pop OS/libcosmic --- i18n/ar/libcosmic.ftl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/ar/libcosmic.ftl b/i18n/ar/libcosmic.ftl index ad86e64e..428bd892 100644 --- a/i18n/ar/libcosmic.ftl +++ b/i18n/ar/libcosmic.ftl @@ -1,5 +1,5 @@ # Context Drawer -close = أغلق +close = أغلِق # About license = الترخيص links = الروابط From aabc8dcda530a6ac70617dd578cea55910af53c8 Mon Sep 17 00:00:00 2001 From: Bryan Hyland Date: Tue, 9 Dec 2025 11:01:57 -0800 Subject: [PATCH 388/556] build(windows): change icon path separator for native windows builds --- build.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.rs b/build.rs index a8f1a4cc..c69feaf5 100644 --- a/build.rs +++ b/build.rs @@ -41,6 +41,9 @@ fn generate_bundled_icons() { ) .into_iter() .fold(String::new(), |mut output, (name, path)| { + // This changes the escape character to the one used by Windows. + #[cfg(windows)] + let path = path.replace("\\", "/"); output.push_str(&format!(" \"{name}\" => include_bytes!(\"{path}\"),\n")); output }); From e4978693b9004cd0cbae8e2ffef978c1b4a49484 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 15 Dec 2025 21:48:29 +0100 Subject: [PATCH 389/556] 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: Ekramul Reza Co-authored-by: Hosted Weblate Co-authored-by: Temuri Doghonadze Co-authored-by: Vilius Paliokas Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ga/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/lt/ Translation: Pop OS/libcosmic --- i18n/bn/libcosmic.ftl | 0 i18n/ga/libcosmic.ftl | 19 +++++++++++++++++++ i18n/ka/libcosmic.ftl | 0 i18n/lt/libcosmic.ftl | 27 +++++++++++++++++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 i18n/bn/libcosmic.ftl create mode 100644 i18n/ka/libcosmic.ftl diff --git a/i18n/bn/libcosmic.ftl b/i18n/bn/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/ga/libcosmic.ftl b/i18n/ga/libcosmic.ftl index 61557ccd..024841bf 100644 --- a/i18n/ga/libcosmic.ftl +++ b/i18n/ga/libcosmic.ftl @@ -6,3 +6,22 @@ designers = Dearthóirí artists = Ealaíontóirí translators = Aistritheoirí documenters = Doiciméadóirí +january = Eanáir { $year } +february = Feabhra { $year } +march = Márta { $year } +april = Aibreán { $year } +may = Bealtaine { $year } +june = Meitheamh { $year } +july = Iúil { $year } +august = Lúnasa { $year } +september = Meán Fómhair { $year } +october = Deireadh Fómhair { $year } +november = Samhain { $year } +december = Nollaig { $year } +monday = Lua +tuesday = Mái +wednesday = Céa +thursday = Déa +friday = Aoi +saturday = Sat +sunday = Dom diff --git a/i18n/ka/libcosmic.ftl b/i18n/ka/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/lt/libcosmic.ftl b/i18n/lt/libcosmic.ftl index e69de29b..6472cbd3 100644 --- a/i18n/lt/libcosmic.ftl +++ b/i18n/lt/libcosmic.ftl @@ -0,0 +1,27 @@ +february = Vasaris { $year } +close = Uždaryti +documenters = Dokumentuotojai +november = Lapkritis { $year } +friday = Penk +tuesday = Antr +may = Gegužė { $year } +wednesday = Treč +april = Balandis { $year } +monday = Pirm +translators = Vertėjai +artists = Menininkai +license = Licencija +december = Gruodis { $year } +sunday = Sekm +links = Nuorodos +march = Kovas { $year } +june = Birželis { $year } +saturday = Šešt +august = Rugpjūtis { $year } +developers = Kūrėjai +july = Liepa { $year } +thursday = Ketv +september = Rugsėjis { $year } +designers = Dizaineriai +october = Spalis { $year } +january = Sausis { $year } From fa26e0e2413cf5cd4c88201be8eb6ddd14917042 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 17 Dec 2025 03:25:00 +0100 Subject: [PATCH 390/556] docs: add link to cosmic-applet-template --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ac8e60aa..23da97bc 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ A platform toolkit based on iced for creating applets and applications for the C ## Templates - https://github.com/pop-os/cosmic-app-template: Application project template +- https://github.com/pop-os/cosmic-applet-template: Panel applet project template ## Dependencies From dd3610b8ae4f1bcf2e2299e82f908913d1a4a57d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 19 Dec 2025 15:35:21 -0500 Subject: [PATCH 391/556] fix(dnd_destination): layout for dnd rectangle children --- src/widget/dnd_destination.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index c943d2c7..947d2fe3 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -597,14 +597,12 @@ impl Widget }; dnd_rectangles.push(my_dest); - if let Some(child_layout) = layout.children().next() { - self.container.as_widget().drag_destinations( - &state.children[0], - child_layout.with_virtual_offset(layout.virtual_offset()), - renderer, - dnd_rectangles, - ); - } + self.container.as_widget().drag_destinations( + &state.children[0], + layout, + renderer, + dnd_rectangles, + ); } fn id(&self) -> Option { From 6f92465fcbed24beca85dec3d7e89db6e63a46de Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Thu, 25 Dec 2025 12:08:02 +0100 Subject: [PATCH 392/556] i18n: translation updates from weblate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Amadɣas Co-authored-by: Hosted Weblate Co-authored-by: Walter William Beckerleg Bruckman Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/pt_BR/ Translation: Pop OS/libcosmic --- i18n/kab/libcosmic.ftl | 0 i18n/pt-BR/libcosmic.ftl | 24 ++++++++++++------------ 2 files changed, 12 insertions(+), 12 deletions(-) create mode 100644 i18n/kab/libcosmic.ftl diff --git a/i18n/kab/libcosmic.ftl b/i18n/kab/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/pt-BR/libcosmic.ftl b/i18n/pt-BR/libcosmic.ftl index f02828bf..51b5f6c3 100644 --- a/i18n/pt-BR/libcosmic.ftl +++ b/i18n/pt-BR/libcosmic.ftl @@ -8,18 +8,18 @@ designers = Designers artists = Artistas translators = Tradutores documenters = Documentadores -january = Janeiro { $year } -february = Fevereiro { $year } -march = Março { $year } -april = Abril { $year } -may = Maio { $year } -june = Junho { $year } -july = Julho { $year } -august = Agosto { $year } -september = Setembro { $year } -october = Outubro { $year } -november = Novembro { $year } -december = Dezembro { $year } +january = Janeiro de { $year } +february = Fevereiro de { $year } +march = Março de { $year } +april = Abril de { $year } +may = Maio de { $year } +june = Junho de { $year } +july = Julho de { $year } +august = Agosto de { $year } +september = Setembro de { $year } +october = Outubro de { $year } +november = Novembro de { $year } +december = Dezembro de { $year } monday = Seg tuesday = Ter wednesday = Qua From a9f64c33ce9159485be5dad1ce07ccf7c12399d5 Mon Sep 17 00:00:00 2001 From: Michael Murphy Date: Tue, 30 Dec 2025 11:53:22 +0100 Subject: [PATCH 393/556] i18n: removing translation for Frankish --- i18n/frk/libcosmic.ftl | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 i18n/frk/libcosmic.ftl diff --git a/i18n/frk/libcosmic.ftl b/i18n/frk/libcosmic.ftl deleted file mode 100644 index e69de29b..00000000 From e9bb5ed97d9120872e1e28d16f7bfcc3a4b81e2c Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 6 Jan 2026 02:25:11 +0100 Subject: [PATCH 394/556] chore: update freedesktop-desktop-entry --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 927444e8..decdac93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -149,7 +149,7 @@ zbus = { version = "5.11.0", default-features = false } [target.'cfg(unix)'.dependencies] freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } -freedesktop-desktop-entry = { version = "0.7.14", optional = true } +freedesktop-desktop-entry = { version = "0.8.1", optional = true } shlex = { version = "1.3.0", optional = true } [target.'cfg(not(unix))'.dependencies] From 421552dea1c06e876d5999333794b9ee918340a1 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 6 Jan 2026 02:25:46 +0100 Subject: [PATCH 395/556] fix!(desktop): IconSourceExt::as_cosmic_icon should return Handle with SVG preference --- src/desktop.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/desktop.rs b/src/desktop.rs index 01698af5..0d3dbb52 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -7,23 +7,22 @@ use std::path::{Path, PathBuf}; use std::{borrow::Cow, collections::HashSet, ffi::OsStr}; pub trait IconSourceExt { - fn as_cosmic_icon(&self) -> crate::widget::icon::Icon; + fn as_cosmic_icon(&self) -> crate::widget::icon::Handle; } #[cfg(not(windows))] impl IconSourceExt for fde::IconSource { - fn as_cosmic_icon(&self) -> crate::widget::icon::Icon { + fn as_cosmic_icon(&self) -> crate::widget::icon::Handle { match self { fde::IconSource::Name(name) => crate::widget::icon::from_name(name.as_str()) + .prefer_svg(true) .size(128) .fallback(Some(crate::widget::icon::IconFallback::Names(vec![ "application-default".into(), "application-x-executable".into(), ]))) - .into(), - fde::IconSource::Path(path) => { - crate::widget::icon(crate::widget::icon::from_path(path.clone())) - } + .handle(), + fde::IconSource::Path(path) => crate::widget::icon::from_path(path.clone()), } } } From f6039597b72d3eefe2ee1d6528a04077982db238 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Fri, 2 Jan 2026 23:01:52 +0100 Subject: [PATCH 396/556] i18n: translation updates from weblate Co-authored-by: Hosted Weblate Co-authored-by: Walter William Beckerleg Bruckman Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/zh_Hans/ Translation: Pop OS/libcosmic --- i18n/zh-Hans/libcosmic.ftl | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/i18n/zh-Hans/libcosmic.ftl b/i18n/zh-Hans/libcosmic.ftl index e7c83e5c..5d9fbd66 100644 --- a/i18n/zh-Hans/libcosmic.ftl +++ b/i18n/zh-Hans/libcosmic.ftl @@ -4,3 +4,23 @@ links = 链接 developers = 开发者 designers = 设计师 translators = 译者 +january = { $year }年1月 +february = { $year }年2月 +march = { $year }年3月 +april = { $year }年4月 +may = { $year }年5月 +june = { $year }年6月 +july = { $year }年7月 +august = { $year }年8月 +september = { $year }年9月 +october = { $year }年10月 +november = { $year }年11月 +december = { $year }年12月 +monday = 周一 +tuesday = 周二 +wednesday = 周三 +thursday = 周四 +friday = 周五 +saturday = 周六 +sunday = 周日 +artists = 艺术家 From b9c24d24212a865977db4871efc13ff890055648 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 9 Jan 2026 23:03:09 +0100 Subject: [PATCH 397/556] feat(a11y): screen reader name and description support for button widgets --- src/widget/button/icon.rs | 11 +++++- src/widget/button/image.rs | 16 ++++++-- src/widget/button/link.rs | 15 ++++++- src/widget/button/mod.rs | 10 +++++ src/widget/button/text.rs | 8 +++- src/widget/spin_button.rs | 81 ++++++++++++++++++++++++++++++++------ 6 files changed, 122 insertions(+), 19 deletions(-) diff --git a/src/widget/button/icon.rs b/src/widget/button/icon.rs index 754bc433..edb54272 100644 --- a/src/widget/button/icon.rs +++ b/src/widget/button/icon.rs @@ -38,6 +38,10 @@ impl Button<'_, Message> { Self { id: Id::unique(), label: Cow::Borrowed(""), + #[cfg(feature = "a11y")] + name: Cow::Borrowed(""), + #[cfg(feature = "a11y")] + description: Cow::Borrowed(""), tooltip: Cow::Borrowed(""), on_press: None, width: Length::Shrink, @@ -151,7 +155,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes ); } - let button = if builder.variant.vertical { + let mut button = if builder.variant.vertical { crate::widget::column::with_children(content) .padding(builder.padding) .spacing(builder.spacing) @@ -167,6 +171,11 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .apply(super::custom) }; + #[cfg(feature = "a11y")] + { + button = button.name(builder.name).description(builder.description); + } + let button = button .padding(0) .id(builder.id) diff --git a/src/widget/button/image.rs b/src/widget/button/image.rs index 6a5c47b1..ab51e667 100644 --- a/src/widget/button/image.rs +++ b/src/widget/button/image.rs @@ -33,6 +33,10 @@ impl<'a, Message> Button<'a, Message> { Self { id: Id::unique(), label: Cow::Borrowed(""), + #[cfg(feature = "a11y")] + name: Cow::Borrowed(""), + #[cfg(feature = "a11y")] + description: Cow::Borrowed(""), tooltip: Cow::Borrowed(""), on_press: None, width: Length::Shrink, @@ -79,12 +83,18 @@ where .width(builder.width) .height(builder.height); - super::custom_image_button(content, builder.variant.on_remove) + let mut button = super::custom_image_button(content, builder.variant.on_remove) .padding(0) .selected(builder.variant.selected) .id(builder.id) .on_press_maybe(builder.on_press) - .class(builder.class) - .into() + .class(builder.class); + + #[cfg(feature = "a11y")] + { + button = button.name(builder.name).description(builder.description); + } + + button.into() } } diff --git a/src/widget/button/link.rs b/src/widget/button/link.rs index b86ef1a3..9ce81268 100644 --- a/src/widget/button/link.rs +++ b/src/widget/button/link.rs @@ -34,6 +34,10 @@ impl<'a, Message> Button<'a, Message> { Self { id: Id::unique(), label: label.into(), + #[cfg(feature = "a11y")] + name: Cow::Borrowed(""), + #[cfg(feature = "a11y")] + description: Cow::Borrowed(""), tooltip: Cow::Borrowed(""), on_press: None, width: Length::Shrink, @@ -62,7 +66,7 @@ pub fn icon() -> Handle { impl<'a, Message: Clone + 'static> From> for Element<'a, Message> { fn from(mut builder: Button<'a, Message>) -> Element<'a, Message> { - let button: super::Button<'a, Message> = row::with_capacity(2) + let mut button: super::Button<'a, Message> = row::with_capacity(2) .push({ // TODO: Avoid allocation crate::widget::text(builder.label.to_string()) @@ -89,6 +93,15 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .on_press_maybe(builder.on_press.take()) .class(builder.class); + #[cfg(feature = "a11y")] + { + if !builder.label.is_empty() { + button = button.name(builder.label); + } + + button = button.description(builder.description); + } + if builder.tooltip.is_empty() { button.into() } else { diff --git a/src/widget/button/mod.rs b/src/widget/button/mod.rs index d9a4df94..f5975d39 100644 --- a/src/widget/button/mod.rs +++ b/src/widget/button/mod.rs @@ -69,6 +69,16 @@ pub struct Builder<'a, Message, Variant> { #[setters(into)] label: Cow<'a, str>, + /// A name for screen reader support + #[cfg(feature = "a11y")] + #[setters(into)] + name: Cow<'a, str>, + + /// A description for screen reader support + #[cfg(feature = "a11y")] + #[setters(into)] + description: Cow<'a, str>, + // Adds a tooltip to the button. #[setters(into)] tooltip: Cow<'a, str>, diff --git a/src/widget/button/text.rs b/src/widget/button/text.rs index 3f58c932..bcdd02ba 100644 --- a/src/widget/button/text.rs +++ b/src/widget/button/text.rs @@ -63,6 +63,10 @@ impl Button<'_, Message> { Self { id: Id::unique(), label: Cow::Borrowed(""), + #[cfg(feature = "a11y")] + name: Cow::Borrowed(""), + #[cfg(feature = "a11y")] + description: Cow::Borrowed(""), tooltip: Cow::Borrowed(""), on_press: None, width: Length::Shrink, @@ -136,8 +140,10 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes #[cfg(feature = "a11y")] { if !builder.label.is_empty() { - button = button.name(builder.label); + button = button.name(builder.label) } + + button = button.description(builder.description); } if builder.tooltip.is_empty() { diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index 6f4a4de2..db90a000 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -16,6 +16,7 @@ use std::ops::{Add, Sub}; /// Horizontal spin button widget. pub fn spin_button<'a, T, M>( label: impl Into>, + #[cfg(feature = "a11y")] name: impl Into>, value: T, step: T, min: T, @@ -25,7 +26,7 @@ pub fn spin_button<'a, T, M>( where T: Copy + Sub + Add + PartialOrd, { - SpinButton::new( + let mut button = SpinButton::new( label, value, step, @@ -33,12 +34,20 @@ where max, Orientation::Horizontal, on_press, - ) + ); + + #[cfg(feature = "a11y")] + { + button = button.name(name.into()); + } + + button } /// Vertical spin button widget. pub fn vertical<'a, T, M>( label: impl Into>, + #[cfg(feature = "a11y")] name: impl Into>, value: T, step: T, min: T, @@ -48,15 +57,22 @@ pub fn vertical<'a, T, M>( where T: Copy + Sub + Add + PartialOrd, { - SpinButton::new( + let mut button = SpinButton::new( label, value, step, min, max, - Orientation::Vertical, + Orientation::Horizontal, on_press, - ) + ); + + #[cfg(feature = "a11y")] + { + button = button.name(name.into()); + } + + button } #[derive(Clone, Copy)] @@ -71,6 +87,9 @@ where { /// The formatted value of the spin button. label: Cow<'a, str>, + /// A name for screen reader support. + #[cfg(feature = "a11y")] + name: Cow<'a, str>, /// The current value of the spin button. value: T, /// The amount to increment or decrement the value. @@ -99,6 +118,8 @@ where ) -> Self { Self { label: label.into(), + #[cfg(feature = "a11y")] + name: Cow::Borrowed(""), step, value: if value < min { min @@ -113,6 +134,12 @@ where on_press: Box::from(on_press), } } + + #[cfg(feature = "a11y")] + pub(self) fn name(mut self, name: Cow<'a, str>) -> Self { + self.name = name; + self + } } fn increment(value: T, step: T, _min: T, max: T) -> T @@ -153,21 +180,28 @@ where fn make_button<'a, T, Message>( spin_button: &SpinButton<'a, T, Message>, icon: &'static str, + #[cfg(feature = "a11y")] name: String, operation: fn(T, T, T, T) -> T, ) -> Element<'a, Message> where Message: Clone + 'static, T: Copy + Sub + Add + PartialOrd, { - icon::from_name(icon) + let mut button = icon::from_name(icon) .apply(button::icon) .on_press((spin_button.on_press)(operation( spin_button.value, spin_button.step, spin_button.min, spin_button.max, - ))) - .into() + ))); + + #[cfg(feature = "a11y")] + { + button = button.name(name.clone()); + } + + button.into() } fn horizontal_variant(spin_button: SpinButton<'_, T, Message>) -> Element<'_, Message> @@ -175,9 +209,20 @@ where Message: Clone + 'static, T: Copy + Sub + Add + PartialOrd, { - let decrement_button = make_button(&spin_button, "list-remove-symbolic", decrement); - let increment_button = make_button(&spin_button, "list-add-symbolic", increment); - + let decrement_button = make_button( + &spin_button, + "list-remove-symbolic", + #[cfg(feature = "a11y")] + [&spin_button.name, " decrease"].concat(), + decrement, + ); + let increment_button = make_button( + &spin_button, + "list-add-symbolic", + #[cfg(feature = "a11y")] + [&spin_button.name, " increase"].concat(), + increment, + ); let label = text::body(spin_button.label) .apply(container) .center_x(Length::Fixed(48.0)) @@ -198,8 +243,18 @@ where Message: Clone + 'static, T: Copy + Sub + Add + PartialOrd, { - let decrement_button = make_button(&spin_button, "list-remove-symbolic", decrement); - let increment_button = make_button(&spin_button, "list-add-symbolic", increment); + let decrement_button = make_button( + &spin_button, + "list-remove-symbolic", + [&spin_button.label, " decrease"].concat(), + decrement, + ); + let increment_button = make_button( + &spin_button, + "list-add-symbolic", + [&spin_button.label, " increase"].concat(), + increment, + ); let label = text::body(spin_button.label) .apply(container) From f453db2425fa80d3be65840f490a6f13cf66af98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Miku=C5=82a?= Date: Mon, 12 Jan 2026 21:15:14 +0100 Subject: [PATCH 398/556] chore: update iced submodule This pulls in the fix made in https://github.com/pop-os/iced/pull/253. --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 10db38f9..176589f6 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 10db38f982001a714bd94e99a082368762b378ee +Subproject commit 176589f64cc9adc3cb65da373d2e56c998326fc2 From f00043369074fc5c9528f16ebf32f5aa06896936 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 13 Jan 2026 17:01:27 +0100 Subject: [PATCH 399/556] fix(spin_button): compiler error on build without a11y --- src/widget/spin_button.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index db90a000..13cc881f 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -246,12 +246,14 @@ where let decrement_button = make_button( &spin_button, "list-remove-symbolic", + #[cfg(feature = "a11y")] [&spin_button.label, " decrease"].concat(), decrement, ); let increment_button = make_button( &spin_button, "list-add-symbolic", + #[cfg(feature = "a11y")] [&spin_button.label, " increase"].concat(), increment, ); From b0cbb54bf2b3528c895f7636c7ad1fd520fd2a9e Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 13 Jan 2026 17:01:57 +0100 Subject: [PATCH 400/556] chore(widget): remove unused RcWrapper method --- src/widget/wrapper.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/widget/wrapper.rs b/src/widget/wrapper.rs index 92f26fd4..59c0a376 100644 --- a/src/widget/wrapper.rs +++ b/src/widget/wrapper.rs @@ -58,14 +58,6 @@ impl RcWrapper { let my_refmut: &mut T = &mut RefCell::borrow_mut(self.data.as_ref()); f(my_refmut) } - - /// # Panics - /// - /// Will panic if used outside of original thread. - pub(crate) unsafe fn as_ptr(&self) -> *mut T { - assert_eq!(self.thread_id, thread::current().id()); - RefCell::as_ptr(self.data.as_ref()) - } } #[derive(Clone)] From 03c440b97a401177ee353ec4b100e56ca80518ba Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 14 Jan 2026 18:46:53 +0100 Subject: [PATCH 401/556] chore(cargo): update all crate dependencies --- Cargo.toml | 30 +++++------ cosmic-config/Cargo.toml | 8 +-- cosmic-theme/Cargo.toml | 6 +-- examples/about/Cargo.toml | 2 +- examples/applet/Cargo.toml | 4 +- examples/application/Cargo.toml | 4 +- examples/calendar/Cargo.toml | 2 +- examples/context-menu/Cargo.toml | 4 +- examples/cosmic/Cargo.toml | 4 +- examples/image-button/Cargo.toml | 4 +- examples/menu/Cargo.toml | 4 +- examples/nav-context/Cargo.toml | 4 +- examples/open-dialog/Cargo.toml | 8 +-- examples/subscriptions/Cargo.toml | 10 ++++ examples/subscriptions/src/main.rs | 80 ++++++++++++++++++++++++++++++ examples/table-view/Cargo.toml | 4 +- examples/text-input/Cargo.toml | 4 +- 17 files changed, 136 insertions(+), 46 deletions(-) create mode 100644 examples/subscriptions/Cargo.toml create mode 100644 examples/subscriptions/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index decdac93..46091bcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,8 +99,8 @@ async-std = [ [dependencies] apply = "0.3.0" -ashpd = { version = "0.12.0", default-features = false, optional = true } -async-fs = { version = "2.1", optional = true } +ashpd = { version = "0.12.1", default-features = false, optional = true } +async-fs = { version = "2.2", optional = true } async-std = { version = "1.13", optional = true } auto_enums = "0.8.7" cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d0e95be", optional = true } @@ -113,15 +113,15 @@ i18n-embed = { version = "0.16.0", features = [ "desktop-requester", ] } i18n-embed-fl = "0.10" -rust-embed = "8.7.2" +rust-embed = "8.11.0" css-color = "0.2.8" derive_setters = "0.1.8" futures = "0.3" -image = { version = "0.25.8", default-features = false, features = [ +image = { version = "0.25.9", default-features = false, features = [ "jpeg", "png", ] } -libc = { version = "0.2.175", optional = true } +libc = { version = "0.2.180", optional = true } log = "0.4" mime = { version = "0.3.17", optional = true } palette = "0.7.6" @@ -130,22 +130,22 @@ rfd = { version = "0.15.4", default-features = false, features = [ "xdg-portal", ], optional = true } rustix = { version = "1.1", features = ["pipe", "process"], optional = true } -serde = { version = "1.0.219", features = ["derive"] } -slotmap = "1.0.7" +serde = { version = "1.0.228", features = ["derive"] } +slotmap = "1.1.1" smol = { version = "2.0.2", optional = true } -thiserror = "2.0.16" -taffy = { version = "0.9.1", features = ["grid"] } -tokio = { version = "1.47.1", optional = true } -tracing = "0.1.41" +thiserror = "2.0.17" +taffy = { version = "0.9.2", features = ["grid"] } +tokio = { version = "1.49.0", optional = true } +tracing = "0.1.44" unicode-segmentation = "1.12" -url = "2.5.7" -zbus = { version = "5.11.0", default-features = false, optional = true } +url = "2.5.8" +zbus = { version = "5.13.1", default-features = false, optional = true } # Enable DBus feature on Linux targets [target.'cfg(target_os = "linux")'.dependencies] cosmic-config = { path = "cosmic-config", features = ["dbus"] } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" } -zbus = { version = "5.11.0", default-features = false } +zbus = { version = "5.13.1", default-features = false } [target.'cfg(unix)'.dependencies] freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } @@ -225,4 +225,4 @@ exclude = ["iced"] dirs = "6.0.0" [dev-dependencies] -tempfile = "3.13.0" +tempfile = "3.24.0" diff --git a/cosmic-config/Cargo.toml b/cosmic-config/Cargo.toml index 9b5aca07..78d671ca 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -11,18 +11,18 @@ subscription = ["iced_futures"] [dependencies] cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } -zbus = { version = "5.11.0", default-features = false, optional = true } +zbus = { version = "5.13.1", default-features = false, optional = true } atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" } calloop = { version = "0.14.3", optional = true } notify = "8.2.0" ron = "0.11.0" -serde = "1.0.219" +serde = "1.0.228" cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true } iced = { path = "../iced/", default-features = false, optional = true } iced_futures = { path = "../iced/futures/", default-features = false, optional = true } futures-util = { version = "0.3", optional = true } dirs.workspace = true -tokio = { version = "1.47", optional = true, features = ["time"] } +tokio = { version = "1.49", optional = true, features = ["time"] } async-std = { version = "1.13", optional = true } tracing = "0.1" @@ -30,4 +30,4 @@ tracing = "0.1" xdg = "3.0" [target.'cfg(windows)'.dependencies] -known-folders = "1.3.1" +known-folders = "1.4.0" diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index 44b0df5a..10b548b4 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -17,8 +17,8 @@ no-default = [] [dependencies] palette = { version = "0.7.6", features = ["serializing"] } almost = "0.2" -serde = { version = "1.0.219", features = ["derive"] } -serde_json = { version = "1.0.143", optional = true, features = [ +serde = { version = "1.0.228", features = ["derive"] } +serde_json = { version = "1.0.149", optional = true, features = [ "preserve_order", ] } ron = "0.11.0" @@ -28,4 +28,4 @@ cosmic-config = { path = "../cosmic-config/", default-features = false, features "macro", ] } dirs.workspace = true -thiserror = "2.0.16" +thiserror = "2.0.17" diff --git a/examples/about/Cargo.toml b/examples/about/Cargo.toml index d2642cd6..f980811c 100644 --- a/examples/about/Cargo.toml +++ b/examples/about/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -open = "5.3.2" +open = "5.3.3" [dependencies.libcosmic] path = "../../" diff --git a/examples/applet/Cargo.toml b/examples/applet/Cargo.toml index c39ca288..f97bff44 100644 --- a/examples/applet/Cargo.toml +++ b/examples/applet/Cargo.toml @@ -7,10 +7,10 @@ edition = "2021" [dependencies] once_cell = "1" -rust-embed = "8.6.0" +rust-embed = "8.11.0" tracing = "0.1" env_logger = "0.10.2" -log = "0.4.26" +log = "0.4.29" [dependencies.libcosmic] git = "https://github.com/pop-os/libcosmic" diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index 28c13117..f05c0418 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -8,8 +8,8 @@ default = ["wayland"] wayland = ["libcosmic/wayland"] [dependencies] -tracing = "0.1.41" -tracing-subscriber = "0.3.19" +tracing = "0.1.44" +tracing-subscriber = "0.3.22" tracing-log = "0.2.0" [dependencies.libcosmic] diff --git a/examples/calendar/Cargo.toml b/examples/calendar/Cargo.toml index 9ffb838c..59b23c0c 100644 --- a/examples/calendar/Cargo.toml +++ b/examples/calendar/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -chrono = "0.4.40" +chrono = "0.4.42" [dependencies.libcosmic] path = "../../" diff --git a/examples/context-menu/Cargo.toml b/examples/context-menu/Cargo.toml index 45cbf78a..39c550f4 100644 --- a/examples/context-menu/Cargo.toml +++ b/examples/context-menu/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -tracing = "0.1.41" -tracing-subscriber = "0.3.19" +tracing = "0.1.44" +tracing-subscriber = "0.3.22" tracing-log = "0.2.0" [dependencies.libcosmic] diff --git a/examples/cosmic/Cargo.toml b/examples/cosmic/Cargo.toml index 695f0c37..8c2a3126 100644 --- a/examples/cosmic/Cargo.toml +++ b/examples/cosmic/Cargo.toml @@ -19,9 +19,9 @@ libcosmic = { path = "../..", features = [ "xdg-portal", ] } once_cell = "1.21" -slotmap = "1.0.7" +slotmap = "1.1.1" env_logger = "0.10" -log = "0.4.26" +log = "0.4.29" [dependencies.cosmic-time] git = "https://github.com/pop-os/cosmic-time" diff --git a/examples/image-button/Cargo.toml b/examples/image-button/Cargo.toml index cf61955a..c219a53b 100644 --- a/examples/image-button/Cargo.toml +++ b/examples/image-button/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -tracing = "0.1.41" -tracing-subscriber = "0.3.19" +tracing = "0.1.44" +tracing-subscriber = "0.3.22" [dependencies.libcosmic] path = "../../" diff --git a/examples/menu/Cargo.toml b/examples/menu/Cargo.toml index dcab1ef5..430b26ea 100644 --- a/examples/menu/Cargo.toml +++ b/examples/menu/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -tracing = "0.1.41" -tracing-subscriber = "0.3.19" +tracing = "0.1.44" +tracing-subscriber = "0.3.22" tracing-log = "0.2.0" [dependencies.libcosmic] diff --git a/examples/nav-context/Cargo.toml b/examples/nav-context/Cargo.toml index 93dbe3e9..d829df0f 100644 --- a/examples/nav-context/Cargo.toml +++ b/examples/nav-context/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -tracing = "0.1.41" -tracing-subscriber = "0.3.19" +tracing = "0.1.44" +tracing-subscriber = "0.3.22" tracing-log = "0.2.0" [dependencies.libcosmic] diff --git a/examples/open-dialog/Cargo.toml b/examples/open-dialog/Cargo.toml index 2a734da0..94049270 100644 --- a/examples/open-dialog/Cargo.toml +++ b/examples/open-dialog/Cargo.toml @@ -10,10 +10,10 @@ xdg-portal = ["libcosmic/xdg-portal"] [dependencies] apply = "0.3.0" -tokio = { version = "1.44", features = ["full"] } -tracing = "0.1.41" -tracing-subscriber = "0.3.19" -url = "2.5.4" +tokio = { version = "1.49", features = ["full"] } +tracing = "0.1.44" +tracing-subscriber = "0.3.22" +url = "2.5.8" [dependencies.libcosmic] features = ["debug", "winit", "wgpu", "wayland", "tokio"] diff --git a/examples/subscriptions/Cargo.toml b/examples/subscriptions/Cargo.toml new file mode 100644 index 00000000..8eb69ff3 --- /dev/null +++ b/examples/subscriptions/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "subscriptions" +version = "0.1.0" +edition = "2024" + +[dependencies] + +[dependencies.libcosmic] +path = "../../" +features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"] diff --git a/examples/subscriptions/src/main.rs b/examples/subscriptions/src/main.rs new file mode 100644 index 00000000..47bd3772 --- /dev/null +++ b/examples/subscriptions/src/main.rs @@ -0,0 +1,80 @@ +// Copyright 2025 System76 +// SPDX-License-Identifier: MPL-2.0 + +//! Application API example + +use cosmic::app::{Core, Settings, Task}; +use cosmic::iced::Subscription; +use cosmic::{executor, prelude::*, widget}; + +/// Runs application with these settings +fn main() -> Result<(), Box> { + cosmic::app::run::(Settings::default(), ())?; + Ok(()) +} + +/// Messages that are used specifically by our [`App`]. +#[derive(Clone, Debug)] +pub enum Message {} + +/// The [`App`] stores application-specific state. +pub struct App { + core: Core, +} + +/// Implement [`cosmic::Application`] to integrate with COSMIC. +impl cosmic::Application for App { + /// Default async executor to use with the app. + type Executor = executor::Default; + + /// Argument received [`cosmic::Application::new`]. + type Flags = (); + + /// Message type specific to our [`App`]. + type Message = Message; + + /// The unique application ID to supply to the window manager. + const APP_ID: &'static str = "org.cosmic.TextInputsDemo"; + + fn core(&self) -> &Core { + &self.core + } + + fn core_mut(&mut self) -> &mut Core { + &mut self.core + } + + /// Creates the application, and optionally emits task on initialize. + fn init(core: Core, _input: Self::Flags) -> (Self, Task) { + let mut app = App { core }; + + let commands = Task::batch(vec![app.update_title()]); + + (app, commands) + } + + fn subscription(&self) -> Subscription { + Subscription::none() + } + + /// Handle application events here. + fn update(&mut self, message: Self::Message) -> Task { + Task::none() + } + + /// Creates a view after each update. + fn view(&self) -> Element<'_, Self::Message> { + widget::row().into() + } +} + +impl App +where + Self: cosmic::Application, +{ + fn update_title(&mut self) -> Task { + let window_title = format!("COSMIC Subscriptions Demo"); + self.set_header_title(window_title.clone()); + self.set_window_title(window_title, self.core.main_window_id().unwrap()) + } +} diff --git a/examples/table-view/Cargo.toml b/examples/table-view/Cargo.toml index 41669cb8..8ed45928 100644 --- a/examples/table-view/Cargo.toml +++ b/examples/table-view/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -tracing = "0.1.37" -tracing-subscriber = "0.3.17" +tracing = "0.1.44" +tracing-subscriber = "0.3.22" tracing-log = "0.2.0" chrono = "*" diff --git a/examples/text-input/Cargo.toml b/examples/text-input/Cargo.toml index fb1bdf28..fe6105c2 100644 --- a/examples/text-input/Cargo.toml +++ b/examples/text-input/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -tracing = "0.1.41" -tracing-subscriber = "0.3.19" +tracing = "0.1.44" +tracing-subscriber = "0.3.22" tracing-log = "0.2.0" [dependencies.libcosmic] From 85709b5c2943648df1293baeef852dc0d7907d2e Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 15 Jan 2026 15:23:51 +0100 Subject: [PATCH 402/556] fix(iced): fix for crash in cosmic-launcher --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 176589f6..2db5545f 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 176589f64cc9adc3cb65da373d2e56c998326fc2 +Subproject commit 2db5545fbee505c2c643c628a8984d1666c4d451 From 3e6c9a6addca2dfe8cadc1dbd03add72cb6d0673 Mon Sep 17 00:00:00 2001 From: Jonatan Pettersson Date: Fri, 16 Jan 2026 14:19:06 +0100 Subject: [PATCH 403/556] feat: add optional placeholder text to dropdown --- src/widget/dropdown/widget.rs | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index d4a9bc87..03be4eb3 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -47,6 +47,8 @@ where gap: f32, #[setters(into)] padding: Padding, + #[setters(strip_option, into)] + placeholder: Option>, #[setters(strip_option)] text_size: Option, text_line_height: text::LineHeight, @@ -86,6 +88,7 @@ where selections, icons: Cow::Borrowed(&[]), selected, + placeholder: None, width: Length::Shrink, gap: Self::DEFAULT_GAP, padding: Self::DEFAULT_PADDING, @@ -115,6 +118,7 @@ where selections, icons, selected, + placeholder, width, gap, padding, @@ -131,6 +135,7 @@ where selections, icons, selected, + placeholder, width, gap, padding, @@ -241,6 +246,7 @@ where .map(AsRef::as_ref) .zip(tree.state.downcast_mut::().selections.get_mut(id)) }), + self.placeholder.as_deref(), !self.icons.is_empty(), ) } @@ -313,6 +319,7 @@ where font, self.selected.and_then(|id| self.selections.get(id)), self.selected.and_then(|id| self.icons.get(id)), + self.placeholder.as_deref(), tree.state.downcast_ref::(), viewport, ); @@ -451,6 +458,7 @@ pub fn layout( text_line_height: text::LineHeight, font: Option, selection: Option<(&str, &mut crate::Plain)>, + placeholder: Option<&str>, has_icons: bool, ) -> layout::Node { use std::f32; @@ -459,8 +467,8 @@ pub fn layout( let max_width = match width { Length::Shrink => { - let measure = move |(label, paragraph): (_, &mut crate::Plain)| -> f32 { - paragraph.update(Text { + let measure = move |(label, paragraph): (_, Option<&mut crate::Plain>)| -> f32 { + let text = Text { content: label, bounds: Size::new(f32::MAX, f32::MAX), size: iced::Pixels(text_size), @@ -470,11 +478,22 @@ pub fn layout( vertical_alignment: alignment::Vertical::Top, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), - }); + }; + let paragraph = match paragraph { + Some(p) => { + p.update(text); + p + } + None => &mut crate::Plain::new(text), + }; paragraph.min_width().round() }; - selection.map(measure).unwrap_or_default() + selection + .map(|(l, p)| (l, Some(p))) + .or_else(|| placeholder.map(|l| (l, None))) + .map(measure) + .unwrap_or_default() } _ => 0.0, }; @@ -841,6 +860,7 @@ pub fn draw<'a, S>( font: crate::font::Font, selected: Option<&'a S>, icon: Option<&'a icon::Handle>, + placeholder: Option<&'a str>, state: &'a State, viewport: &Rectangle, ) where @@ -880,7 +900,7 @@ pub fn draw<'a, S>( ); } - if let Some(content) = selected.map(AsRef::as_ref) { + if let Some(content) = selected.map(AsRef::as_ref).or(placeholder) { let text_size = text_size.unwrap_or_else(|| text::Renderer::default_size(renderer).0); let mut bounds = Rectangle { From 097c76f0e56919f4c168e8a53aa5e67e207ac8b1 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Fri, 16 Jan 2026 17:23:40 +0100 Subject: [PATCH 404/556] i18n: translation updates from weblate Co-authored-by: Baurzhan Muftakhidinov Co-authored-by: Hosted Weblate --- i18n/kk/libcosmic.ftl | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 i18n/kk/libcosmic.ftl diff --git a/i18n/kk/libcosmic.ftl b/i18n/kk/libcosmic.ftl new file mode 100644 index 00000000..e69de29b From 689f25be539bb7163fe01dd3daaa253dc212f131 Mon Sep 17 00:00:00 2001 From: vacenty <193441458+vacenty@users.noreply.github.com> Date: Wed, 21 Jan 2026 14:08:25 +0100 Subject: [PATCH 405/556] feat(spin_button): when value is min/maxed, disable decrease/increase button --- src/widget/spin_button.rs | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index 13cc881f..9ad81b4d 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -181,20 +181,22 @@ fn make_button<'a, T, Message>( spin_button: &SpinButton<'a, T, Message>, icon: &'static str, #[cfg(feature = "a11y")] name: String, - operation: fn(T, T, T, T) -> T, + operation: Option T>, ) -> Element<'a, Message> where Message: Clone + 'static, T: Copy + Sub + Add + PartialOrd, { - let mut button = icon::from_name(icon) - .apply(button::icon) - .on_press((spin_button.on_press)(operation( + let mut button = icon::from_name(icon).apply(button::icon); + + if let Some(f) = operation { + button = button.on_press((spin_button.on_press)(f( spin_button.value, spin_button.step, spin_button.min, spin_button.max, - ))); + ))) + }; #[cfg(feature = "a11y")] { @@ -214,14 +216,20 @@ where "list-remove-symbolic", #[cfg(feature = "a11y")] [&spin_button.name, " decrease"].concat(), - decrement, + match spin_button.value == spin_button.min { + true => None, + false => Some(decrement), + }, ); let increment_button = make_button( &spin_button, "list-add-symbolic", #[cfg(feature = "a11y")] [&spin_button.name, " increase"].concat(), - increment, + match spin_button.value == spin_button.max { + true => None, + false => Some(increment), + }, ); let label = text::body(spin_button.label) .apply(container) @@ -248,14 +256,20 @@ where "list-remove-symbolic", #[cfg(feature = "a11y")] [&spin_button.label, " decrease"].concat(), - decrement, + match spin_button.value == spin_button.min { + true => None, + false => Some(decrement), + }, ); let increment_button = make_button( &spin_button, "list-add-symbolic", #[cfg(feature = "a11y")] [&spin_button.label, " increase"].concat(), - increment, + match spin_button.value == spin_button.max { + true => None, + false => Some(increment), + }, ); let label = text::body(spin_button.label) From d71c42102d9899d8a6a924c4b064175d2e4a2230 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 21 Jan 2026 19:21:46 -0500 Subject: [PATCH 406/556] fix(segmented button): tab dnd --- src/widget/segmented_button/widget.rs | 117 +++++++++++++++++--------- 1 file changed, 75 insertions(+), 42 deletions(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 72bc7580..7a01749e 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -297,11 +297,8 @@ where } /// Enable drag-and-drop support for tabs using the provided payload builder. - pub fn enable_tab_drag( - mut self, - payload: impl Fn(Entity) -> Option<(String, Vec)> + 'static, - ) -> Self { - self.tab_drag = Some(TabDragSource::new(payload)); + pub fn enable_tab_drag(mut self, mime: String) -> Self { + self.tab_drag = Some(TabDragSource::new(mime)); self } @@ -664,28 +661,29 @@ where bounds: Rectangle, cursor: Point, ) -> Option { - let dragging = state.dragging_tab?; + let _ = state.dragging_tab?; self.variant_bounds(state, bounds) .filter_map(|item| match item { ItemBounds::Button(entity, rect) if rect.contains(cursor) => Some((entity, rect)), _ => None, }) - .find_map(|(entity, rect)| { + .map(|(entity, rect)| { let before = if Self::VERTICAL { cursor.y < rect.center_y() } else { cursor.x < rect.center_x() }; - Some(DropHint { + DropHint { entity, side: if before { DropSide::Before } else { DropSide::After }, - }) + } }) + .next() } fn start_tab_drag( @@ -713,33 +711,24 @@ where tab_drag.threshold ); - let Some((mime, data)) = (tab_drag.payload)(entity) else { - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "start_tab_drag aborted entity={:?}: payload builder returned None", - entity - ); - return false; - }; - - let data_len = data.len(); - let mime_label = mime.clone(); + let data_len = 0; iced_core::clipboard::start_dnd::( clipboard, false, Some(iced_core::clipboard::DndSource::Widget(self.id.0.clone())), None, - Box::new(SimpleDragData::new(mime, data)), + Box::new(SimpleDragData::new(tab_drag.mime.clone(), vec![1])), DndAction::Move, ); log::trace!( target: TAB_REORDER_LOG_TARGET, "tab drag started entity={:?} mime={} bytes={}", entity, - mime_label, + tab_drag.mime, data_len ); + state.dragging_tab = Some(entity); state.tab_drag_candidate = None; state.pressed_item = None; @@ -815,6 +804,7 @@ where tab_drag_candidate: None, dragging_tab: None, drop_hint: None, + offer_mimes: Vec::new(), }) } @@ -966,26 +956,29 @@ where "offer enter id={my_id:?} entity={entity:?} @ ({x},{y}) mimes={mime_types:?}" ); - let on_dnd_enter = - self.on_dnd_enter - .as_ref() - .zip(entity) - .map(|(on_enter, entity)| { - move |_, _, mime_types| on_enter(entity, mime_types) - }); + let on_dnd_enter = self + .on_dnd_enter + .as_ref() + .zip(entity) + .map(|(on_enter, entity)| move |_, _, mimes| on_enter(entity, mimes)); + let mimes = if let Some(mime) = self.tab_drag.as_ref().map(|d| &d.mime) + && mime_types.is_empty() + { + vec![mime.clone()] + } else { + mime_types.clone() + }; + state.offer_mimes = mimes.clone(); - _ = state.dnd_state.on_enter::( - *x, - *y, - mime_types.clone(), - on_dnd_enter, - entity, - ); + _ = state + .dnd_state + .on_enter::(*x, *y, mimes, on_dnd_enter, entity); } DndEvent::Offer(id, OfferEvent::LeaveDestination) if Some(my_id) != *id => {} DndEvent::Offer(id, OfferEvent::Leave | OfferEvent::LeaveDestination) if Some(my_id) == *id => { + state.dragging_tab = None; state.drop_hint = None; self.emit_drop_hint(shell, state.drop_hint); if let Some(Some(entity)) = entity { @@ -999,7 +992,6 @@ where ); _ = state.dnd_state.on_leave::(None); } - DndEvent::Offer(_, OfferEvent::Leave | OfferEvent::LeaveDestination) => {} DndEvent::Offer(id, OfferEvent::Motion { x, y }) if Some(my_id) == *id => { log::trace!( target: TAB_REORDER_LOG_TARGET, @@ -1034,7 +1026,7 @@ where .as_ref() .map(|dnd| dnd.selected_action); if let Some(on_dnd_enter) = self.on_dnd_enter.as_ref() { - shell.publish(on_dnd_enter(new_entity, Vec::new())); + shell.publish(on_dnd_enter(new_entity, state.offer_mimes.clone())); } if let Some(dnd) = state.dnd_state.drag_offer.as_mut() { dnd.data = Some(new_entity); @@ -1097,7 +1089,11 @@ where .drag_offer .as_ref() .is_some_and(|offer| offer.selected_action.contains(DndAction::Move)); - let pending_reorder = if allow_reorder && self.on_reorder.is_some() { + let pending_reorder = if allow_reorder + && self.on_reorder.is_some() + && self.tab_drag.as_ref().is_some_and(|d| d.mime == *mime_type) + && state.dragging_tab.is_some() + { drop_entity.and_then(|target| self.reorder_event_for_drop(state, target)) } else { None @@ -1122,6 +1118,8 @@ where shell.publish(msg); } state.drop_hint = None; + state.dragging_tab = None; + self.emit_drop_hint(shell, state.drop_hint); if let Some(event) = pending_reorder { if let Some(on_reorder) = self.on_reorder.as_ref() { @@ -1135,6 +1133,8 @@ where "data received without entity id={my_id:?}" ); state.drop_hint = None; + state.dragging_tab = None; + self.emit_drop_hint(shell, state.drop_hint); if let Some(event) = pending_reorder { if let Some(on_reorder) = self.on_reorder.as_ref() { @@ -2118,6 +2118,36 @@ where } } + if let Some(mime) = self.tab_drag.as_ref().map(|d| &d.mime) { + for item in self.variant_bounds(local_state, layout.bounds()) { + if let ItemBounds::Button(_entity, rect) = item { + pushed = true; + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "register drag destination id={:?} bounds=({:.2},{:.2},{:.2},{:.2}) mimes={:?}", + my_id, + rect.x, + rect.y, + rect.width, + rect.height, + mime + ); + dnd_rectangles.push(DndDestinationRectangle { + id: my_id, + rectangle: dnd::Rectangle { + x: f64::from(rect.x), + y: f64::from(rect.y), + width: f64::from(rect.width), + height: f64::from(rect.height), + }, + mime_types: vec![Cow::Owned(mime.clone())], + actions: DndAction::Copy | DndAction::Move, + preferred: DndAction::Move, + }); + } + } + } + if !pushed { let bounds = layout.bounds(); log::trace!( @@ -2165,15 +2195,15 @@ where } struct TabDragSource { - payload: Box Option<(String, Vec)>>, + mime: String, threshold: f32, _marker: PhantomData, } impl TabDragSource { - fn new(payload: impl Fn(Entity) -> Option<(String, Vec)> + 'static) -> Self { + fn new(mime: String) -> Self { Self { - payload: Box::new(payload), + mime, threshold: 8.0, _marker: PhantomData, } @@ -2254,6 +2284,8 @@ pub struct LocalState { wheel_timestamp: Option, /// Dnd state pub dnd_state: crate::widget::dnd_destination::State>, + /// Dnd state + pub offer_mimes: Vec, /// Tracks multi-touch events fingers_pressed: HashSet, /// The currently pressed item @@ -2391,6 +2423,7 @@ mod tests { tab_drag_candidate: None, dragging_tab: Some(dragging), drop_hint: None, + offer_mimes: Vec::new(), }; state.buttons_visible = len; state.known_length = len; From beddbf17703728182395a13267954d839226331d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 22 Jan 2026 10:21:05 -0500 Subject: [PATCH 407/556] improv(segmented_button): dnd state handling --- src/widget/segmented_button/widget.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 7a01749e..4206e727 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -968,17 +968,20 @@ where } else { mime_types.clone() }; - state.offer_mimes = mimes.clone(); + state.offer_mimes.clone_from(&mimes); _ = state .dnd_state .on_enter::(*x, *y, mimes, on_dnd_enter, entity); } DndEvent::Offer(id, OfferEvent::LeaveDestination) if Some(my_id) != *id => {} - DndEvent::Offer(id, OfferEvent::Leave | OfferEvent::LeaveDestination) - if Some(my_id) == *id => + DndEvent::Offer(id, leave) + if matches!(leave, OfferEvent::Leave | OfferEvent::LeaveDestination) + && Some(my_id) == *id => { - state.dragging_tab = None; + if matches!(leave, OfferEvent::Leave) { + state.dragging_tab = None; + } state.drop_hint = None; self.emit_drop_hint(shell, state.drop_hint); if let Some(Some(entity)) = entity { From 927035809f1564674434c27cbecdc67e199db28e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 22 Jan 2026 15:49:47 -0500 Subject: [PATCH 408/556] refactor(segmented button): only clear tab drag after source event cancel or finish --- src/widget/segmented_button/widget.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 4206e727..9d276be8 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -1121,7 +1121,6 @@ where shell.publish(msg); } state.drop_hint = None; - state.dragging_tab = None; self.emit_drop_hint(shell, state.drop_hint); if let Some(event) = pending_reorder { @@ -1136,7 +1135,6 @@ where "data received without entity id={my_id:?}" ); state.drop_hint = None; - state.dragging_tab = None; self.emit_drop_hint(shell, state.drop_hint); if let Some(event) = pending_reorder { From f1c43f79abd4d5c0c610241def1d51f5ba0fbe3a Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 24 Jan 2026 17:02:07 +0100 Subject: [PATCH 409/556] i18n: translation updates from weblate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Aman Alam Co-authored-by: Baurzhan Muftakhidinov Co-authored-by: Hosted Weblate Co-authored-by: Jun Hwi Ku Co-authored-by: Walter William Beckerleg Bruckman Co-authored-by: gift983 <983649@my.leicestercollege.ac.uk> Co-authored-by: summoner001 Co-authored-by: 김유빈 Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/hu/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/kk/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ko/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/zh_Hans/ Translation: Pop OS/libcosmic --- i18n/hu/libcosmic.ftl | 2 +- i18n/kk/libcosmic.ftl | 27 +++++++++++++++++++++++++++ i18n/ko/libcosmic.ftl | 27 +++++++++++++++++++++++++++ i18n/pa/libcosmic.ftl | 0 i18n/ti/libcosmic.ftl | 0 i18n/zh-Hans/libcosmic.ftl | 1 + 6 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 i18n/pa/libcosmic.ftl create mode 100644 i18n/ti/libcosmic.ftl diff --git a/i18n/hu/libcosmic.ftl b/i18n/hu/libcosmic.ftl index 583fbe5c..02069244 100644 --- a/i18n/hu/libcosmic.ftl +++ b/i18n/hu/libcosmic.ftl @@ -2,7 +2,7 @@ close = Bezárás # About license = Licenc -links = Linkek +links = Hivatkozások developers = Fejlesztők designers = Tervezők artists = Művészek diff --git a/i18n/kk/libcosmic.ftl b/i18n/kk/libcosmic.ftl index e69de29b..bb06e98f 100644 --- a/i18n/kk/libcosmic.ftl +++ b/i18n/kk/libcosmic.ftl @@ -0,0 +1,27 @@ +close = Жабу +license = Лицензия +links = Сілтемелер +developers = Әзірлеушілер +designers = Дизайнерлер +artists = Суретшілер +translators = Аудармашылар +documenters = Құжаттаушылар +january = Қаңтар { $year } +february = Ақпан { $year } +march = Наурыз { $year } +april = Сәуір { $year } +may = Мамыр { $year } +june = Маусым { $year } +july = Шілде { $year } +august = Тамыз { $year } +september = Қыркүйек { $year } +october = Қазан { $year } +november = Қараша { $year } +december = Желтоқсан { $year } +monday = Дс +tuesday = Сс +wednesday = Ср +thursday = Бс +friday = Жм +saturday = Сб +sunday = Жс diff --git a/i18n/ko/libcosmic.ftl b/i18n/ko/libcosmic.ftl index e69de29b..8d499756 100644 --- a/i18n/ko/libcosmic.ftl +++ b/i18n/ko/libcosmic.ftl @@ -0,0 +1,27 @@ +february = { $year }년 2월 +close = 닫기 +documenters = 문서 작성자 +november = { $year }년 11월 +friday = 금 +tuesday = 화 +may = { $year }년 5월 +wednesday = 수 +april = { $year }년 4월 +monday = 월 +translators = 번역가 +artists = 아티스트 +license = 라이선스 +december = { $year }년 12월 +sunday = 일 +links = 링크 +march = { $year }년 3월 +june = { $year }년 6월 +saturday = 토 +august = { $year }년 8월 +developers = 개발자 +july = { $year }년 7월 +thursday = 목 +september = { $year }년 9월 +designers = 디자이너 +october = { $year }년 10월 +january = { $year }년 1월 diff --git a/i18n/pa/libcosmic.ftl b/i18n/pa/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/ti/libcosmic.ftl b/i18n/ti/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/zh-Hans/libcosmic.ftl b/i18n/zh-Hans/libcosmic.ftl index 5d9fbd66..9dfd6139 100644 --- a/i18n/zh-Hans/libcosmic.ftl +++ b/i18n/zh-Hans/libcosmic.ftl @@ -24,3 +24,4 @@ friday = 周五 saturday = 周六 sunday = 周日 artists = 艺术家 +documenters = 文档作者 From 9fcd449611d30891d5fe5272520672da5ef6a723 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 27 Jan 2026 13:38:58 -0500 Subject: [PATCH 410/556] fix(segmented_button): hover state handling when hover state changes, paragraphs also need to be updated. I'll make a not to check this again after the rebase though. --- src/widget/segmented_button/widget.rs | 182 +++++++++++++++----------- 1 file changed, 106 insertions(+), 76 deletions(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 9d276be8..4f68b3de 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -242,6 +242,49 @@ where } } + fn update_entity_paragraph(&mut self, state: &mut LocalState, key: Entity) { + if let Some(text) = self.model.text.get(key) { + let font = if self.button_is_focused(state, key) { + self.font_active + } else if state.show_context.is_some() || self.button_is_hovered(state, key) { + self.font_hovered + } else if self.model.is_active(key) { + self.font_active + } else { + self.font_inactive + }; + + let mut hasher = DefaultHasher::new(); + text.hash(&mut hasher); + font.hash(&mut hasher); + let text_hash = hasher.finish(); + + if let Some(prev_hash) = state.text_hashes.insert(key, text_hash) { + if prev_hash == text_hash { + return; + } + } + + let text = Text { + content: text.as_ref(), + size: iced::Pixels(self.font_size), + bounds: Size::INFINITY, + font, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + shaping: Shaping::Advanced, + wrapping: Wrapping::None, + line_height: self.line_height, + }; + + if let Some(paragraph) = state.paragraphs.get_mut(key) { + paragraph.update(text); + } else { + state.paragraphs.insert(key, crate::Plain::new(text)); + } + } + } + pub fn context_menu(mut self, context_menu: Option>>) -> Self where Message: Clone + 'static, @@ -761,6 +804,14 @@ where SelectionMode: Default, Message: 'static + Clone, { + fn id(&self) -> Option { + Some(self.id.0.clone()) + } + + fn set_id(&mut self, id: widget::Id) { + self.id = Id(id); + } + fn children(&self) -> Vec { let mut children = Vec::new(); @@ -812,46 +863,7 @@ where let state = tree.state.downcast_mut::(); for key in self.model.order.iter().copied() { - if let Some(text) = self.model.text.get(key) { - let font = if self.button_is_focused(state, key) { - self.font_active - } else if state.show_context.is_some() || self.button_is_hovered(state, key) { - self.font_hovered - } else if self.model.is_active(key) { - self.font_active - } else { - self.font_inactive - }; - - let mut hasher = DefaultHasher::new(); - text.hash(&mut hasher); - font.hash(&mut hasher); - let text_hash = hasher.finish(); - - if let Some(prev_hash) = state.text_hashes.insert(key, text_hash) { - if prev_hash == text_hash { - continue; - } - } - - let text = Text { - content: text.as_ref(), - size: iced::Pixels(self.font_size), - bounds: Size::INFINITY, - font, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, - shaping: Shaping::Advanced, - wrapping: Wrapping::None, - line_height: self.line_height, - }; - - if let Some(paragraph) = state.paragraphs.get_mut(key) { - paragraph.update(text); - } else { - state.paragraphs.insert(key, crate::Plain::new(text)); - } - } + self.update_entity_paragraph(state, key); } // Diff the context menu @@ -899,9 +911,8 @@ where shell: &mut Shell<'_, Message>, _viewport: &iced::Rectangle, ) -> event::Status { - let bounds = layout.bounds(); + let my_bounds = layout.bounds(); let state = tree.state.downcast_mut::(); - state.hovered = Item::None; let my_id = self.get_drag_id(); @@ -938,7 +949,7 @@ where }, ) if Some(my_id) == *id => { let entity = self - .variant_bounds(state, bounds) + .variant_bounds(state, my_bounds) .filter_map(|item| match item { ItemBounds::Button(entity, bounds) => Some((entity, bounds)), _ => None, @@ -947,7 +958,7 @@ where .map(|(key, _)| key); state.drop_hint = self.drop_hint_for_position( state, - bounds, + my_bounds, Point::new(*x as f32, *y as f32), ); self.emit_drop_hint(shell, state.drop_hint); @@ -979,9 +990,6 @@ where if matches!(leave, OfferEvent::Leave | OfferEvent::LeaveDestination) && Some(my_id) == *id => { - if matches!(leave, OfferEvent::Leave) { - state.dragging_tab = None; - } state.drop_hint = None; self.emit_drop_hint(shell, state.drop_hint); if let Some(Some(entity)) = entity { @@ -1001,7 +1009,7 @@ where "offer motion id={my_id:?} cursor=({x},{y}) current_entity={entity:?}" ); let new = self - .variant_bounds(state, bounds) + .variant_bounds(state, my_bounds) .filter_map(|item| match item { ItemBounds::Button(entity, bounds) => Some((entity, bounds)), _ => None, @@ -1018,11 +1026,15 @@ where ); state.drop_hint = self.drop_hint_for_position( state, - bounds, + my_bounds, Point::new(*x as f32, *y as f32), ); self.emit_drop_hint(shell, state.drop_hint); if Some(Some(new_entity)) != entity { + state.hovered = Item::Tab(new_entity); + for key in self.model.order.iter().copied() { + self.update_entity_paragraph(state, key); + } let prev_action = state .dnd_state .drag_offer @@ -1039,6 +1051,10 @@ where } } } else if entity.is_some() { + state.hovered = Item::None; + for key in self.model.order.iter().copied() { + self.update_entity_paragraph(state, key); + } log::trace!( target: TAB_REORDER_LOG_TARGET, "offer motion leaving id={my_id:?}" @@ -1124,31 +1140,24 @@ where self.emit_drop_hint(shell, state.drop_hint); if let Some(event) = pending_reorder { + state.focused_item = Item::Tab(event.dragged); + state.hovered = Item::None; + for key in self.model.order.iter().copied() { + self.update_entity_paragraph(state, key); + } if let Some(on_reorder) = self.on_reorder.as_ref() { shell.publish(on_reorder(event)); + return event::Status::Captured; } } return ret; - } else { - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "data received without entity id={my_id:?}" - ); - state.drop_hint = None; - - self.emit_drop_hint(shell, state.drop_hint); - if let Some(event) = pending_reorder { - if let Some(on_reorder) = self.on_reorder.as_ref() { - shell.publish(on_reorder(event)); - } - } } } _ => {} } } - if cursor_position.is_over(bounds) { + if cursor_position.is_over(my_bounds) { let fingers_pressed = state.fingers_pressed.len(); match event { @@ -1166,10 +1175,14 @@ where // Check for clicks on the previous and next tab buttons, when tabs are collapsed. if state.collapsed { // Check if the prev tab button was clicked. - if cursor_position.is_over(prev_tab_bounds(&bounds, f32::from(self.button_height))) + if cursor_position + .is_over(prev_tab_bounds(&my_bounds, f32::from(self.button_height))) && self.prev_tab_sensitive(state) { state.hovered = Item::PrevButton; + for key in self.model.order.iter().copied() { + self.update_entity_paragraph(state, key); + } if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerLifted { .. }) = event { @@ -1178,11 +1191,13 @@ where } else { // Check if the next tab button was clicked. if cursor_position - .is_over(next_tab_bounds(&bounds, f32::from(self.button_height))) + .is_over(next_tab_bounds(&my_bounds, f32::from(self.button_height))) && self.next_tab_sensitive(state) { state.hovered = Item::NextButton; - + for key in self.model.order.iter().copied() { + self.update_entity_paragraph(state, key); + } if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerLifted { .. }) = event { @@ -1193,7 +1208,7 @@ where } for (key, bounds) in self - .variant_bounds(state, bounds) + .variant_bounds(state, my_bounds) .filter_map(|item| match item { ItemBounds::Button(entity, bounds) => Some((entity, bounds)), _ => None, @@ -1203,7 +1218,12 @@ where if cursor_position.is_over(bounds) { if self.model.items[key].enabled { // Record that the mouse is hovering over this button. - state.hovered = Item::Tab(key); + if state.hovered != Item::Tab(key) { + state.hovered = Item::Tab(key); + for key in self.model.order.iter().copied() { + self.update_entity_paragraph(state, key); + } + } let close_button_bounds = close_bounds(bounds, f32::from(self.close_icon.size)); @@ -1320,6 +1340,9 @@ where } break; + } else if state.hovered == Item::Tab(key) { + state.hovered = Item::None; + self.update_entity_paragraph(state, key); } } @@ -1377,15 +1400,22 @@ where } } } - } else if state.is_focused() { - // Unfocus on clicks outside of the boundaries of the segmented button. - if is_pressed(&event) { - state.unfocus(); - state.pressed_item = None; - return event::Status::Ignored; + } else { + if let Item::Tab(key) = std::mem::replace(&mut state.hovered, Item::None) { + for key in self.model.order.iter().copied() { + self.update_entity_paragraph(state, key); + } + } + if state.is_focused() { + // Unfocus on clicks outside of the boundaries of the segmented button. + if is_pressed(&event) { + state.unfocus(); + state.pressed_item = None; + return event::Status::Ignored; + } + } else if is_lifted(&event) { + state.pressed_item = None; } - } else if is_lifted(&event) { - state.pressed_item = None; } if let (Some(tab_drag), Some(candidate)) = From b71a7c9edffa6b278836da90106976ded9e90159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Wed, 28 Jan 2026 00:38:23 +0100 Subject: [PATCH 411/556] improv: remove double coloring of `content_container` windows This sets the main content and the header bar to transparent when `content_container` is true, so that things aren't colored twice and overlayed on top of each other. This ensures that modifying color alpha behaves as expected, especially for frosted glass. --- src/app/mod.rs | 2 +- src/theme/style/iced.rs | 8 +++++++- src/widget/header_bar.rs | 5 +++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 090698df..67636dac 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -689,7 +689,6 @@ impl ApplicationExt for App { .apply(container) .width(iced::Length::Fill) .height(iced::Length::Fill) - .class(crate::theme::Container::WindowBackground) .apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_content_container"))) .into() } else { @@ -713,6 +712,7 @@ impl ApplicationExt for App { .focused(focused) .maximized(maximized) .sharp_corners(sharp_corners) + .transparent(content_container) .title(&core.window.header_title) .on_drag(crate::Action::Cosmic(Action::Drag)) .on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu)) diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 32309860..937ee388 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -396,6 +396,7 @@ pub enum Container<'a> { HeaderBar { focused: bool, sharp_corners: bool, + transparent: bool, }, List, Primary, @@ -511,6 +512,7 @@ impl iced_container::Catalog for Theme { Container::HeaderBar { focused, sharp_corners, + transparent, } => { let (icon_color, text_color) = if *focused { ( @@ -527,7 +529,11 @@ impl iced_container::Catalog for Theme { iced_container::Style { icon_color: Some(icon_color), text_color: Some(text_color), - background: Some(iced::Background::Color(cosmic.background.base.into())), + background: if *transparent { + None + } else { + Some(iced::Background::Color(cosmic.background.base.into())) + }, border: Border { radius: [ if *sharp_corners { diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index d500bde3..c5bde28f 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -28,6 +28,7 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { is_ssd: false, on_double_click: None, is_condensed: false, + transparent: false, } } @@ -92,6 +93,9 @@ pub struct HeaderBar<'a, Message> { /// Whether the headerbar should be compact is_condensed: bool, + + /// Whether the headerbar should be transparent + transparent: bool, } impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { @@ -412,6 +416,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .class(crate::theme::Container::HeaderBar { focused: self.focused, sharp_corners: self.sharp_corners, + transparent: self.transparent, }) .center_y(Length::Shrink) .apply(widget::mouse_area); From cf19ac665f353bbca0bad945403976ccdf6c8191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Wed, 28 Jan 2026 00:49:48 +0100 Subject: [PATCH 412/556] chore: update dependencies --- Cargo.toml | 16 ++++++++-------- cosmic-config-derive/Cargo.toml | 4 ++-- cosmic-config-derive/src/lib.rs | 4 ++-- cosmic-config/Cargo.toml | 6 +++--- cosmic-theme/Cargo.toml | 8 ++++---- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 46091bcc..feaa8c74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "libcosmic" -version = "0.1.0" +version = "1.0.0" edition = "2024" -rust-version = "1.85" +rust-version = "1.90" [lib] name = "cosmic" @@ -104,7 +104,7 @@ async-fs = { version = "2.2", optional = true } async-std = { version = "1.13", optional = true } auto_enums = "0.8.7" cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d0e95be", optional = true } -chrono = "0.4.42" +chrono = "0.4.43" cosmic-config = { path = "cosmic-config" } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } # Internationalization @@ -126,26 +126,26 @@ log = "0.4" mime = { version = "0.3.17", optional = true } palette = "0.7.6" raw-window-handle = "0.6" -rfd = { version = "0.15.4", default-features = false, features = [ +rfd = { version = "0.16.0", default-features = false, features = [ "xdg-portal", ], optional = true } rustix = { version = "1.1", features = ["pipe", "process"], optional = true } serde = { version = "1.0.228", features = ["derive"] } slotmap = "1.1.1" smol = { version = "2.0.2", optional = true } -thiserror = "2.0.17" +thiserror = "2.0.18" taffy = { version = "0.9.2", features = ["grid"] } tokio = { version = "1.49.0", optional = true } tracing = "0.1.44" unicode-segmentation = "1.12" url = "2.5.8" -zbus = { version = "5.13.1", default-features = false, optional = true } +zbus = { version = "5.13.2", default-features = false, optional = true } # Enable DBus feature on Linux targets [target.'cfg(target_os = "linux")'.dependencies] cosmic-config = { path = "cosmic-config", features = ["dbus"] } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" } -zbus = { version = "5.13.1", default-features = false } +zbus = { version = "5.13.2", default-features = false } [target.'cfg(unix)'.dependencies] freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } @@ -209,7 +209,7 @@ git = "https://github.com/pop-os/cosmic-panel" optional = true [dependencies.ron] -version = "0.11" +version = "0.12" optional = true [workspace] diff --git a/cosmic-config-derive/Cargo.toml b/cosmic-config-derive/Cargo.toml index 55eeb871..9d5f4b88 100644 --- a/cosmic-config-derive/Cargo.toml +++ b/cosmic-config-derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cosmic-config-derive" -version = "0.1.0" -edition = "2021" +version = "1.0.0" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] diff --git a/cosmic-config-derive/src/lib.rs b/cosmic-config-derive/src/lib.rs index 668154cd..cc19a91e 100644 --- a/cosmic-config-derive/src/lib.rs +++ b/cosmic-config-derive/src/lib.rs @@ -106,7 +106,7 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream { }) }); - let gen = quote! { + let generate = quote! { impl CosmicConfigEntry for #name { const VERSION: u64 = #version; @@ -147,5 +147,5 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream { } }; - gen.into() + generate.into() } diff --git a/cosmic-config/Cargo.toml b/cosmic-config/Cargo.toml index 78d671ca..6103c15e 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-config" -version = "0.1.0" +version = "1.0.0" edition = "2024" [features] @@ -11,11 +11,11 @@ subscription = ["iced_futures"] [dependencies] cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } -zbus = { version = "5.13.1", default-features = false, optional = true } +zbus = { version = "5.13.2", default-features = false, optional = true } atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" } calloop = { version = "0.14.3", optional = true } notify = "8.2.0" -ron = "0.11.0" +ron = "0.12.0" serde = "1.0.228" cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true } iced = { path = "../iced/", default-features = false, optional = true } diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index 10b548b4..cf6afe74 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-theme" -version = "0.1.0" +version = "1.0.0" edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -21,11 +21,11 @@ serde = { version = "1.0.228", features = ["derive"] } serde_json = { version = "1.0.149", optional = true, features = [ "preserve_order", ] } -ron = "0.11.0" -csscolorparser = { version = "0.7.2", features = ["serde"] } +ron = "0.12.0" +csscolorparser = { version = "0.8.1", features = ["serde"] } cosmic-config = { path = "../cosmic-config/", default-features = false, features = [ "subscription", "macro", ] } dirs.workspace = true -thiserror = "2.0.17" +thiserror = "2.0.18" From fdcba7d8ececc35c09a7871b018930f752ac784b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 28 Jan 2026 18:04:55 -0500 Subject: [PATCH 413/556] fix(segmented_button): dnd hover --- src/widget/segmented_button/widget.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 4f68b3de..e4f416bf 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -966,6 +966,13 @@ where target: TAB_REORDER_LOG_TARGET, "offer enter id={my_id:?} entity={entity:?} @ ({x},{y}) mimes={mime_types:?}" ); + // force hovered state update + if let Some(entity) = entity { + state.hovered = Item::Tab(entity); + for key in self.model.order.iter().copied() { + self.update_entity_paragraph(state, key); + } + } let on_dnd_enter = self .on_dnd_enter @@ -1001,6 +1008,10 @@ where target: TAB_REORDER_LOG_TARGET, "offer leave id={my_id:?} entity={entity:?}" ); + state.hovered = Item::None; + for key in self.model.order.iter().copied() { + self.update_entity_paragraph(state, key); + } _ = state.dnd_state.on_leave::(None); } DndEvent::Offer(id, OfferEvent::Motion { x, y }) if Some(my_id) == *id => { From 3e78eb238159d90956e85e95e868164671b649f6 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Fri, 30 Jan 2026 21:07:58 +0100 Subject: [PATCH 414/556] i18n: translation updates from weblate Co-authored-by: Hafidz Nasruddin Co-authored-by: Hosted Weblate Co-authored-by: Languages add-on Co-authored-by: Zahid Rizky Fakhri Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/id/ Translation: Pop OS/libcosmic --- i18n/id/libcosmic.ftl | 27 +++++++++++++++++++++++++++ i18n/ms/libcosmic.ftl | 0 i18n/uz/libcosmic.ftl | 0 3 files changed, 27 insertions(+) create mode 100644 i18n/ms/libcosmic.ftl create mode 100644 i18n/uz/libcosmic.ftl diff --git a/i18n/id/libcosmic.ftl b/i18n/id/libcosmic.ftl index e69de29b..2ce82dab 100644 --- a/i18n/id/libcosmic.ftl +++ b/i18n/id/libcosmic.ftl @@ -0,0 +1,27 @@ +close = Tutup +license = Lisensi +links = Tautan +developers = Pengembang +designers = Perancang +artists = Artis +translators = Penerjemah +documenters = Dokumenter +january = Januari { $year } +february = Februari { $year } +march = Maret { $year } +april = April { $year } +may = Mei { $year } +june = Juni { $year } +july = Juli { $year } +august = Agustus { $year } +september = September { $year } +october = Oktober { $year } +november = November { $year } +december = Desember { $year } +monday = Sen +tuesday = Sel +wednesday = Rab +sunday = Min +saturday = Sab +friday = Jum +thursday = Kam diff --git a/i18n/ms/libcosmic.ftl b/i18n/ms/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/uz/libcosmic.ftl b/i18n/uz/libcosmic.ftl new file mode 100644 index 00000000..e69de29b From 30a02ec0bb3cccabb664572d98a77740ab56c2fe Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 7 Feb 2026 22:08:52 +0100 Subject: [PATCH 415/556] i18n: translation updates from weblate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Aliaksandr Truš Co-authored-by: Drugi Sapog Co-authored-by: Hosted Weblate Co-authored-by: Quentin PAGÈS Co-authored-by: jickson john Co-authored-by: jonnysemon Co-authored-by: Димко Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ar/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/be/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/uk/ Translation: Pop OS/libcosmic --- i18n/ar/libcosmic.ftl | 21 ++++++++++++++++++++- i18n/be/libcosmic.ftl | 19 +++++++++++++++++++ i18n/ml/libcosmic.ftl | 0 i18n/oc/libcosmic.ftl | 0 i18n/uk/libcosmic.ftl | 2 +- 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 i18n/ml/libcosmic.ftl create mode 100644 i18n/oc/libcosmic.ftl diff --git a/i18n/ar/libcosmic.ftl b/i18n/ar/libcosmic.ftl index 428bd892..ce3eb1e8 100644 --- a/i18n/ar/libcosmic.ftl +++ b/i18n/ar/libcosmic.ftl @@ -4,7 +4,26 @@ close = أغلِق license = الترخيص links = الروابط developers = المطورون -designers = المصممون +designers = المصمّمون artists = الفنانون translators = المترجمون documenters = الموثقون +january = يناير { $year } +february = فبراير { $year } +march = مارس { $year } +april = ابريل { $year } +may = مايو { $year } +june = يونيو { $year } +july = يوليو { $year } +august = أغسطس { $year } +september = سبتمبر { $year } +october = أكتوبر { $year } +november = نوفمبر { $year } +december = ديسمبر { $year } +monday = الاثنين +tuesday = الثلاثاء +wednesday = الأربعاء +thursday = الخميس +friday = الجمعة +saturday = السبت +sunday = الأحد diff --git a/i18n/be/libcosmic.ftl b/i18n/be/libcosmic.ftl index eb3abf33..1682a174 100644 --- a/i18n/be/libcosmic.ftl +++ b/i18n/be/libcosmic.ftl @@ -6,3 +6,22 @@ designers = Дызайнеры artists = Мастакі translators = Перакладчыкі documenters = Дакументалісты +february = Люты { $year } +november = Лістапад { $year } +friday = Пт +tuesday = Аў +may = Май { $year } +wednesday = Ср +april = Красавік { $year } +monday = Пн +december = Снежань { $year } +sunday = Нд +march = Сакавік { $year } +june = Чэрвень { $year } +saturday = Сб +august = Жнівень { $year } +july = Ліпень { $year } +thursday = Чц +september = Верасень { $year } +october = Кастрычнік { $year } +january = Студзень { $year } diff --git a/i18n/ml/libcosmic.ftl b/i18n/ml/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/oc/libcosmic.ftl b/i18n/oc/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/uk/libcosmic.ftl b/i18n/uk/libcosmic.ftl index 73278ae4..d82c2a6e 100644 --- a/i18n/uk/libcosmic.ftl +++ b/i18n/uk/libcosmic.ftl @@ -2,7 +2,7 @@ close = Закрити # About license = Ліцензія -links = Посилання +links = Ланки developers = Розробники designers = Дизайнери artists = Художники From a3cf875793aa56bda4963bd5eaa8877a4d3aefb0 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 9 Feb 2026 22:04:13 +0100 Subject: [PATCH 416/556] fix(single-instance): unminimize main window on dbus activate --- src/app/cosmic.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index ae554846..803a56bd 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -369,7 +369,16 @@ where crate::Action::Cosmic(message) => self.cosmic_update(message), crate::Action::None => iced::Task::none(), #[cfg(feature = "single-instance")] - crate::Action::DbusActivation(message) => self.app.dbus_activation(message), + crate::Action::DbusActivation(message) => { + let mut task = self.app.dbus_activation(message); + + if let Some(id) = self.app.core().main_window_id() { + let unminimize = iced_runtime::window::minimize::<()>(id, false); + task = task.chain(unminimize.discard()); + } + + task + } }; #[cfg(all(target_env = "gnu", not(target_os = "windows")))] From ae830ca21dd9f2da3c1f4a1617daeec126d3867e Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 12 Feb 2026 15:52:40 +0100 Subject: [PATCH 417/556] perf(font): use RwLock when getting fonts instead of Mutex --- src/config/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 1253ce8d..5a96a5e1 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -8,7 +8,7 @@ use cosmic_config::cosmic_config_derive::CosmicConfigEntry; use cosmic_config::{Config, CosmicConfigEntry}; use serde::{Deserialize, Serialize}; use std::collections::BTreeSet; -use std::sync::{LazyLock, Mutex, RwLock}; +use std::sync::{LazyLock, RwLock}; /// ID for the `CosmicTk` config. pub const ID: &str = "com.system76.CosmicTk"; @@ -17,7 +17,7 @@ const MONO_FAMILY_DEFAULT: &str = "Noto Sans Mono"; const SANS_FAMILY_DEFAULT: &str = "Open Sans"; /// Stores static strings of the family names for `iced::Font` compatibility. -pub static FAMILY_MAP: LazyLock>> = LazyLock::new(Mutex::default); +pub static FAMILY_MAP: LazyLock>> = LazyLock::new(RwLock::default); pub static COSMIC_TK: LazyLock> = LazyLock::new(|| { RwLock::new( @@ -156,14 +156,14 @@ pub struct FontConfig { impl From for iced::Font { fn from(font: FontConfig) -> Self { - let mut family_map = FAMILY_MAP.lock().unwrap(); - - let name: &'static str = family_map + let name = FAMILY_MAP + .read() + .unwrap() .get(font.family.as_str()) .copied() .unwrap_or_else(|| { - let value = font.family.clone().leak(); - family_map.insert(value); + let value: &'static str = font.family.clone().leak(); + FAMILY_MAP.write().unwrap().insert(value); value }); From 031818c6b08d706459f41a793e99338dd922bdbe Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 13 Feb 2026 18:30:14 +0100 Subject: [PATCH 418/556] fix(font): explicitly drop read guard in on font family lookup --- src/config/mod.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 5a96a5e1..9807961c 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -16,9 +16,6 @@ pub const ID: &str = "com.system76.CosmicTk"; const MONO_FAMILY_DEFAULT: &str = "Noto Sans Mono"; const SANS_FAMILY_DEFAULT: &str = "Open Sans"; -/// Stores static strings of the family names for `iced::Font` compatibility. -pub static FAMILY_MAP: LazyLock>> = LazyLock::new(RwLock::default); - pub static COSMIC_TK: LazyLock> = LazyLock::new(|| { RwLock::new( CosmicTk::config() @@ -156,16 +153,19 @@ pub struct FontConfig { impl From for iced::Font { fn from(font: FontConfig) -> Self { - let name = FAMILY_MAP - .read() - .unwrap() - .get(font.family.as_str()) - .copied() - .unwrap_or_else(|| { - let value: &'static str = font.family.clone().leak(); - FAMILY_MAP.write().unwrap().insert(value); - value - }); + /// Stores static strings of the family names for `iced::Font` compatibility. + static FAMILY_MAP: LazyLock>> = + LazyLock::new(RwLock::default); + + let read_guard = FAMILY_MAP.read().unwrap(); + let name: Option<&'static str> = read_guard.get(font.family.as_str()).copied(); + drop(read_guard); + + let name = name.unwrap_or_else(|| { + let value: &'static str = font.family.clone().leak(); + FAMILY_MAP.write().unwrap().insert(value); + value + }); Self { family: iced::font::Family::Name(name), From ae1f15f37ee6d1fde579c6a6557e44b1a208f95e Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 13 Feb 2026 12:36:03 -0700 Subject: [PATCH 419/556] Add pull request template --- .github/PULL_REQUEST_TEMPLATE.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..e6ca28bc --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +- [ ] I have disclosed use of any AI generated code in my commit messages. + - If you are using an LLM, and do not fully understand the changes it is making to the code base, do not create a PR. + - In our experience, AI generated code often results in overly complex code that lacks enough context for a proper fix or feature inclusion. This results in considerably longer code reviews. Due to this, AI authored or partially authored PRs may be closed without comment. +- [ ] I understand these changes in full and will be able to respond to review comments. +- [ ] My change is accurately described in the commit message. +- [ ] My contribution is tested and working as described. +- [ ] I have read the [Developer Certificate of Origin](https://developercertificate.org/) and certify my contribution under its conditions. + From 21c5a4f34a33795d7836ff673a360ef1472f7567 Mon Sep 17 00:00:00 2001 From: Frieder Hannenheim Date: Mon, 16 Feb 2026 15:41:35 +0000 Subject: [PATCH 420/556] feat(dnd_destination): xdg file transfer portal support Requires the `xdg-portal` feature to be enabled to use these features. - Adds `DndDestination::on_file_transfer` method to handle `application/vnd.portal.filetransfer` drop requests - Adds `command::file_transfer_receive` function to handle the file transfer request messages - Adds `command::file_transfer_send` to initiate a file transfer from the application --- src/command.rs | 24 ++++++++++++++++++++++++ src/widget/dnd_destination.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/command.rs b/src/command.rs index 73c900c1..14d326b4 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,6 +1,9 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 +#[cfg(feature = "xdg-portal")] +use std::os::fd::AsFd; + use iced::window; /// Initiates a window drag. @@ -43,3 +46,24 @@ pub fn set_windowed(id: window::Id) -> iced::Task> { pub fn toggle_maximize(id: window::Id) -> iced::Task> { iced_runtime::window::toggle_maximize(id) } + +#[cfg(feature = "xdg-portal")] +pub fn file_transfer_send(writeable: bool, auto_stop: bool, files: Vec) -> iced::Task> { + iced::Task::future(async move { + let file_transfer = ashpd::documents::FileTransfer::new().await?; + let key = file_transfer.start_transfer(writeable, auto_stop).await?; + file_transfer.add_files(&key, &files).await?; + Ok(key) + }) +} + +/// Receive the files offered over the xdg share portal using the `key`. +/// Returns a list of file paths. +#[cfg(feature = "xdg-portal")] +pub fn file_transfer_receive(key: String) -> iced::Task>> { + dbg!(&key); + iced::Task::future(async move { + let file_transfer = ashpd::documents::FileTransfer::new().await?; + file_transfer.retrieve_files(&key).await + }) +} diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index 947d2fe3..a32a9fba 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -40,6 +40,8 @@ pub fn dnd_destination_for_data<'a, T: AllowedMimeTypes, Message: 'static>( static DRAG_ID_COUNTER: AtomicU64 = AtomicU64::new(0); const DND_DEST_LOG_TARGET: &str = "libcosmic::widget::dnd_destination"; +#[cfg(feature = "xdg-portal")] +pub const FILE_TRANSFER_MIME: &str = "application/vnd.portal.filetransfer"; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct DragId(pub u128); @@ -73,6 +75,8 @@ pub struct DndDestination<'a, Message> { on_action_selected: Option Message>>, on_data_received: Option) -> Message>>, on_finish: Option, DndAction, f64, f64) -> Message>>, + #[cfg(feature = "xdg-portal")] + on_file_transfer: Option Message>>, } impl<'a, Message: 'static> DndDestination<'a, Message> { @@ -99,6 +103,8 @@ impl<'a, Message: 'static> DndDestination<'a, Message> { on_action_selected: None, on_data_received: None, on_finish: None, + #[cfg(feature = "xdg-portal")] + on_file_transfer: None, } } @@ -124,6 +130,8 @@ impl<'a, Message: 'static> DndDestination<'a, Message> { on_finish: Some(Box::new(move |mime, data, action, _, _| { on_finish(T::try_from((data, mime)).ok(), action) })), + #[cfg(feature = "xdg-portal")] + on_file_transfer: None, } } @@ -159,6 +167,8 @@ impl<'a, Message: 'static> DndDestination<'a, Message> { on_action_selected: None, on_data_received: None, on_finish: None, + #[cfg(feature = "xdg-portal")] + on_file_transfer: None, } } @@ -237,6 +247,20 @@ impl<'a, Message: 'static> DndDestination<'a, Message> { self } + /// Add a message that will be emitted instead of [`on_data_received`](Self::on_data_received) if the dropped files + /// are offered through the xdg share portal. You can then use [`crate::command::file_transfer_receive`] + /// with the key to receive the files. + #[cfg(feature = "xdg-portal")] + #[must_use] + pub fn on_file_transfer(mut self, f: impl Fn(String) -> Message + 'static) -> Self { + match self.mime_types.iter().position(|v| v == "text/uri-list") { + Some(i) => self.mime_types.insert(i, Cow::Borrowed(FILE_TRANSFER_MIME)), + None => self.mime_types.push(Cow::Borrowed(FILE_TRANSFER_MIME)), + } + self.on_file_transfer = Some(Box::new(f)); + self + } + /// Returns the drag id of the destination. /// /// # Panics @@ -496,6 +520,13 @@ impl Widget "offer data id={my_id:?} mime={mime_type:?} bytes={}", data.len() ); + + #[cfg(feature = "xdg-portal")] + if mime_type == FILE_TRANSFER_MIME && let Some(f) = self.on_file_transfer.as_ref() && let Ok(s) = String::from_utf8(data[..data.len() - 1].to_vec()) { + shell.publish(f(s)); + return event::Status::Captured; + } + if let (Some(msg), ret) = state.on_data_received( mime_type, data, From 6328c40ef763e165f365d9af680912348414d17b Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 16 Feb 2026 16:51:02 +0100 Subject: [PATCH 421/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 2db5545f..e2a24417 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 2db5545fbee505c2c643c628a8984d1666c4d451 +Subproject commit e2a2441789a7e302f099c0e8e9493ef81b58e265 From a2e903ad94c6c2728c22454f098a81cb10f212bc Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Tue, 17 Feb 2026 16:39:37 +0000 Subject: [PATCH 422/556] feat(cosmic-theme): add color schemes for qt apps --- cosmic-theme/Cargo.toml | 1 + cosmic-theme/src/model/theme.rs | 3 + cosmic-theme/src/output/gtk4_output.rs | 16 +- cosmic-theme/src/output/mod.rs | 30 ++ cosmic-theme/src/output/qt56ct_output.rs | 113 +++++ cosmic-theme/src/output/qt_output.rs | 517 +++++++++++++++++++++++ 6 files changed, 677 insertions(+), 3 deletions(-) create mode 100644 cosmic-theme/src/output/qt56ct_output.rs create mode 100644 cosmic-theme/src/output/qt_output.rs diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index cf6afe74..80f4805d 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -27,5 +27,6 @@ cosmic-config = { path = "../cosmic-config/", default-features = false, features "subscription", "macro", ] } +configparser = "3.1.0" dirs.workspace = true thiserror = "2.0.18" diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 1f94f5a2..cef479ae 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -690,6 +690,9 @@ impl Theme { let config = Config::new(Self::id(), Self::VERSION).map_err(|e| (vec![e], Self::default()))?; let is_dark = ThemeMode::is_dark(&config).map_err(|e| (vec![e], Self::default()))?; + Self::get_active_with_brightness(is_dark) + } + pub fn get_active_with_brightness(is_dark: bool) -> Result, Self)> { let config = if is_dark { Self::dark_config() } else { diff --git a/cosmic-theme/src/output/gtk4_output.rs b/cosmic-theme/src/output/gtk4_output.rs index 6fdf26d5..40eba5b4 100644 --- a/cosmic-theme/src/output/gtk4_output.rs +++ b/cosmic-theme/src/output/gtk4_output.rs @@ -163,9 +163,19 @@ impl Theme { std::fs::create_dir_all(&config_dir).map_err(OutputError::Io)?; } - let mut file = File::create(config_dir.join(name)).map_err(OutputError::Io)?; - file.write_all(css_str.as_bytes()) - .map_err(OutputError::Io)?; + let file_path = config_dir.join(name); + let tmp_file_path = config_dir.join(name.to_owned() + "~"); + + // Write to tmp_file_path first, then move it to file_path + let mut tmp_file = File::create(&tmp_file_path).map_err(OutputError::Io)?; + let res = tmp_file + .write_all(css_str.as_bytes()) + .and_then(|_| tmp_file.flush()) + .and_then(|_| std::fs::rename(&tmp_file_path, file_path)); + if let Err(e) = res { + _ = std::fs::remove_file(&tmp_file_path); + return Err(OutputError::Io(e)); + } Ok(()) } diff --git a/cosmic-theme/src/output/mod.rs b/cosmic-theme/src/output/mod.rs index 832771d4..61f0e49d 100644 --- a/cosmic-theme/src/output/mod.rs +++ b/cosmic-theme/src/output/mod.rs @@ -6,6 +6,11 @@ use crate::Theme; /// Module for outputting the Cosmic gtk4 theme type as CSS pub mod gtk4_output; +/// Module for configuring qt5ct and qt6ct to use our qt theme +pub mod qt56ct_output; +/// Module for outputting the Cosmic qt theme type as kdeglobals +pub mod qt_output; + pub mod vs_code; #[derive(Error, Debug)] @@ -14,32 +19,57 @@ pub enum OutputError { Io(std::io::Error), #[error("Missing config directory")] MissingConfigDir, + #[error("Missing data directory")] + MissingDataDir, #[error("Serde Error: {0}")] Serde(#[from] serde_json::Error), + #[error("Ini Error: {0}")] + Ini(String), } impl Theme { #[inline] pub fn apply_exports(&self) -> Result<(), OutputError> { let gtk_res = Theme::apply_gtk(self.is_dark); + let qt_res = Theme::apply_qt(self.is_dark); + let qt56ct_res = Theme::apply_qt56ct(self.is_dark); let vs_res = self.clone().apply_vs_code(); gtk_res?; + qt_res?; + qt56ct_res?; vs_res?; Ok(()) } + #[inline] + /// To avoid rewriting too much code, I replaced calls to `Theme::apply_gtk` with this. + /// Note that vscode isn't touched by this function. + pub fn apply_exports_static(is_dark: bool) -> Result<(), OutputError> { + let gtk_res = Theme::apply_gtk(is_dark); + let qt_res = Theme::apply_qt(is_dark); + let qt56ct_res = Theme::apply_qt56ct(is_dark); + gtk_res?; + qt_res?; + qt56ct_res?; + Ok(()) + } + #[inline] pub fn write_exports(&self) -> Result<(), OutputError> { let gtk_res = self.write_gtk4(); + let qt_res = self.write_qt(); gtk_res?; + qt_res?; Ok(()) } #[inline] pub fn reset_exports() -> Result<(), OutputError> { let gtk_res = Theme::reset_gtk(); + let qt_res = Theme::reset_qt(); let vs_res = Theme::reset_vs_code(); gtk_res?; + qt_res?; vs_res?; Ok(()) } diff --git a/cosmic-theme/src/output/qt56ct_output.rs b/cosmic-theme/src/output/qt56ct_output.rs new file mode 100644 index 00000000..d4736597 --- /dev/null +++ b/cosmic-theme/src/output/qt56ct_output.rs @@ -0,0 +1,113 @@ +use crate::Theme; +use configparser::ini::Ini; +use std::{ + fs::{self, File}, + path::PathBuf, +}; + +use super::OutputError; + +impl Theme { + /// The "version" of this theme. + /// + /// To avoid repeatedly overwriting the user's config, we use a version system. + /// + /// Increment this value when changes to qt{5,6}ct.conf are needed. + /// If the config's version is outdated, we update several sections. + /// Otherwise, only the light/dark mode is updated. + const COSMIC_QT_VERSION: u64 = 1; + + /// Edits qt{5,6}ct.conf to use COSMIC styles if needed. + #[cold] + pub fn apply_qt56ct(is_dark: bool) -> Result<(), OutputError> { + let qt5ct_res = Self::apply_ct("qt5ct", is_dark); + let qt6ct_res = Self::apply_ct("qt6ct", is_dark); + qt5ct_res?; + qt6ct_res?; + Ok(()) + } + #[must_use] + #[cold] + fn apply_ct(ct: &str, is_dark: bool) -> Result<(), OutputError> { + let path = Self::get_conf_path(ct)?; + let file_content = fs::read_to_string(&path).map_err(OutputError::Io)?; + let mut ini = Ini::new_cs(); + ini.read(file_content).map_err(OutputError::Ini)?; + + let old_version = ini + .getuint("Appearance", "cosmic_qt_version") + .map_err(OutputError::Ini)? + .unwrap_or_default(); + + let color_scheme_path = Self::get_qt_colors_path(is_dark)?; + let icon_theme = if is_dark { "breeze-dark" } else { "breeze" }; + + ini.set( + "Appearance", + "cosmic_qt_version", + Some(Theme::COSMIC_QT_VERSION.to_string()), + ); + + if old_version < Theme::COSMIC_QT_VERSION { + // Config is outdated, update it unconditionally! + + ini.setstr( + "Appearance", + "color_scheme_path", + color_scheme_path.to_str(), + ); + // Enable the above color scheme, instead of using the default color scheme of e.g. Breeze + ini.setstr("Appearance", "custom_palette", Some("true")); + // COSMIC icons are stuck in light mode, so use breeze icons instead + ini.setstr("Appearance", "icon_theme", Some(icon_theme)); + // Use COSMIC dialogs instead of KDE's + ini.setstr("Appearance", "standard_dialogs", Some("xdgdesktopportal")); + + // TODO: Add fonts section to match COSMIC + } else { + // Config is not outdated, check before updating light/dark mode only! + + let old_color_scheme_path = ini + .get("Appearance", "color_scheme_path") + .unwrap_or_else(|| "CosmicPlease".to_owned()); + if old_color_scheme_path.contains("Cosmic") { + ini.setstr( + "Appearance", + "color_scheme_path", + color_scheme_path.to_str(), + ); + } + + let old_icon_theme = ini + .get("Appearance", "icon_theme") + .unwrap_or_else(|| "breeze".to_owned()); + if old_icon_theme.contains("breeze") { + ini.setstr("Appearance", "icon_theme", Some(icon_theme)); + } + } + + ini.write(path).map_err(OutputError::Io)?; + Ok(()) + } + + /// Returns the file paths of the form `~/.config/ct/ct.conf`: + /// e.g. `~/.config/qt6ct/qt6ct.conf`. + /// + /// The file and its parent directory are created if they don't exist. + fn get_conf_path(ct: &str) -> Result { + let Some(mut config_dir) = dirs::config_dir() else { + return Err(OutputError::MissingConfigDir); + }; + config_dir.push(&ct); + if !config_dir.exists() { + fs::create_dir_all(&config_dir).map_err(OutputError::Io)?; + } + + let file_path = config_dir.join(ct.to_owned() + ".conf"); + if !file_path.exists() { + File::create_new(&file_path).map_err(OutputError::Io)?; + } + + Ok(file_path) + } +} diff --git a/cosmic-theme/src/output/qt_output.rs b/cosmic-theme/src/output/qt_output.rs new file mode 100644 index 00000000..0d9a4258 --- /dev/null +++ b/cosmic-theme/src/output/qt_output.rs @@ -0,0 +1,517 @@ +use crate::Theme; +use configparser::ini::Ini; +use palette::{Mix, Srgba, blend::Compose}; +use std::{ + fs::{self, File}, + io::{self, Write}, + path::{Path, PathBuf}, +}; + +use super::OutputError; + +impl Theme { + /// Produces a color scheme ini file for Qt. + /// + /// Some high-level documentation for this file can be found at: + /// https://web.archive.org/web/20250402234329/https://docs.kde.org/stable5/en/plasma-workspace/kcontrol/colors/ + #[must_use] + #[cold] + pub fn as_qt(&self) -> String { + // Usually, disabled elements will have strongly reduced contrast and are often notably darker or lighter + let disabled_color_effects = IniColorEffects { + color: self.button.disabled, + color_amount: 0.0, + color_effect: ColorEffect::Desaturate, + contrast_amount: 0.65, + contrast_effect: ColorEffect::Fade, + intensity_amount: 0.1, + intensity_effect: IntensityEffect::Lighten, + }; + // Usually, inactive elements will have reduced contrast (text fades slightly into the background) and may have slightly reduced intensity + let inactive_color_effects = IniColorEffects { + color: self.palette.gray_1, + color_amount: 0.025, + color_effect: ColorEffect::Tint, + contrast_amount: 0.1, + contrast_effect: ColorEffect::Tint, + intensity_amount: 0.0, + intensity_effect: IntensityEffect::Shade, + }; + + let bg = self.background.base; + // the background container + let view_colors = IniColors { + background_alternate: bg.mix(self.accent.base, 0.05), + background_normal: bg, + decoration_focus: self.accent_text_color(), + decoration_hover: self.accent_text_color(), + foreground_active: self.accent_text_color(), + foreground_inactive: self.background.on.mix(bg, 0.1), + foreground_link: self.link_button.base, + foreground_negative: self.destructive_text_color(), + foreground_neutral: self.warning_text_color(), + foreground_normal: self.background.on, + foreground_positive: self.success_text_color(), + foreground_visited: self.accent_text_color(), + }; + // components inside the background container + let window_colors = IniColors { + background_alternate: self.background.component.base.mix(self.accent.base, 0.05), + background_normal: self.background.component.base, + ..view_colors + }; + + // selected text and items + let selection_colors = { + let selected = self.background.component.selected; + let selected_text = self.background.component.selected_text; + IniColors { + background_alternate: selected.mix(bg, 0.5), + background_normal: selected, + decoration_focus: selected, + decoration_hover: selected, + foreground_active: selected_text, + foreground_inactive: selected_text.mix(selected, 0.5), + foreground_link: self.link_button.on, + foreground_negative: self.destructive_color(), + foreground_neutral: self.warning_color(), + foreground_normal: selected_text, + foreground_positive: self.success_color(), + foreground_visited: self.accent_color(), + } + }; + + let button_colors = IniColors { + background_alternate: self.accent_button.base, + background_normal: self.button.base, + ..view_colors + }; + + // Complementary: Areas of applications with an alternative color scheme; usually with a dark background for light color schemes. + let complementary_colors = { + let dark = if self.is_dark { + self.clone() + } else { + Self::get_active_with_brightness(false).unwrap_or_else(|_| self.clone()) + }; + IniColors { + background_alternate: dark.accent.base, + background_normal: dark.background.base, + decoration_focus: dark.accent_text_color(), + decoration_hover: dark.accent_text_color(), + foreground_active: dark.accent_text_color(), + foreground_inactive: dark.background.on.mix(dark.background.base, 0.1), + foreground_link: dark.link_button.base, + foreground_negative: dark.destructive_text_color(), + foreground_neutral: dark.warning_text_color(), + foreground_normal: dark.background.on, + foreground_positive: dark.success_text_color(), + foreground_visited: dark.accent_text_color(), + } + }; + + // headers in cosmic don't have a background + let header_colors = &view_colors; + let header_colors_inactive = &view_colors; + // tool tips, "What's This" tips, and similar elements + let tooltip_colors = &window_colors; + + let general_color_scheme = if self.is_dark { + "CosmicDark" + } else { + "CosmicLight" + }; + let general_name = if self.is_dark { + "COSMIC Dark" + } else { + "COSMIC Light" + }; + // COSMIC icons are stuck in light mode, so use breeze icons instead + let icons_theme = if self.is_dark { + "breeze-dark" + } else { + "breeze" + }; + + format!( + r#"# GENERATED BY COSMIC + +[ColorEffects:Disabled] +{} + +[ColorEffects:Inactive] +ChangeSelectionColor=false +Enable=false +{} + +[Colors:Button] +{} + +[Colors:Complementary] +{} + +[Colors:Header] +{} + +[Colors:Header][Inactive] +{} + +[Colors:Selection] +{} + +[Colors:Tooltip] +{} + +[Colors:View] +{} + +[Colors:Window] +{} + +[General] +ColorScheme={general_color_scheme} +Name={general_name} +shadeSortColumn=true + +[Icons] +Theme={icons_theme} + +[KDE] +contrast=4 +widgetStyle=qt6ct-style + +[WM] +{} +"#, + format_ini_color_effects(&disabled_color_effects, bg), + format_ini_color_effects(&inactive_color_effects, bg), + format_ini_colors(&button_colors, bg), + format_ini_colors(&complementary_colors, bg), + format_ini_colors(&header_colors, bg), + format_ini_colors(&header_colors_inactive, bg), + format_ini_colors(&selection_colors, bg), + format_ini_colors(&tooltip_colors, bg), + format_ini_colors(&view_colors, bg), + format_ini_colors(&window_colors, bg), + format_ini_wm_colors(&view_colors, self.is_dark), + ) + } + + /// Write the color scheme to the appropriate directory. + /// Should be written in `~/.local/share/color-schemes/`. + /// + /// See the docs: https://develop.kde.org/docs/plasma/#color-scheme + /// + /// # Errors + /// + /// Returns an `OutputError` if there is an error writing the colors file. + #[cold] + pub fn write_qt(&self) -> Result<(), OutputError> { + let colors = self.as_qt(); + let file_path = Self::get_qt_colors_path(self.is_dark)?; + let tmp_file_path = file_path.with_extension("colors.new"); + + // Write to tmp_file_path first, then move it to file_path + let mut tmp_file = File::create(&tmp_file_path).map_err(OutputError::Io)?; + let res = tmp_file + .write_all(colors.as_bytes()) + .and_then(|_| tmp_file.flush()) + .and_then(|_| std::fs::rename(&tmp_file_path, file_path)); + if let Err(e) = res { + _ = std::fs::remove_file(&tmp_file_path); + return Err(OutputError::Io(e)); + } + + Ok(()) + } + + /// Apply the color scheme by copying its values to `~/.config/kdeglobals`. + /// + /// See the docs: https://develop.kde.org/docs/plasma/#color-scheme + /// + /// # Errors + /// + /// Returns an `OutputError` if there is an error applying the color scheme. + #[cold] + pub fn apply_qt(is_dark: bool) -> Result<(), OutputError> { + let Some(config_dir) = dirs::config_dir() else { + return Err(OutputError::MissingConfigDir); + }; + let kdeglobals_file = config_dir.join("kdeglobals"); + let mut kdeglobals_ini = Self::read_ini(&kdeglobals_file)?; + + let src_file = Self::get_qt_colors_path(is_dark)?; + let src_ini = Self::read_ini(&src_file)?; + + Self::backup_non_cosmic_kdeglobals(&kdeglobals_ini, &kdeglobals_file) + .map_err(OutputError::Io)?; + + for (section, key_value) in src_ini.get_map_ref() { + for (key, value) in key_value { + kdeglobals_ini.set(section, key, value.clone()); + } + } + + kdeglobals_ini + .write(kdeglobals_file) + .map_err(OutputError::Io)?; + Ok(()) + } + + /// Reset the applied qt colors by removing color scheme values from the + /// `~/.config/kdeglobals` file. + /// + /// This does not restore the backed up kdeglobals file. + /// + /// # Errors + /// + /// Returns an `OutputError` if there is an error resetting the CSS file. + #[cold] + pub fn reset_qt() -> Result<(), OutputError> { + let Some(config_dir) = dirs::config_dir() else { + return Err(OutputError::MissingConfigDir); + }; + let kdeglobals_file = config_dir.join("kdeglobals"); + let mut kdeglobals_ini = Self::read_ini(&kdeglobals_file)?; + + if !Self::is_cosmic_kdeglobals(&kdeglobals_ini) + .map_err(OutputError::Io)? + .unwrap_or_default() + { + // Not a cosmic kdeglobals file, do nothing + return Ok(()); + } + + let is_dark = false; // doesn't matter since we're only reading keys + let src_file = Self::get_qt_colors_path(is_dark)?; + let src_ini = Self::read_ini(&src_file)?; + + for (section, key_value) in src_ini.get_map_ref() { + for (key, _) in key_value { + kdeglobals_ini.remove_key(section, key); + } + } + + kdeglobals_ini + .write(kdeglobals_file) + .map_err(OutputError::Io)?; + Ok(()) + } + + /// Gets a path like `~/.config/color-schemes/CosmicDark.colors` + pub fn get_qt_colors_path(is_dark: bool) -> Result { + let Some(mut data_dir) = dirs::data_dir() else { + return Err(OutputError::MissingDataDir); + }; + + let file_name = if is_dark { + "CosmicDark.colors" + } else { + "CosmicLight.colors" + }; + + data_dir.push("color-schemes"); + if !data_dir.exists() { + std::fs::create_dir_all(&data_dir).map_err(OutputError::Io)?; + } + + Ok(data_dir.join(file_name)) + } + + #[cold] + fn read_ini(path: &PathBuf) -> Result { + let mut ini = Ini::new_cs(); + if !path.exists() { + return Ok(ini); + } + let file_content = fs::read_to_string(path).map_err(OutputError::Io)?; + ini.read(file_content).map_err(OutputError::Ini)?; + Ok(ini) + } + + #[cold] + fn backup_non_cosmic_kdeglobals(ini: &Ini, path: &Path) -> io::Result<()> { + if !Self::is_cosmic_kdeglobals(&ini)?.unwrap_or(true) { + let backup_path = path.with_extension("bak"); + fs::rename(path, &backup_path)?; + } + Ok(()) + } + + #[cold] + fn is_cosmic_kdeglobals(ini: &Ini) -> io::Result> { + let color_scheme = ini.get("General", "ColorScheme"); + if let Some(color_scheme) = color_scheme { + Ok(Some( + color_scheme == "CosmicDark" || color_scheme == "CosmicLight", + )) + } else { + Ok(None) + } + } +} + +/// Formats a color in the form `r,g,b` e.g. `255,255,255`. +/// If the color has transparency, it is mixed with bg first. +fn to_rgb(c: Srgba, bg: Srgba) -> String { + let c_u8: Srgba = c.over(bg).into_format(); + format!("{},{},{}", c_u8.red, c_u8.green, c_u8.blue) +} + +fn format_ini_color_effects(color_effects: &IniColorEffects, bg: Srgba) -> String { + format!( + r#"Color={} +ColorAmount={} +ColorEffect={} +ContrastAmount={} +ContrastEffect={} +IntensityAmount={} +IntensityEffect={}"#, + to_rgb(color_effects.color, bg), + color_effects.color_amount, + color_effects.color_effect.as_u8(), + color_effects.contrast_amount, + color_effects.contrast_effect.as_u8(), + color_effects.intensity_amount, + color_effects.intensity_effect.as_u8(), + ) +} + +fn format_ini_colors(colors: &IniColors, bg: Srgba) -> String { + format!( + r#"BackgroundAlternate={} +BackgroundNormal={} +DecorationFocus={} +DecorationHover={} +ForegroundActive={} +ForegroundInactive={} +ForegroundLink={} +ForegroundNegative={} +ForegroundNeutral={} +ForegroundNormal={} +ForegroundPositive={} +ForegroundVisited={}"#, + to_rgb(colors.background_alternate, bg), + to_rgb(colors.background_normal, bg), + to_rgb(colors.decoration_focus, bg), + to_rgb(colors.decoration_hover, bg), + to_rgb(colors.foreground_active, bg), + to_rgb(colors.foreground_inactive, bg), + to_rgb(colors.foreground_link, bg), + to_rgb(colors.foreground_negative, bg), + to_rgb(colors.foreground_neutral, bg), + to_rgb(colors.foreground_normal, bg), + to_rgb(colors.foreground_positive, bg), + to_rgb(colors.foreground_visited, bg), + ) +} + +/// Sets the colors for the titlebars of active and inactive windows. +fn format_ini_wm_colors(view_colors: &IniColors, is_dark: bool) -> String { + let bg = view_colors.background_normal; + let fg = view_colors.foreground_active; + let blend = if is_dark { fg } else { bg }; + + format!( + r#"activeBackground={} +activeBlend={} +activeForeground={} +inactiveBackground={} +inactiveBlend={} +inactiveForeground={}"#, + to_rgb(bg, bg), + to_rgb(blend, bg), + to_rgb(fg, bg), + to_rgb(bg, bg), + to_rgb(blend, bg), + to_rgb(fg, bg), + ) +} + +struct IniColorEffects { + color: Srgba, + color_amount: f32, + color_effect: ColorEffect, + contrast_amount: f32, + /// Applied to the text, using the background as the reference color. + contrast_effect: ColorEffect, + intensity_amount: f32, + intensity_effect: IntensityEffect, +} +/// Each color set is made up of a number of roles which are available in all other sets. +/// In addition, except for Inactive Text, there is a corresponding background role for each of the text roles. Currently (except for Normal and Alternate Background), these colors are not chosen here but are automatically determined based on Normal Background and the corresponding Text color. +struct IniColors { + /// used when there is a need to subtly change the background to aid in item association. This might be used e.g. as the background of a heading, but is mostly used for alternating rows in lists, especially multi-column lists, to aid in visually tracking rows. + background_alternate: Srgba, + /// Normal background + background_normal: Srgba, + /// Used for drawing lines or shading UI elements to indicate the item which has active input focus. + /// Typically the same as foreground_active. + decoration_focus: Srgba, + /// Used for drawing lines or shading UI elements for mouse-over effects, e.g. the "illumination" effects for buttons. + /// Typically the same as foreground_active. + decoration_hover: Srgba, + /// used to indicate an active element or attract attention, e.g. alerts, notifications; also for hovered hyperlinks + foreground_active: Srgba, + /// used for text which should be unobtrusive, e.g. comments, "subtitles", unimportant information, etc. + foreground_inactive: Srgba, + /// used for hyperlinks or to otherwise indicate "something which may be visited", or to show relationships + foreground_link: Srgba, + /// used for errors, failure notices, notifications that an action may be dangerous (e.g. unsafe web page or security context), etc. + foreground_negative: Srgba, + /// used to draw attention when another role is not appropriate; e.g. warnings, to indicate secure/encrypted content, etc. + foreground_neutral: Srgba, + /// Normal foreground + foreground_normal: Srgba, + /// used for success notices, to indicate trusted content, etc. + foreground_positive: Srgba, + /// used for "something (e.g. a hyperlink) that has been visited", or to indicate something that is "old". + foreground_visited: Srgba, +} + +/// Intensity allows the overall color to be lightened or darkened. +#[allow(dead_code)] +enum IntensityEffect { + /// Makes everything lighter or darker in a controlled manner. + /// + /// intensity_amount increases or decreases the overall intensity (i.e. perceived brightness) by an absolute amount. + Shade, + /// Changes the intensity to a percentage of the initial value. + Darken, + /// Conceptually the opposite of darken; lighten can be thought of as working with "distance from white", where darken works with "distance from black". + Lighten, +} + +impl IntensityEffect { + pub fn as_u8(&self) -> u8 { + match self { + Self::Shade => 0, + Self::Darken => 1, + Self::Lighten => 2, + } + } +} + +/// This also changes the overall color like [IntensityEffect], +/// but is not limited to intensity. +#[allow(dead_code)] +enum ColorEffect { + /// changes the relative chroma + /// + /// This is available for "ColorEffect" but not "ContrastEffect". + Desaturate, + /// smoothly blends the original color into a reference color + Fade, + /// similar to Fade, except that the color (hue and chroma) changes more quickly while the intensity changes more slowly as the amount is increased + Tint, +} + +impl ColorEffect { + pub fn as_u8(&self) -> u8 { + match self { + Self::Desaturate => 0, + Self::Fade => 1, + Self::Tint => 2, + } + } +} From b05f040e5f0dc390af1847caf5f50b6813215795 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 16 Feb 2026 08:03:35 +0100 Subject: [PATCH 423/556] i18n: translation updates from weblate Co-authored-by: Benmak Kizuna Co-authored-by: Fedorov Alexei Co-authored-by: Hosted Weblate Co-authored-by: jonnysemon Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ar/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ru/ Translation: Pop OS/libcosmic --- i18n/ar/libcosmic.ftl | 2 +- i18n/ru/libcosmic.ftl | 19 +++++++++++++++++++ i18n/yue-Hant/libcosmic.ftl | 0 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 i18n/yue-Hant/libcosmic.ftl diff --git a/i18n/ar/libcosmic.ftl b/i18n/ar/libcosmic.ftl index ce3eb1e8..92f016f0 100644 --- a/i18n/ar/libcosmic.ftl +++ b/i18n/ar/libcosmic.ftl @@ -3,7 +3,7 @@ close = أغلِق # About license = الترخيص links = الروابط -developers = المطورون +developers = المطوِّرون designers = المصمّمون artists = الفنانون translators = المترجمون diff --git a/i18n/ru/libcosmic.ftl b/i18n/ru/libcosmic.ftl index 0ef03fb1..7fe9b3dc 100644 --- a/i18n/ru/libcosmic.ftl +++ b/i18n/ru/libcosmic.ftl @@ -6,3 +6,22 @@ designers = Дизайнеры artists = Художники translators = Переводчики documenters = Авторы документации +january = Январь { $year } +february = Февраль { $year } +march = Март { $year } +april = Апрель { $year } +may = Май { $year } +june = Июнь { $year } +july = Июль { $year } +august = Август { $year } +september = Сентябрь { $year } +october = Октябрь { $year } +november = Ноябрь { $year } +december = Декабрь { $year } +monday = Пн +tuesday = Вт +wednesday = Ср +thursday = Чт +friday = Пт +saturday = Сб +sunday = Вс diff --git a/i18n/yue-Hant/libcosmic.ftl b/i18n/yue-Hant/libcosmic.ftl new file mode 100644 index 00000000..e69de29b From 990e2e291b33379f985bae0f81e46292b3d0e9cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:32:21 +0100 Subject: [PATCH 424/556] refactor(calendar): use `jiff` instead of `chrono` This refactors the calendar widget to use `jiff` instead of `chrono`. Also mostly matches the design of the widget to the time applet. --- Cargo.toml | 2 +- examples/calendar/Cargo.toml | 6 +- examples/calendar/src/main.rs | 8 +- i18n/en/libcosmic.ftl | 21 ++-- src/widget/calendar.rs | 197 +++++++++++++++------------------- 5 files changed, 111 insertions(+), 123 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index feaa8c74..4aaf9d0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,7 +104,7 @@ async-fs = { version = "2.2", optional = true } async-std = { version = "1.13", optional = true } auto_enums = "0.8.7" cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d0e95be", optional = true } -chrono = "0.4.43" +jiff = "0.2" cosmic-config = { path = "cosmic-config" } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } # Internationalization diff --git a/examples/calendar/Cargo.toml b/examples/calendar/Cargo.toml index 59b23c0c..b7286825 100644 --- a/examples/calendar/Cargo.toml +++ b/examples/calendar/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "calendar" -version = "0.1.0" -edition = "2021" +version = "1.0.0" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -chrono = "0.4.42" +jiff = "0.2" [dependencies.libcosmic] path = "../../" diff --git a/examples/calendar/src/main.rs b/examples/calendar/src/main.rs index 589bc1ff..240684c6 100644 --- a/examples/calendar/src/main.rs +++ b/examples/calendar/src/main.rs @@ -3,10 +3,10 @@ //! Calendar widget example -use chrono::NaiveDate; use cosmic::app::{Core, Settings, Task}; use cosmic::widget::calendar::CalendarModel; -use cosmic::{executor, iced, ApplicationExt, Element}; +use cosmic::{ApplicationExt, Element, executor, iced}; +use jiff::civil::{Date, Weekday}; /// Runs application with these settings #[rustfmt::skip] @@ -19,7 +19,7 @@ fn main() -> Result<(), Box> { /// Messages that are used specifically by our [`App`]. #[derive(Clone, Debug)] pub enum Message { - DateSelected(NaiveDate), + DateSelected(Date), PrevMonth, NextMonth, } @@ -92,7 +92,7 @@ impl cosmic::Application for App { |date| Message::DateSelected(date), || Message::PrevMonth, || Message::NextMonth, - chrono::Weekday::Sun, + Weekday::Sunday, ); content = content.push(calendar); diff --git a/i18n/en/libcosmic.ftl b/i18n/en/libcosmic.ftl index 119ac38e..257fc44f 100644 --- a/i18n/en/libcosmic.ftl +++ b/i18n/en/libcosmic.ftl @@ -23,10 +23,17 @@ september = September { $year } october = October { $year } november = November { $year } december = December { $year } -monday = Mon -tuesday = Tue -wednesday = Wed -thursday = Thu -friday = Fri -saturday = Sat -sunday = Sun +monday = Monday +mon = Mon +tuesday = Tuesday +tue = Tue +wednesday = Wednesday +wed = Wed +thursday = Thursday +thu = Thu +friday = Friday +fri = Fri +saturday = Saturday +sat = Sat +sunday = Sunday +sun = Sun diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index 7ee06204..ea10fddb 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -3,19 +3,20 @@ //! A widget that displays an interactive calendar. -use std::cmp; - use crate::fl; -use crate::iced_core::{Alignment, Length, Padding}; -use crate::widget::{Grid, button, column, grid, icon, row, text}; +use crate::iced_core::{Alignment, Length}; +use crate::widget::{button, column, grid, icon, row, text}; use apply::Apply; -use chrono::{Datelike, Days, Local, Month, Months, NaiveDate, Weekday}; use iced::alignment::Vertical; +use jiff::{ + ToSpan, + civil::{Date, Weekday}, +}; /// A widget that displays an interactive calendar. pub fn calendar( model: &CalendarModel, - on_select: impl Fn(NaiveDate) -> M + 'static, + on_select: impl Fn(Date) -> M + 'static, on_prev: impl Fn() -> M + 'static, on_next: impl Fn() -> M + 'static, first_day_of_week: Weekday, @@ -29,61 +30,40 @@ pub fn calendar( } } -pub fn set_day(date_selected: NaiveDate, day: u32) -> NaiveDate { - let current = date_selected.day(); - - let new_date = match current.cmp(&day) { - cmp::Ordering::Less => date_selected.checked_add_days(Days::new((day - current) as u64)), - - cmp::Ordering::Greater => date_selected.checked_sub_days(Days::new((current - day) as u64)), - - _ => None, - }; - - if let Some(new) = new_date { - new - } else { - date_selected - } +pub fn set_day(date_selected: Date, day: i8) -> Date { + date_selected + .with() + .day(day) + .build() + .unwrap_or(date_selected) } #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct CalendarModel { - pub selected: NaiveDate, - pub visible: NaiveDate, + pub selected: Date, + pub visible: Date, } impl CalendarModel { pub fn now() -> Self { - let now = Local::now(); - let naive_now = NaiveDate::from(now.naive_local()); + let now = jiff::Zoned::now().date(); CalendarModel { - selected: naive_now, - visible: naive_now, + selected: now, + visible: now, } } #[inline] - pub fn new(selected: NaiveDate, visible: NaiveDate) -> Self { + pub fn new(selected: Date, visible: Date) -> Self { CalendarModel { selected, visible } } pub fn show_prev_month(&mut self) { - let prev_month_date = self - .visible - .checked_sub_months(Months::new(1)) - .expect("valid naivedate"); - - self.visible = prev_month_date; + self.visible = self.visible.checked_sub(1.month()).expect("valid date"); } pub fn show_next_month(&mut self) { - let next_month_date = self - .visible - .checked_add_months(Months::new(1)) - .expect("valid naivedate"); - - self.visible = next_month_date; + self.visible = self.visible.checked_add(1.month()).expect("valid date"); } #[inline] @@ -99,7 +79,7 @@ impl CalendarModel { } #[inline] - pub fn set_selected_visible(&mut self, selected: NaiveDate) { + pub fn set_selected_visible(&mut self, selected: Date) { self.selected = selected; self.visible = self.selected; } @@ -107,7 +87,7 @@ impl CalendarModel { pub struct Calendar<'a, M> { model: &'a CalendarModel, - on_select: Box M>, + on_select: Box M>, on_prev: Box M>, on_next: Box M>, first_day_of_week: Weekday, @@ -121,45 +101,57 @@ where macro_rules! translate_month { ($month:expr, $year:expr) => {{ match $month { - chrono::Month::January => fl!("january", year = $year), - chrono::Month::February => fl!("february", year = $year), - chrono::Month::March => fl!("march", year = $year), - chrono::Month::April => fl!("april", year = $year), - chrono::Month::May => fl!("may", year = $year), - chrono::Month::June => fl!("june", year = $year), - chrono::Month::July => fl!("july", year = $year), - chrono::Month::August => fl!("august", year = $year), - chrono::Month::September => fl!("september", year = $year), - chrono::Month::October => fl!("october", year = $year), - chrono::Month::November => fl!("november", year = $year), - chrono::Month::December => fl!("december", year = $year), + 1 => fl!("january", year = $year), + 2 => fl!("february", year = $year), + 3 => fl!("march", year = $year), + 4 => fl!("april", year = $year), + 5 => fl!("may", year = $year), + 6 => fl!("june", year = $year), + 7 => fl!("july", year = $year), + 8 => fl!("august", year = $year), + 9 => fl!("september", year = $year), + 10 => fl!("october", year = $year), + 11 => fl!("november", year = $year), + 12 => fl!("december", year = $year), + _ => unreachable!(), } }}; } macro_rules! translate_weekday { - ($weekday:expr) => {{ + ($weekday:expr, short) => {{ match $weekday { - Weekday::Mon => fl!("monday"), - Weekday::Tue => fl!("tuesday"), - Weekday::Wed => fl!("wednesday"), - Weekday::Thu => fl!("thursday"), - Weekday::Fri => fl!("friday"), - Weekday::Sat => fl!("saturday"), - Weekday::Sun => fl!("sunday"), + Weekday::Monday => fl!("mon"), + Weekday::Tuesday => fl!("tue"), + Weekday::Wednesday => fl!("wed"), + Weekday::Thursday => fl!("thu"), + Weekday::Friday => fl!("fri"), + Weekday::Saturday => fl!("sat"), + Weekday::Sunday => fl!("sun"), + } + }}; + ($weekday:expr, long) => {{ + match $weekday { + Weekday::Monday => fl!("monday"), + Weekday::Tuesday => fl!("tuesday"), + Weekday::Wednesday => fl!("wednesday"), + Weekday::Thursday => fl!("thursday"), + Weekday::Friday => fl!("friday"), + Weekday::Saturday => fl!("saturday"), + Weekday::Sunday => fl!("sunday"), } }}; } let date = text(translate_month!( - Month::try_from(this.model.visible.month() as u8) - .expect("Previously valid month is suddenly invalid"), + this.model.visible.month(), this.model.visible.year() )) .size(18); - let day = text::body(translate_weekday!(this.model.visible.weekday())); + let day = text::body(translate_weekday!(this.model.visible.weekday(), long)); let month_controls = row::with_capacity(2) + .spacing(8) .push( icon::from_name("go-previous-symbolic") .apply(button::icon) @@ -171,46 +163,49 @@ where .on_press((this.on_next)()), ); - // Calender - let mut calendar_grid: Grid<'_, Message> = - grid().padding([0, 12].into()).width(Length::Fill); + // Calendar + let mut calendar_grid = grid().padding([0, 12].into()).width(Length::Fill); let mut first_day_of_week = this.first_day_of_week; for _ in 0..7 { calendar_grid = calendar_grid.push( - text(translate_weekday!(first_day_of_week)) - .size(12) - .width(Length::Fixed(36.0)) + text::caption(translate_weekday!(first_day_of_week, short)) + .width(Length::Fixed(44.0)) .align_x(Alignment::Center), ); - first_day_of_week = first_day_of_week.succ(); + first_day_of_week = first_day_of_week.next(); } calendar_grid = calendar_grid.insert_row(); - let monday = get_calender_first( + let first = get_calendar_first( this.model.visible.year(), this.model.visible.month(), - first_day_of_week, + this.first_day_of_week, ); - let mut day_iter = monday.iter_days(); + + let today = jiff::Zoned::now().date(); for i in 0..42 { if i > 0 && i % 7 == 0 { calendar_grid = calendar_grid.insert_row(); } - let date = day_iter.next().unwrap(); - let is_currently_viewed_month = date.month() == this.model.visible.month() - && date.year_ce() == this.model.visible.year_ce(); - let is_currently_selected_month = date.month() == this.model.selected.month() - && date.year_ce() == this.model.selected.year_ce(); + let date = first + .checked_add(i.days()) + .expect("valid date in calendar range"); + let is_currently_viewed_month = + date.first_of_month() == this.model.visible.first_of_month(); + let is_currently_selected_month = + date.first_of_month() == this.model.selected.first_of_month(); let is_currently_selected_day = date.day() == this.model.selected.day() && is_currently_selected_month; + let is_today = date == today; calendar_grid = calendar_grid.push(date_button( date, is_currently_viewed_month, is_currently_selected_day, + is_today, &this.on_select, )); } @@ -225,9 +220,8 @@ where .padding([12, 20]) .into(), calendar_grid.into(), - padded_control(crate::widget::divider::horizontal::default()).into(), ]) - .width(315) + .width(360) .padding([8, 0]); Self::new(content_list) @@ -235,21 +229,24 @@ where } fn date_button( - date: NaiveDate, + date: Date, is_currently_viewed_month: bool, is_currently_selected_day: bool, - on_select: &dyn Fn(NaiveDate) -> Message, + is_today: bool, + on_select: &dyn Fn(Date) -> Message, ) -> crate::widget::Button<'static, Message> { let style = if is_currently_selected_day { button::ButtonClass::Suggested + } else if is_today { + button::ButtonClass::Standard } else { button::ButtonClass::Text }; let button = button::custom(text(format!("{}", date.day())).center()) .class(style) - .height(Length::Fixed(36.0)) - .width(Length::Fixed(36.0)); + .height(Length::Fixed(44.0)) + .width(Length::Fixed(44.0)); if is_currently_viewed_month { button.on_press((on_select)(set_day(date, date.day()))) @@ -258,26 +255,10 @@ fn date_button( } } -/// Gets the first date that will be visible on the calender +/// Gets the first date that will be visible on the calendar #[must_use] -pub fn get_calender_first(year: i32, month: u32, from_weekday: Weekday) -> NaiveDate { - let date = NaiveDate::from_ymd_opt(year, month, 1).unwrap(); - let num_days = (date.weekday() as u32 + 7 - from_weekday as u32) % 7; // chrono::Weekday.num_days_from - date.checked_sub_days(Days::new(num_days as u64)).unwrap() -} - -// TODO: Refactor to use same function from applet module. -fn padded_control<'a, Message>( - content: impl Into>, -) -> crate::widget::container::Container<'a, Message, crate::Theme, crate::Renderer> { - crate::widget::container(content) - .padding(menu_control_padding()) - .width(Length::Fill) -} - -#[inline] -fn menu_control_padding() -> Padding { - let guard = crate::theme::THEME.lock().unwrap(); - let cosmic = guard.cosmic(); - [cosmic.space_xxs(), cosmic.space_m()].into() +pub fn get_calendar_first(year: i16, month: i8, from_weekday: Weekday) -> Date { + let date = Date::new(year, month, 1).expect("valid date"); + let num_days = date.weekday().since(from_weekday); + date.checked_sub(num_days.days()).expect("valid date") } From cb288070af610e0858e0c5e1818dc4dee3dcc00b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:37:56 +0100 Subject: [PATCH 425/556] chore: cargo fmt --- cosmic-theme/src/model/theme.rs | 4 +++- src/command.rs | 6 +++++- src/widget/dnd_destination.rs | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index cef479ae..89d87b6b 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -692,7 +692,9 @@ impl Theme { let is_dark = ThemeMode::is_dark(&config).map_err(|e| (vec![e], Self::default()))?; Self::get_active_with_brightness(is_dark) } - pub fn get_active_with_brightness(is_dark: bool) -> Result, Self)> { + pub fn get_active_with_brightness( + is_dark: bool, + ) -> Result, Self)> { let config = if is_dark { Self::dark_config() } else { diff --git a/src/command.rs b/src/command.rs index 14d326b4..00684e55 100644 --- a/src/command.rs +++ b/src/command.rs @@ -48,7 +48,11 @@ pub fn toggle_maximize(id: window::Id) -> iced::Task> { } #[cfg(feature = "xdg-portal")] -pub fn file_transfer_send(writeable: bool, auto_stop: bool, files: Vec) -> iced::Task> { +pub fn file_transfer_send( + writeable: bool, + auto_stop: bool, + files: Vec, +) -> iced::Task> { iced::Task::future(async move { let file_transfer = ashpd::documents::FileTransfer::new().await?; let key = file_transfer.start_transfer(writeable, auto_stop).await?; diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index a32a9fba..7225e917 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -522,7 +522,10 @@ impl Widget ); #[cfg(feature = "xdg-portal")] - if mime_type == FILE_TRANSFER_MIME && let Some(f) = self.on_file_transfer.as_ref() && let Ok(s) = String::from_utf8(data[..data.len() - 1].to_vec()) { + if mime_type == FILE_TRANSFER_MIME + && let Some(f) = self.on_file_transfer.as_ref() + && let Ok(s) = String::from_utf8(data[..data.len() - 1].to_vec()) + { shell.publish(f(s)); return event::Status::Captured; } From be98b7dd6f618c28778cb7c0fb9982a96c8a36aa Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 18 Feb 2026 14:18:27 +0100 Subject: [PATCH 426/556] refactor(cosmic-theme): remove recently-added `Theme::get_active_with_brightness` The added method was not necessary. Also improves the code in the get_active method. --- cosmic-theme/src/model/theme.rs | 26 ++++++++++---------------- cosmic-theme/src/output/qt_output.rs | 7 ++++++- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 89d87b6b..8e1cd9f7 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -685,23 +685,17 @@ impl Theme { self.shade } - /// get the active theme + /// Get the active theme based on the current theme mode. pub fn get_active() -> Result, Self)> { - let config = - Config::new(Self::id(), Self::VERSION).map_err(|e| (vec![e], Self::default()))?; - let is_dark = ThemeMode::is_dark(&config).map_err(|e| (vec![e], Self::default()))?; - Self::get_active_with_brightness(is_dark) - } - pub fn get_active_with_brightness( - is_dark: bool, - ) -> Result, Self)> { - let config = if is_dark { - Self::dark_config() - } else { - Self::light_config() - } - .map_err(|e| (vec![e], Self::default()))?; - Self::get_entry(&config) + (|| { + (if ThemeMode::is_dark(&Config::new(Self::id(), Self::VERSION)?)? { + Self::dark_config + } else { + Self::light_config + })() + })() + .map_err(|error| (vec![error], Self::default())) + .and_then(|theme_config| Self::get_entry(&theme_config)) } #[must_use] diff --git a/cosmic-theme/src/output/qt_output.rs b/cosmic-theme/src/output/qt_output.rs index 0d9a4258..78bdec61 100644 --- a/cosmic-theme/src/output/qt_output.rs +++ b/cosmic-theme/src/output/qt_output.rs @@ -1,5 +1,6 @@ use crate::Theme; use configparser::ini::Ini; +use cosmic_config::CosmicConfigEntry; use palette::{Mix, Srgba, blend::Compose}; use std::{ fs::{self, File}, @@ -92,7 +93,11 @@ impl Theme { let dark = if self.is_dark { self.clone() } else { - Self::get_active_with_brightness(false).unwrap_or_else(|_| self.clone()) + Theme::light_config() + .ok() + .as_ref() + .and_then(|conf| Theme::get_entry(conf).ok()) + .unwrap_or_else(|| self.clone()) }; IniColors { background_alternate: dark.accent.base, From 7c49a736ec628150fd656e50a24ad2541b28f3ef Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 18 Feb 2026 14:23:09 +0100 Subject: [PATCH 427/556] refactor(cosmic-theme): remove `Theme::apply_exports_static` Recently-added method is redundant with `apply_exports`, and the dark mode preference is already defined in the theme being applied. --- cosmic-theme/src/output/mod.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/cosmic-theme/src/output/mod.rs b/cosmic-theme/src/output/mod.rs index 61f0e49d..37271dbb 100644 --- a/cosmic-theme/src/output/mod.rs +++ b/cosmic-theme/src/output/mod.rs @@ -41,19 +41,6 @@ impl Theme { Ok(()) } - #[inline] - /// To avoid rewriting too much code, I replaced calls to `Theme::apply_gtk` with this. - /// Note that vscode isn't touched by this function. - pub fn apply_exports_static(is_dark: bool) -> Result<(), OutputError> { - let gtk_res = Theme::apply_gtk(is_dark); - let qt_res = Theme::apply_qt(is_dark); - let qt56ct_res = Theme::apply_qt56ct(is_dark); - gtk_res?; - qt_res?; - qt56ct_res?; - Ok(()) - } - #[inline] pub fn write_exports(&self) -> Result<(), OutputError> { let gtk_res = self.write_gtk4(); From e1dad541b281bbc54f3f798380ccd4141a22d67c Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 18 Feb 2026 14:59:14 +0100 Subject: [PATCH 428/556] chore(cosmic-theme): `Theme::apply_exports` should not apply VS Code theme currently --- cosmic-theme/src/output/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cosmic-theme/src/output/mod.rs b/cosmic-theme/src/output/mod.rs index 37271dbb..0baefb86 100644 --- a/cosmic-theme/src/output/mod.rs +++ b/cosmic-theme/src/output/mod.rs @@ -29,19 +29,19 @@ pub enum OutputError { impl Theme { #[inline] + /// Apply COSMIC theme exports for GTK and Qt applications. pub fn apply_exports(&self) -> Result<(), OutputError> { let gtk_res = Theme::apply_gtk(self.is_dark); let qt_res = Theme::apply_qt(self.is_dark); let qt56ct_res = Theme::apply_qt56ct(self.is_dark); - let vs_res = self.clone().apply_vs_code(); gtk_res?; qt_res?; qt56ct_res?; - vs_res?; Ok(()) } #[inline] + /// Write COSMIC theme exports for GTK and Qt applications. pub fn write_exports(&self) -> Result<(), OutputError> { let gtk_res = self.write_gtk4(); let qt_res = self.write_qt(); @@ -51,6 +51,7 @@ impl Theme { } #[inline] + /// Un-export GTK and Qt theme configurations applied by us. pub fn reset_exports() -> Result<(), OutputError> { let gtk_res = Theme::reset_gtk(); let qt_res = Theme::reset_qt(); From dc3c194f09734256498b23b218b2c69f0eb21bf4 Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Wed, 18 Feb 2026 20:02:58 +0000 Subject: [PATCH 429/556] fix(cosmic-theme): inverted Qt link_button colors --- cosmic-theme/src/output/qt_output.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cosmic-theme/src/output/qt_output.rs b/cosmic-theme/src/output/qt_output.rs index 78bdec61..2e926a2e 100644 --- a/cosmic-theme/src/output/qt_output.rs +++ b/cosmic-theme/src/output/qt_output.rs @@ -48,7 +48,7 @@ impl Theme { decoration_hover: self.accent_text_color(), foreground_active: self.accent_text_color(), foreground_inactive: self.background.on.mix(bg, 0.1), - foreground_link: self.link_button.base, + foreground_link: self.link_button.on, foreground_negative: self.destructive_text_color(), foreground_neutral: self.warning_text_color(), foreground_normal: self.background.on, @@ -73,7 +73,7 @@ impl Theme { decoration_hover: selected, foreground_active: selected_text, foreground_inactive: selected_text.mix(selected, 0.5), - foreground_link: self.link_button.on, + foreground_link: self.link_button.base, foreground_negative: self.destructive_color(), foreground_neutral: self.warning_color(), foreground_normal: selected_text, @@ -106,7 +106,7 @@ impl Theme { decoration_hover: dark.accent_text_color(), foreground_active: dark.accent_text_color(), foreground_inactive: dark.background.on.mix(dark.background.base, 0.1), - foreground_link: dark.link_button.base, + foreground_link: dark.link_button.on, foreground_negative: dark.destructive_text_color(), foreground_neutral: dark.warning_text_color(), foreground_normal: dark.background.on, From 3ed5c173fd78e97b24fab0a059e5fe23b2f63b8f Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Wed, 18 Feb 2026 20:10:42 +0000 Subject: [PATCH 430/556] fix(cosmic-theme): copy for backup, not rename We're now merging the colors with kdeglobals, not replacing it with a symlink. So renaming the file gives us a missing file Io error: [2026-02-18T20:03:08Z ERROR cosmic_settings_daemon::theme] Failed to apply COSMIC theme exports. Io(Os { code: 2, kind: NotFound, message: "No such file or directory" }) --- cosmic-theme/src/output/qt_output.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cosmic-theme/src/output/qt_output.rs b/cosmic-theme/src/output/qt_output.rs index 2e926a2e..67ffbd69 100644 --- a/cosmic-theme/src/output/qt_output.rs +++ b/cosmic-theme/src/output/qt_output.rs @@ -338,7 +338,7 @@ widgetStyle=qt6ct-style fn backup_non_cosmic_kdeglobals(ini: &Ini, path: &Path) -> io::Result<()> { if !Self::is_cosmic_kdeglobals(&ini)?.unwrap_or(true) { let backup_path = path.with_extension("bak"); - fs::rename(path, &backup_path)?; + fs::copy(path, &backup_path)?; } Ok(()) } From 754b064bffbb399b955662d6c1fcca3468accb0c Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Wed, 18 Feb 2026 20:42:34 +0000 Subject: [PATCH 431/556] tweak(cosmic-theme): pretty write ini --- cosmic-theme/src/output/mod.rs | 7 +++++++ cosmic-theme/src/output/qt56ct_output.rs | 5 +++-- cosmic-theme/src/output/qt_output.rs | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cosmic-theme/src/output/mod.rs b/cosmic-theme/src/output/mod.rs index 0baefb86..f331e8d2 100644 --- a/cosmic-theme/src/output/mod.rs +++ b/cosmic-theme/src/output/mod.rs @@ -1,3 +1,4 @@ +use configparser::ini::WriteOptions; use palette::{Srgba, rgb::Rgba}; use thiserror::Error; @@ -78,3 +79,9 @@ pub fn to_rgba(c: Srgba) -> String { c_u8.red, c_u8.green, c_u8.blue, c.alpha ) } + +pub fn qt_settings_ini_style() -> WriteOptions { + let mut write_options = WriteOptions::default(); + write_options.blank_lines_between_sections = 1; + write_options +} diff --git a/cosmic-theme/src/output/qt56ct_output.rs b/cosmic-theme/src/output/qt56ct_output.rs index d4736597..552e7fec 100644 --- a/cosmic-theme/src/output/qt56ct_output.rs +++ b/cosmic-theme/src/output/qt56ct_output.rs @@ -5,7 +5,7 @@ use std::{ path::PathBuf, }; -use super::OutputError; +use super::{OutputError, qt_settings_ini_style}; impl Theme { /// The "version" of this theme. @@ -86,7 +86,8 @@ impl Theme { } } - ini.write(path).map_err(OutputError::Io)?; + ini.pretty_write(path, &qt_settings_ini_style()) + .map_err(OutputError::Io)?; Ok(()) } diff --git a/cosmic-theme/src/output/qt_output.rs b/cosmic-theme/src/output/qt_output.rs index 67ffbd69..9bca3d18 100644 --- a/cosmic-theme/src/output/qt_output.rs +++ b/cosmic-theme/src/output/qt_output.rs @@ -8,7 +8,7 @@ use std::{ path::{Path, PathBuf}, }; -use super::OutputError; +use super::{OutputError, qt_settings_ini_style}; impl Theme { /// Produces a color scheme ini file for Qt. @@ -258,7 +258,7 @@ widgetStyle=qt6ct-style } kdeglobals_ini - .write(kdeglobals_file) + .pretty_write(kdeglobals_file, &qt_settings_ini_style()) .map_err(OutputError::Io)?; Ok(()) } From c1c09624bd5f46cdcfc042561070bab6faabaffc Mon Sep 17 00:00:00 2001 From: mariinkys Date: Thu, 19 Feb 2026 16:32:30 +0100 Subject: [PATCH 432/556] fix: right-clicking any sidebar item makes all sidebar items bold --- src/widget/segmented_button/widget.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index e4f416bf..5201c908 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -246,7 +246,7 @@ where if let Some(text) = self.model.text.get(key) { let font = if self.button_is_focused(state, key) { self.font_active - } else if state.show_context.is_some() || self.button_is_hovered(state, key) { + } else if state.show_context == Some(key) || self.button_is_hovered(state, key) { self.font_hovered } else if self.model.is_active(key) { self.font_active From 1f6086e5ead97063bfc654f2ca9ad31eaf6944d3 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 19 Feb 2026 09:18:35 -0700 Subject: [PATCH 433/556] Update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index e2a24417..ecc29a83 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit e2a2441789a7e302f099c0e8e9493ef81b58e265 +Subproject commit ecc29a83982839f628e2ed1c01605c694a1fd3ac From b9bd773940950dc07b2cfa7c62c2588b0a653017 Mon Sep 17 00:00:00 2001 From: Hojjat Abdollahi Date: Thu, 19 Feb 2026 10:06:45 -0700 Subject: [PATCH 434/556] feat: ellipsize text (#1132) --- iced | 2 +- src/widget/dropdown/menu/mod.rs | 1 + src/widget/dropdown/multi/menu.rs | 2 ++ src/widget/dropdown/multi/widget.rs | 3 +++ src/widget/dropdown/widget.rs | 3 +++ src/widget/segmented_button/widget.rs | 4 +++- src/widget/text_input/input.rs | 7 +++++++ 7 files changed, 20 insertions(+), 2 deletions(-) diff --git a/iced b/iced index ecc29a83..d36e4df4 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit ecc29a83982839f628e2ed1c01605c694a1fd3ac +Subproject commit d36e4df47f2e277fafcd3505229d53438c7f128d diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index 1d42d01f..3fd099b3 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -682,6 +682,7 @@ where vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), + ellipsize: text::Ellipsize::default(), }, bounds.position(), color, diff --git a/src/widget/dropdown/multi/menu.rs b/src/widget/dropdown/multi/menu.rs index 0035829f..39e89ee2 100644 --- a/src/widget/dropdown/multi/menu.rs +++ b/src/widget/dropdown/multi/menu.rs @@ -594,6 +594,7 @@ where vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), + ellipsize: text::Ellipsize::default(), }, bounds.position(), color, @@ -643,6 +644,7 @@ where vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), + ellipsize: text::Ellipsize::default(), }, bounds.position(), appearance.description_color, diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index 458cf5e6..43a0836f 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -279,6 +279,7 @@ pub fn layout( vertical_alignment: alignment::Vertical::Top, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), + ellipsize: text::Ellipsize::default(), }); paragraph.min_width().round() }; @@ -423,6 +424,7 @@ pub fn overlay<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static vertical_alignment: alignment::Vertical::Top, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), + ellipsize: text::Ellipsize::default(), }); paragraph.min_width().round() }; @@ -555,6 +557,7 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>( vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), + ellipsize: text::Ellipsize::default(), }, bounds.position(), style.text_color, diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 03be4eb3..67101d26 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -212,6 +212,7 @@ where vertical_alignment: alignment::Vertical::Top, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), + ellipsize: text::Ellipsize::default(), }); } @@ -478,6 +479,7 @@ pub fn layout( vertical_alignment: alignment::Vertical::Top, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), + ellipsize: text::Ellipsize::default(), }; let paragraph = match paragraph { Some(p) => { @@ -934,6 +936,7 @@ pub fn draw<'a, S>( vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), + ellipsize: text::Ellipsize::default(), }, bounds.position(), style.text_color, diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 5201c908..1f009cc6 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -23,7 +23,7 @@ use iced::{ event, keyboard, mouse, touch, window, }; use iced_core::mouse::ScrollDelta; -use iced_core::text::{LineHeight, Renderer as TextRenderer, Shaping, Wrapping}; +use iced_core::text::{Ellipsize, LineHeight, Renderer as TextRenderer, Shaping, Wrapping}; use iced_core::widget::operation::Focusable; use iced_core::widget::{self, operation, tree}; use iced_core::{Border, Point, Renderer as IcedRenderer, Shadow, Text}; @@ -274,6 +274,7 @@ where vertical_alignment: alignment::Vertical::Center, shaping: Shaping::Advanced, wrapping: Wrapping::None, + ellipsize: Ellipsize::None, line_height: self.line_height, }; @@ -602,6 +603,7 @@ where vertical_alignment: alignment::Vertical::Center, shaping: Shaping::Advanced, wrapping: Wrapping::default(), + ellipsize: Ellipsize::default(), line_height: self.line_height, }) }); diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 7dd92e12..e98d4cfa 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -728,6 +728,7 @@ where line_height: text::LineHeight::default(), shaping: text::Shaping::Advanced, wrapping: text::Wrapping::None, + ellipsize: text::Ellipsize::None, }); let Size { width, height } = @@ -1160,6 +1161,7 @@ pub fn layout( line_height, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::None, + ellipsize: text::Ellipsize::None, }); let label_size = label_paragraph.min_bounds(); @@ -1297,6 +1299,7 @@ pub fn layout( line_height: helper_text_line_height, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::None, + ellipsize: text::Ellipsize::None, }); let helper_text_size = helper_text_paragraph.min_bounds(); let helper_text_node = layout::Node::new(helper_text_size).translate(helper_pos); @@ -2260,6 +2263,7 @@ pub fn draw<'a, Message>( line_height, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::None, + ellipsize: text::Ellipsize::None, }, label_layout.bounds().position(), appearance.label_color, @@ -2449,6 +2453,7 @@ pub fn draw<'a, Message>( line_height: text::LineHeight::default(), shaping: text::Shaping::Advanced, wrapping: text::Wrapping::None, + ellipsize: text::Ellipsize::None, }, bounds.position(), color, @@ -2497,6 +2502,7 @@ pub fn draw<'a, Message>( line_height: helper_line_height, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::None, + ellipsize: text::Ellipsize::None, }, helper_text_layout.bounds().position(), text_color, @@ -2877,6 +2883,7 @@ fn replace_paragraph( vertical_alignment: alignment::Vertical::Top, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::None, + ellipsize: text::Ellipsize::None, }); } From 384e8f6e219bb458720eafa5bb971b832c057f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Mar=C3=ADn?= <62134857+mariinkys@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:06:45 +0100 Subject: [PATCH 435/556] fix(segmented_button): clear bold button text on context menu close --- src/widget/segmented_button/widget.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 1f009cc6..0e1af1d0 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -2073,7 +2073,7 @@ where _renderer: &Renderer, translation: Vector, ) -> Option> { - let state = tree.state.downcast_ref::(); + let state = tree.state.downcast_mut::(); let menu_state = state.menu_state.clone(); let entity = state.show_context?; @@ -2089,6 +2089,12 @@ where if !menu_state.inner.with_data(|data| data.open) { // If the menu is not open, we don't need to show it. + // We also clear the context entity and update the text + // cache so that the item is not bold when the context menu is closed + state.show_context = None; + for key in self.model.order.iter().copied() { + self.update_entity_paragraph(state, key); + } return None; } bounds.x = state.context_cursor.x; From a37be90e811f365273bf632bf7090b63a368f092 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 9 Feb 2026 22:04:13 +0100 Subject: [PATCH 436/556] fix(single-instance): unminimize main window on dbus activate --- src/app/cosmic.rs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 803a56bd..bfda4a1d 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -1034,15 +1034,28 @@ impl Cosmic { } return Task::batch(cmds); } - Action::Activate(_token) => - { - #[cfg(feature = "wayland")] + Action::Activate(_token) => { if let Some(id) = self.app.core().main_window_id() { - return iced_winit::platform_specific::commands::activation::activate( - id, - #[allow(clippy::used_underscore_binding)] - _token, - ); + // Unminimize window before requesting to activate it. + let mut task = iced_runtime::window::minimize(id, false); + + #[cfg(feature = "wayland")] + { + task = task.chain( + iced_winit::platform_specific::commands::activation::activate( + id, + #[allow(clippy::used_underscore_binding)] + _token, + ), + ) + } + + #[cfg(not(feature = "wayland"))] + { + task = task.chain(iced_runtime::window::gain_focus(id)); + } + + return task; } } From f2caa66f0ff8e8fc1172d12742d08356fd55a78c Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 2 Mar 2026 17:10:06 +0100 Subject: [PATCH 437/556] 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: Anonymous Co-authored-by: Arve Eriksson <031299870@telia.com> Co-authored-by: Baurzhan Muftakhidinov Co-authored-by: Benmak Kizuna Co-authored-by: David Carvalho Co-authored-by: Ettore Atalan Co-authored-by: Fedorov Alexei Co-authored-by: Feike Donia Co-authored-by: Geeson Wan Co-authored-by: Hosted Weblate Co-authored-by: Jiri Grönroos Co-authored-by: Julien Brouillard Co-authored-by: Marko X Co-authored-by: Tommi Nieminen Co-authored-by: VandaL Co-authored-by: Zahid Rizky Fakhri Co-authored-by: jonnysemon Co-authored-by: lorduskordus Co-authored-by: therealmate Co-authored-by: yakup Co-authored-by: Димко Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ar/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/cs/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/de/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/fi/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/fr/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ga/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/hu/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/id/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/kk/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/nl/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/pl/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/pt_BR/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ru/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/sv/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/tr/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/uk/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/zh_Hans/ Translation: Pop OS/libcosmic --- i18n/ar/libcosmic.ftl | 7 +++++++ i18n/cs/libcosmic.ftl | 21 ++++++++++++++------- i18n/de/libcosmic.ftl | 23 ++++++++++++++--------- i18n/fi/libcosmic.ftl | 34 ++++++++++++++++++++++++++++++++++ i18n/fr/libcosmic.ftl | 21 ++++++++++++++------- i18n/ga/libcosmic.ftl | 21 ++++++++++++++------- i18n/hu/libcosmic.ftl | 21 ++++++++++++++------- i18n/id/libcosmic.ftl | 21 ++++++++++++++------- i18n/kk/libcosmic.ftl | 21 ++++++++++++++------- i18n/nl/libcosmic.ftl | 8 +++++++- i18n/pl/libcosmic.ftl | 21 ++++++++++++++------- i18n/pt-BR/libcosmic.ftl | 21 ++++++++++++++------- i18n/ru/libcosmic.ftl | 21 ++++++++++++++------- i18n/sl/libcosmic.ftl | 0 i18n/sv/libcosmic.ftl | 21 ++++++++++++++------- i18n/tr/libcosmic.ftl | 27 ++++++++++++++++++++++++++- i18n/uk/libcosmic.ftl | 21 ++++++++++++++------- i18n/zh-Hans/libcosmic.ftl | 21 ++++++++++++++------- 18 files changed, 256 insertions(+), 95 deletions(-) create mode 100644 i18n/sl/libcosmic.ftl diff --git a/i18n/ar/libcosmic.ftl b/i18n/ar/libcosmic.ftl index 92f016f0..35e6050f 100644 --- a/i18n/ar/libcosmic.ftl +++ b/i18n/ar/libcosmic.ftl @@ -27,3 +27,10 @@ thursday = الخميس friday = الجمعة saturday = السبت sunday = الأحد +mon = ن +tue = ث +wed = ر +thu = خ +fri = ج +sat = س +sun = ح diff --git a/i18n/cs/libcosmic.ftl b/i18n/cs/libcosmic.ftl index 8f2ef348..850870d9 100644 --- a/i18n/cs/libcosmic.ftl +++ b/i18n/cs/libcosmic.ftl @@ -8,7 +8,7 @@ designers = Designéři artists = Grafici translators = Překladatelé documenters = Tvůrci dokumentace -sunday = Ne +sunday = Neděle january = Leden { $year } february = Únor { $year } march = Březen { $year } @@ -21,9 +21,16 @@ september = Září { $year } october = Říjen { $year } november = Listopad { $year } december = Prosinec { $year } -monday = Po -tuesday = Út -wednesday = St -thursday = Čt -friday = Pá -saturday = So +monday = Pondělí +tuesday = Úterý +wednesday = Středa +thursday = Čtvrtek +friday = Pátek +saturday = Sobota +mon = Po +tue = Út +wed = St +thu = Čt +fri = Pá +sat = So +sun = Ne diff --git a/i18n/de/libcosmic.ftl b/i18n/de/libcosmic.ftl index 2ef7b765..1f17c924 100644 --- a/i18n/de/libcosmic.ftl +++ b/i18n/de/libcosmic.ftl @@ -3,11 +3,11 @@ close = Schließen # About license = Lizenz links = Links -developers = Entwickler*innen -designers = Designer*innen -artists = Künstler*innen +developers = Entwickler(innen) +designers = Designer(innen) +artists = Künstler(innen) translators = Übersetzer*innen -documenters = Dokumentierer*innen +documenters = Dokumentierer(innen) # Calendar january = Januar { $year } february = Februar { $year } @@ -23,8 +23,13 @@ november = November { $year } december = Dezember { $year } monday = Mo tuesday = Di -wednesday = Mi -thursday = Do -friday = Fr -saturday = Sa -sunday = So +wednesday = Mittwoch +thursday = Donnerstag +friday = Freitag +saturday = Samstag +sunday = Sonntag +wed = Mi +thu = Do +fri = Fr +sat = Sa +sun = So diff --git a/i18n/fi/libcosmic.ftl b/i18n/fi/libcosmic.ftl index e69de29b..877f225d 100644 --- a/i18n/fi/libcosmic.ftl +++ b/i18n/fi/libcosmic.ftl @@ -0,0 +1,34 @@ +monday = Maanantai +mon = ma +tuesday = Tiistai +tue = ti +wednesday = Keskiviikko +wed = ke +thursday = Torstai +thu = to +friday = Perjantai +fri = pe +saturday = Lauantai +sat = la +sunday = Sunnuntai +sun = su +close = Sulje +license = Lisenssi +links = Linkit +developers = Kehittäjät +designers = Suunnittelijat +artists = Artistit +translators = Kääntäjät +documenters = Dokumentoijat +january = Tammikuu { $year } +february = Helmikuu { $year } +march = Maaliskuu { $year } +april = Huhtikuu { $year } +may = Toukokuu { $year } +june = Kesäkuu { $year } +july = Heinäkuu { $year } +august = Elokuu { $year } +september = Syyskuu { $year } +october = Lokakuu { $year } +november = Marraskuu { $year } +december = Joulukuu { $year } diff --git a/i18n/fr/libcosmic.ftl b/i18n/fr/libcosmic.ftl index 43e2d6f7..1ec6c0cf 100644 --- a/i18n/fr/libcosmic.ftl +++ b/i18n/fr/libcosmic.ftl @@ -10,18 +10,25 @@ february = Février { $year } april = Avril { $year } march = Mars { $year } november = Novembre { $year } -friday = Ven -tuesday = Mar +friday = Vendredi +tuesday = Mardi may = Mai { $year } -wednesday = Mer -monday = Lun +wednesday = Mercredi +monday = Lundi december = Décembre { $year } -sunday = Dim +sunday = Dimanche june = Juin { $year } -saturday = Sam +saturday = Samedi august = Août { $year } july = Juillet { $year } -thursday = Jeu +thursday = Jeudi september = Septembre { $year } october = Octobre { $year } designers = Designers +mon = Lun +tue = Mar +wed = Mer +thu = Jeu +fri = Ven +sat = Sam +sun = Dim diff --git a/i18n/ga/libcosmic.ftl b/i18n/ga/libcosmic.ftl index 024841bf..bdf38d20 100644 --- a/i18n/ga/libcosmic.ftl +++ b/i18n/ga/libcosmic.ftl @@ -18,10 +18,17 @@ september = Meán Fómhair { $year } october = Deireadh Fómhair { $year } november = Samhain { $year } december = Nollaig { $year } -monday = Lua -tuesday = Mái -wednesday = Céa -thursday = Déa -friday = Aoi -saturday = Sat -sunday = Dom +monday = Dé Luain +tuesday = Dé Máirt +wednesday = Dé Céadaoin +thursday = Déardaoin +friday = Dé hAoine +saturday = Dé Sathairn +sunday = Dé Domhnaigh +mon = Lua +tue = Mái +wed = Céa +thu = Déa +fri = Aoi +sat = Sat +sun = Dom diff --git a/i18n/hu/libcosmic.ftl b/i18n/hu/libcosmic.ftl index 02069244..7ff046b3 100644 --- a/i18n/hu/libcosmic.ftl +++ b/i18n/hu/libcosmic.ftl @@ -20,10 +20,17 @@ september = { $year } szeptember october = { $year } október november = { $year } november december = { $year } december -monday = H -tuesday = K -wednesday = Sze -thursday = Cs -friday = P -saturday = Szo -sunday = V +monday = Hétfő +tuesday = Kedd +wednesday = Szerda +thursday = Csütörtök +friday = Péntek +saturday = Szombat +sunday = Vasárnap +mon = H +tue = K +wed = Sze +thu = Cs +fri = P +sat = Szo +sun = V diff --git a/i18n/id/libcosmic.ftl b/i18n/id/libcosmic.ftl index 2ce82dab..53e7736b 100644 --- a/i18n/id/libcosmic.ftl +++ b/i18n/id/libcosmic.ftl @@ -18,10 +18,17 @@ september = September { $year } october = Oktober { $year } november = November { $year } december = Desember { $year } -monday = Sen -tuesday = Sel -wednesday = Rab -sunday = Min -saturday = Sab -friday = Jum -thursday = Kam +monday = Senin +tuesday = Selasa +wednesday = Rabu +sunday = Minggu +saturday = Sabtu +friday = Jum'at +thursday = Kamis +mon = Sen +tue = Sel +wed = Rab +thu = Kam +fri = Jum +sat = Sab +sun = Min diff --git a/i18n/kk/libcosmic.ftl b/i18n/kk/libcosmic.ftl index bb06e98f..9d257114 100644 --- a/i18n/kk/libcosmic.ftl +++ b/i18n/kk/libcosmic.ftl @@ -18,10 +18,17 @@ september = Қыркүйек { $year } october = Қазан { $year } november = Қараша { $year } december = Желтоқсан { $year } -monday = Дс -tuesday = Сс -wednesday = Ср -thursday = Бс -friday = Жм -saturday = Сб -sunday = Жс +monday = Дүйсенбі +tuesday = Сейсенбі +wednesday = Сәрсенбі +thursday = Бейсенбі +friday = Жұма +saturday = Сенбі +sunday = Жексенбі +mon = Дс +tue = Сс +wed = Ср +thu = Бс +fri = Жм +sat = Сн +sun = Жк diff --git a/i18n/nl/libcosmic.ftl b/i18n/nl/libcosmic.ftl index 75fc8cdf..7676b811 100644 --- a/i18n/nl/libcosmic.ftl +++ b/i18n/nl/libcosmic.ftl @@ -18,4 +18,10 @@ wednesday = Woe thursday = Do friday = Vrij saturday = Za -sunday = Zon +sunday = Zo +links = Links +developers = Ontwikkeling +designers = Ontwerp +translators = Vertaling +documenters = Documentatie +artists = Vormgeving diff --git a/i18n/pl/libcosmic.ftl b/i18n/pl/libcosmic.ftl index 4bbfd67f..0d1649d4 100644 --- a/i18n/pl/libcosmic.ftl +++ b/i18n/pl/libcosmic.ftl @@ -20,10 +20,17 @@ september = Wrzesień { $year } october = Październik { $year } november = Listopad { $year } december = Grudzień { $year } -monday = Pon -tuesday = Wto -wednesday = Śro -thursday = Czw -friday = Pią -saturday = Sob -sunday = Nie +monday = Poniedziałek +tuesday = Wtorek +wednesday = Środa +thursday = Czwartek +friday = Piątek +saturday = Sobota +sunday = Niedziela +mon = Pon +tue = Wto +wed = Śro +thu = Czw +fri = Pia +sat = Sob +sun = Nie diff --git a/i18n/pt-BR/libcosmic.ftl b/i18n/pt-BR/libcosmic.ftl index 51b5f6c3..1a51c799 100644 --- a/i18n/pt-BR/libcosmic.ftl +++ b/i18n/pt-BR/libcosmic.ftl @@ -20,10 +20,17 @@ september = Setembro de { $year } october = Outubro de { $year } november = Novembro de { $year } december = Dezembro de { $year } -monday = Seg -tuesday = Ter -wednesday = Qua -thursday = Qui -friday = Sex -saturday = Sáb -sunday = Dom +monday = Segunda-feira +tuesday = Terça-feira +wednesday = Quarta-feira +thursday = Quinta-feira +friday = Sexta-feira +saturday = Sábado +sunday = Domingo +mon = Seg +tue = Ter +wed = Qua +thu = Qui +fri = Sex +sat = Sáb +sun = Dom diff --git a/i18n/ru/libcosmic.ftl b/i18n/ru/libcosmic.ftl index 7fe9b3dc..1ff78655 100644 --- a/i18n/ru/libcosmic.ftl +++ b/i18n/ru/libcosmic.ftl @@ -18,10 +18,17 @@ september = Сентябрь { $year } october = Октябрь { $year } november = Ноябрь { $year } december = Декабрь { $year } -monday = Пн -tuesday = Вт -wednesday = Ср -thursday = Чт -friday = Пт -saturday = Сб -sunday = Вс +monday = Понедельник +tuesday = Вторник +wednesday = Среда +thursday = Четверг +friday = Пятница +saturday = Суббота +sunday = Воскресенье +mon = Пн +tue = Вт +wed = Ср +thu = Чт +fri = Пт +sat = Сб +sun = Вс diff --git a/i18n/sl/libcosmic.ftl b/i18n/sl/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/sv/libcosmic.ftl b/i18n/sv/libcosmic.ftl index f0c647a1..27cdb393 100644 --- a/i18n/sv/libcosmic.ftl +++ b/i18n/sv/libcosmic.ftl @@ -18,10 +18,17 @@ september = September { $year } october = Oktober { $year } november = November { $year } december = December { $year } -monday = Mån -tuesday = Tis -wednesday = Ons -thursday = Tor -friday = Fre -saturday = Lör -sunday = Sön +monday = Måndag +tuesday = Tisdag +wednesday = Onsdag +thursday = Torsdag +friday = Fredag +saturday = Lördag +sunday = Söndag +sun = Sön +mon = Mån +tue = Tis +wed = Ons +thu = Tor +fri = Fre +sat = Lör diff --git a/i18n/tr/libcosmic.ftl b/i18n/tr/libcosmic.ftl index fd0f5475..39690200 100644 --- a/i18n/tr/libcosmic.ftl +++ b/i18n/tr/libcosmic.ftl @@ -1,6 +1,5 @@ # Context Drawer close = Kapat - # About license = Lisans links = Bağlantılar @@ -9,3 +8,29 @@ designers = Tasarımcılar artists = Sanatçılar translators = Çevirmenler documenters = Belgelendiriciler +january = Ocak { $year } +february = Şubat { $year } +march = Mart { $year } +april = Nisan { $year } +may = Mayıs { $year } +june = Haziran { $year } +july = Temmuz { $year } +august = Ağustos { $year } +september = Eylül { $year } +october = Ekim { $year } +november = Kasım { $year } +december = Aralık { $year } +monday = Pazartesi +mon = Pzt +tuesday = Salı +tue = Sal +wednesday = Çarşamba +wed = Çar +thursday = Perşembe +thu = Per +friday = Cuma +fri = Cum +saturday = Cumartesi +sat = Cmt +sunday = Pazar +sun = Paz diff --git a/i18n/uk/libcosmic.ftl b/i18n/uk/libcosmic.ftl index d82c2a6e..cbe1cfaf 100644 --- a/i18n/uk/libcosmic.ftl +++ b/i18n/uk/libcosmic.ftl @@ -10,20 +10,27 @@ translators = Перекладачі documenters = Документатори february = Лютий { $year } november = Листопад { $year } -friday = Пт -tuesday = Вт +friday = П'ятниця +tuesday = Вівторок may = Травень { $year } -wednesday = Ср +wednesday = Середа april = Квітень { $year } -monday = Пн +monday = Понеділок december = Грудень { $year } -sunday = Нд +sunday = Неділя march = Березень { $year } june = Червень { $year } -saturday = Сб +saturday = Субота august = Серпень { $year } july = Липень { $year } -thursday = Чт +thursday = Четвер september = Вересень { $year } october = Жовтень { $year } january = Січень { $year } +mon = Пн +tue = Вт +wed = Ср +thu = Чт +fri = Пт +sat = Cб +sun = Нд diff --git a/i18n/zh-Hans/libcosmic.ftl b/i18n/zh-Hans/libcosmic.ftl index 9dfd6139..42330dcb 100644 --- a/i18n/zh-Hans/libcosmic.ftl +++ b/i18n/zh-Hans/libcosmic.ftl @@ -16,12 +16,19 @@ september = { $year }年9月 october = { $year }年10月 november = { $year }年11月 december = { $year }年12月 -monday = 周一 -tuesday = 周二 -wednesday = 周三 -thursday = 周四 -friday = 周五 -saturday = 周六 -sunday = 周日 +monday = 星期一 +tuesday = 星期二 +wednesday = 星期三 +thursday = 星期四 +friday = 星期五 +saturday = 星期六 +sunday = 星期日 artists = 艺术家 documenters = 文档作者 +mon = 周一 +tue = 周二 +wed = 周三 +thu = 周四 +fri = 周五 +sat = 周六 +sun = 周日 From bd1d3d5a73a858443c920ed8f83607846650a3e6 Mon Sep 17 00:00:00 2001 From: Hojjat Abdollahi Date: Mon, 2 Mar 2026 12:01:19 -0700 Subject: [PATCH 438/556] fix: ellipsize headerbar title instead of wrapping (#1140) --- src/widget/header_bar.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index c5bde28f..b0957d68 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -445,6 +445,10 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { std::mem::swap(&mut title, &mut self.title); widget::text::heading(title) + .wrapping(iced_core::text::Wrapping::None) + .ellipsize(iced_core::text::Ellipsize::End( + iced_core::text::EllipsizeHeightLimit::Lines(1), + )) .apply(widget::container) .center(Length::FillPortion(title_portion)) .into() From 85c27a99604f343bfa0fb2d78e6726f511f6a57a Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 3 Mar 2026 21:18:45 +0100 Subject: [PATCH 439/556] fix(cosmic-theme): on reset of theme exports, do not remove VS code configs Closes #1139 --- cosmic-theme/src/output/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/cosmic-theme/src/output/mod.rs b/cosmic-theme/src/output/mod.rs index f331e8d2..b2474dc1 100644 --- a/cosmic-theme/src/output/mod.rs +++ b/cosmic-theme/src/output/mod.rs @@ -56,10 +56,8 @@ impl Theme { pub fn reset_exports() -> Result<(), OutputError> { let gtk_res = Theme::reset_gtk(); let qt_res = Theme::reset_qt(); - let vs_res = Theme::reset_vs_code(); gtk_res?; qt_res?; - vs_res?; Ok(()) } } From 86dcf8af6cfcb3ab65e41649e4793f47c5595433 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 3 Mar 2026 23:32:00 +0100 Subject: [PATCH 440/556] feat(cosmic-icons): new icons for cosmic image viewer app --- cosmic-icons | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cosmic-icons b/cosmic-icons index 70b07582..52520957 160000 --- a/cosmic-icons +++ b/cosmic-icons @@ -1 +1 @@ -Subproject commit 70b07582e24ec2114672256b9657ca80670bca8a +Subproject commit 5252095787cc96e2aed64604158f94e450703455 From e10459fb375d6a84e4ad296df98b1af610fcc531 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 10 Feb 2026 15:37:41 -0500 Subject: [PATCH 441/556] wip rebase updates --- Cargo.toml | 21 +- cosmic-config/src/dbus.rs | 335 ++++++++++--------- cosmic-config/src/subscription.rs | 51 ++- examples/application/Cargo.toml | 1 + iced | 2 +- src/app/action.rs | 2 - src/app/cosmic.rs | 15 +- src/app/mod.rs | 96 ++++-- src/app/multi_window.rs | 244 -------------- src/applet/column.rs | 46 +-- src/applet/mod.rs | 37 +- src/applet/row.rs | 46 +-- src/applet/token/subscription.rs | 7 +- src/command.rs | 2 +- src/dbus_activation.rs | 121 +++---- src/executor/multi.rs | 4 + src/executor/single.rs | 4 + src/theme/portal.rs | 9 +- src/theme/style/iced.rs | 158 +++++++-- src/widget/about.rs | 4 +- src/widget/aspect_ratio.rs | 20 +- src/widget/autosize.rs | 31 +- src/widget/button/widget.rs | 89 +++-- src/widget/calendar.rs | 4 +- src/widget/color_picker/mod.rs | 64 ++-- src/widget/context_drawer/overlay.rs | 29 +- src/widget/context_drawer/widget.rs | 23 +- src/widget/context_menu.rs | 22 +- src/widget/dialog.rs | 6 +- src/widget/dnd_destination.rs | 108 +++--- src/widget/dnd_source.rs | 65 ++-- src/widget/dropdown/menu/mod.rs | 81 +++-- src/widget/dropdown/mod.rs | 16 +- src/widget/dropdown/multi/menu.rs | 63 ++-- src/widget/dropdown/multi/widget.rs | 61 ++-- src/widget/dropdown/operation.rs | 96 +++--- src/widget/dropdown/widget.rs | 111 +++--- src/widget/flex_row/layout.rs | 10 +- src/widget/flex_row/widget.rs | 37 +- src/widget/frames.rs | 70 ++-- src/widget/grid/layout.rs | 14 +- src/widget/grid/widget.rs | 37 +- src/widget/header_bar.rs | 27 +- src/widget/icon/mod.rs | 17 +- src/widget/id_container.rs | 29 +- src/widget/layer_container.rs | 18 +- src/widget/list/column.rs | 4 +- src/widget/menu/flex.rs | 34 +- src/widget/menu/menu_bar.rs | 39 +-- src/widget/menu/menu_inner.rs | 124 +++---- src/widget/menu/menu_tree.rs | 29 +- src/widget/mod.rs | 18 +- src/widget/nav_bar.rs | 1 + src/widget/popover.rs | 87 +++-- src/widget/radio.rs | 31 +- src/widget/rectangle_tracker/mod.rs | 18 +- src/widget/rectangle_tracker/subscription.rs | 8 +- src/widget/responsive_container.rs | 31 +- src/widget/segmented_button/widget.rs | 164 +++++---- src/widget/settings/item.rs | 5 +- src/widget/spin_button.rs | 1 + src/widget/table/widget/compact.rs | 1 + src/widget/table/widget/standard.rs | 1 + src/widget/text_input/input.rs | 230 +++++++------ src/widget/toaster/widget.rs | 53 +-- src/widget/warning.rs | 1 + src/widget/wayland/tooltip/widget.rs | 65 ++-- src/widget/wrapper.rs | 22 +- 68 files changed, 1776 insertions(+), 1544 deletions(-) delete mode 100644 src/app/multi_window.rs diff --git a/Cargo.toml b/Cargo.toml index 4aaf9d0a..62b8ee7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,13 +8,27 @@ rust-version = "1.90" name = "cosmic" [features] -default = ["dbus-config", "multi-window", "a11y"] +# default = ["dbus-config", "multi-window", "a11y"] +default = [ "debug", + "winit", + "tokio", + # "xdg-portal", + "a11y", + "wgpu", + "single-instance", + "surface-message", + "dbus-config", + "x11", + "wayland", + "multi-window", + "about","animated-image","autosize", "dbus-config", "pipewire", "process", "rfd", "desktop", "desktop-systemd-scope", "serde-keycode", "qr_code", "markdown", "highlighter" +] # Accessibility support a11y = ["iced/a11y", "iced_accessibility"] # Enable about widget about = [] # Builds support for animated images -animated-image = ["dep:async-fs", "image/gif", "tokio?/io-util", "tokio?/fs"] +animated-image = ["dep:async-fs", "image/gif", "image/webp", "image/png", "tokio?/io-util", "tokio?/fs"] # XXX autosize should not be used on winit windows unless dialogs autosize = [] applet = [ @@ -76,7 +90,7 @@ wayland = [ ] surface-message = [] # multi-window support -multi-window = ["iced/multi-window"] +multi-window = [] # Render with wgpu wgpu = ["iced/wgpu", "iced_wgpu"] # X11 window support via winit @@ -96,6 +110,7 @@ async-std = [ "zbus?/async-io", "iced/async-std", ] +x11 = ["iced/x11", "iced_winit/x11"] [dependencies] apply = "0.3.0" diff --git a/cosmic-config/src/dbus.rs b/cosmic-config/src/dbus.rs index e9e3395c..da7bcb68 100644 --- a/cosmic-config/src/dbus.rs +++ b/cosmic-config/src/dbus.rs @@ -1,11 +1,11 @@ -use std::ops::Deref; +use std::{any::TypeId, ops::Deref}; use crate::{CosmicConfigEntry, Update}; use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy}; use futures_util::SinkExt; use iced_futures::{ Subscription, - futures::{self, Stream, StreamExt, future::pending}, + futures::{self, StreamExt, future::pending}, stream, }; @@ -57,6 +57,20 @@ impl Watcher { } } +#[derive(Clone)] +struct Wrapper( + TypeId, + CosmicSettingsDaemonProxy<'static>, + &'static str, + bool, +); + +impl std::hash::Hash for Wrapper { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + #[allow(clippy::too_many_lines)] pub fn watcher_subscription( settings_daemon: CosmicSettingsDaemonProxy<'static>, @@ -64,166 +78,185 @@ pub fn watcher_subscription iced_futures::Subscription> { let id = std::any::TypeId::of::(); - Subscription::run_with_id( - (id, config_id), - watcher_stream(settings_daemon, config_id, is_state), - ) -} + Subscription::run_with( + Wrapper(id, settings_daemon, config_id, is_state), + |&Wrapper(_, ref settings_daemon, ref config_id, ref is_state)| { + let is_state = *is_state; + let config_id = *config_id; + let settings_daemon = settings_daemon.clone(); + enum Change { + Changes(Changed), + OwnerChanged(bool), + } + stream::channel( + 5, + move |mut tx: futures::channel::mpsc::Sender>| async move { + let version = T::VERSION; -fn watcher_stream( - settings_daemon: CosmicSettingsDaemonProxy<'static>, - config_id: &'static str, - is_state: bool, -) -> impl Stream> { - enum Change { - Changes(Changed), - OwnerChanged(bool), - } - stream::channel(5, move |mut tx| async move { - let version = T::VERSION; + let Ok(cosmic_config) = (if is_state { + crate::Config::new_state(config_id, version) + } else { + crate::Config::new(config_id, version) + }) else { + pending::<()>().await; + unreachable!(); + }; - let Ok(cosmic_config) = (if is_state { - crate::Config::new_state(config_id, version) - } else { - crate::Config::new(config_id, version) - }) else { - pending::<()>().await; - unreachable!(); - }; + let mut attempts = 0; - let mut attempts = 0; + loop { + let watcher = if is_state { + Watcher::new_state(&settings_daemon, config_id, version).await + } else { + Watcher::new_config(&settings_daemon, config_id, version).await + }; + let Ok(watcher) = watcher else { + tracing::error!("Failed to create watcher for {config_id}"); - loop { - let watcher = if is_state { - Watcher::new_state(&settings_daemon, config_id, version).await - } else { - Watcher::new_config(&settings_daemon, config_id, version).await - }; - let Ok(watcher) = watcher else { - tracing::error!("Failed to create watcher for {config_id}"); + #[cfg(feature = "tokio")] + ::tokio::time::sleep(::tokio::time::Duration::from_secs( + 2_u64.pow(attempts), + )) + .await; + #[cfg(feature = "async-std")] + async_std::task::sleep(std::time::Duration::from_secs( + 2_u64.pow(attempts), + )) + .await; + #[cfg(not(any(feature = "tokio", feature = "async-std")))] + { + pending::<()>().await; + unreachable!(); + } + attempts += 1; + // The settings daemon has exited + continue; + }; + let Ok(changes) = watcher.receive_changed().await else { + tracing::error!("Failed to listen for changes for {config_id}"); - #[cfg(feature = "tokio")] - ::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts))).await; - #[cfg(feature = "async-std")] - async_std::task::sleep(std::time::Duration::from_secs(2_u64.pow(attempts))).await; - #[cfg(not(any(feature = "tokio", feature = "async-std")))] - { - pending::<()>().await; - unreachable!(); - } - attempts += 1; - // The settings daemon has exited - continue; - }; - let Ok(changes) = watcher.receive_changed().await else { - tracing::error!("Failed to listen for changes for {config_id}"); + #[cfg(feature = "tokio")] + ::tokio::time::sleep(::tokio::time::Duration::from_secs( + 2_u64.pow(attempts), + )) + .await; + #[cfg(feature = "async-std")] + async_std::task::sleep(std::time::Duration::from_secs( + 2_u64.pow(attempts), + )) + .await; + #[cfg(not(any(feature = "tokio", feature = "async-std")))] + { + pending::<()>().await; + unreachable!(); + } + attempts += 1; + // The settings daemon has exited + continue; + }; - #[cfg(feature = "tokio")] - ::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts))).await; - #[cfg(feature = "async-std")] - async_std::task::sleep(std::time::Duration::from_secs(2_u64.pow(attempts))).await; - #[cfg(not(any(feature = "tokio", feature = "async-std")))] - { - pending::<()>().await; - unreachable!(); - } - attempts += 1; - // The settings daemon has exited - continue; - }; + let mut changes = changes.map(Change::Changes).fuse(); - let mut changes = changes.map(Change::Changes).fuse(); + let Ok(owner_changed) = watcher.inner().receive_owner_changed().await + else { + tracing::error!("Failed to listen for owner changes for {config_id}"); + #[cfg(feature = "tokio")] + ::tokio::time::sleep(::tokio::time::Duration::from_secs( + 2_u64.pow(attempts), + )) + .await; + #[cfg(feature = "async-std")] + async_std::task::sleep(std::time::Duration::from_secs( + 2_u64.pow(attempts), + )) + .await; + #[cfg(not(any(feature = "tokio", feature = "async-std")))] + { + pending::<()>().await; + unreachable!(); + } + attempts += 1; + // The settings daemon has exited + continue; + }; + let mut owner_changed = owner_changed + .map(|c| Change::OwnerChanged(c.is_some())) + .fuse(); - let Ok(owner_changed) = watcher.inner().receive_owner_changed().await else { - tracing::error!("Failed to listen for owner changes for {config_id}"); - #[cfg(feature = "tokio")] - ::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts))).await; - #[cfg(feature = "async-std")] - async_std::task::sleep(std::time::Duration::from_secs(2_u64.pow(attempts))).await; - #[cfg(not(any(feature = "tokio", feature = "async-std")))] - { - pending::<()>().await; - unreachable!(); - } - attempts += 1; - // The settings daemon has exited - continue; - }; - let mut owner_changed = owner_changed - .map(|c| Change::OwnerChanged(c.is_some())) - .fuse(); + // update now, just in case we missed changes while setting up stream + let mut config = match T::get_entry(&cosmic_config) { + Ok(config) => config, + Err((errors, default)) => { + for why in &errors { + if why.is_err() { + if let crate::Error::GetKey(_, err) = &why { + if err.kind() == std::io::ErrorKind::NotFound { + // No system default config installed; don't error + continue; + } + } + tracing::error!("error getting config: {config_id} {why}"); + } + } + default + } + }; - // update now, just in case we missed changes while setting up stream - let mut config = match T::get_entry(&cosmic_config) { - Ok(config) => config, - Err((errors, default)) => { - for why in &errors { - if why.is_err() { - if let crate::Error::GetKey(_, err) = &why { - if err.kind() == std::io::ErrorKind::NotFound { - // No system default config installed; don't error - continue; + if let Err(err) = tx + .send(Update { + errors: Vec::new(), + keys: Vec::new(), + config: config.clone(), + }) + .await + { + tracing::error!("Failed to send config: {err}"); + } + + loop { + let change: Changed = futures::select! { + c = changes.next() => { + let Some(Change::Changes(c)) = c else { + break; + }; + c + } + c = owner_changed.next() => { + let Some(Change::OwnerChanged(cont)) = c else { + break; + }; + if cont { + continue; + } else { + // The settings daemon has exited + break; + } + }, + }; + + // Reset the attempts counter if we received a change + attempts = 0; + let Ok(args) = change.args() else { + // The settings daemon has exited + break; + }; + let (errors, keys) = config.update_keys(&cosmic_config, &[args.key]); + if !keys.is_empty() { + if let Err(err) = tx + .send(Update { + errors, + keys, + config: config.clone(), + }) + .await + { + tracing::error!("Failed to send config update: {err}"); } } - tracing::error!("error getting config: {config_id} {why}"); } } - default - } - }; - - if let Err(err) = tx - .send(Update { - errors: Vec::new(), - keys: Vec::new(), - config: config.clone(), - }) - .await - { - tracing::error!("Failed to send config: {err}"); - } - - loop { - let change: Changed = futures::select! { - c = changes.next() => { - let Some(Change::Changes(c)) = c else { - break; - }; - c - } - c = owner_changed.next() => { - let Some(Change::OwnerChanged(cont)) = c else { - break; - }; - if cont { - continue; - } else { - // The settings daemon has exited - break; - } - }, - }; - - // Reset the attempts counter if we received a change - attempts = 0; - let Ok(args) = change.args() else { - // The settings daemon has exited - break; - }; - let (errors, keys) = config.update_keys(&cosmic_config, &[args.key]); - if !keys.is_empty() { - if let Err(err) = tx - .send(Update { - errors, - keys, - config: config.clone(), - }) - .await - { - tracing::error!("Failed to send config update: {err}"); - } - } - } - } - }) + }, + ) + }, + ) } diff --git a/cosmic-config/src/subscription.rs b/cosmic-config/src/subscription.rs index 45e021fe..d16b9b65 100644 --- a/cosmic-config/src/subscription.rs +++ b/cosmic-config/src/subscription.rs @@ -25,7 +25,24 @@ pub fn config_subscription< config_id: Cow<'static, str>, config_version: u64, ) -> iced_futures::Subscription> { - iced_futures::Subscription::run_with_id(id, watcher_stream(config_id, config_version, false)) + iced_futures::Subscription::run_with( + (id, config_id, config_version, false), + // FIXME there are type issues related to the 'static lifetime of the Cow if this is extracted to a named function... + |(_, config_id, config_version, is_state)| { + let config_id = config_id.clone(); + let config_version = *config_version; + let is_state = *is_state; + + stream::channel(100, move |mut output| async move { + let config_id = config_id.clone(); + let mut state = ConfigState::Init(config_id, config_version, is_state); + + loop { + state = start_listening::(state, &mut output).await; + } + }) + }, + ) } #[cold] @@ -37,25 +54,23 @@ pub fn config_state_subscription< config_id: Cow<'static, str>, config_version: u64, ) -> iced_futures::Subscription> { - iced_futures::Subscription::run_with_id(id, watcher_stream(config_id, config_version, true)) -} - -fn watcher_stream( - config_id: Cow<'static, str>, - config_version: u64, - is_state: bool, -) -> impl Stream> { - stream::channel(100, move |mut output| { - let config_id = config_id.clone(); - async move { + iced_futures::Subscription::run_with( + (id, config_id, config_version, true), + |(_, config_id, config_version, is_state)| { let config_id = config_id.clone(); - let mut state = ConfigState::Init(config_id, config_version, is_state); + let config_version = *config_version; + let is_state = *is_state; - loop { - state = start_listening::(state, &mut output).await; - } - } - }) + stream::channel(100, move |mut output| async move { + let config_id = config_id.clone(); + let mut state = ConfigState::Init(config_id, config_version, is_state); + + loop { + state = start_listening::(state, &mut output).await; + } + }) + }, + ) } async fn start_listening( diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index f05c0418..35ff3d30 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -23,4 +23,5 @@ features = [ "wgpu", "single-instance", "surface-message", + "multi-window", ] diff --git a/iced b/iced index d36e4df4..73369a18 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit d36e4df47f2e277fafcd3505229d53438c7f128d +Subproject commit 73369a18eb4069f3f3d1916fd1e17537ee87a587 diff --git a/src/app/action.rs b/src/app/action.rs index cbdd1a55..05fc7cbe 100644 --- a/src/app/action.rs +++ b/src/app/action.rs @@ -8,8 +8,6 @@ use crate::{config::CosmicTk, keyboard_nav}; #[cfg(feature = "wayland")] use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use cosmic_theme::ThemeMode; -#[cfg(not(any(feature = "multi-window", feature = "wayland")))] -use iced::Application as IcedApplication; /// A message managed internally by COSMIC. #[derive(Clone, Debug)] diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index bfda4a1d..edd7b157 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -15,7 +15,7 @@ use cosmic_theme::ThemeMode; use iced::Application as IcedApplication; #[cfg(feature = "wayland")] use iced::event::wayland; -use iced::{Task, window}; +use iced::{Task, theme, window}; use iced_futures::event::listen_with; #[cfg(feature = "wayland")] use iced_winit::SurfaceIdWrapper; @@ -397,15 +397,16 @@ where f64::from(self.app.core().scale_factor()) } - pub fn style(&self, theme: &Theme) -> iced_runtime::Appearance { + pub fn style(&self, theme: &Theme) -> theme::Style { if let Some(style) = self.app.style() { style } else if self.app.core().window.is_maximized { let theme = THEME.lock().unwrap(); - crate::style::iced::application::appearance(theme.borrow()) + crate::style::iced::application::style(theme.borrow()) } else { let theme = THEME.lock().unwrap(); - iced_runtime::Appearance { + + theme::Style { background_color: iced_core::Color::TRANSPARENT, icon_color: theme.cosmic().on_bg_color().into(), text_color: theme.cosmic().on_bg_color().into(), @@ -635,7 +636,7 @@ impl Cosmic { self.app.on_window_resize(id, width, height); //TODO: more efficient test of maximized (winit has no event for maximize if set by the OS) - return iced::window::get_maximized(id).map(move |maximized| { + return iced::window::is_maximized(id).map(move |maximized| { crate::Action::Cosmic(Action::WindowMaximized(id, maximized)) }); } @@ -711,10 +712,10 @@ impl Cosmic { Action::KeyboardNav(message) => match message { keyboard_nav::Action::FocusNext => { - return iced::widget::focus_next().map(crate::Action::Cosmic); + return iced::widget::operation::focus_next().map(crate::Action::Cosmic); } keyboard_nav::Action::FocusPrevious => { - return iced::widget::focus_previous().map(crate::Action::Cosmic); + return iced::widget::operation::focus_previous().map(crate::Action::Cosmic); } keyboard_nav::Action::Escape => return self.app.on_escape(), keyboard_nav::Action::Search => return self.app.on_search(), diff --git a/src/app/mod.rs b/src/app/mod.rs index 67636dac..1287dc27 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -11,9 +11,8 @@ pub use action::Action; use cosmic_config::CosmicConfigEntry; pub mod context_drawer; pub use context_drawer::{ContextDrawer, context_drawer}; +use iced::application::BootFn; pub mod cosmic; -#[cfg(all(feature = "winit", feature = "multi-window"))] -pub(crate) mod multi_window; pub mod settings; pub type Task = iced::Task>; @@ -21,12 +20,13 @@ pub type Task = iced::Task>; pub use crate::Core; use crate::prelude::*; use crate::theme::THEME; -use crate::widget::{container, horizontal_space, id_container, menu, nav_bar, popover}; +use crate::widget::{container, id_container, menu, nav_bar, popover, space}; use apply::Apply; -use iced::window; use iced::{Length, Subscription}; +use iced::{theme, window}; pub use settings::Settings; use std::borrow::Cow; +use std::{cell::RefCell, rc::Rc}; #[cold] pub(crate) fn iced_settings( @@ -72,7 +72,7 @@ pub(crate) fn iced_settings( core.exit_on_main_window_closed = exit_on_close; if let Some(border_size) = settings.resizable { - window_settings.resize_border = border_size as u32; + // window_settings.resize_border = border_size as u32; window_settings.resizable = true; } window_settings.decorations = !settings.client_decorations; @@ -82,7 +82,7 @@ pub(crate) fn iced_settings( window_settings.min_size = Some(min_size); } let max_size = settings.size_limits.max(); - if max_size != iced::Size::INFINITY { + if max_size != iced::Size::INFINITE { window_settings.max_size = Some(max_size); } @@ -90,6 +90,22 @@ pub(crate) fn iced_settings( (iced, (core, flags), window_settings) } +pub(crate) struct BootDataInner { + pub flags: A::Flags, + pub core: Core, +} + +pub(crate) struct BootData(pub Rc>>>); + +impl BootFn, crate::Action> + for BootData +{ + fn boot(&self) -> (cosmic::Cosmic, iced::Task>) { + let mut data = self.0.borrow_mut(); + let data = data.take().unwrap(); + cosmic::Cosmic::::init((data.core, data.flags)) + } +} /// Launch a COSMIC application with the given [`Settings`]. /// /// # Errors @@ -102,39 +118,50 @@ pub fn run(settings: Settings, flags: App::Flags) -> iced::Res } let default_font = settings.default_font; - let (settings, mut flags, window_settings) = iced_settings::(settings, flags); + let (settings, (mut core, flags), window_settings) = iced_settings::(settings, flags); #[cfg(not(feature = "multi-window"))] { - flags.0.main_window = Some(iced::window::Id::RESERVED); + core.main_window = Some(iced::window::Id::RESERVED); + iced::application( - cosmic::Cosmic::title, + BootData(Rc::new(RefCell::new(Some(BootDataInner:: { + flags, + core, + })))), cosmic::Cosmic::update, cosmic::Cosmic::view, ) .subscription(cosmic::Cosmic::subscription) + .title(cosmic::Cosmic::title) .style(cosmic::Cosmic::style) .theme(cosmic::Cosmic::theme) .window_size((500.0, 800.0)) .settings(settings) .window(window_settings) - .run_with(move || cosmic::Cosmic::::init(flags)) + .run() } #[cfg(feature = "multi-window")] { - let mut app = multi_window::multi_window::<_, _, _, _, App::Executor>( - cosmic::Cosmic::title, + let no_main_window = core.main_window.is_none(); + if no_main_window { + // app = app.window(window_settings); + core.main_window = Some(iced_core::window::Id::RESERVED); + } + let mut app = iced::daemon( + BootData(Rc::new(RefCell::new(Some(BootDataInner:: { + flags, + core, + })))), cosmic::Cosmic::update, cosmic::Cosmic::view, ); - if flags.0.main_window.is_none() { - app = app.window(window_settings); - flags.0.main_window = Some(iced_core::window::Id::RESERVED); - } + app.subscription(cosmic::Cosmic::subscription) + .title(cosmic::Cosmic::title) .style(cosmic::Cosmic::style) .theme(cosmic::Cosmic::theme) .settings(settings) - .run_with(move || cosmic::Cosmic::::init(flags)) + .run() } } @@ -204,13 +231,16 @@ where tracing::info!("Another instance is running"); Ok(()) } else { - let (settings, mut flags, window_settings) = iced_settings::(settings, flags); - flags.0.single_instance = true; + let (settings, (mut core, flags), window_settings) = iced_settings::(settings, flags); + core.single_instance = true; #[cfg(not(feature = "multi-window"))] { iced::application( - cosmic::Cosmic::title, + BootData(Rc::new(RefCell::new(Some(BootDataInner:: { + flags, + core, + })))), cosmic::Cosmic::update, cosmic::Cosmic::view, ) @@ -220,24 +250,30 @@ where .window_size((500.0, 800.0)) .settings(settings) .window(window_settings) - .run_with(move || cosmic::Cosmic::::init(flags)) + .run() } #[cfg(feature = "multi-window")] { - let mut app = multi_window::multi_window::<_, _, _, _, App::Executor>( - cosmic::Cosmic::title, + let no_main_window = core.main_window.is_none(); + if no_main_window { + // app = app.window(window_settings); + core.main_window = Some(iced_core::window::Id::RESERVED); + } + let mut app = iced::daemon( + BootData(Rc::new(RefCell::new(Some(BootDataInner:: { + flags, + core, + })))), cosmic::Cosmic::update, cosmic::Cosmic::view, ); - if flags.0.main_window.is_none() { - app = app.window(window_settings); - flags.0.main_window = Some(iced_core::window::Id::RESERVED); - } + app.subscription(cosmic::Cosmic::subscription) .style(cosmic::Cosmic::style) + .title(cosmic::Cosmic::title) .theme(cosmic::Cosmic::theme) .settings(settings) - .run_with(move || cosmic::Cosmic::::init(flags)) + .run() } } } @@ -428,7 +464,7 @@ where } /// Overrides the default style for applications - fn style(&self) -> Option { + fn style(&self) -> Option { None } @@ -667,7 +703,7 @@ impl ApplicationExt for App { ) } else { //TODO: this element is added to workaround state issues - widgets.push(horizontal_space().width(Length::Shrink).into()); + widgets.push(space::horizontal().width(Length::Shrink).into()); } } } diff --git a/src/app/multi_window.rs b/src/app/multi_window.rs deleted file mode 100644 index 65ac61f7..00000000 --- a/src/app/multi_window.rs +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2024 System76 -// SPDX-License-Identifier: MPL-2.0 - -//! Create and run daemons that run in the background. -//! Copied from iced 0.13, but adds optional initial window - -use iced::application; -use iced::window; -use iced::{ - self, Program, - program::{self, with_style, with_subscription, with_theme, with_title}, - runtime::{Appearance, DefaultStyle}, -}; -use iced::{Element, Result, Settings, Subscription, Task}; - -use std::marker::PhantomData; - -pub(crate) struct Instance { - update: Update, - view: View, - _state: PhantomData, - _message: PhantomData, - _theme: PhantomData, - _renderer: PhantomData, - _executor: PhantomData, -} - -/// Creates an iced [`MultiWindow`] given its title, update, and view logic. -pub fn multi_window( - title: impl Title, - update: impl application::Update, - view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>, -) -> MultiWindow> -where - State: 'static, - Message: Send + std::fmt::Debug + 'static, - Theme: Default + DefaultStyle, - Renderer: program::Renderer, - Executor: iced::Executor, -{ - use std::marker::PhantomData; - - impl Program - for Instance - where - Message: Send + std::fmt::Debug + 'static, - Theme: Default + DefaultStyle, - Renderer: program::Renderer, - Update: application::Update, - View: for<'a> self::View<'a, State, Message, Theme, Renderer>, - Executor: iced::Executor, - { - type State = State; - type Message = Message; - type Theme = Theme; - type Renderer = Renderer; - type Executor = Executor; - - fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { - self.update.update(state, message).into() - } - - fn view<'a>( - &self, - state: &'a Self::State, - window: window::Id, - ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { - self.view.view(state, window).into() - } - } - - MultiWindow { - raw: Instance { - update, - view, - _state: PhantomData, - _message: PhantomData, - _theme: PhantomData, - _renderer: PhantomData, - _executor: PhantomData::, - }, - settings: Settings::default(), - window: None, - } - .title(title) -} - -/// The underlying definition and configuration of an iced daemon. -/// -/// You can use this API to create and run iced applications -/// step by step—without coupling your logic to a trait -/// or a specific type. -/// -/// You can create a [`MultiWindow`] with the [`daemon`] helper. -#[derive(Debug)] -pub struct MultiWindow { - raw: P, - settings: Settings, - window: Option, -} - -impl MultiWindow

    { - #[cfg(any(feature = "winit", feature = "wayland"))] - /// Runs the [`MultiWindow`]. - /// - /// The state of the [`MultiWindow`] must implement [`Default`]. - /// If your state does not implement [`Default`], use [`run_with`] - /// instead. - /// - /// [`run_with`]: Self::run_with - pub fn run(self) -> Result - where - Self: 'static, - P::State: Default, - { - self.raw.run(self.settings, self.window) - } - - #[cfg(any(feature = "winit", feature = "wayland"))] - /// Runs the [`MultiWindow`] with a closure that creates the initial state. - pub fn run_with(self, initialize: I) -> Result - where - Self: 'static, - I: FnOnce() -> (P::State, Task) + 'static, - { - self.raw.run_with(self.settings, self.window, initialize) - } - - /// Sets the [`Settings`] that will be used to run the [`MultiWindow`]. - pub fn settings(self, settings: Settings) -> Self { - Self { settings, ..self } - } - - /// Sets the [`Title`] of the [`MultiWindow`]. - pub(crate) fn title( - self, - title: impl Title, - ) -> MultiWindow> { - MultiWindow { - raw: with_title(self.raw, move |state, window| title.title(state, window)), - settings: self.settings, - window: self.window, - } - } - - /// Sets the subscription logic of the [`MultiWindow`]. - pub fn subscription( - self, - f: impl Fn(&P::State) -> Subscription, - ) -> MultiWindow> { - MultiWindow { - raw: with_subscription(self.raw, f), - settings: self.settings, - window: self.window, - } - } - - /// Sets the theme logic of the [`MultiWindow`]. - pub fn theme( - self, - f: impl Fn(&P::State, window::Id) -> P::Theme, - ) -> MultiWindow> { - MultiWindow { - raw: with_theme(self.raw, f), - settings: self.settings, - window: self.window, - } - } - - /// Sets the style logic of the [`MultiWindow`]. - pub fn style( - self, - f: impl Fn(&P::State, &P::Theme) -> Appearance, - ) -> MultiWindow> { - MultiWindow { - raw: with_style(self.raw, f), - settings: self.settings, - window: self.window, - } - } - - /// Sets the window settings of the [`MultiWindow`]. - pub fn window(self, window: window::Settings) -> Self { - Self { - raw: self.raw, - settings: self.settings, - window: Some(window), - } - } -} - -/// The title logic of some [`MultiWindow`]. -/// -/// This trait is implemented both for `&static str` and -/// any closure `Fn(&State, window::Id) -> String`. -/// -/// This trait allows the [`daemon`] builder to take any of them. -pub trait Title { - /// Produces the title of the [`MultiWindow`]. - fn title(&self, state: &State, window: window::Id) -> String; -} - -impl Title for &'static str { - fn title(&self, _state: &State, _window: window::Id) -> String { - (*self).to_string() - } -} - -impl Title for T -where - T: Fn(&State, window::Id) -> String, -{ - fn title(&self, state: &State, window: window::Id) -> String { - self(state, window) - } -} - -/// The view logic of some [`MultiWindow`]. -/// -/// This trait allows the [`daemon`] builder to take any closure that -/// returns any `Into>`. -pub trait View<'a, State, Message, Theme, Renderer> { - /// Produces the widget of the [`MultiWindow`]. - fn view( - &self, - state: &'a State, - window: window::Id, - ) -> impl Into>; -} - -impl<'a, T, State, Message, Theme, Renderer, Widget> View<'a, State, Message, Theme, Renderer> for T -where - T: Fn(&'a State, window::Id) -> Widget, - State: 'static, - Widget: Into>, -{ - fn view( - &self, - state: &'a State, - window: window::Id, - ) -> impl Into> { - self(state, window) - } -} diff --git a/src/applet/column.rs b/src/applet/column.rs index 8fa2fa9f..8b3c68e9 100644 --- a/src/applet/column.rs +++ b/src/applet/column.rs @@ -217,7 +217,7 @@ where } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, @@ -233,25 +233,26 @@ where self.padding, self.spacing, self.align, - &self.children, + &mut self.children, &mut tree.children, ) } fn operate( - &self, + &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation, ) { - operation.container(None, layout.bounds(), &mut |operation| { + operation.container(None, layout.bounds()); + operation.traverse(&mut |operation| { self.children - .iter() + .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .for_each(|((child, state), c_layout)| { - child.as_widget().operate( + child.as_widget_mut().operate( state, c_layout.with_virtual_offset(layout.virtual_offset()), renderer, @@ -261,17 +262,17 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { let my_state = tree.state.downcast_mut::(); if let Some(hovered) = my_state.hovered { @@ -285,7 +286,7 @@ where e, mouse::Event::CursorLeft | mouse::Event::ButtonReleased { .. } ) { - return self.children[hovered].as_widget_mut().on_event( + return self.children[hovered].as_widget_mut().update( &mut tree.children[hovered], event, child_layout.with_virtual_offset(layout.virtual_offset()), @@ -302,7 +303,7 @@ where iced::core::touch::Event::FingerLifted { .. } | iced::core::touch::Event::FingerLost { .. } ) { - return self.children[hovered].as_widget_mut().on_event( + return self.children[hovered].as_widget_mut().update( &mut tree.children[hovered], event, child_layout.with_virtual_offset(layout.virtual_offset()), @@ -336,9 +337,9 @@ where ) && cursor.is_over(c_layout.bounds()) { my_state.hovered = Some(i); - return child.as_widget_mut().on_event( + return child.as_widget_mut().update( state, - event.clone(), + &event, c_layout.with_virtual_offset(layout.virtual_offset()), cursor_virtual, renderer, @@ -350,9 +351,9 @@ where cursor_virtual = mouse::Cursor::Unavailable; } - child.as_widget_mut().on_event( + child.as_widget_mut().update( state, - event.clone(), + &event, c_layout.with_virtual_offset(layout.virtual_offset()), cursor_virtual, renderer, @@ -360,8 +361,7 @@ where shell, viewport, ) - }) - .fold(event::Status::Ignored, event::Status::merge) + }); } fn mouse_interaction( @@ -436,11 +436,19 @@ where fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { - overlay::from_children(&mut self.children, tree, layout, renderer, translation) + overlay::from_children( + &mut self.children, + tree, + layout, + renderer, + viewport, + translation, + ) } #[cfg(feature = "a11y")] diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 0ab18817..ff376aab 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -1,7 +1,7 @@ #[cfg(feature = "applet-token")] pub mod token; -use crate::app::cosmic; +use crate::app::{BootData, BootDataInner, cosmic}; use crate::{ Application, Element, Renderer, app::iced_settings, @@ -18,17 +18,19 @@ use crate::{ self, autosize::{self, Autosize, autosize}, column::Column, - horizontal_space, layer_container, + layer_container, row::Row, - vertical_space, + space::horizontal, + space::vertical, }, }; pub use cosmic_panel_config; use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize}; use iced_core::{Padding, Shadow}; +use iced_runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner}; use iced_widget::Text; -use iced_widget::runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner}; use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity}; +use std::cell::RefCell; use std::{borrow::Cow, num::NonZeroU32, rc::Rc, sync::LazyLock, time::Duration}; use tracing::info; @@ -386,6 +388,7 @@ impl Context { }, shadow: Shadow::default(), icon_color: Some(cosmic.background.on.into()), + snap: true, } }), ) @@ -567,30 +570,36 @@ pub fn run(flags: App::Flags) -> iced::Result { window_settings.decorations = false; window_settings.exit_on_close_request = true; window_settings.resizable = false; - window_settings.resize_border = 0; + // window_settings.resize_border = 0; // TODO make multi-window not mandatory - let mut app = super::app::multi_window::multi_window::<_, _, _, _, App::Executor>( - cosmic::Cosmic::title, + let no_main_window = core.main_window.is_none(); + if no_main_window { + // TODO still apply window settings? + // window_settings = window_settings.clone(); + core.main_window = Some(iced_core::window::Id::RESERVED); + } + let mut app = iced::daemon( + BootData(Rc::new(RefCell::new(Some(BootDataInner:: { + flags, + core, + })))), cosmic::Cosmic::update, cosmic::Cosmic::view, ); - if core.main_window.is_none() { - app = app.window(window_settings.clone()); - core.main_window = Some(iced_core::window::Id::RESERVED); - } + app.subscription(cosmic::Cosmic::subscription) .style(cosmic::Cosmic::style) .theme(cosmic::Cosmic::theme) .settings(iced_settings) - .run_with(move || cosmic::Cosmic::::init((core, flags))) + .run() } #[must_use] -pub fn style() -> iced_runtime::Appearance { +pub fn style() -> iced::theme::Style { let theme = crate::theme::THEME.lock().unwrap(); - iced_runtime::Appearance { + iced::theme::Style { background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0), text_color: theme.cosmic().on_bg_color().into(), icon_color: theme.cosmic().on_bg_color().into(), diff --git a/src/applet/row.rs b/src/applet/row.rs index b5cf851f..2a770503 100644 --- a/src/applet/row.rs +++ b/src/applet/row.rs @@ -208,7 +208,7 @@ where } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, @@ -222,25 +222,26 @@ where self.padding, self.spacing, self.align, - &self.children, + &mut self.children, &mut tree.children, ) } fn operate( - &self, + &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation, ) { - operation.container(None, layout.bounds(), &mut |operation| { + operation.container(None, layout.bounds()); + operation.traverse(&mut |operation| { self.children - .iter() + .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .for_each(|((child, state), c_layout)| { - child.as_widget().operate( + child.as_widget_mut().operate( state, c_layout.with_virtual_offset(layout.virtual_offset()), renderer, @@ -250,17 +251,17 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { let my_state = tree.state.downcast_mut::(); if let Some(hovered) = my_state.hovered { @@ -274,7 +275,7 @@ where e, mouse::Event::CursorLeft | mouse::Event::ButtonReleased { .. } ) { - return self.children[hovered].as_widget_mut().on_event( + return self.children[hovered].as_widget_mut().update( &mut tree.children[hovered], event, child_layout.with_virtual_offset(layout.virtual_offset()), @@ -291,7 +292,7 @@ where iced::core::touch::Event::FingerLifted { .. } | iced::core::touch::Event::FingerLost { .. } ) { - return self.children[hovered].as_widget_mut().on_event( + return self.children[hovered].as_widget_mut().update( &mut tree.children[hovered], event, child_layout.with_virtual_offset(layout.virtual_offset()), @@ -326,9 +327,9 @@ where ) && cursor.is_over(c_layout.bounds()) { my_state.hovered = Some(i); - return child.as_widget_mut().on_event( + return child.as_widget_mut().update( state, - event.clone(), + &event, c_layout.with_virtual_offset(layout.virtual_offset()), cursor_virtual, renderer, @@ -340,9 +341,9 @@ where cursor_virtual = mouse::Cursor::Unavailable; } - child.as_widget_mut().on_event( + child.as_widget_mut().update( state, - event.clone(), + &event, c_layout.with_virtual_offset(layout.virtual_offset()), cursor_virtual, renderer, @@ -350,8 +351,7 @@ where shell, viewport, ) - }) - .fold(event::Status::Ignored, event::Status::merge) + }); } fn mouse_interaction( @@ -426,11 +426,19 @@ where fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { - overlay::from_children(&mut self.children, tree, layout, renderer, translation) + overlay::from_children( + &mut self.children, + tree, + layout, + renderer, + viewport, + translation, + ) } #[cfg(feature = "a11y")] diff --git a/src/applet/token/subscription.rs b/src/applet/token/subscription.rs index 706c0301..82763303 100644 --- a/src/applet/token/subscription.rs +++ b/src/applet/token/subscription.rs @@ -14,16 +14,15 @@ use super::wayland_handler::wayland_handler; pub fn activation_token_subscription( id: I, ) -> iced::Subscription { - Subscription::run_with_id( - id, + Subscription::run_with(id, |_| { stream::channel(50, move |mut output| async move { let mut state = State::Ready; loop { state = start_listening(state, &mut output).await; } - }), - ) + }) + }) } pub enum State { diff --git a/src/command.rs b/src/command.rs index 00684e55..1d6f635c 100644 --- a/src/command.rs +++ b/src/command.rs @@ -39,7 +39,7 @@ pub fn set_theme(theme: crate::Theme) -> iced::Task(id: window::Id) -> iced::Task> { - iced_runtime::window::change_mode(id, window::Mode::Windowed) + iced_runtime::window::set_mode(id, window::Mode::Windowed) } /// Toggles the windows' maximize state. diff --git a/src/dbus_activation.rs b/src/dbus_activation.rs index c8931dd4..99e2f9f0 100644 --- a/src/dbus_activation.rs +++ b/src/dbus_activation.rs @@ -16,75 +16,80 @@ use { #[cold] pub fn subscription() -> Subscription> { use iced_futures::futures::StreamExt; - iced_futures::Subscription::run_with_id( - TypeId::of::(), - iced::stream::channel(10, move |mut output| async move { - let mut single_instance: DbusActivation = DbusActivation::new(); - let mut rx = single_instance.rx(); - if let Ok(builder) = zbus::connection::Builder::session() { - let path: String = format!("/{}", App::APP_ID.replace('.', "/")); - if let Ok(conn) = builder.build().await { - // XXX Setup done this way seems to be more reliable. - // - // the docs for serve_at seem to imply it will replace the - // existing interface at the requested path, but it doesn't - // seem to work that way all the time. The docs for - // object_server().at() imply it won't replace the existing - // interface. - // - // request_name is used either way, with the builder or - // with the connection, but it must be done after the - // object server is setup. - if conn.object_server().at(path, single_instance).await != Ok(true) { - tracing::error!("Failed to serve dbus"); - std::process::exit(1); - } - if conn.request_name(App::APP_ID).await.is_err() { - tracing::error!("Failed to serve dbus"); - std::process::exit(1); - } + iced_futures::Subscription::run_with(TypeId::of::(), |_| { + iced::stream::channel( + 10, + move |mut output: Sender>| async move { + let mut single_instance: DbusActivation = DbusActivation::new(); + let mut rx = single_instance.rx(); + if let Ok(builder) = zbus::connection::Builder::session() { + let path: String = format!("/{}", App::APP_ID.replace('.', "/")); + if let Ok(conn) = builder.build().await { + // XXX Setup done this way seems to be more reliable. + // + // the docs for serve_at seem to imply it will replace the + // existing interface at the requested path, but it doesn't + // seem to work that way all the time. The docs for + // object_server().at() imply it won't replace the existing + // interface. + // + // request_name is used either way, with the builder or + // with the connection, but it must be done after the + // object server is setup. + if conn.object_server().at(path, single_instance).await != Ok(true) { + tracing::error!("Failed to serve dbus"); + std::process::exit(1); + } + if conn.request_name(App::APP_ID).await.is_err() { + tracing::error!("Failed to serve dbus"); + std::process::exit(1); + } - output - .send(crate::Action::Cosmic(crate::app::Action::DbusConnection( - conn.clone(), - ))) - .await; + output + .send(crate::Action::Cosmic(crate::app::Action::DbusConnection( + conn.clone(), + ))) + .await; - #[cfg(feature = "smol")] - let handle = { - std::thread::spawn(move || { - let conn_clone = _conn.clone(); + #[cfg(feature = "smol")] + let handle = { + std::thread::spawn(move || { + let conn_clone = _conn.clone(); - zbus::block_on(async move { - loop { - conn_clone.executor().tick().await; - } + zbus::block_on(async move { + loop { + conn_clone.executor().tick().await; + } + }) }) - }) - }; - while let Some(mut msg) = rx.next().await { - if let Some(token) = msg.activation_token.take() { - if let Err(err) = output - .send(crate::Action::Cosmic(crate::app::Action::Activate(token))) - .await + }; + while let Some(mut msg) = rx.next().await { + if let Some(token) = msg.activation_token.take() { + if let Err(err) = output + .send(crate::Action::Cosmic(crate::app::Action::Activate( + token, + ))) + .await + { + tracing::error!(?err, "Failed to send message"); + } + } + if let Err(err) = output.send(crate::Action::DbusActivation(msg)).await { tracing::error!(?err, "Failed to send message"); } } - if let Err(err) = output.send(crate::Action::DbusActivation(msg)).await { - tracing::error!(?err, "Failed to send message"); - } } + } else { + tracing::warn!("Failed to connect to dbus for single instance"); } - } else { - tracing::warn!("Failed to connect to dbus for single instance"); - } - loop { - iced::futures::pending!(); - } - }), - ) + loop { + iced::futures::pending!(); + } + }, + ) + }) } #[derive(Debug, Clone)] diff --git a/src/executor/multi.rs b/src/executor/multi.rs index 50aa111e..5536db54 100644 --- a/src/executor/multi.rs +++ b/src/executor/multi.rs @@ -26,4 +26,8 @@ impl iced::Executor for Executor { let _guard = self.0.enter(); f() } + + fn block_on(&self, future: impl Future) -> T { + self.0.block_on(future) + } } diff --git a/src/executor/single.rs b/src/executor/single.rs index aaa4f9f5..7c42ae84 100644 --- a/src/executor/single.rs +++ b/src/executor/single.rs @@ -30,4 +30,8 @@ impl iced::Executor for Executor { let _guard = self.0.enter(); f() } + + fn block_on(&self, future: impl Future) -> T { + self.0.block_on(future) + } } diff --git a/src/theme/portal.rs b/src/theme/portal.rs index f0c88c01..0154ff58 100644 --- a/src/theme/portal.rs +++ b/src/theme/portal.rs @@ -13,9 +13,8 @@ pub enum Desktop { #[cold] pub fn desktop_settings() -> iced_futures::Subscription { - iced_futures::Subscription::run_with_id( - std::any::TypeId::of::(), - stream::channel(10, |mut tx| { + iced_futures::Subscription::run(|| { + stream::channel(10, |mut tx: futures::channel::mpsc::Sender| { async move { let mut attempts = 0; loop { @@ -99,6 +98,6 @@ pub fn desktop_settings() -> iced_futures::Subscription { } } } - }), - ) + }) + }) } diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 937ee388..4633477d 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -7,6 +7,7 @@ use crate::theme::{CosmicComponent, TRANSPARENT_COMPONENT, Theme}; use cosmic_theme::composite::over; use iced::{ overlay::menu, + theme::Base, widget::{ button as iced_button, checkbox as iced_checkbox, combo_box, container as iced_container, pane_grid, pick_list, progress_bar, radio, rule, scrollable, @@ -15,7 +16,7 @@ use iced::{ }, }; use iced_core::{Background, Border, Color, Shadow, Vector}; -use iced_widget::{pane_grid::Highlight, text_editor, text_input}; +use iced_widget::{pane_grid::Highlight, scrollable::AutoScroll, text_editor, text_input}; use palette::WithAlpha; use std::rc::Rc; @@ -36,13 +37,13 @@ pub mod application { } } - pub fn appearance(theme: &Theme) -> Appearance { + pub fn style(theme: &Theme) -> iced::theme::Style { let cosmic = theme.cosmic(); - Appearance { - icon_color: cosmic.bg_color().into(), + iced::theme::Style { background_color: cosmic.bg_color().into(), text_color: cosmic.on_bg_color().into(), + icon_color: cosmic.bg_color().into(), } } } @@ -422,6 +423,7 @@ impl<'a> Container<'a> { ..Default::default() }, shadow: Shadow::default(), + snap: true, } } @@ -436,6 +438,7 @@ impl<'a> Container<'a> { ..Default::default() }, shadow: Shadow::default(), + snap: true, } } @@ -450,6 +453,7 @@ impl<'a> Container<'a> { ..Default::default() }, shadow: Shadow::default(), + snap: true, } } } @@ -493,6 +497,7 @@ impl iced_container::Catalog for Theme { ..Default::default() }, shadow: Shadow::default(), + snap: true, }, Container::List => { @@ -506,6 +511,7 @@ impl iced_container::Catalog for Theme { ..Default::default() }, shadow: Shadow::default(), + snap: true, } } @@ -552,6 +558,7 @@ impl iced_container::Catalog for Theme { .into(), ..Default::default() }, + snap: true, shadow: Shadow::default(), } } @@ -582,6 +589,7 @@ impl iced_container::Catalog for Theme { radius: cosmic.corner_radii.radius_s.into(), }, shadow: Shadow::default(), + snap: true, }, Container::Tooltip => iced_container::Style { @@ -593,6 +601,7 @@ impl iced_container::Catalog for Theme { ..Default::default() }, shadow: Shadow::default(), + snap: true, }, Container::Card => { @@ -610,6 +619,7 @@ impl iced_container::Catalog for Theme { ..Default::default() }, shadow: Shadow::default(), + snap: true, }, cosmic_theme::Layer::Primary => iced_container::Style { icon_color: Some(Color::from(cosmic.primary.component.on)), @@ -622,6 +632,7 @@ impl iced_container::Catalog for Theme { ..Default::default() }, shadow: Shadow::default(), + snap: true, }, cosmic_theme::Layer::Secondary => iced_container::Style { icon_color: Some(Color::from(cosmic.secondary.component.on)), @@ -634,6 +645,7 @@ impl iced_container::Catalog for Theme { ..Default::default() }, shadow: Shadow::default(), + snap: true, }, } } @@ -652,6 +664,7 @@ impl iced_container::Catalog for Theme { offset: Vector::new(0.0, 4.0), blur_radius: 16.0, }, + snap: true, }, } } @@ -791,6 +804,7 @@ impl menu::Catalog for Theme { }, selected_text_color: cosmic.accent_text_color().into(), selected_background: Background::Color(cosmic.background.component.hover.into()), + shadow: Default::default(), } } } @@ -830,7 +844,7 @@ impl pick_list::Catalog for Theme { background: Background::Color(cosmic.background.base.into()), ..appearance }, - pick_list::Status::Opened => appearance, + pick_list::Status::Opened { is_hovered: _ } => appearance, } } } @@ -920,6 +934,8 @@ impl toggler::Catalog for Theme { background_border_color: Color::TRANSPARENT, foreground_border_width: 0.0, foreground_border_color: Color::TRANSPARENT, + text_color: None, + padding_ratio: 0.0, }; match status { toggler::Status::Active { is_toggled } => active, @@ -942,9 +958,9 @@ impl toggler::Catalog for Theme { ..active } } - toggler::Status::Disabled => { - active.background.a /= 2.; - active.foreground.a /= 2.; + toggler::Status::Disabled { is_toggled } => { + active.background = active.background.scale_alpha(0.5); + active.foreground = active.foreground.scale_alpha(0.5); active } } @@ -1086,21 +1102,21 @@ impl rule::Catalog for Theme { match class { Rule::Default => rule::Style { color: self.current_container().divider.into(), - width: 1, radius: 0.0.into(), fill_mode: rule::FillMode::Full, + snap: true, }, Rule::LightDivider => rule::Style { color: self.current_container().divider.into(), - width: 1, radius: 0.0.into(), fill_mode: rule::FillMode::Padded(8), + snap: true, }, Rule::HeavyDivider => rule::Style { color: self.current_container().divider.into(), - width: 4, radius: 2.0.into(), fill_mode: rule::FillMode::Full, + snap: true, }, Rule::Custom(f) => f(self), } @@ -1126,7 +1142,10 @@ impl scrollable::Catalog for Theme { fn style(&self, class: &Self::Class<'_>, status: scrollable::Status) -> scrollable::Style { match status { - scrollable::Status::Active => { + scrollable::Status::Active { + is_horizontal_scrollbar_disabled, + is_vertical_scrollbar_disabled, + } => { let cosmic = self.cosmic(); let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7); let neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7); @@ -1139,7 +1158,7 @@ impl scrollable::Catalog for Theme { }, background: None, scroller: scrollable::Scroller { - color: if cosmic.is_dark { + background: if cosmic.is_dark { neutral_6.into() } else { neutral_5.into() @@ -1157,7 +1176,7 @@ impl scrollable::Catalog for Theme { }, background: None, scroller: scrollable::Scroller { - color: if cosmic.is_dark { + background: if cosmic.is_dark { neutral_6.into() } else { neutral_5.into() @@ -1169,6 +1188,13 @@ impl scrollable::Catalog for Theme { }, }, gap: None, + // TODO: what is auto scroll? + auto_scroll: AutoScroll { + background: Color::TRANSPARENT.into(), + border: Border::default(), + shadow: Shadow::default(), + icon: Color::TRANSPARENT.into(), + }, }; let small_widget_container = self.current_container().small_widget.with_alpha(0.7); @@ -1200,7 +1226,7 @@ impl scrollable::Catalog for Theme { }, background: None, scroller: scrollable::Scroller { - color: if cosmic.is_dark { + background: if cosmic.is_dark { neutral_6.into() } else { neutral_5.into() @@ -1218,7 +1244,7 @@ impl scrollable::Catalog for Theme { }, background: None, scroller: scrollable::Scroller { - color: if cosmic.is_dark { + background: if cosmic.is_dark { neutral_6.into() } else { neutral_5.into() @@ -1230,6 +1256,13 @@ impl scrollable::Catalog for Theme { }, }, gap: None, + // TODO: what is auto scroll? + auto_scroll: AutoScroll { + background: Color::TRANSPARENT.into(), + border: Border::default(), + shadow: Shadow::default(), + icon: Color::TRANSPARENT.into(), + }, }; if matches!(class, Scrollable::Permanent) { @@ -1400,7 +1433,7 @@ impl text_input::Catalog for Theme { }, } } - text_input::Status::Focused => { + text_input::Status::Focused { is_hovered } => { let bg = self.current_container().small_widget.with_alpha(0.25); match class { @@ -1477,7 +1510,8 @@ impl iced_widget::text_editor::Catalog for Theme { let selection = cosmic.accent.base.into(); let value = cosmic.palette.neutral_9.into(); let placeholder = cosmic.palette.neutral_9.with_alpha(0.7).into(); - let icon = cosmic.background.on.into(); + let icon: Color = cosmic.background.on.into(); + // TODO do we need to add icon color back? match status { iced_widget::text_editor::Status::Active @@ -1489,23 +1523,23 @@ impl iced_widget::text_editor::Catalog for Theme { width: f32::from(cosmic.space_xxxs()), color: iced::Color::from(cosmic.bg_divider()), }, - icon, - placeholder, - value, - selection, - }, - iced_widget::text_editor::Status::Focused => iced_widget::text_editor::Style { - background: iced::Color::from(cosmic.bg_color()).into(), - border: Border { - radius: cosmic.corner_radii.radius_0.into(), - width: f32::from(cosmic.space_xxxs()), - color: iced::Color::from(cosmic.accent.base), - }, - icon, placeholder, value, selection, }, + iced_widget::text_editor::Status::Focused { is_hovered } => { + iced_widget::text_editor::Style { + background: iced::Color::from(cosmic.bg_color()).into(), + border: Border { + radius: cosmic.corner_radii.radius_0.into(), + width: f32::from(cosmic.space_xxxs()), + color: iced::Color::from(cosmic.accent.base), + }, + placeholder, + value, + selection, + } + } } } } @@ -1522,6 +1556,21 @@ impl iced_widget::markdown::Catalog for Theme { } } +impl iced_widget::table::Catalog for Theme { + type Class<'a> = iced_widget::table::StyleFn<'a, Self>; + + fn default<'a>() -> Self::Class<'a> { + Box::new(|theme| iced_widget::table::Style { + separator_x: theme.current_container().divider.into(), + separator_y: theme.current_container().divider.into(), + }) + } + + fn style(&self, class: &Self::Class<'_>) -> iced_widget::table::Style { + class(self) + } +} + #[cfg(feature = "qr_code")] impl iced_widget::qr_code::Catalog for Theme { type Class<'a> = iced_widget::qr_code::StyleFn<'a, Self>; @@ -1539,3 +1588,50 @@ impl iced_widget::qr_code::Catalog for Theme { } impl combo_box::Catalog for Theme {} + +impl Base for Theme { + fn default(preference: iced::theme::Mode) -> Self { + match preference { + iced::theme::Mode::Light => Theme::light(), + iced::theme::Mode::Dark | iced::theme::Mode::None => Theme::dark(), + } + } + + fn mode(&self) -> iced::theme::Mode { + if self.theme_type.is_dark() { + iced::theme::Mode::Dark + } else { + iced::theme::Mode::Light + } + } + + fn base(&self) -> iced::theme::Style { + iced::theme::Style { + background_color: self.cosmic().bg_color().into(), + text_color: self.cosmic().on_bg_color().into(), + icon_color: self.cosmic().on_bg_color().into(), + } + } + + fn palette(&self) -> Option { + Some(iced::theme::Palette { + primary: self.cosmic().accent.base.into(), + success: self.cosmic().success.base.into(), + warning: self.cosmic().warning.base.into(), + danger: self.cosmic().destructive.base.into(), + background: iced::Color::from(self.cosmic().bg_color()), + text: iced::Color::from(self.cosmic().on_bg_color()), + }) + } + + fn name(&self) -> &str { + match &self.theme_type { + crate::theme::ThemeType::Dark => "Cosmic Dark Theme", + crate::theme::ThemeType::Light => "Cosmic Light Theme", + crate::theme::ThemeType::HighContrastDark => "Cosmic High Contrast Dark Theme", + crate::theme::ThemeType::HighContrastLight => "Cosmic High Contrast Light Theme", + crate::theme::ThemeType::Custom(theme) => "Custom Cosmic Theme", + crate::theme::ThemeType::System { prefer_dark, theme } => &theme.name, + } + } +} diff --git a/src/widget/about.rs b/src/widget/about.rs index 384aee4a..ba88e03a 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -1,7 +1,7 @@ use crate::{ Apply, Element, fl, iced::{Alignment, Length}, - widget::{self, horizontal_space}, + widget::{self, space}, }; #[derive(Debug, Default, Clone, derive_setters::Setters)] @@ -99,7 +99,7 @@ pub fn about<'a, Message: Clone + 'static>( let section_button = |name: &'a str, url: &'a str| -> Element<'a, Message> { widget::row() .push(widget::text(name)) - .push(horizontal_space()) + .push(space::horizontal()) .push_maybe( (!url.is_empty()).then_some(crate::widget::icon::from_name("link-symbolic").icon()), ) diff --git a/src/widget/aspect_ratio.rs b/src/widget/aspect_ratio.rs index e66c14d0..577bea95 100644 --- a/src/widget/aspect_ratio.rs +++ b/src/widget/aspect_ratio.rs @@ -2,7 +2,7 @@ use iced::Size; use iced::widget::Container; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; @@ -172,7 +172,7 @@ where } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, @@ -186,7 +186,7 @@ where } fn operate( - &self, + &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -195,18 +195,18 @@ where self.container.operate(tree, layout, renderer, operation); } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor_position: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - self.container.on_event( + ) { + self.container.update( tree, event, layout, @@ -254,11 +254,13 @@ where fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { - self.container.overlay(tree, layout, renderer, translation) + self.container + .overlay(tree, layout, renderer, viewport, translation) } #[cfg(feature = "a11y")] diff --git a/src/widget/autosize.rs b/src/widget/autosize.rs index 172d505f..6a1e6060 100644 --- a/src/widget/autosize.rs +++ b/src/widget/autosize.rs @@ -5,7 +5,7 @@ use iced_core::layout; use iced_core::mouse; use iced_core::overlay; use iced_core::renderer; -use iced_core::widget::{Id, Tree}; +use iced_core::widget::{Id, Operation, Tree}; use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; pub use iced_widget::container::{Catalog, Style}; @@ -115,7 +115,7 @@ where } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, @@ -131,22 +131,23 @@ where } let node = self .content - .as_widget() + .as_widget_mut() .layout(&mut tree.children[0], renderer, &my_limits); let size = node.size(); layout::Node::with_children(size, vec![node]) } fn operate( - &self, + &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn iced_core::widget::Operation<()>, + operation: &mut dyn Operation, ) { - operation.container(Some(&self.id), layout.bounds(), &mut |operation| { - self.content.as_widget().operate( - &mut tree.children[0], + operation.container(Some(&self.id), layout.bounds()); + operation.traverse(&mut |operation| { + self.content.as_widget_mut().operate( + tree, layout .children() .next() @@ -158,17 +159,17 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor_position: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { #[cfg(feature = "wayland")] if matches!( event, @@ -179,9 +180,9 @@ where let bounds = layout.bounds().size(); clipboard.request_logical_window_size(bounds.width.max(1.), bounds.height.max(1.)); } - self.content.as_widget_mut().on_event( + self.content.as_widget_mut().update( &mut tree.children[0], - event.clone(), + event, layout .children() .next() @@ -238,8 +239,9 @@ where fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { self.content.as_widget_mut().overlay( @@ -250,6 +252,7 @@ where .unwrap() .with_virtual_offset(layout.virtual_offset()), renderer, + viewport, translation, ) } diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 87233330..54e29786 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -318,7 +318,7 @@ impl<'a, Message: 'a + Clone> Widget } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &crate::Renderer, limits: &layout::Limits, @@ -331,21 +331,22 @@ impl<'a, Message: 'a + Clone> Widget self.padding, |renderer, limits| { self.content - .as_widget() + .as_widget_mut() .layout(&mut tree.children[0], renderer, limits) }, ) } fn operate( - &self, + &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &crate::Renderer, operation: &mut dyn Operation<()>, ) { - operation.container(None, layout.bounds(), &mut |operation| { - self.content.as_widget().operate( + operation.container(None, layout.bounds()); + operation.traverse(&mut |operation| { + self.content.as_widget_mut().operate( &mut tree.children[0], layout .children() @@ -357,20 +358,19 @@ impl<'a, Message: 'a + Clone> Widget ); }); let state = tree.state.downcast_mut::(); - operation.focusable(state, Some(&self.id)); } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { if let Variant::Image { on_remove: Some(on_remove), .. @@ -383,7 +383,8 @@ impl<'a, Message: 'a + Clone> Widget if let Some(position) = cursor.position() { if removal_bounds(layout.bounds(), 4.0).contains(position) { shell.publish(on_remove.clone()); - return event::Status::Captured; + shell.capture_event(); + return; } } } @@ -391,10 +392,9 @@ impl<'a, Message: 'a + Clone> Widget _ => (), } } - - if self.content.as_widget_mut().on_event( + self.content.as_widget_mut().update( &mut tree.children[0], - event.clone(), + event, layout .children() .next() @@ -405,9 +405,9 @@ impl<'a, Message: 'a + Clone> Widget clipboard, shell, viewport, - ) == event::Status::Captured - { - return event::Status::Captured; + ); + if shell.is_event_captured() { + return; } update( @@ -541,6 +541,7 @@ impl<'a, Message: 'a + Clone> Widget ..Default::default() }, shadow: Shadow::default(), + snap: true, }, selection_background, ); @@ -554,7 +555,7 @@ impl<'a, Message: 'a + Clone> Widget y: bounds.y + (bounds.height - 18.0 - styling.border_width), }; if bounds.intersects(viewport) { - iced_core::svg::Renderer::draw_svg(renderer, svg_handle, bounds); + iced_core::svg::Renderer::draw_svg(renderer, svg_handle, bounds, bounds); } } @@ -570,6 +571,7 @@ impl<'a, Message: 'a + Clone> Widget radius: c_rad.radius_m.into(), ..Default::default() }, + snap: true, }, selection_background, ); @@ -583,6 +585,12 @@ impl<'a, Message: 'a + Clone> Widget x: bounds.x + 4.0, y: bounds.y + 4.0, }, + Rectangle { + width: 16.0, + height: 16.0, + x: bounds.x + 4.0, + y: bounds.y + 4.0, + }, ); } } @@ -609,8 +617,9 @@ impl<'a, Message: 'a + Clone> Widget fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &crate::Renderer, + viewport: &Rectangle, mut translation: Vector, ) -> Option> { let position = layout.bounds().position(); @@ -624,6 +633,7 @@ impl<'a, Message: 'a + Clone> Widget .unwrap() .with_virtual_offset(layout.virtual_offset()), renderer, + viewport, translation, ) } @@ -638,7 +648,7 @@ impl<'a, Message: 'a + Clone> Widget ) -> iced_accessibility::A11yTree { use iced_accessibility::{ A11yNode, A11yTree, - accesskit::{Action, DefaultActionVerb, NodeBuilder, NodeId, Rect, Role}, + accesskit::{Action, Node, NodeId, Rect, Role}, }; // TODO why is state None sometimes? if matches!(state.state, iced_core::widget::tree::State::None) { @@ -658,12 +668,12 @@ impl<'a, Message: 'a + Clone> Widget let bounds = Rect::new(x as f64, y as f64, (x + width) as f64, (y + height) as f64); let is_hovered = state.state.downcast_ref::().is_hovered; - let mut node = NodeBuilder::new(Role::Button); + let mut node = Node::new(Role::Button); node.add_action(Action::Focus); - node.add_action(Action::Default); + node.add_action(Action::Click); node.set_bounds(bounds); if let Some(name) = self.name.as_ref() { - node.set_name(name.clone()); + node.set_label(name.clone()); } match self.description.as_ref() { Some(iced_accessibility::Description::Id(id)) => { @@ -682,10 +692,10 @@ impl<'a, Message: 'a + Clone> Widget if self.on_press.is_none() { node.set_disabled(); } - if is_hovered { - node.set_hovered(); - } - node.set_default_action_verb(DefaultActionVerb::Click); + // TODO hover + // if is_hovered { + // node.set_hovered(); + // } if let Some(child_tree) = child_tree.map(|child_tree| { self.content.as_widget().a11y_nodes( @@ -761,14 +771,14 @@ impl State { #[allow(clippy::needless_pass_by_value, clippy::too_many_arguments)] pub fn update<'a, Message: Clone>( _id: Id, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, shell: &mut Shell<'_, Message>, on_press: Option<&dyn Fn(Vector, Rectangle) -> Message>, on_press_down: Option<&dyn Fn(Vector, Rectangle) -> Message>, state: impl FnOnce() -> &'a mut State, -) -> event::Status { +) { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { @@ -787,7 +797,8 @@ pub fn update<'a, Message: Clone>( shell.publish(msg); } - return event::Status::Captured; + shell.capture_event(); + return; } } } @@ -806,7 +817,8 @@ pub fn update<'a, Message: Clone>( shell.publish(msg); } - return event::Status::Captured; + shell.capture_event(); + return; } } else if on_press_down.is_some() { let state = state(); @@ -816,7 +828,7 @@ pub fn update<'a, Message: Clone>( #[cfg(feature = "a11y")] Event::A11y(event_id, iced_accessibility::accesskit::ActionRequest { action, .. }) => { let state = state(); - if let Some(on_press) = matches!(action, iced_accessibility::accesskit::Action::Default) + if let Some(on_press) = matches!(action, iced_accessibility::accesskit::Action::Click) .then_some(on_press) .flatten() { @@ -825,17 +837,19 @@ pub fn update<'a, Message: Clone>( shell.publish(msg); } - return event::Status::Captured; + shell.capture_event(); + return; } Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { if let Some(on_press) = on_press { let state = state(); - if state.is_focused && key == keyboard::Key::Named(keyboard::key::Named::Enter) { + if state.is_focused && *key == keyboard::Key::Named(keyboard::key::Named::Enter) { state.is_pressed = true; let msg = (on_press)(layout.virtual_offset(), layout.bounds()); shell.publish(msg); - return event::Status::Captured; + shell.capture_event(); + return; } } } @@ -846,8 +860,6 @@ pub fn update<'a, Message: Clone>( } _ => {} } - - event::Status::Ignored } #[allow(clippy::too_many_arguments)] @@ -879,6 +891,7 @@ pub fn draw( radius: styling.border_radius, }, shadow: Shadow::default(), + snap: true, }, Color::TRANSPARENT, ); @@ -900,6 +913,7 @@ pub fn draw( ..Default::default() }, shadow: Shadow::default(), + snap: true, }, Background::Color([0.0, 0.0, 0.0, 0.5].into()), ); @@ -915,6 +929,7 @@ pub fn draw( ..Default::default() }, shadow: Shadow::default(), + snap: true, }, background, ); @@ -930,6 +945,7 @@ pub fn draw( ..Default::default() }, shadow: Shadow::default(), + snap: true, }, overlay, ); @@ -953,6 +969,7 @@ pub fn draw( radius: styling.border_radius, }, shadow: Shadow::default(), + snap: true, }, Color::TRANSPARENT, ); diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index ea10fddb..7c09d39c 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -213,7 +213,9 @@ where let content_list = column::with_children([ row::with_children([ column().push(date).push(day).into(), - crate::widget::Space::with_width(Length::Fill).into(), + crate::widget::space::horizontal() + .width(Length::Fill) + .into(), month_controls.into(), ]) .align_y(Vertical::Center) diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 40a4a940..d484bb62 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -26,7 +26,10 @@ use iced_core::{ }; use iced_widget::slider::HandleShape; -use iced_widget::{Row, canvas, column, horizontal_space, row, scrollable, vertical_space}; +use iced_widget::{ + Row, canvas, column, row, scrollable, + space::{horizontal, vertical}, +}; use palette::{FromColor, RgbHue}; use super::divider::horizontal; @@ -334,7 +337,7 @@ where .width(self.width), // canvas with gradient for the current color // still needs the canvas and the handle to be drawn on it - container(vertical_space().height(self.height)) + container(vertical().height(self.height)) .width(self.width) .height(self.height), slider( @@ -548,13 +551,13 @@ where } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &crate::Renderer, limits: &layout::Limits, ) -> layout::Node { self.inner - .as_widget() + .as_widget_mut() .layout(&mut tree.children[0], renderer, limits) } @@ -657,6 +660,7 @@ where radius: (1.0 + handle_radius).into(), }, shadow: Shadow::default(), + snap: true, }, Color::TRANSPARENT, ); @@ -674,6 +678,7 @@ where radius: handle_radius.into(), }, shadow: Shadow::default(), + snap: true, }, Color::TRANSPARENT, ); @@ -684,26 +689,31 @@ where fn overlay<'b>( &'b mut self, state: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &crate::Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { - self.inner - .as_widget_mut() - .overlay(&mut state.children[0], layout, renderer, translation) + self.inner.as_widget_mut().overlay( + &mut state.children[0], + layout, + renderer, + viewport, + translation, + ) } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { // if the pointer is performing a drag, intercept pointer motion and button events // else check if event is handled by child elements // if the event is not handled by a child element, check if it is over the canvas when pressing a button @@ -732,24 +742,26 @@ where shell.publish((self.on_update)(ColorPickerUpdate::ActionFinished)); state.dragging = false; } - _ => return event::Status::Ignored, + _ => return, }; - return event::Status::Captured; + shell.capture_event(); + return; } let column_tree = &mut tree.children[0]; - if self.inner.as_widget_mut().on_event( + self.inner.as_widget_mut().update( column_tree, - event.clone(), + &event, column_layout, cursor, renderer, clipboard, shell, viewport, - ) == event::Status::Captured - { - return event::Status::Captured; + ); + if shell.is_event_captured() { + shell.capture_event(); + return; } match event { @@ -764,12 +776,10 @@ where state.dragging = true; let hsv: palette::Hsv = palette::Hsv::new(self.active_color.hue, s, v); shell.publish((self.on_update)(ColorPickerUpdate::ActiveColor(hsv))); - event::Status::Captured - } else { - event::Status::Ignored + shell.capture_event(); } } - _ => event::Status::Ignored, + _ => {} } } @@ -812,12 +822,12 @@ pub fn color_button<'a, Message: Clone + 'static>( let spacing = THEME.lock().unwrap().cosmic().spacing; button::custom(if color.is_some() { - Element::from(vertical_space().height(Length::Fixed(f32::from(spacing.space_s)))) + Element::from(vertical().height(Length::Fixed(f32::from(spacing.space_s)))) } else { Element::from(column![ - vertical_space().height(Length::FillPortion(6)), + vertical().height(Length::FillPortion(6)), row![ - horizontal_space().width(Length::FillPortion(6)), + horizontal().width(Length::FillPortion(6)), Icon::from( icon::from_name("list-add-symbolic") .prefer_svg(true) @@ -827,11 +837,11 @@ pub fn color_button<'a, Message: Clone + 'static>( .width(icon_portion) .height(Length::Fill) .content_fit(iced_core::ContentFit::Contain), - horizontal_space().width(Length::FillPortion(6)), + horizontal().width(Length::FillPortion(6)), ] .height(icon_portion) .width(Length::Fill), - vertical_space().height(Length::FillPortion(6)), + vertical().height(Length::FillPortion(6)), ]) }) .width(Length::Fixed(f32::from(spacing.space_s))) diff --git a/src/widget/context_drawer/overlay.rs b/src/widget/context_drawer/overlay.rs index 4f72e113..eef9183b 100644 --- a/src/widget/context_drawer/overlay.rs +++ b/src/widget/context_drawer/overlay.rs @@ -7,7 +7,7 @@ use iced::advanced::layout::{self, Layout}; use iced::advanced::widget::{self, Operation}; use iced::advanced::{Clipboard, Shell}; use iced::advanced::{overlay, renderer}; -use iced::{Event, Point, Rectangle, Size, event, mouse}; +use iced::{Event, Point, Size, mouse}; use iced_core::Renderer; pub(super) struct Overlay<'a, 'b, Message> { @@ -29,7 +29,7 @@ where let node = self .content - .as_widget() + .as_widget_mut() .layout(self.tree, renderer, &limits); let node_size = node.size(); @@ -47,16 +47,16 @@ where }) } - fn on_event( + fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.content.as_widget_mut().on_event( + ) { + self.content.as_widget_mut().update( self.tree, event, layout, @@ -104,9 +104,10 @@ where &self, layout: Layout<'_>, cursor: mouse::Cursor, - viewport: &Rectangle, renderer: &crate::Renderer, ) -> mouse::Interaction { + // TODO how to handle viewport here? + let viewport = &layout.bounds(); self.content .as_widget() .mouse_interaction(self.tree, layout, cursor, viewport, renderer) @@ -114,11 +115,17 @@ where fn overlay<'c>( &'c mut self, - layout: Layout<'_>, + layout: Layout<'c>, renderer: &crate::Renderer, ) -> Option> { - self.content - .as_widget_mut() - .overlay(self.tree, layout, renderer, iced::Vector::default()) + let viewport = &layout.bounds(); + + self.content.as_widget_mut().overlay( + self.tree, + layout, + renderer, + viewport, + iced::Vector::default(), + ) } } diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index 5366832f..e7ca5dab 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -7,7 +7,7 @@ use crate::{Apply, Element, Renderer, Theme, fl}; use std::borrow::Cow; use iced_core::Alignment; -use iced_core::event::{self, Event}; +use iced_core::event::Event; use iced_core::widget::{Operation, Tree}; use iced_core::{ Clipboard, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, @@ -65,7 +65,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { } else { let title = title .map(|title| text::title4(title).width(Length::Fill).apply(Element::from)) - .unwrap_or_else(|| widget::horizontal_space().apply(Element::from)); + .unwrap_or_else(|| widget::space::horizontal().apply(Element::from)); (title, None) }; @@ -196,40 +196,40 @@ impl Widget for ContextDrawer<' } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { self.content - .as_widget() + .as_widget_mut() .layout(&mut tree.children[0], renderer, limits) } fn operate( - &self, + &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation<()>, ) { self.content - .as_widget() + .as_widget_mut() .operate(&mut tree.children[0], layout, renderer, operation); } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - self.content.as_widget_mut().on_event( + ) { + self.content.as_widget_mut().update( &mut tree.children[0], event, layout, @@ -282,8 +282,9 @@ impl Widget for ContextDrawer<' fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, _renderer: &Renderer, + _viewport: &Rectangle, translation: Vector, ) -> Option> { let bounds = layout.bounds(); diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index d9dc529a..008660a7 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -270,13 +270,13 @@ impl Widget } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &crate::Renderer, limits: &iced_core::layout::Limits, ) -> iced_core::layout::Node { self.content - .as_widget() + .as_widget_mut() .layout(&mut tree.children[0], renderer, limits) } @@ -302,29 +302,29 @@ impl Widget } fn operate( - &self, + &mut self, tree: &mut Tree, layout: iced_core::Layout<'_>, renderer: &crate::Renderer, operation: &mut dyn iced_core::widget::Operation<()>, ) { self.content - .as_widget() + .as_widget_mut() .operate(&mut tree.children[0], layout, renderer, operation); } #[allow(clippy::too_many_lines)] - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: iced::Event, + event: &iced::Event, layout: iced_core::Layout<'_>, cursor: iced_core::mouse::Cursor, renderer: &crate::Renderer, clipboard: &mut dyn iced_core::Clipboard, shell: &mut iced_core::Shell<'_, Message>, viewport: &iced::Rectangle, - ) -> iced_core::event::Status { + ) { let state = tree.state.downcast_mut::(); let bounds = layout.bounds(); @@ -384,7 +384,7 @@ impl Widget match event { Event::Touch(touch::Event::FingerPressed { id, .. }) => { - state.fingers_pressed.insert(id); + state.fingers_pressed.insert(*id); } Event::Touch(touch::Event::FingerLifted { id, .. }) => { @@ -410,7 +410,8 @@ impl Widget self.create_popup(layout, cursor, renderer, shell, viewport, state); } - return event::Status::Captured; + shell.capture_event(); + return; } else if !was_open && right_button_released(&event) || (touch_lifted(&event)) || left_button_released(&event) @@ -440,7 +441,7 @@ impl Widget }); } } - self.content.as_widget_mut().on_event( + self.content.as_widget_mut().update( &mut tree.children[0], event, layout, @@ -457,6 +458,7 @@ impl Widget tree: &'b mut Tree, layout: iced_core::Layout<'_>, _renderer: &crate::Renderer, + viewport: &iced::Rectangle, translation: Vector, ) -> Option> { #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] diff --git a/src/widget/dialog.rs b/src/widget/dialog.rs index ba5b55e2..7d084626 100644 --- a/src/widget/dialog.rs +++ b/src/widget/dialog.rs @@ -123,7 +123,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes if let Some(body) = dialog.body { if should_space { content_col = content_col - .push(widget::vertical_space().height(Length::Fixed(space_xxs.into()))); + .push(widget::space::vertical().height(Length::Fixed(space_xxs.into()))); } content_col = content_col.push( widget::container(widget::scrollable(widget::text::body(body))).max_height(300.), @@ -133,7 +133,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes for control in dialog.controls { if should_space { content_col = content_col - .push(widget::vertical_space().height(Length::Fixed(space_s.into()))); + .push(widget::space::vertical().height(Length::Fixed(space_s.into()))); } content_col = content_col.push(control); should_space = true; @@ -149,7 +149,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes if let Some(button) = dialog.tertiary_action { button_row = button_row.push(button); } - button_row = button_row.push(widget::horizontal_space()); + button_row = button_row.push(widget::space::horizontal()); if let Some(button) = dialog.secondary_action { button_row = button_row.push(button); } diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index 7225e917..9faa2605 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -303,43 +303,43 @@ impl Widget } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &crate::Renderer, limits: &layout::Limits, ) -> layout::Node { self.container - .as_widget() + .as_widget_mut() .layout(&mut tree.children[0], renderer, limits) } fn operate( - &self, + &mut self, tree: &mut Tree, layout: layout::Layout<'_>, renderer: &crate::Renderer, operation: &mut dyn iced_core::widget::Operation<()>, ) { self.container - .as_widget() + .as_widget_mut() .operate(&mut tree.children[0], layout, renderer, operation); } #[allow(clippy::too_many_lines)] - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: layout::Layout<'_>, cursor: mouse::Cursor, renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - let s = self.container.as_widget_mut().on_event( + ) { + let s = self.container.as_widget_mut().update( &mut tree.children[0], - event.clone(), + event, layout, cursor, renderer, @@ -347,8 +347,8 @@ impl Widget shell, viewport, ); - if matches!(s, event::Status::Captured) { - return event::Status::Captured; + if shell.is_event_captured() { + return; } let state = tree.state.downcast_mut::>(); @@ -367,23 +367,23 @@ impl Widget OfferEvent::Enter { x, y, mime_types, .. }, - )) if id == Some(my_id) => { + )) if *id == Some(my_id) => { if !self.mime_matches(&mime_types) { log::trace!( target: DND_DEST_LOG_TARGET, "offer enter id={my_id:?} ignored (mimes={mime_types:?} not in {:?})", self.mime_types ); - return event::Status::Ignored; + return; } log::trace!( target: DND_DEST_LOG_TARGET, "offer enter id={my_id:?} coords=({x},{y}) mimes={mime_types:?}" ); if let Some(msg) = state.on_enter( - x, - y, - mime_types, + *x, + *y, + mime_types.clone(), self.on_enter.as_ref().map(std::convert::AsRef::as_ref), (), ) { @@ -391,13 +391,13 @@ impl Widget } if self.forward_drag_as_cursor { #[allow(clippy::cast_possible_truncation)] - let drag_cursor = mouse::Cursor::Available((x as f32, y as f32).into()); + let drag_cursor = mouse::Cursor::Available((*x as f32, *y as f32).into()); let event = Event::Mouse(mouse::Event::CursorMoved { position: drag_cursor.position().unwrap(), }); - self.container.as_widget_mut().on_event( + self.container.as_widget_mut().update( &mut tree.children[0], - event, + &event, layout, drag_cursor, renderer, @@ -406,7 +406,8 @@ impl Widget viewport, ); } - return event::Status::Captured; + shell.capture_event(); + return; } Event::Dnd(DndEvent::Offer(_, OfferEvent::Leave)) => { log::trace!( @@ -423,9 +424,9 @@ impl Widget if self.forward_drag_as_cursor { let drag_cursor = mouse::Cursor::Unavailable; let event = Event::Mouse(mouse::Event::CursorLeft); - self.container.as_widget_mut().on_event( + self.container.as_widget_mut().update( &mut tree.children[0], - event, + &event, layout, drag_cursor, renderer, @@ -434,16 +435,16 @@ impl Widget viewport, ); } - return event::Status::Ignored; + return; } - Event::Dnd(DndEvent::Offer(id, OfferEvent::Motion { x, y })) if id == Some(my_id) => { + Event::Dnd(DndEvent::Offer(id, OfferEvent::Motion { x, y })) if *id == Some(my_id) => { log::trace!( target: DND_DEST_LOG_TARGET, "offer motion id={my_id:?} coords=({x},{y})" ); if let Some(msg) = state.on_motion( - x, - y, + *x, + *y, self.on_motion.as_ref().map(std::convert::AsRef::as_ref), self.on_enter.as_ref().map(std::convert::AsRef::as_ref), (), @@ -453,13 +454,13 @@ impl Widget if self.forward_drag_as_cursor { #[allow(clippy::cast_possible_truncation)] - let drag_cursor = mouse::Cursor::Available((x as f32, y as f32).into()); + let drag_cursor = mouse::Cursor::Available((*x as f32, *y as f32).into()); let event = Event::Mouse(mouse::Event::CursorMoved { position: drag_cursor.position().unwrap(), }); - self.container.as_widget_mut().on_event( + self.container.as_widget_mut().update( &mut tree.children[0], - event, + &event, layout, drag_cursor, renderer, @@ -468,7 +469,8 @@ impl Widget viewport, ); } - return event::Status::Captured; + shell.capture_event(); + return; } Event::Dnd(DndEvent::Offer(_, OfferEvent::LeaveDestination)) => { log::trace!( @@ -481,9 +483,9 @@ impl Widget { shell.publish(msg); } - return event::Status::Ignored; + return; } - Event::Dnd(DndEvent::Offer(id, OfferEvent::Drop)) if id == Some(my_id) => { + Event::Dnd(DndEvent::Offer(id, OfferEvent::Drop)) if *id == Some(my_id) => { log::trace!( target: DND_DEST_LOG_TARGET, "offer drop id={my_id:?}" @@ -493,27 +495,29 @@ impl Widget { shell.publish(msg); } - return event::Status::Captured; + shell.capture_event(); + return; } Event::Dnd(DndEvent::Offer(id, OfferEvent::SelectedAction(action))) - if id == Some(my_id) => + if *id == Some(my_id) => { log::trace!( target: DND_DEST_LOG_TARGET, "offer selected-action id={my_id:?} action={action:?}" ); if let Some(msg) = state.on_action_selected( - action, + *action, self.on_action_selected .as_ref() .map(std::convert::AsRef::as_ref), ) { shell.publish(msg); } - return event::Status::Captured; + shell.capture_event(); + return; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Data { data, mime_type })) - if id == Some(my_id) => + if *id == Some(my_id) => { log::trace!( target: DND_DEST_LOG_TARGET, @@ -531,21 +535,28 @@ impl Widget } if let (Some(msg), ret) = state.on_data_received( - mime_type, - data, + mime_type.clone(), + data.clone(), self.on_data_received .as_ref() .map(std::convert::AsRef::as_ref), self.on_finish.as_ref().map(std::convert::AsRef::as_ref), ) { shell.publish(msg); - return ret; + if ret == event::Status::Captured { + log::trace!( + target: DND_DEST_LOG_TARGET, + "offer data id={my_id:?} captured" + ); + shell.capture_event(); + } + return; } - return event::Status::Captured; + shell.capture_event(); + return; } _ => {} } - event::Status::Ignored } fn mouse_interaction( @@ -589,13 +600,18 @@ impl Widget fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: layout::Layout<'_>, + layout: layout::Layout<'b>, renderer: &crate::Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { - self.container - .as_widget_mut() - .overlay(&mut tree.children[0], layout, renderer, translation) + self.container.as_widget_mut().overlay( + &mut tree.children[0], + layout, + renderer, + viewport, + translation, + ) } fn drag_destinations( diff --git a/src/widget/dnd_source.rs b/src/widget/dnd_source.rs index f21f9670..c8627482 100644 --- a/src/widget/dnd_source.rs +++ b/src/widget/dnd_source.rs @@ -1,6 +1,6 @@ use std::any::Any; -use iced_core::window; +use iced_core::{widget::Operation, window}; use crate::{ Element, @@ -176,7 +176,7 @@ impl(); let node = self .container - .as_widget() + .as_widget_mut() .layout(&mut tree.children[0], renderer, limits); state.cached_bounds = node.bounds(); node } fn operate( - &self, + &mut self, tree: &mut Tree, layout: layout::Layout<'_>, renderer: &crate::Renderer, - operation: &mut dyn iced_core::widget::Operation<()>, + operation: &mut dyn Operation, ) { - operation.custom((&mut tree.state) as &mut dyn Any, Some(&self.id)); - operation.container(Some(&self.id), layout.bounds(), &mut |operation| { - self.container - .as_widget() - .operate(&mut tree.children[0], layout, renderer, operation) + operation.container(Some(&self.id), layout.bounds()); + operation.traverse(&mut |operation| { + self.container.as_widget_mut().operate( + tree, + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), + renderer, + operation, + ); }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: layout::Layout<'_>, cursor: mouse::Cursor, renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - let ret = self.container.as_widget_mut().on_event( + ) { + let ret = self.container.as_widget_mut().update( &mut tree.children[0], - event.clone(), + &event, layout, cursor, renderer, @@ -238,14 +245,16 @@ impl { state.left_pressed_position = None; - return event::Status::Captured; + shell.capture_event(); + return; } mouse::Event::CursorMoved { .. } => { if let Some(position) = cursor.position() { @@ -277,7 +286,8 @@ impl return ret, @@ -288,7 +298,8 @@ impl( &'b mut self, tree: &'b mut Tree, - layout: layout::Layout<'_>, + layout: layout::Layout<'b>, renderer: &crate::Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { - self.container - .as_widget_mut() - .overlay(&mut tree.children[0], layout, renderer, translation) + self.container.as_widget_mut().overlay( + &mut tree.children[0], + layout, + renderer, + viewport, + translation, + ) } fn drag_destinations( diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index 3fd099b3..0c96c1c6 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -213,7 +213,7 @@ impl<'a, Message: Clone + 'a> Overlay<'a, Message> { } } - fn _layout(&self, renderer: &crate::Renderer, bounds: Size) -> layout::Node { + fn _layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node { let space_below = bounds.height - (self.position.y + self.target_height); let space_above = self.position.y; @@ -242,19 +242,19 @@ impl<'a, Message: Clone + 'a> Overlay<'a, Message> { }) } - fn _on_event( + fn _update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + ) { let bounds = layout.bounds(); self.state.with_data_mut(|tree| { - self.container.on_event( + self.container.update( tree, event, layout, cursor, renderer, clipboard, shell, &bounds, ) }) @@ -293,6 +293,7 @@ impl<'a, Message: Clone + 'a> Overlay<'a, Message> { radius: appearance.border_radius, }, shadow: Shadow::default(), + snap: true, }, appearance.background, ); @@ -311,26 +312,25 @@ impl<'a, Message: Clone + 'a> iced_core::Overlay, cursor: mouse::Cursor, renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { - self._on_event(event, layout, cursor, renderer, clipboard, shell) + ) { + self._update(event, layout, cursor, renderer, clipboard, shell) } fn mouse_interaction( &self, layout: Layout<'_>, cursor: mouse::Cursor, - viewport: &Rectangle, renderer: &crate::Renderer, ) -> mouse::Interaction { - self._mouse_interaction(layout, cursor, viewport, renderer) + self._mouse_interaction(layout, cursor, &layout.bounds(), renderer) } fn draw( @@ -353,7 +353,7 @@ impl<'a, Message: Clone + 'a> crate::widget::Widget crate::widget::Widget, cursor: mouse::Cursor, renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { - self._on_event(event, layout, cursor, renderer, clipboard, shell) + ) { + self._update(event, layout, cursor, renderer, clipboard, shell) } fn draw( @@ -435,7 +435,7 @@ where } fn layout( - &self, + &mut self, _tree: &mut Tree, renderer: &crate::Renderer, limits: &layout::Limits, @@ -452,7 +452,7 @@ where let size = { let intrinsic = Size::new( 0.0, - (f32::from(text_line_height) + self.padding.vertical()) * self.options.len() as f32, + (f32::from(text_line_height) + self.padding.y()) * self.options.len() as f32, ); limits.resolve(Length::Fill, Length::Shrink, intrinsic) @@ -461,17 +461,17 @@ where layout::Node::new(size) } - fn on_event( + fn update( &mut self, _state: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &crate::Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { let hovered_guard = self.hovered_option.lock().unwrap(); @@ -481,7 +481,8 @@ where if let Some(close_on_selected) = self.close_on_selected.as_ref() { shell.publish(close_on_selected.clone()); } - return event::Status::Captured; + shell.capture_event(); + return; } } } @@ -493,7 +494,7 @@ where let option_height = f32::from(self.text_line_height.to_absolute(Pixels(text_size))) - + self.padding.vertical(); + + self.padding.y(); let new_hovered_option = (cursor_position.y / option_height) as usize; let mut hovered_guard = self.hovered_option.lock().unwrap(); @@ -515,7 +516,7 @@ where let option_height = f32::from(self.text_line_height.to_absolute(Pixels(text_size))) - + self.padding.vertical(); + + self.padding.y(); let mut hovered_guard = self.hovered_option.lock().unwrap(); *hovered_guard = Some((cursor_position.y / option_height) as usize); @@ -525,14 +526,13 @@ where if let Some(close_on_selected) = self.close_on_selected.as_ref() { shell.publish(close_on_selected.clone()); } - return event::Status::Captured; + shell.capture_event(); + return; } } } _ => {} } - - event::Status::Ignored } fn mouse_interaction( @@ -568,8 +568,8 @@ where let text_size = self .text_size .unwrap_or_else(|| text::Renderer::default_size(renderer).0); - let option_height = f32::from(self.text_line_height.to_absolute(Pixels(text_size))) - + self.padding.vertical(); + let option_height = + f32::from(self.text_line_height.to_absolute(Pixels(text_size))) + self.padding.y(); let offset = viewport.y - bounds.y; let start = (offset / option_height) as usize; @@ -605,6 +605,7 @@ where ..Default::default() }, shadow: Shadow::default(), + snap: true, }, appearance.selected_background, ); @@ -614,16 +615,13 @@ where .color(appearance.selected_text_color) .border_radius(appearance.border_radius); - svg::Renderer::draw_svg( - renderer, - svg_handle, - Rectangle { - x: item_x + item_width - 16.0 - 8.0, - y: bounds.y + (bounds.height / 2.0 - 8.0), - width: 16.0, - height: 16.0, - }, - ); + let bounds = Rectangle { + x: item_x + item_width - 16.0 - 8.0, + y: bounds.y + (bounds.height / 2.0 - 8.0), + width: 16.0, + height: 16.0, + }; + svg::Renderer::draw_svg(renderer, svg_handle, bounds, bounds); (appearance.selected_text_color, crate::font::semibold()) } else if *hovered_guard == Some(i) { @@ -642,6 +640,7 @@ where ..Default::default() }, shadow: Shadow::default(), + snap: true, }, appearance.hovered_background, ); @@ -678,8 +677,8 @@ where size: Pixels(text_size), line_height: self.text_line_height, font, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Center, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), ellipsize: text::Ellipsize::default(), diff --git a/src/widget/dropdown/mod.rs b/src/widget/dropdown/mod.rs index fa4184c4..b2d3fbed 100644 --- a/src/widget/dropdown/mod.rs +++ b/src/widget/dropdown/mod.rs @@ -56,12 +56,12 @@ pub fn popup_dropdown< dropdown } -/// Produces a [`Task`] that closes the [`Dropdown`]. -pub fn close(id: Id) -> iced_runtime::Task { - iced_runtime::task::effect(iced_runtime::Action::Widget(Box::new(operation::close(id)))) -} +// /// Produces a [`Task`] that closes the [`Dropdown`]. +// pub fn close(id: Id) -> iced_runtime::Task { +// iced_runtime::task::effect(iced_runtime::Action::Widget(Box::new(operation::close(id)))) +// } -/// Produces a [`Task`] that opens the [`Dropdown`]. -pub fn open(id: Id) -> iced_runtime::Task { - iced_runtime::task::effect(iced_runtime::Action::Widget(Box::new(operation::open(id)))) -} +// /// Produces a [`Task`] that opens the [`Dropdown`]. +// pub fn open(id: Id) -> iced_runtime::Task { +// iced_runtime::task::effect(iced_runtime::Action::Widget(Box::new(operation::open(id)))) +// } diff --git a/src/widget/dropdown/multi/menu.rs b/src/widget/dropdown/multi/menu.rs index 39e89ee2..0a761097 100644 --- a/src/widget/dropdown/multi/menu.rs +++ b/src/widget/dropdown/multi/menu.rs @@ -209,18 +209,18 @@ impl iced_core::Overlay for Ove }) } - fn on_event( + fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + ) { let bounds = layout.bounds(); - self.container.on_event( + self.container.update( self.state, event, layout, cursor, renderer, clipboard, shell, &bounds, ) } @@ -229,11 +229,10 @@ impl iced_core::Overlay for Ove &self, layout: Layout<'_>, cursor: mouse::Cursor, - viewport: &Rectangle, renderer: &crate::Renderer, ) -> mouse::Interaction { self.container - .mouse_interaction(self.state, layout, cursor, viewport, renderer) + .mouse_interaction(self.state, layout, cursor, &layout.bounds(), renderer) } fn draw( @@ -256,6 +255,7 @@ impl iced_core::Overlay for Ove radius: appearance.border_radius, }, shadow: Shadow::default(), + snap: true, }, appearance.background, ); @@ -287,7 +287,7 @@ where } fn layout( - &self, + &mut self, _tree: &mut Tree, renderer: &crate::Renderer, limits: &layout::Limits, @@ -309,7 +309,7 @@ where ) }); - let vertical_padding = self.padding.vertical(); + let vertical_padding = self.padding.y(); let text_line_height = f32::from(text_line_height); let size = { @@ -328,17 +328,17 @@ where layout::Node::new(size) } - fn on_event( + fn update( &mut self, _state: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &crate::Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let bounds = layout.bounds(); match event { @@ -346,7 +346,8 @@ where if cursor.is_over(bounds) { if let Some(item) = self.hovered_option.as_ref() { shell.publish((self.on_selected)(item.clone())); - return event::Status::Captured; + shell.capture_event(); + return; } } } @@ -361,7 +362,7 @@ where let heights = self .options - .element_heights(self.padding.vertical(), text_line_height); + .element_heights(self.padding.y(), text_line_height); let mut current_offset = 0.0; @@ -408,7 +409,7 @@ where let heights = self .options - .element_heights(self.padding.vertical(), text_line_height); + .element_heights(self.padding.y(), text_line_height); let mut current_offset = 0.0; @@ -446,8 +447,6 @@ where } _ => {} } - - event::Status::Ignored } fn mouse_interaction( @@ -490,7 +489,7 @@ where let text_line_height = f32::from(self.text_line_height.to_absolute(Pixels(text_size))); let visible_options = self.options.visible_options( - self.padding.vertical(), + self.padding.y(), text_line_height, offset, viewport.height, @@ -528,24 +527,23 @@ where ..Default::default() }, shadow: Shadow::default(), + snap: true, }, appearance.selected_background, ); + let svg_bounds = Rectangle { + x: item_x + item_width - 16.0 - 8.0, + y: bounds.y + (bounds.height / 2.0 - 8.0), + width: 16.0, + height: 16.0, + }; + let svg_handle = svg::Svg::new(crate::widget::common::object_select().clone()) .color(appearance.selected_text_color) .border_radius(appearance.border_radius); - svg::Renderer::draw_svg( - renderer, - svg_handle, - Rectangle { - x: item_x + item_width - 16.0 - 8.0, - y: bounds.y + (bounds.height / 2.0 - 8.0), - width: 16.0, - height: 16.0, - }, - ); + svg::Renderer::draw_svg(renderer, svg_handle, svg_bounds, svg_bounds); (appearance.selected_text_color, crate::font::semibold()) } else if self.hovered_option.as_ref() == Some(item) { @@ -566,6 +564,7 @@ where ..Default::default() }, shadow: Shadow::default(), + snap: true, }, appearance.hovered_background, ); @@ -590,8 +589,8 @@ where size: iced::Pixels(text_size), line_height: self.text_line_height, font, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Center, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), ellipsize: text::Ellipsize::default(), @@ -611,7 +610,7 @@ where }) .move_to(Point { x: bounds.x, - y: bounds.y + (self.padding.vertical() / 2.0) - 4.0, + y: bounds.y + (self.padding.y() / 2.0) - 4.0, }); Widget::::draw( @@ -640,8 +639,8 @@ where size: iced::Pixels(text_size), line_height: text::LineHeight::Absolute(Pixels(text_line_height + 4.0)), font: crate::font::default(), - horizontal_alignment: alignment::Horizontal::Center, - vertical_alignment: alignment::Vertical::Center, + align_x: text::Alignment::Center, + align_y: alignment::Vertical::Center, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), ellipsize: text::Ellipsize::default(), diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index 43a0836f..a46c6dcc 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -78,7 +78,7 @@ impl<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static> } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &crate::Renderer, limits: &layout::Limits, @@ -116,17 +116,17 @@ impl<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static> ) } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &crate::Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { update( &event, layout, @@ -183,8 +183,9 @@ impl<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static> fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &crate::Renderer, + _viewport: &Rectangle, translation: Vector, ) -> Option> { let state = tree.state.downcast_mut::>(); @@ -275,8 +276,8 @@ pub fn layout( size: iced::Pixels(text_size), line_height: text_line_height, font: font.unwrap_or_else(crate::font::default), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Top, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), ellipsize: text::Ellipsize::default(), @@ -314,7 +315,7 @@ pub fn update<'a, S: AsRef, Message, Item: Clone + PartialEq + 'static + 'a on_selected: &dyn Fn(Item) -> Message, selections: &super::Model, state: impl FnOnce() -> &'a mut State, -) -> event::Status { +) { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { @@ -325,14 +326,12 @@ pub fn update<'a, S: AsRef, Message, Item: Clone + PartialEq + 'static + 'a // bounds or on the drop-down, either way we close the overlay. state.is_open = false; - event::Status::Captured + shell.capture_event(); } else if cursor.is_over(layout.bounds()) { state.is_open = true; state.hovered_option = selections.selected.clone(); - event::Status::Captured - } else { - event::Status::Ignored + shell.capture_event(); } } Event::Mouse(mouse::Event::WheelScrolled { @@ -348,19 +347,15 @@ pub fn update<'a, S: AsRef, Message, Item: Clone + PartialEq + 'static + 'a shell.publish((on_selected)(option.1.clone())); } - event::Status::Captured - } else { - event::Status::Ignored + shell.capture_event(); } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { let state = state(); state.keyboard_modifiers = *modifiers; - - event::Status::Ignored } - _ => event::Status::Ignored, + _ => {} } } @@ -420,8 +415,8 @@ pub fn overlay<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static size: iced::Pixels(text_size), line_height, font: font.unwrap_or_else(crate::font::default), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Top, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), ellipsize: text::Ellipsize::default(), @@ -430,7 +425,7 @@ pub fn overlay<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static }; let mut desc_count = 0; - padding.horizontal().mul_add( + padding.x().mul_add( 2.0, selections .elements() @@ -517,22 +512,20 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>( bounds, border: style.border, shadow: Shadow::default(), + snap: true, }, style.background, ); 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, - Rectangle { - x: bounds.x + bounds.width - gap - 16.0, - y: bounds.center_y() - 8.0, - width: 16.0, - height: 16.0, - }, - ); + let svg_bounds = Rectangle { + x: bounds.x + bounds.width - gap - 16.0, + y: bounds.center_y() - 8.0, + width: 16.0, + height: 16.0, + }; + svg::Renderer::draw_svg(renderer, svg_handle, svg_bounds, svg_bounds); } if let Some(content) = selected.map(AsRef::as_ref) { @@ -541,7 +534,7 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>( let bounds = Rectangle { x: bounds.x + padding.left, y: bounds.center_y(), - width: bounds.width - padding.horizontal(), + width: bounds.width - padding.x(), height: f32::from(text_line_height.to_absolute(Pixels(text_size))), }; @@ -553,8 +546,8 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>( line_height: text_line_height, font, bounds: bounds.size(), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Center, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), ellipsize: text::Ellipsize::default(), diff --git a/src/widget/dropdown/operation.rs b/src/widget/dropdown/operation.rs index 8cea4566..1a4e1a9f 100644 --- a/src/widget/dropdown/operation.rs +++ b/src/widget/dropdown/operation.rs @@ -11,62 +11,62 @@ pub trait Dropdown { fn open(&mut self); } -/// Produces a [`Task`] that closes a [`Dropdown`] popup. -pub fn close(id: Id) -> impl Operation { - struct Close(Id); +// /// Produces a [`Task`] that closes a [`Dropdown`] popup. +// pub fn close(id: Id) -> impl Operation { +// struct Close(Id); - impl Operation for Close { - fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) { - if id.map_or(true, |id| id != &self.0) { - return; - } +// impl Operation for Close { +// fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) { +// if id.map_or(true, |id| id != &self.0) { +// return; +// } - let Some(state) = state.downcast_mut::() else { - return; - }; +// let Some(state) = state.downcast_mut::() else { +// return; +// }; - state.close(); - } +// state.close(); +// } - fn container( - &mut self, - _id: Option<&Id>, - _bounds: Rectangle, - operate_on_children: &mut dyn FnMut(&mut dyn Operation), - ) { - operate_on_children(self) - } - } +// fn container( +// &mut self, +// _id: Option<&Id>, +// _bounds: Rectangle, +// operate_on_children: &mut dyn FnMut(&mut dyn Operation), +// ) { +// operate_on_children(self) +// } +// } - Close(id) -} +// Close(id) +// } -/// Produces a [`Task`] that opens a [`Dropdown`] popup. -pub fn open(id: Id) -> impl Operation { - struct Open(Id); +// /// Produces a [`Task`] that opens a [`Dropdown`] popup. +// pub fn open(id: Id) -> impl Operation { +// struct Open(Id); - impl Operation for Open { - fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) { - if id.map_or(true, |id| id != &self.0) { - return; - } +// impl Operation for Open { +// fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) { +// if id.map_or(true, |id| id != &self.0) { +// return; +// } - let Some(state) = state.downcast_mut::() else { - return; - }; +// let Some(state) = state.downcast_mut::() else { +// return; +// }; - state.open(); - } +// state.open(); +// } - fn container( - &mut self, - _id: Option<&Id>, - _bounds: Rectangle, - operate_on_children: &mut dyn FnMut(&mut dyn Operation), - ) { - operate_on_children(self) - } - } +// fn container( +// &mut self, +// _id: Option<&Id>, +// _bounds: Rectangle, +// operate_on_children: &mut dyn FnMut(&mut dyn Operation), +// ) { +// operate_on_children(self) +// } +// } - Open(id) -} +// Open(id) +// } diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 67101d26..b6244c07 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -203,13 +203,13 @@ where state.hashes[i] = text_hash; state.selections[i].update(Text { content: selection.as_ref(), - bounds: Size::INFINITY, + bounds: Size::INFINITE, // TODO use the renderer default size size: iced::Pixels(self.text_size.unwrap_or(14.0)), line_height: self.text_line_height, font: self.font.unwrap_or_else(crate::font::default), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Top, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), ellipsize: text::Ellipsize::default(), @@ -227,7 +227,7 @@ where } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &crate::Renderer, limits: &layout::Limits, @@ -252,17 +252,17 @@ where ) } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &crate::Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { update::( &event, layout, @@ -327,21 +327,23 @@ where } fn operate( - &self, + &mut self, tree: &mut Tree, _layout: Layout<'_>, _renderer: &crate::Renderer, operation: &mut dyn iced_core::widget::Operation, ) { - let state = tree.state.downcast_mut::(); - operation.custom(state, self.id.as_ref()); + // TODO: double check operation handling + // let state = tree.state.downcast_mut::(); + // operation.custom(state, self.id.as_ref()); } fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &crate::Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { #[cfg(all(feature = "winit", feature = "wayland"))] @@ -469,24 +471,38 @@ pub fn layout( let max_width = match width { Length::Shrink => { let measure = move |(label, paragraph): (_, Option<&mut crate::Plain>)| -> f32 { - let text = Text { - content: label, - bounds: Size::new(f32::MAX, f32::MAX), - size: iced::Pixels(text_size), - line_height: text_line_height, - font: font.unwrap_or_else(crate::font::default), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - shaping: text::Shaping::Advanced, - wrapping: text::Wrapping::default(), - ellipsize: text::Ellipsize::default(), - }; let paragraph = match paragraph { Some(p) => { + let text = Text { + content: label, + bounds: Size::new(f32::MAX, f32::MAX), + size: iced::Pixels(text_size), + line_height: text_line_height, + font: font.unwrap_or_else(crate::font::default), + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Top, + shaping: text::Shaping::Advanced, + wrapping: text::Wrapping::default(), + ellipsize: text::Ellipsize::default(), + }; p.update(text); p } - None => &mut crate::Plain::new(text), + None => { + let text = Text { + content: label.to_string(), + bounds: Size::new(f32::MAX, f32::MAX), + size: iced::Pixels(text_size), + line_height: text_line_height, + font: font.unwrap_or_else(crate::font::default), + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Top, + shaping: text::Shaping::Advanced, + wrapping: text::Wrapping::default(), + ellipsize: text::Ellipsize::default(), + }; + &mut crate::Plain::new(text) + } }; paragraph.min_width().round() }; @@ -544,7 +560,7 @@ pub fn update< text_size: Option, font: Option, selected_option: Option, -) -> event::Status { +) { let state = state(); let open = |shell: &mut Shell<'_, Message>, @@ -575,7 +591,7 @@ pub fn update< let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 { selection_paragraph.min_width().round() }; - let pad_width = padding.horizontal().mul_add(2.0, 16.0); + let pad_width = padding.x().mul_add(2.0, 16.0); let selections_width = selections .iter() @@ -669,12 +685,10 @@ pub fn update< if let Some(on_close) = on_surface_action { shell.publish(on_close(surface::action::destroy_popup(state.popup_id))); } - event::Status::Captured + shell.capture_event(); } else if cursor.is_over(layout.bounds()) { open(shell, state, on_selected); - event::Status::Captured - } else { - event::Status::Ignored + shell.capture_event(); } } Event::Mouse(mouse::Event::WheelScrolled { @@ -689,17 +703,13 @@ pub fn update< shell.publish((on_selected)(next_index)); } - event::Status::Captured - } else { - event::Status::Ignored + shell.capture_event(); } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { state.keyboard_modifiers = *modifiers; - - event::Status::Ignored } - _ => event::Status::Ignored, + _ => {} } } @@ -746,7 +756,7 @@ where .zip(state.selections.iter()) .map(|(label, selection)| measure(label.as_ref(), selection.raw())) .fold(0.0, |next, current| current.max(next)); - let pad_width = padding.horizontal().mul_add(2.0, 16.0); + let pad_width = padding.x().mul_add(2.0, 16.0); let width = selections_width + gap + pad_width + icon_width; let is_open = state.is_open.clone(); @@ -822,7 +832,7 @@ where selection_paragraph.min_width().round() }; - let pad_width = padding.horizontal().mul_add(2.0, 16.0); + let pad_width = padding.x().mul_add(2.0, 16.0); let icon_width = if icons.is_empty() { 0.0 } else { 24.0 }; @@ -883,23 +893,20 @@ pub fn draw<'a, S>( bounds, border: style.border, shadow: Shadow::default(), + snap: true, }, style.background, ); if let Some(handle) = state.icon.clone() { let svg_handle = svg::Svg::new(handle).color(style.text_color); - - svg::Renderer::draw_svg( - renderer, - svg_handle, - Rectangle { - x: bounds.x + bounds.width - gap - 16.0, - y: bounds.center_y() - 8.0, - width: 16.0, - height: 16.0, - }, - ); + let bounds = Rectangle { + x: bounds.x + bounds.width - gap - 16.0, + y: bounds.center_y() - 8.0, + width: 16.0, + height: 16.0, + }; + svg::Renderer::draw_svg(renderer, svg_handle, bounds, bounds); } if let Some(content) = selected.map(AsRef::as_ref).or(placeholder) { @@ -908,7 +915,7 @@ pub fn draw<'a, S>( let mut bounds = Rectangle { x: bounds.x + padding.left, y: bounds.center_y(), - width: bounds.width - padding.horizontal(), + width: bounds.width - padding.x(), height: f32::from(text_line_height.to_absolute(Pixels(text_size))), }; @@ -932,8 +939,8 @@ pub fn draw<'a, S>( line_height: text_line_height, font, bounds: bounds.size(), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Center, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), ellipsize: text::Ellipsize::default(), diff --git a/src/widget/flex_row/layout.rs b/src/widget/flex_row/layout.rs index 744b607d..ae0c28d6 100644 --- a/src/widget/flex_row/layout.rs +++ b/src/widget/flex_row/layout.rs @@ -15,7 +15,7 @@ use taffy::{AlignContent, TaffyTree}; pub fn resolve( renderer: &Renderer, limits: &Limits, - items: &[Element<'_, Message>], + items: &mut [Element<'_, Message>], padding: Padding, column_spacing: f32, row_spacing: f32, @@ -61,8 +61,8 @@ pub fn resolve( ..taffy::Style::default() }; - for (child, tree) in items.iter().zip(tree.iter_mut()) { - let child_widget = child.as_widget(); + for (child, tree) in items.iter_mut().zip(tree.iter_mut()) { + let child_widget = child.as_widget_mut(); let child_node = child_widget.layout(tree, renderer, limits); let size = child_node.size(); @@ -138,7 +138,7 @@ pub fn resolve( leafs .into_iter() - .zip(items.iter()) + .zip(items.iter_mut()) .zip(nodes.iter_mut()) .zip(tree) .for_each(|(((leaf, child), node), tree)| { @@ -146,7 +146,7 @@ pub fn resolve( return; }; - let child_widget = child.as_widget(); + let child_widget = child.as_widget_mut(); let c_size = child_widget.size(); match c_size.width { Length::Fill | Length::FillPortion(_) => { diff --git a/src/widget/flex_row/widget.rs b/src/widget/flex_row/widget.rs index 264201c1..f7b90f66 100644 --- a/src/widget/flex_row/widget.rs +++ b/src/widget/flex_row/widget.rs @@ -100,7 +100,7 @@ impl Widget for FlexR } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, @@ -114,7 +114,7 @@ impl Widget for FlexR super::layout::resolve( renderer, &limits, - &self.children, + &mut self.children, self.padding, f32::from(self.column_spacing), f32::from(self.row_spacing), @@ -127,19 +127,19 @@ impl Widget for FlexR } fn operate( - &self, + &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation<()>, ) { - operation.container(None, layout.bounds(), &mut |operation| { + operation.traverse(&mut |operation| { self.children - .iter() + .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .for_each(|((child, state), c_layout)| { - child.as_widget().operate( + child.as_widget_mut().operate( state, c_layout.with_virtual_offset(layout.virtual_offset()), renderer, @@ -149,25 +149,25 @@ impl Widget for FlexR }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { self.children .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .map(|((child, state), c_layout)| { - child.as_widget_mut().on_event( + child.as_widget_mut().update( state, - event.clone(), + event, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, @@ -175,8 +175,7 @@ impl Widget for FlexR shell, viewport, ) - }) - .fold(event::Status::Ignored, event::Status::merge) + }); } fn mouse_interaction( @@ -235,11 +234,19 @@ impl Widget for FlexR fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { - overlay::from_children(&mut self.children, tree, layout, renderer, translation) + overlay::from_children( + &mut self.children, + tree, + layout, + renderer, + viewport, + translation, + ) } #[cfg(feature = "a11y")] diff --git a/src/widget/frames.rs b/src/widget/frames.rs index 1c379ac1..056a55ba 100644 --- a/src/widget/frames.rs +++ b/src/widget/frames.rs @@ -8,6 +8,8 @@ use std::path::Path; use std::time::{Duration, Instant}; use ::image as image_rs; +use iced::Task; +use iced::mouse; use iced_core::image::Renderer as ImageRenderer; use iced_core::mouse::Cursor; use iced_core::widget::{Tree, tree}; @@ -15,7 +17,6 @@ use iced_core::{ Clipboard, ContentFit, Element, Event, Layout, Length, Rectangle, Shell, Size, Vector, Widget, event, layout, renderer, window, }; -use iced_runtime::Command; use iced_widget::image::{self, Handle}; use image_rs::AnimationDecoder; use image_rs::codecs::gif::GifDecoder; @@ -27,7 +28,7 @@ use iced_futures::futures::{AsyncRead, AsyncReadExt}; #[cfg(feature = "tokio")] use tokio::io::{AsyncRead, AsyncReadExt}; -use super::icon::load_icon; +use crate::widget::icon; #[must_use] /// Creates a new [`AnimatedImage`] with the given [`animated_image::Frames`] @@ -74,13 +75,13 @@ impl Frames { size: u16, theme: Option<&str>, default_fallbacks: bool, - ) -> Command> { + ) -> Task> { let mut name_path_buffer = None; - if let Some(path) = load_icon(name, size, theme) { + if let Some(path) = icon::Named::new(name).size(size).path() { name_path_buffer = Some(path); } else if default_fallbacks { for name in name.rmatch_indices('-').map(|(pos, _)| &name[..pos]) { - if let Some(path) = load_icon(name, size, theme) { + if let Some(path) = icon::Named::new(name).size(size).path() { name_path_buffer = Some(path); break; } @@ -90,14 +91,14 @@ impl Frames { if let Some(name_path_buffer) = name_path_buffer { Self::load_from_path(name_path_buffer) } else { - Command::perform(async { Err(Error::Missing) }, std::convert::identity) + Task::perform(async { Err(Error::Missing) }, std::convert::identity) } } /// Load [`Frames`] from the supplied path - pub fn load_from_path(path: impl AsRef) -> Command> { + pub fn load_from_path(path: impl AsRef) -> Task> { #[inline(never)] - fn inner(path: &Path) -> Command> { + fn inner(path: &Path) -> Task> { #[cfg(feature = "tokio")] use tokio::fs::File; #[cfg(feature = "tokio")] @@ -108,7 +109,7 @@ impl Frames { #[cfg(not(feature = "tokio"))] use iced_futures::futures::io::BufReader; - let path = path.as_ref().to_path_buf(); + let path = path.to_path_buf(); let f = async move { let image_type = match &path.extension() { @@ -119,10 +120,10 @@ impl Frames { }; let reader = BufReader::new(File::open(path).await?); - Self::from_reader(reader, image_type).await + Frames::from_reader(reader, image_type).await }; - Command::perform(f, std::convert::identity) + Task::perform(f, std::convert::identity) } inner(path.as_ref()) @@ -168,9 +169,9 @@ impl Frames { let total_bytes = frames .iter() .map(|f| match f.handle.data() { - iced_core::image::Data::Path(_) => 0, - iced_core::image::Data::Bytes(b) => b.len(), - iced_core::image::Data::Rgba { pixels, .. } => pixels.len(), + iced_core::image::Handle::Path(..) => 0, + iced_core::image::Handle::Bytes(_, b) => b.len(), + iced_core::image::Handle::Rgba { pixels, .. } => pixels.len(), }) .sum::() .try_into() @@ -195,7 +196,7 @@ impl From for Frame { let delay = frame.delay().into(); - let handle = image::Handle::from_pixels(width, height, frame.into_buffer().into_vec()); + let handle = image::Handle::from_rgba(width, height, frame.into_buffer().into_vec()); Self { delay, handle } } @@ -278,12 +279,8 @@ impl<'a, Message, Renderer> Widget for Animated where Renderer: ImageRenderer, { - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height + fn size(&self) -> Size { + Size::new(self.width.into(), self.height.into()) } fn tag(&self) -> tree::Tag { @@ -315,7 +312,12 @@ where } } - fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { + fn layout( + &mut self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { iced_widget::image::layout( renderer, limits, @@ -326,19 +328,20 @@ where ) } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, - _layout: Layout<'_>, - _cursor_position: Cursor, - _renderer: &Renderer, - _clipboard: &mut dyn Clipboard, + event: &Event, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + viewport: &Rectangle, + ) { let state = tree.state.downcast_mut::(); - if let Event::Window(_, window::Event::RedrawRequested(now)) = event { + if let Event::Window(window::Event::RedrawRequested(now)) = event { let elapsed = now.duration_since(state.current.started); if elapsed > state.current.frame.delay { @@ -346,15 +349,14 @@ where state.current = self.frames.frames[state.index].clone().into(); - shell.request_redraw(window::RedrawRequest::At(now + state.current.frame.delay)); + shell + .request_redraw_at(window::RedrawRequest::At(*now + state.current.frame.delay)); } else { let remaining = state.current.frame.delay - elapsed; - shell.request_redraw(window::RedrawRequest::At(now + remaining)); + shell.request_redraw_at(window::RedrawRequest::At(*now + remaining)); } } - - event::Status::Ignored } fn draw( diff --git a/src/widget/grid/layout.rs b/src/widget/grid/layout.rs index a7e42759..8ed4c0ec 100644 --- a/src/widget/grid/layout.rs +++ b/src/widget/grid/layout.rs @@ -17,7 +17,7 @@ use taffy::{AlignContent, TaffyTree}; pub fn resolve( renderer: &Renderer, limits: &Limits, - items: &[Element<'_, Message>], + items: &mut [Element<'_, Message>], assignments: &[Assignment], width: Length, height: Length, @@ -37,9 +37,13 @@ pub fn resolve( let mut taffy = TaffyTree::<()>::with_capacity(items.len() + 1); // Attach widgets as child nodes. - for ((child, assignment), tree) in items.iter().zip(assignments.iter()).zip(tree.iter_mut()) { + for ((child, assignment), tree) in items + .iter_mut() + .zip(assignments.iter()) + .zip(tree.iter_mut()) + { // Calculate the dimensions of the item. - let child_widget = child.as_widget(); + let child_widget = child.as_widget_mut(); let child_node = child_widget.layout(tree, renderer, limits); let size = child_node.size(); @@ -172,12 +176,12 @@ pub fn resolve( for (((leaf, child), node), tree) in leafs .into_iter() - .zip(items.iter()) + .zip(items.iter_mut()) .zip(nodes.iter_mut()) .zip(tree) { if let Ok(leaf_layout) = taffy.layout(leaf) { - let child_widget = child.as_widget(); + let child_widget = child.as_widget_mut(); let c_size = child_widget.size(); match c_size.width { Length::Fill | Length::FillPortion(_) => { diff --git a/src/widget/grid/widget.rs b/src/widget/grid/widget.rs index 0aca7943..f88dfc2a 100644 --- a/src/widget/grid/widget.rs +++ b/src/widget/grid/widget.rs @@ -127,7 +127,7 @@ impl Widget for Grid< } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, @@ -141,7 +141,7 @@ impl Widget for Grid< super::layout::resolve( renderer, &limits, - &self.children, + &mut self.children, &self.assignments, self.width, self.height, @@ -156,19 +156,19 @@ impl Widget for Grid< } fn operate( - &self, + &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation<()>, ) { - operation.container(None, layout.bounds(), &mut |operation| { + operation.traverse(&mut |operation| { self.children - .iter() + .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .for_each(|((child, state), c_layout)| { - child.as_widget().operate( + child.as_widget_mut().operate( state, c_layout.with_virtual_offset(layout.virtual_offset()), renderer, @@ -178,25 +178,25 @@ impl Widget for Grid< }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { self.children .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .map(|((child, state), c_layout)| { - child.as_widget_mut().on_event( + child.as_widget_mut().update( state, - event.clone(), + event, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, @@ -204,8 +204,7 @@ impl Widget for Grid< shell, viewport, ) - }) - .fold(event::Status::Ignored, event::Status::merge) + }); } fn mouse_interaction( @@ -264,11 +263,19 @@ impl Widget for Grid< fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { - overlay::from_children(&mut self.children, tree, layout, renderer, translation) + overlay::from_children( + &mut self.children, + tree, + layout, + renderer, + viewport, + translation, + ) } #[cfg(feature = "a11y")] diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index b0957d68..695c8405 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -157,7 +157,7 @@ impl Widget } fn layout( - &self, + &mut self, tree: &mut tree::Tree, renderer: &crate::Renderer, limits: &iced_core::layout::Limits, @@ -165,7 +165,7 @@ impl Widget let child_tree = &mut tree.children[0]; let child = self .header_bar_inner - .as_widget() + .as_widget_mut() .layout(child_tree, renderer, limits); iced_core::layout::Node::with_children(child.size(), vec![child]) } @@ -193,20 +193,20 @@ impl Widget ); } - fn on_event( + fn update( &mut self, state: &mut tree::Tree, - event: iced_core::Event, + event: &iced_core::Event, layout: iced_core::Layout<'_>, cursor: iced_core::mouse::Cursor, renderer: &crate::Renderer, clipboard: &mut dyn iced_core::Clipboard, shell: &mut iced_core::Shell<'_, Message>, viewport: &iced_core::Rectangle, - ) -> iced_core::event::Status { + ) { let child_state = &mut state.children[0]; let child_layout = layout.children().next().unwrap(); - self.header_bar_inner.as_widget_mut().on_event( + self.header_bar_inner.as_widget_mut().update( child_state, event, child_layout, @@ -238,7 +238,7 @@ impl Widget } fn operate( - &self, + &mut self, state: &mut tree::Tree, layout: iced_core::Layout<'_>, renderer: &crate::Renderer, @@ -246,16 +246,20 @@ impl Widget ) { let child_tree = &mut state.children[0]; let child_layout = layout.children().next().unwrap(); - self.header_bar_inner - .as_widget() - .operate(child_tree, child_layout, renderer, operation); + self.header_bar_inner.as_widget_mut().operate( + child_tree, + child_layout, + renderer, + operation, + ); } fn overlay<'b>( &'b mut self, state: &'b mut tree::Tree, - layout: iced_core::Layout<'_>, + layout: iced_core::Layout<'b>, renderer: &crate::Renderer, + viewport: &iced_core::Rectangle, translation: Vector, ) -> Option> { let child_tree = &mut state.children[0]; @@ -264,6 +268,7 @@ impl Widget child_tree, child_layout, renderer, + viewport, translation, ) } diff --git a/src/widget/icon/mod.rs b/src/widget/icon/mod.rs index 6c6a9f08..031b4b0c 100644 --- a/src/widget/icon/mod.rs +++ b/src/widget/icon/mod.rs @@ -15,7 +15,7 @@ pub use handle::{Data, Handle, from_path, from_raster_bytes, from_raster_pixels, use crate::Element; use derive_setters::Setters; use iced::widget::{Image, Svg}; -use iced::{ContentFit, Length, Rectangle}; +use iced::{ContentFit, Length, Radians, Rectangle}; use iced_core::Rotation; /// Create an [`Icon`] from a pre-existing [`Handle`] @@ -125,17 +125,22 @@ pub fn draw(renderer: &mut crate::Renderer, handle: &Handle, icon_bounds: Rectan renderer, iced_core::svg::Svg::new(handle), icon_bounds, + icon_bounds, ), Data::Image(handle) => { iced_core::image::Renderer::draw_image( renderer, - handle, - iced_core::image::FilterMethod::Linear, + iced_core::Image { + handle, + filter_method: iced_core::image::FilterMethod::Linear, + rotation: Radians(0.), + border_radius: [0.0; 4].into(), + opacity: 1.0, + snap: true, + }, + icon_bounds, icon_bounds, - iced_core::Radians::from(0), - 1.0, - [0.0; 4], ); } } diff --git a/src/widget/id_container.rs b/src/widget/id_container.rs index 3d468b20..c8e49e04 100644 --- a/src/widget/id_container.rs +++ b/src/widget/id_container.rs @@ -3,7 +3,7 @@ use iced_core::layout; use iced_core::mouse; use iced_core::overlay; use iced_core::renderer; -use iced_core::widget::{Id, Tree}; +use iced_core::widget::{Id, Operation, Tree}; use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; pub use iced_widget::container::{Catalog, Style}; @@ -65,29 +65,30 @@ where } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { let node = self .content - .as_widget() + .as_widget_mut() .layout(&mut tree.children[0], renderer, limits); let size = node.size(); layout::Node::with_children(size, vec![node]) } fn operate( - &self, + &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn iced_core::widget::Operation<()>, + operation: &mut dyn Operation, ) { - operation.container(Some(&self.id), layout.bounds(), &mut |operation| { - self.content.as_widget().operate( - &mut tree.children[0], + operation.container(Some(&self.id), layout.bounds()); + operation.traverse(&mut |operation| { + self.content.as_widget_mut().operate( + tree, layout .children() .next() @@ -99,18 +100,18 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor_position: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - self.content.as_widget_mut().on_event( + ) { + self.content.as_widget_mut().update( &mut tree.children[0], event, layout @@ -169,8 +170,9 @@ where fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { self.content.as_widget_mut().overlay( @@ -181,6 +183,7 @@ where .unwrap() .with_virtual_offset(layout.virtual_offset()), renderer, + viewport, translation, ) } diff --git a/src/widget/layer_container.rs b/src/widget/layer_container.rs index 74521b3d..110af518 100644 --- a/src/widget/layer_container.rs +++ b/src/widget/layer_container.rs @@ -172,7 +172,7 @@ where } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, @@ -181,7 +181,7 @@ where } fn operate( - &self, + &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -190,18 +190,18 @@ where self.container.operate(tree, layout, renderer, operation); } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor_position: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - self.container.on_event( + ) { + self.container.update( tree, event, layout, @@ -257,11 +257,13 @@ where fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { - self.container.overlay(tree, layout, renderer, translation) + self.container + .overlay(tree, layout, renderer, viewport, translation) } fn drag_destinations( diff --git a/src/widget/list/column.rs b/src/widget/list/column.rs index a3dedd96..49df998a 100644 --- a/src/widget/list/column.rs +++ b/src/widget/list/column.rs @@ -6,7 +6,7 @@ use iced_widget::container::Catalog; use crate::{ Apply, Element, theme, - widget::{container, divider, vertical_space}, + widget::{container, divider, space::vertical}, }; #[inline] @@ -65,7 +65,7 @@ impl<'a, Message: 'static> ListColumn<'a, Message> { // Ensure a minimum height of 32. let list_item = iced::widget::row![ container(item).align_y(iced::Alignment::Center), - vertical_space().height(iced::Length::Fixed(32.)) + vertical().height(iced::Length::Fixed(32.)) ] .padding(this.list_item_padding) .align_y(iced::Alignment::Center); diff --git a/src/widget/menu/flex.rs b/src/widget/menu/flex.rs index 8eb08d4e..4a58f13a 100644 --- a/src/widget/menu/flex.rs +++ b/src/widget/menu/flex.rs @@ -57,11 +57,11 @@ pub fn resolve<'a, E, Message, Renderer>( padding: Padding, spacing: f32, align_items: Alignment, - items: &[E], + items: &mut [E], tree: &mut [&mut Tree], ) -> Node where - E: std::borrow::Borrow>, + E: std::borrow::BorrowMut>, Renderer: renderer::Renderer, { let limits = limits.shrink(padding); @@ -69,7 +69,7 @@ where let max_cross = axis.cross(limits.max()); let mut fill_sum = 0; - let mut cross = axis.cross(limits.min()).max(axis.cross(Size::INFINITY)); + let mut cross = axis.cross(limits.min()).max(axis.cross(Size::INFINITE)); let mut available = axis.main(limits.max()) - total_spacing; let mut nodes: Vec = Vec::with_capacity(items.len()); @@ -78,8 +78,8 @@ where if align_items == Alignment::Center { let mut fill_cross = axis.cross(limits.min()); - for (child, tree) in items.iter().zip(tree.iter_mut()) { - let child = child.borrow(); + for (child, tree) in items.iter_mut().zip(tree.iter_mut()) { + let child = child.borrow_mut(); let c_size = child.as_widget().size(); let cross_fill_factor = match axis { Axis::Horizontal => c_size.height, @@ -92,7 +92,7 @@ where let child_limits = Limits::new(Size::ZERO, Size::new(max_width, max_height)); - let layout = child.as_widget().layout(tree, renderer, &child_limits); + let layout = child.as_widget_mut().layout(tree, renderer, &child_limits); let size = layout.size(); fill_cross = fill_cross.max(axis.cross(size)); @@ -102,8 +102,8 @@ where cross = fill_cross; } - for (i, (child, tree)) in items.iter().zip(tree.iter_mut()).enumerate() { - let child = child.borrow(); + for (i, (child, tree)) in items.iter_mut().zip(tree.iter_mut()).enumerate() { + let child = child.borrow_mut(); let c_size = child.as_widget().size(); let fill_factor = match axis { Axis::Horizontal => c_size.width, @@ -129,7 +129,7 @@ where Size::new(max_width, max_height), ); - let layout = child.as_widget().layout(tree, renderer, &child_limits); + let layout = child.as_widget_mut().layout(tree, renderer, &child_limits); let size = layout.size(); available -= axis.main(size); @@ -146,8 +146,8 @@ where let remaining = available.max(0.0); - for (i, (child, tree)) in items.iter().zip(tree.iter_mut()).enumerate() { - let child = child.borrow(); + for (i, (child, tree)) in items.iter_mut().zip(tree.iter_mut()).enumerate() { + let child = child.borrow_mut(); let c_size = child.as_widget().size(); let fill_factor = match axis { Axis::Horizontal => c_size.width, @@ -180,7 +180,7 @@ where Size::new(max_width, max_height), ); - let layout = child.as_widget().layout(tree, renderer, &child_limits); + let layout = child.as_widget_mut().layout(tree, renderer, &child_limits); if align_items != Alignment::Center { cross = cross.max(axis.cross(layout.size())); @@ -231,7 +231,7 @@ pub fn resolve_wrapper<'a, Message>( padding: Padding, spacing: f32, align_items: Alignment, - items: &[&RcElementWrapper], + items: &mut [&mut RcElementWrapper], tree: &mut [&mut Tree], ) -> Node { let limits = limits.shrink(padding); @@ -239,7 +239,7 @@ pub fn resolve_wrapper<'a, Message>( let max_cross = axis.cross(limits.max()); let mut fill_sum = 0; - let mut cross = axis.cross(limits.min()).max(axis.cross(Size::INFINITY)); + let mut cross = axis.cross(limits.min()).max(axis.cross(Size::INFINITE)); let mut available = axis.main(limits.max()) - total_spacing; let mut nodes: Vec = Vec::with_capacity(items.len()); @@ -248,7 +248,7 @@ pub fn resolve_wrapper<'a, Message>( if align_items == Alignment::Center { let mut fill_cross = axis.cross(limits.min()); - for (child, tree) in items.iter().zip(tree.iter_mut()) { + for (child, tree) in items.into_iter().zip(tree.iter_mut()) { let c_size = child.size(); let cross_fill_factor = match axis { Axis::Horizontal => c_size.height, @@ -271,7 +271,7 @@ pub fn resolve_wrapper<'a, Message>( cross = fill_cross; } - for (i, (child, tree)) in items.iter().zip(tree.iter_mut()).enumerate() { + for (i, (child, tree)) in items.into_iter().zip(tree.iter_mut()).enumerate() { let c_size = child.size(); let fill_factor = match axis { Axis::Horizontal => c_size.width, @@ -314,7 +314,7 @@ pub fn resolve_wrapper<'a, Message>( let remaining = available.max(0.0); - for (i, (child, tree)) in items.iter().zip(tree.iter_mut()).enumerate() { + for (i, (child, tree)) in items.into_iter().zip(tree.iter_mut()).enumerate() { let c_size = child.size(); let fill_factor = match axis { Axis::Horizontal => c_size.width, diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index bbbb4a2b..05fcc133 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -26,7 +26,7 @@ use crate::{ }, }; -use iced::{Point, Shadow, Vector, window}; +use iced::{Point, Shadow, Vector, event::Status, window}; use iced_core::Border; use iced_widget::core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, event, @@ -533,14 +533,14 @@ where menu_roots_children(&self.menu_roots) } - fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { + fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { use super::flex; let limits = limits.width(self.width).height(self.height); - let children = self + let mut children = self .menu_roots - .iter() - .map(|root| &root.item) + .iter_mut() + .map(|root| &mut root.item) .collect::>(); // the first children of the tree are the menu roots items let mut tree_children = tree @@ -555,28 +555,28 @@ where self.padding, self.spacing, Alignment::Center, - &children, + &mut children, &mut tree_children, ) } #[allow(clippy::too_many_lines)] - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: event::Event, + event: &event::Event, layout: Layout<'_>, view_cursor: Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { use event::Event::{Mouse, Touch}; use mouse::{Button::Left, Event::ButtonReleased}; use touch::Event::{FingerLifted, FingerLost}; - let root_status = process_root_events( + process_root_events( &mut self.menu_roots, view_cursor, tree, @@ -638,7 +638,7 @@ where }); if !create_popup { - return event::Status::Ignored; + return; } #[cfg(all( feature = "multi-window", @@ -665,8 +665,6 @@ where } _ => (), } - - root_status } fn draw( @@ -704,6 +702,7 @@ where ..Default::default() }, shadow: Shadow::default(), + snap: true, }; renderer.fill_quad(path_quad, styling.path); @@ -731,8 +730,9 @@ where fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, _renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { #[cfg(all( @@ -799,18 +799,16 @@ fn process_root_events( clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, -) -> event::Status -where -{ +) { menu_roots .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .map(|((root, t), lo)| { // assert!(t.tag == tree::Tag::stateless()); - root.item.on_event( + root.item.update( &mut t.children[root.index], - event.clone(), + event, lo, view_cursor, renderer, @@ -818,6 +816,5 @@ where shell, viewport, ) - }) - .fold(event::Status::Ignored, event::Status::merge) + }); } diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index c455cd13..d52c929d 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -310,7 +310,7 @@ pub(crate) struct MenuState { } impl MenuState { pub(super) fn layout( - &self, + &mut self, overlay_offset: Vector, slice: MenuSlice, renderer: &crate::Renderer, @@ -329,8 +329,8 @@ impl MenuState { // viewport space children bounds let children_bounds = self.menu_bounds.children_bounds + overlay_offset; let child_nodes = self.menu_bounds.child_positions[start_index..=end_index] - .iter() - .zip(self.menu_bounds.child_sizes[start_index..=end_index].iter()) + .iter_mut() + .zip(self.menu_bounds.child_sizes[start_index..=end_index].iter_mut()) .zip(menu_tree[start_index..=end_index].iter()) .map(|((cp, size), mt)| { let mut position = *cp; @@ -347,7 +347,11 @@ impl MenuState { let limits = Limits::new(size, size); mt.item - .layout(&mut tree[mt.index], renderer, &limits) + .element + .with_data_mut(|e| { + e.as_widget_mut() + .layout(&mut tree[mt.index], renderer, &limits) + }) .move_to(Point::new(0.0, position + self.scroll_offset)) }) .collect::>(); @@ -360,7 +364,7 @@ impl MenuState { overlay_offset: Vector, index: usize, renderer: &crate::Renderer, - menu_tree: &MenuTree, + menu_tree: &mut MenuTree, tree: &mut Tree, ) -> Node { // viewport space children bounds @@ -499,7 +503,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { } else { self.depth }] - .iter() + .iter_mut() .enumerate() .filter(|ms| self.is_overlay || ms.0 < 1) .fold( @@ -545,15 +549,15 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { } #[allow(clippy::too_many_lines)] - fn on_event( + fn update( &mut self, - event: event::Event, + event: &event::Event, layout: Layout<'_>, view_cursor: Cursor, renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> (Option<(usize, MenuState)>, event::Status) { + ) -> Option<(usize, MenuState)> { use event::{ Event::{Mouse, Touch}, Status::{Captured, Ignored}, @@ -569,7 +573,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { .inner .with_data(|data| data.open || data.active_root.len() <= self.depth) { - return (None, Ignored); + return None; } let viewport = layout.bounds(); @@ -583,7 +587,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { }; let menu_status = process_menu_events( self, - event.clone(), + &event, view_cursor, renderer, clipboard, @@ -602,25 +606,28 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { self.main_offset as f32, ); - let ret = match event { - Mouse(WheelScrolled { delta }) => { - process_scroll_events(self, delta, overlay_cursor, viewport_size, overlay_offset) - .merge(menu_status) - } + match event { + Mouse(WheelScrolled { delta }) => process_scroll_events( + self, + shell, + *delta, + overlay_cursor, + viewport_size, + overlay_offset, + ), Mouse(ButtonPressed(Left)) | Touch(FingerPressed { .. }) => { self.tree.inner.with_data_mut(|data| { data.pressed = true; data.view_cursor = view_cursor; }); - Captured } Mouse(CursorMoved { position }) | Touch(FingerMoved { position, .. }) => { - let view_cursor = Cursor::Available(position); + let view_cursor = Cursor::Available(*position); let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset; if !self.is_overlay && !view_cursor.is_over(viewport) { - return (None, menu_status); + return None; } let (new_root, status) = process_overlay_events( @@ -634,7 +641,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { shell, ); - return (new_root, status.merge(menu_status)); + return new_root; } Mouse(ButtonReleased(_)) | Touch(FingerLifted { .. }) => { @@ -694,23 +701,19 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { } state.reset(); - return Captured; } } // close all menus when clicking inside the menu bar if self.bar_bounds.contains(overlay_cursor) { state.reset(); - Captured - } else { - menu_status } }) } - _ => menu_status, + _ => {} }; - (None, ret) + None } #[allow(unused_results, clippy::too_many_lines)] @@ -734,7 +737,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { let render_bounds = if self.is_overlay { Rectangle::new(Point::ORIGIN, viewport.size()) } else { - Rectangle::new(Point::ORIGIN, Size::INFINITY) + Rectangle::new(Point::ORIGIN, Size::INFINITE) }; let styling = theme.appearance(&self.style); @@ -796,6 +799,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { color: styling.border_color, }, shadow: Shadow::default(), + snap: true, }; let menu_color = styling.background; r.fill_quad(menu_quad, menu_color); @@ -815,6 +819,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { ..Default::default() }, shadow: Shadow::default(), + snap: true, }; r.fill_quad(path_quad, styling.path); @@ -867,17 +872,16 @@ impl overlay::Overlay, cursor: mouse::Cursor, renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.on_event(event, layout, cursor, renderer, clipboard, shell) - .1 + ) { + self.update(event, layout, cursor, renderer, clipboard, shell); } fn draw( @@ -903,7 +907,7 @@ impl Widget Widget, cursor: mouse::Cursor, renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - let (new_root, status) = self.on_event(event, layout, cursor, renderer, clipboard, shell); + ) { + let new_root = self.update(event, layout, cursor, renderer, clipboard, shell); #[cfg(all( feature = "multi-window", @@ -997,7 +1001,7 @@ impl Widget Widget Widget Rectangle { Rectangle { x: rect.x - padding.left, y: rect.y - padding.top, - width: rect.width + padding.horizontal(), - height: rect.height + padding.vertical(), + width: rect.width + padding.x(), + height: rect.height + padding.y(), } } @@ -1274,15 +1277,13 @@ pub(super) fn init_root_popup_menu( #[allow(clippy::too_many_arguments)] fn process_menu_events( menu: &mut Menu, - event: event::Event, + event: &event::Event, view_cursor: Cursor, renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, overlay_offset: Vector, -) -> event::Status { - use event::Status; - +) { let my_state = &mut menu.tree; let menu_roots = match &mut menu.menu_roots { Cow::Borrowed(_) => panic!(), @@ -1290,15 +1291,15 @@ fn process_menu_events( }; my_state.inner.with_data_mut(|state| { if state.active_root.len() <= menu.depth { - return event::Status::Ignored; + return; } let Some(hover) = state.menu_states.last_mut() else { - return Status::Ignored; + return; }; let Some(hover_index) = hover.index else { - return Status::Ignored; + return; }; let mt = state.active_root.iter().skip(1).fold( @@ -1321,7 +1322,7 @@ fn process_menu_events( let child_layout = Layout::new(&child_node); // process only the last widget - mt.item.on_event( + mt.item.update( tree, event, child_layout, @@ -1330,7 +1331,7 @@ fn process_menu_events( clipboard, shell, &Rectangle::default(), - ) + ); }) } @@ -1561,12 +1562,12 @@ where fn process_scroll_events( menu: &mut Menu<'_, Message>, + shell: &mut Shell<'_, Message>, delta: mouse::ScrollDelta, overlay_cursor: Point, viewport_size: Size, overlay_offset: Vector, -) -> event::Status -where +) where Message: Clone, { use event::Status::{Captured, Ignored}; @@ -1590,12 +1591,12 @@ where // update if state.menu_states.is_empty() { - return Ignored; + return; } else if state.menu_states.len() == 1 { let last_ms = &mut state.menu_states[0]; if last_ms.index.is_none() { - return Captured; + return; } let (max_offset, min_offset) = calc_offset_bounds(last_ms, viewport_size); @@ -1616,7 +1617,8 @@ where .children_bounds .contains(overlay_cursor) { - return Captured; + shell.capture_event(); + return; } // scroll the second last one @@ -1632,8 +1634,8 @@ where last_two[1].menu_bounds.check_bounds.y += clamped_delta_y; } } - Captured - }) + shell.capture_event(); + }); } #[allow(clippy::pedantic)] @@ -1666,11 +1668,11 @@ fn get_children_layout( .map(|mt| { mt.item .element - .with_data(|w| match w.as_widget().size().height { + .with_data_mut(|w| match w.as_widget_mut().size().height { Length::Fixed(f) => Size::new(width, f), Length::Shrink => { let l_height = w - .as_widget() + .as_widget_mut() .layout( &mut tree[mt.index], renderer, diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index 15dd5810..bd182b9c 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -253,13 +253,16 @@ pub fn menu_items< let key = find_key(&action, key_binds); let mut items = vec![ widget::text(l).into(), - widget::horizontal_space().into(), + widget::space::horizontal().into(), widget::text(key).class(key_class).into(), ]; if let Some(icon) = icon { items.insert(0, widget::icon::icon(icon).size(14).into()); - items.insert(1, widget::Space::with_width(spacing.space_xxs).into()); + items.insert( + 1, + widget::space::horizontal().width(spacing.space_xxs).into(), + ); } let menu_button = menu_button(items).on_press(action.message()); @@ -273,13 +276,16 @@ pub fn menu_items< let mut items = vec![ widget::text(l).into(), - widget::horizontal_space().into(), + widget::space::horizontal().into(), widget::text(key).class(key_class).into(), ]; if let Some(icon) = icon { items.insert(0, widget::icon::icon(icon).size(14).into()); - items.insert(1, widget::Space::with_width(spacing.space_xxs).into()); + items.insert( + 1, + widget::space::horizontal().width(spacing.space_xxs).into(), + ); } let menu_button = menu_button(items); @@ -301,16 +307,21 @@ pub fn menu_items< .width(Length::Fixed(16.0)) .into() } else { - widget::Space::with_width(Length::Fixed(16.0)).into() + widget::space::horizontal() + .width(Length::Fixed(16.0)) + .into() }, - widget::Space::with_width(spacing.space_xxs).into(), + widget::space::horizontal().width(spacing.space_xxs).into(), widget::text(label).align_x(iced::Alignment::Start).into(), - widget::horizontal_space().into(), + widget::space::horizontal().into(), widget::text(key).class(key_class).into(), ]; if let Some(icon) = icon { - items.insert(1, widget::Space::with_width(spacing.space_xxs).into()); + items.insert( + 1, + widget::space::horizontal().width(spacing.space_xxs).into(), + ); items.insert(2, widget::icon::icon(icon).size(14).into()); } @@ -325,7 +336,7 @@ pub fn menu_items< RcElementWrapper::new(crate::Element::from( menu_button::<'static, _>(vec![ widget::text(l.clone()).into(), - widget::horizontal_space().into(), + widget::space::horizontal().into(), widget::icon::from_name("pan-end-symbolic") .size(16) .icon() diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 202173ef..30b75a10 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -60,7 +60,7 @@ pub use iced::widget::{ComboBox, combo_box}; pub use iced::widget::{Container, container}; #[doc(inline)] -pub use iced::widget::{Space, horizontal_space, vertical_space}; +pub use iced::widget::{Space, space}; #[doc(inline)] pub use iced::widget::{Image, image}; @@ -175,47 +175,47 @@ pub use dialog::{Dialog, dialog}; pub mod divider { /// Horizontal variant of a divider. pub mod horizontal { - use iced::widget::{Rule, horizontal_rule}; + use iced::{widget::Rule, widget::rule}; /// Horizontal divider with default thickness #[must_use] pub fn default<'a>() -> Rule<'a, crate::Theme> { - horizontal_rule(1).class(crate::theme::Rule::Default) + rule::horizontal(1).class(crate::theme::Rule::Default) } /// Horizontal divider with light thickness #[must_use] pub fn light<'a>() -> Rule<'a, crate::Theme> { - horizontal_rule(1).class(crate::theme::Rule::LightDivider) + rule::horizontal(1).class(crate::theme::Rule::LightDivider) } /// Horizontal divider with heavy thickness. #[must_use] pub fn heavy<'a>() -> Rule<'a, crate::Theme> { - horizontal_rule(4).class(crate::theme::Rule::HeavyDivider) + rule::horizontal(4).class(crate::theme::Rule::HeavyDivider) } } /// Vertical variant of a divider. pub mod vertical { - use iced::widget::{Rule, vertical_rule}; + use iced::widget::{Rule, rule}; /// Vertical divider with default thickness #[must_use] pub fn default<'a>() -> Rule<'a, crate::Theme> { - vertical_rule(1).class(crate::theme::Rule::Default) + rule::vertical(1).class(crate::theme::Rule::Default) } /// Vertical divider with light thickness #[must_use] pub fn light<'a>() -> Rule<'a, crate::Theme> { - vertical_rule(4).class(crate::theme::Rule::LightDivider) + rule::vertical(4).class(crate::theme::Rule::LightDivider) } /// Vertical divider with heavy thickness. #[must_use] pub fn heavy<'a>() -> Rule<'a, crate::Theme> { - vertical_rule(10).class(crate::theme::Rule::HeavyDivider) + rule::vertical(10).class(crate::theme::Rule::HeavyDivider) } } } diff --git a/src/widget/nav_bar.rs b/src/widget/nav_bar.rs index 140385bc..ad6f9206 100644 --- a/src/widget/nav_bar.rs +++ b/src/widget/nav_bar.rs @@ -180,5 +180,6 @@ pub fn nav_bar_style(theme: &Theme) -> iced_widget::container::Style { radius: cosmic.corner_radii.radius_s.into(), }, shadow: Shadow::default(), + snap: true, } } diff --git a/src/widget/popover.rs b/src/widget/popover.rs index ddc31455..951b3757 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -3,6 +3,7 @@ //! A container which displays an overlay when a popup widget is attached. +use iced::widget; use iced_core::event::{self, Event}; use iced_core::layout; use iced_core::mouse; @@ -33,6 +34,7 @@ pub enum Position { /// A container which displays overlays when a popup widget is assigned. #[must_use] pub struct Popover<'a, Message, Renderer> { + id: widget::Id, content: Element<'a, Message, crate::Theme, Renderer>, modal: bool, popup: Option>, @@ -43,6 +45,7 @@ pub struct Popover<'a, Message, Renderer> { impl<'a, Message, Renderer> Popover<'a, Message, Renderer> { pub fn new(content: impl Into>) -> Self { Self { + id: widget::Id::unique(), content: content.into(), modal: false, popup: None, @@ -51,6 +54,13 @@ impl<'a, Message, Renderer> Popover<'a, Message, Renderer> { } } + /// Set the Id + #[inline] + pub fn id(mut self, id: widget::Id) -> Self { + self.id = id; + self + } + /// A modal popup intercepts user inputs while a popup is active. #[inline] pub fn modal(mut self, modal: bool) -> Self { @@ -83,6 +93,14 @@ impl Widget where Renderer: iced_core::Renderer, { + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn set_id(&mut self, id: widget::Id) { + self.id = id; + } + fn children(&self) -> Vec { if let Some(popup) = &self.popup { vec![Tree::new(&self.content), Tree::new(popup)] @@ -104,42 +122,53 @@ where } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { let tree = content_tree_mut(tree); - self.content.as_widget().layout(tree, renderer, limits) + self.content.as_widget_mut().layout(tree, renderer, limits) } fn operate( - &self, + &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation<()>, + operation: &mut dyn Operation, ) { - self.content - .as_widget() - .operate(content_tree_mut(tree), layout, renderer, operation); + operation.container(Some(&self.id), layout.bounds()); + operation.traverse(&mut |operation| { + self.content.as_widget_mut().operate( + tree, + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), + renderer, + operation, + ); + }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor_position: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { if self.popup.is_some() { if self.modal { if matches!(event, Event::Mouse(_) | Event::Touch(_)) { - return event::Status::Captured; + shell.capture_event(); + return; } } else if let Some(on_close) = self.on_close.as_ref() { if matches!( @@ -153,7 +182,7 @@ where } } - self.content.as_widget_mut().on_event( + self.content.as_widget_mut().update( content_tree_mut(tree), event, layout, @@ -209,8 +238,9 @@ where fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &Renderer, + viewport: &Rectangle, mut translation: Vector, ) -> Option> { if let Some(popup) = &mut self.popup { @@ -248,6 +278,7 @@ where content_tree_mut(tree), layout, renderer, + viewport, translation, ) } @@ -312,7 +343,7 @@ where let limits = layout::Limits::new(Size::UNIT, bounds); let node = self .content - .as_widget() + .as_widget_mut() .layout(self.tree, renderer, &limits); match self.position { Position::Center => { @@ -353,27 +384,28 @@ where operation: &mut dyn Operation<()>, ) { self.content - .as_widget() + .as_widget_mut() .operate(self.tree, layout, renderer, operation); } - fn on_event( + fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor_position: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + ) { if self.modal && matches!(event, Event::Mouse(_) | Event::Touch(_)) && !cursor_position.is_over(layout.bounds()) { - return event::Status::Captured; + shell.capture_event(); + return; } - self.content.as_widget_mut().on_event( + self.content.as_widget_mut().update( self.tree, event, layout, @@ -389,7 +421,6 @@ where &self, layout: Layout<'_>, cursor_position: mouse::Cursor, - viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { if self.modal && !cursor_position.is_over(layout.bounds()) { @@ -400,7 +431,7 @@ where self.tree, layout, cursor_position, - viewport, + &layout.bounds(), renderer, ) } @@ -427,12 +458,16 @@ where fn overlay<'c>( &'c mut self, - layout: Layout<'_>, + layout: Layout<'c>, renderer: &Renderer, ) -> Option> { - self.content - .as_widget_mut() - .overlay(self.tree, layout, renderer, Default::default()) + self.content.as_widget_mut().overlay( + self.tree, + layout, + renderer, + &layout.bounds(), + Default::default(), + ) } } diff --git a/src/widget/radio.rs b/src/widget/radio.rs index ebb75ee2..831e9460 100644 --- a/src/widget/radio.rs +++ b/src/widget/radio.rs @@ -175,7 +175,7 @@ where } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, @@ -186,20 +186,20 @@ where |_| layout::Node::new(Size::new(self.size, self.size)), |limits| { self.label - .as_widget() + .as_widget_mut() .layout(&mut tree.children[0], renderer, limits) }, ) } fn operate( - &self, + &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn iced_core::widget::Operation<()>, ) { - self.label.as_widget().operate( + self.label.as_widget_mut().operate( &mut tree.children[0], layout.children().nth(1).unwrap(), renderer, @@ -207,20 +207,20 @@ where ); } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - let status = self.label.as_widget_mut().on_event( + ) { + self.label.as_widget_mut().update( &mut tree.children[0], - event.clone(), + event, layout.children().nth(1).unwrap(), cursor, renderer, @@ -229,22 +229,19 @@ where viewport, ); - if status == event::Status::Ignored { + if !shell.is_event_captured() { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { if cursor.is_over(layout.bounds()) { shell.publish(self.on_click.clone()); - return event::Status::Captured; + shell.capture_event(); + return; } } _ => {} } - - event::Status::Ignored - } else { - status } } @@ -359,14 +356,16 @@ where fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { self.label.as_widget_mut().overlay( &mut tree.children[0], layout.children().nth(1).unwrap(), renderer, + viewport, translation, ) } diff --git a/src/widget/rectangle_tracker/mod.rs b/src/widget/rectangle_tracker/mod.rs index 632578ff..b3066ecb 100644 --- a/src/widget/rectangle_tracker/mod.rs +++ b/src/widget/rectangle_tracker/mod.rs @@ -204,7 +204,7 @@ where } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, @@ -221,7 +221,7 @@ where } fn operate( - &self, + &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -230,18 +230,18 @@ where self.container.operate(tree, layout, renderer, operation); } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor_position: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &iced_core::Rectangle, - ) -> event::Status { - self.container.on_event( + ) { + self.container.update( tree, event, layout, @@ -290,11 +290,13 @@ where fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { - self.container.overlay(tree, layout, renderer, translation) + self.container + .overlay(tree, layout, renderer, viewport, translation) } fn drag_destinations( diff --git a/src/widget/rectangle_tracker/subscription.rs b/src/widget/rectangle_tracker/subscription.rs index 541862cd..02fa4329 100644 --- a/src/widget/rectangle_tracker/subscription.rs +++ b/src/widget/rectangle_tracker/subscription.rs @@ -18,10 +18,10 @@ pub fn rectangle_tracker_subscription< >( id: I, ) -> Subscription<(I, RectangleUpdate)> { - Subscription::run_with_id( - id, - stream::unfold(State::Ready, move |state| start_listening(id, state)), - ) + Subscription::run_with(id, |id| { + let id = *id; + stream::unfold(State::Ready, move |state| start_listening(id, state)) + }) } pub enum State { diff --git a/src/widget/responsive_container.rs b/src/widget/responsive_container.rs index fbc2df9e..0c7fbad3 100644 --- a/src/widget/responsive_container.rs +++ b/src/widget/responsive_container.rs @@ -6,7 +6,7 @@ use iced_core::layout; use iced_core::mouse; use iced_core::overlay; use iced_core::renderer; -use iced_core::widget::{Id, Tree, tree}; +use iced_core::widget::{Id, Operation, Tree, tree}; use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; pub(crate) fn responsive_container<'a, Message: 'static, Theme, E>( @@ -89,7 +89,7 @@ where } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, @@ -98,7 +98,7 @@ where let unrestricted_size = self.size.unwrap_or_else(|| { let node = self.content - .as_widget() + .as_widget_mut() .layout(&mut tree.children[0], renderer, &Limits::NONE); node.size() }); @@ -115,22 +115,23 @@ where let node = self .content - .as_widget() + .as_widget_mut() .layout(&mut tree.children[0], renderer, limits); let size = node.size(); layout::Node::with_children(size, vec![node]) } fn operate( - &self, + &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn iced_core::widget::Operation<()>, + operation: &mut dyn Operation, ) { - operation.container(Some(&self.id), layout.bounds(), &mut |operation| { - self.content.as_widget().operate( - &mut tree.children[0], + operation.container(Some(&self.id), layout.bounds()); + operation.traverse(&mut |operation| { + self.content.as_widget_mut().operate( + tree, layout .children() .next() @@ -142,17 +143,17 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor_position: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::(); if state.needs_update { @@ -166,7 +167,7 @@ where state.needs_update = false; } - self.content.as_widget_mut().on_event( + self.content.as_widget_mut().update( &mut tree.children[0], event, layout @@ -225,8 +226,9 @@ where fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { self.content.as_widget_mut().overlay( @@ -237,6 +239,7 @@ where .unwrap() .with_virtual_offset(layout.virtual_offset()), renderer, + viewport, translation, ) } diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 0e1af1d0..162d1d21 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -23,7 +23,7 @@ use iced::{ event, keyboard, mouse, touch, window, }; use iced_core::mouse::ScrollDelta; -use iced_core::text::{Ellipsize, LineHeight, Renderer as TextRenderer, Shaping, Wrapping}; +use iced_core::text::{self, Ellipsize, LineHeight, Renderer as TextRenderer, Shaping, Wrapping}; use iced_core::widget::operation::Focusable; use iced_core::widget::{self, operation, tree}; use iced_core::{Border, Point, Renderer as IcedRenderer, Shadow, Text}; @@ -265,22 +265,33 @@ where } } - let text = Text { - content: text.as_ref(), - size: iced::Pixels(self.font_size), - bounds: Size::INFINITY, - font, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, - shaping: Shaping::Advanced, - wrapping: Wrapping::None, - ellipsize: Ellipsize::None, - line_height: self.line_height, - }; - if let Some(paragraph) = state.paragraphs.get_mut(key) { + let text = Text { + content: text.as_ref(), + size: iced::Pixels(self.font_size), + bounds: Size::INFINITE, + font, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Center, + shaping: Shaping::Advanced, + wrapping: Wrapping::None, + line_height: self.line_height, + ellipsize: Ellipsize::default(), + }; paragraph.update(text); } else { + let text = Text { + content: text.to_string(), + size: iced::Pixels(self.font_size), + bounds: Size::INFINITE, + font, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Center, + shaping: Shaping::Advanced, + wrapping: Wrapping::None, + line_height: self.line_height, + ellipsize: Ellipsize::default(), + }; state.paragraphs.insert(key, crate::Plain::new(text)); } } @@ -441,7 +452,7 @@ where } /// Item the previous item in the widget. - fn focus_previous(&mut self, state: &mut LocalState) -> event::Status { + fn focus_previous(&mut self, state: &mut LocalState, shell: &mut Shell<'_, Message>) { match state.focused_item { Item::Tab(entity) => { let mut keys = self.iterate_visible_tabs(state).rev(); @@ -455,7 +466,8 @@ where } state.focused_item = Item::Tab(key); - return event::Status::Captured; + shell.capture_event(); + return; } break; @@ -464,24 +476,28 @@ where if self.prev_tab_sensitive(state) { state.focused_item = Item::PrevButton; - return event::Status::Captured; + shell.capture_event(); + return; } } Item::NextButton => { if let Some(last) = self.last_tab(state) { state.focused_item = Item::Tab(last); - return event::Status::Captured; + shell.capture_event(); + return; } } Item::None => { if self.next_tab_sensitive(state) { state.focused_item = Item::NextButton; - return event::Status::Captured; + shell.capture_event(); + return; } else if let Some(last) = self.last_tab(state) { state.focused_item = Item::Tab(last); - return event::Status::Captured; + shell.capture_event(); + return; } } @@ -489,11 +505,10 @@ where } state.focused_item = Item::None; - event::Status::Ignored } /// Item the next item in the widget. - fn focus_next(&mut self, state: &mut LocalState) -> event::Status { + fn focus_next(&mut self, state: &mut LocalState, shell: &mut Shell<'_, Message>) { match state.focused_item { Item::Tab(entity) => { let mut keys = self.iterate_visible_tabs(state); @@ -506,7 +521,8 @@ where } state.focused_item = Item::Tab(key); - return event::Status::Captured; + shell.capture_event(); + return; } break; @@ -515,24 +531,28 @@ where if self.next_tab_sensitive(state) { state.focused_item = Item::NextButton; - return event::Status::Captured; + shell.capture_event(); + return; } } Item::PrevButton => { if let Some(first) = self.first_tab(state) { state.focused_item = Item::Tab(first); - return event::Status::Captured; + shell.capture_event(); + return; } } Item::None => { if self.prev_tab_sensitive(state) { state.focused_item = Item::PrevButton; - return event::Status::Captured; + shell.capture_event(); + return; } else if let Some(first) = self.first_tab(state) { state.focused_item = Item::Tab(first); - return event::Status::Captured; + shell.capture_event(); + return; } } @@ -540,7 +560,6 @@ where } state.focused_item = Item::None; - event::Status::Ignored } fn iterate_visible_tabs<'b>( @@ -595,12 +614,12 @@ where icon_spacing = f32::from(self.button_spacing); let paragraph = entry.or_insert_with(|| { crate::Plain::new(Text { - content: text.as_ref(), + content: text.to_string(), // TODO should we just use String at this point? size: iced::Pixels(self.font_size), - bounds: Size::INFINITY, + bounds: Size::INFINITE, font, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Center, shaping: Shaping::Advanced, wrapping: Wrapping::default(), ellipsize: Ellipsize::default(), @@ -888,7 +907,7 @@ where } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, @@ -902,17 +921,17 @@ where } #[allow(clippy::too_many_lines)] - fn on_event( + fn update( &mut self, tree: &mut Tree, - mut event: Event, + mut event: &Event, layout: Layout<'_>, cursor_position: mouse::Cursor, _renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &iced::Rectangle, - ) -> event::Status { + ) { let my_bounds = layout.bounds(); let state = tree.state.downcast_mut::(); @@ -941,7 +960,8 @@ where "tab drag source finished id={:?}", my_id ); - return event::Status::Captured; + shell.capture_event(); + return; } } DndEvent::Offer( @@ -1137,8 +1157,8 @@ where }); let (maybe_msg, ret) = state.dnd_state.on_data_received( - mem::take(mime_type), - mem::take(data), + mime_type.clone(), + data.clone(), None:: Message>, on_drop, ); @@ -1160,10 +1180,11 @@ where } if let Some(on_reorder) = self.on_reorder.as_ref() { shell.publish(on_reorder(event)); - return event::Status::Captured; + shell.capture_event(); + return; } } - return ret; + return; } } _ => {} @@ -1175,7 +1196,7 @@ where match event { Event::Touch(touch::Event::FingerPressed { id, .. }) => { - state.fingers_pressed.insert(id); + state.fingers_pressed.insert(*id); } Event::Touch(touch::Event::FingerLifted { id, .. }) => { @@ -1252,7 +1273,8 @@ where || (touch_lifted(&event) && fingers_pressed == 1)) { shell.publish(on_close(key)); - return event::Status::Captured; + shell.capture_event(); + return; } if self.on_middle_press.is_none() { @@ -1263,7 +1285,8 @@ where { if state.middle_clicked == Some(Item::Tab(key)) { shell.publish(on_close(key)); - return event::Status::Captured; + shell.capture_event(); + return; } state.middle_clicked = None; @@ -1315,7 +1338,8 @@ where state.set_focused(); state.focused_item = Item::Tab(key); state.pressed_item = None; - return event::Status::Captured; + shell.capture_event(); + return; } } } @@ -1336,7 +1360,8 @@ where }); shell.publish(on_context(key)); - return event::Status::Captured; + shell.capture_event(); + return; } } } @@ -1347,7 +1372,8 @@ where state.middle_clicked = Some(Item::Tab(key)); if let Some(on_middle_press) = self.on_middle_press.as_ref() { shell.publish(on_middle_press(key)); - return event::Status::Captured; + shell.capture_event(); + return; } } } @@ -1374,7 +1400,7 @@ where ScrollDelta::Lines { y, .. } | ScrollDelta::Pixels { y, .. } => { let mut activate_key = None; - if y < 0.0 { + if *y < 0.0 { let mut prev_key = Entity::null(); for key in self.model.order.iter().copied() { @@ -1386,7 +1412,7 @@ where prev_key = key; } } - } else if y > 0.0 { + } else if *y > 0.0 { let mut buttons = self.model.order.iter().copied(); while let Some(key) = buttons.next() { if self.model.is_active(key) { @@ -1405,7 +1431,8 @@ where shell.publish(on_activate(key)); state.set_focused(); state.focused_item = Item::Tab(key); - return event::Status::Captured; + shell.capture_event(); + return; } } } @@ -1424,7 +1451,7 @@ where if is_pressed(&event) { state.unfocus(); state.pressed_item = None; - return event::Status::Ignored; + return; } } else if is_lifted(&event) { state.pressed_item = None; @@ -1452,7 +1479,8 @@ where position, clipboard, ) { - return event::Status::Captured; + shell.capture_event(); + return; } } } @@ -1475,12 +1503,10 @@ where }) = event { state.focused_visible = true; - return if modifiers == keyboard::Modifiers::SHIFT { - self.focus_previous(state) + return if *modifiers == keyboard::Modifiers::SHIFT { + self.focus_previous(state, shell) } else if modifiers.is_empty() { - self.focus_next(state) - } else { - event::Status::Ignored + self.focus_next(state, shell) }; } @@ -1524,24 +1550,23 @@ where Item::None | Item::Set => (), } - return event::Status::Captured; + shell.capture_event(); + return; } } } - - event::Status::Ignored } fn operate( - &self, + &mut self, tree: &mut Tree, - _layout: Layout<'_>, + layout: Layout<'_>, _renderer: &Renderer, operation: &mut dyn iced_core::widget::Operation<()>, ) { let state = tree.state.downcast_mut::(); - operation.focusable(state, Some(&self.id.0)); - operation.custom(state, Some(&self.id.0)); + operation.focusable(Some(&self.id.0), layout.bounds(), state); + operation.custom(Some(&self.id.0), layout.bounds(), state); if let Item::Set = state.focused_item { if self.prev_tab_sensitive(state) { @@ -1616,6 +1641,7 @@ where bounds, border: appearance.border, shadow: Shadow::default(), + snap: true, }, background, ); @@ -1644,6 +1670,7 @@ where ..Default::default() }, shadow: Shadow::default(), + snap: true, }, background_appearance .background @@ -1692,6 +1719,7 @@ where ..Default::default() }, shadow: Shadow::default(), + snap: true, }, background_appearance .background @@ -1747,6 +1775,7 @@ where bounds, border: Border::default(), shadow: Shadow::default(), + snap: true, }, { let theme = crate::theme::active(); @@ -1842,6 +1871,7 @@ where ..Default::default() }, shadow: Shadow::default(), + snap: true, }, appearance.active.text_color, ); @@ -1878,6 +1908,7 @@ where ..Default::default() }, shadow: Shadow::default(), + snap: true, }, divider_background, ); @@ -1910,6 +1941,7 @@ where button_appearance.border }, shadow: Shadow::default(), + snap: true, }, status_appearance .background @@ -2069,8 +2101,9 @@ where fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: iced_core::Layout<'_>, + layout: iced_core::Layout<'b>, _renderer: &Renderer, + viewport: &iced_core::Rectangle, translation: Vector, ) -> Option> { let state = tree.state.downcast_mut::(); @@ -2662,6 +2695,7 @@ fn draw_drop_indicator( ..Default::default() }, shadow: Shadow::default(), + snap: true, }, Background::Color(color), ); diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index d62bbc99..a17f2071 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -5,10 +5,11 @@ use std::borrow::Cow; use crate::{ Element, theme, - widget::{FlexRow, Row, column, container, flex_row, horizontal_space, row, text}, + widget::{FlexRow, Row, column, container, flex_row, row, text}, }; use derive_setters::Setters; use iced_core::{Length, text::Wrapping}; +use iced_widget::space; use taffy::AlignContent; /// A settings item aligned in a row @@ -25,7 +26,7 @@ pub fn item<'a, Message: 'static>( ) -> Row<'a, Message> { item_row(vec![ text(title).wrapping(Wrapping::Word).into(), - horizontal_space().into(), + space::horizontal().into(), widget, ]) } diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index 9ad81b4d..833e90b8 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -313,6 +313,7 @@ fn container_style(theme: &crate::Theme) -> iced_widget::container::Style { background: None, border, shadow: Shadow::default(), + snap: true, } } diff --git a/src/widget/table/widget/compact.rs b/src/widget/table/widget/compact.rs index 0ad92166..85b5cfce 100644 --- a/src/widget/table/widget/compact.rs +++ b/src/widget/table/widget/compact.rs @@ -131,6 +131,7 @@ where ..Default::default() }, shadow: Default::default(), + snap: true, } })) .apply(widget::mouse_area) diff --git a/src/widget/table/widget/standard.rs b/src/widget/table/widget/standard.rs index c0207f06..79107074 100644 --- a/src/widget/table/widget/standard.rs +++ b/src/widget/table/widget/standard.rs @@ -192,6 +192,7 @@ where ..Default::default() }, shadow: Default::default(), + snap: true, } })) .apply(widget::mouse_area) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index e98d4cfa..5b6a53f3 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -699,7 +699,7 @@ where } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &crate::Renderer, limits: &layout::Limits, @@ -711,7 +711,7 @@ where let size = self.size.unwrap_or_else(|| renderer.default_size().0); - let bounds = limits.resolve(Length::Shrink, Length::Fill, Size::INFINITY); + let bounds = limits.resolve(Length::Shrink, Length::Fill, Size::INFINITE); let value_paragraph = &mut state.value; let v = self.value.to_string(); value_paragraph.update(Text { @@ -723,8 +723,8 @@ where font, bounds, size: iced::Pixels(size), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Center, line_height: text::LineHeight::default(), shaping: text::Shaping::Advanced, wrapping: text::Wrapping::None, @@ -743,8 +743,8 @@ where self.width, self.padding, self.size, - self.leading_icon.as_ref(), - self.trailing_icon.as_ref(), + self.leading_icon.as_mut(), + self.trailing_icon.as_mut(), self.line_height, self.label.as_deref(), self.helper_text.as_deref(), @@ -780,24 +780,25 @@ where } fn operate( - &self, + &mut self, tree: &mut Tree, - _layout: Layout<'_>, - _renderer: &crate::Renderer, - operation: &mut dyn Operation<()>, + layout: Layout<'_>, + renderer: &crate::Renderer, + operation: &mut dyn Operation, ) { + operation.container(Some(&self.id), layout.bounds()); let state = tree.state.downcast_mut::(); - operation.custom(state, Some(&self.id)); - operation.focusable(state, Some(&self.id)); - operation.text_input(state, Some(&self.id)); + operation.focusable(Some(&self.id), layout.bounds(), state); + operation.text_input(Some(&self.id), layout.bounds(), state); } fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &crate::Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { let mut layout_ = Vec::with_capacity(2); @@ -823,24 +824,24 @@ where .filter_map(|((child, state), layout)| { child .as_widget_mut() - .overlay(state, layout, renderer, translation) + .overlay(state, layout, renderer, viewport, translation) }) .collect::>(); (!children.is_empty()).then(|| Group::with_children(children).overlay()) } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor_position: mouse::Cursor, renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { let text_layout = self.text_layout(layout); let mut trailing_icon_layout = None; let font = self.font.unwrap_or_else(|| renderer.default_font()); @@ -877,9 +878,9 @@ where // Enable custom buttons defined on the trailing icon position to be handled. if !self.is_editable_variant { if let Some(trailing_layout) = trailing_icon_layout { - let res = trailing_icon.as_widget_mut().on_event( + let res = trailing_icon.as_widget_mut().update( tree, - event.clone(), + event, trailing_layout, cursor_position, renderer, @@ -888,8 +889,8 @@ where viewport, ); - if res == event::Status::Captured { - return res; + if shell.is_event_captured() { + return; } } } @@ -1133,8 +1134,8 @@ pub fn layout( width: Length, padding: Padding, size: Option, - leading_icon: Option<&Element<'_, Message, crate::Theme, crate::Renderer>>, - trailing_icon: Option<&Element<'_, Message, crate::Theme, crate::Renderer>>, + leading_icon: Option<&mut Element<'_, Message, crate::Theme, crate::Renderer>>, + trailing_icon: Option<&mut Element<'_, Message, crate::Theme, crate::Renderer>>, line_height: text::LineHeight, label: Option<&str>, helper_text: Option<&str>, @@ -1148,7 +1149,7 @@ pub fn layout( let mut nodes = Vec::with_capacity(3); let text_pos = if let Some(label) = label { - let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITY); + let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITE); let state = tree.state.downcast_mut::(); let label_paragraph = &mut state.label; label_paragraph.update(Text { @@ -1156,8 +1157,8 @@ pub fn layout( font, bounds: text_bounds, size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Center, line_height, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::None, @@ -1186,7 +1187,7 @@ pub fn layout( let (leading_icon_width, mut leading_icon) = if let Some((icon, tree)) = leading_icon.zip(children.get_mut(c_i)) { let size = icon.as_widget().size(); - let icon_node = icon.as_widget().layout( + let icon_node = icon.as_widget_mut().layout( tree, renderer, &Limits::NONE.width(size.width).height(size.height), @@ -1201,7 +1202,7 @@ pub fn layout( let (trailing_icon_width, mut trailing_icon) = if let Some((icon, tree)) = trailing_icon.zip(children.get_mut(c_i)) { let size = icon.as_widget().size(); - let icon_node = icon.as_widget().layout( + let icon_node = icon.as_widget_mut().layout( tree, renderer, &Limits::NONE.width(size.width).height(size.height), @@ -1214,7 +1215,7 @@ pub fn layout( let text_limits = limits .width(width) .height(line_height.to_absolute(text_size.into())); - let text_bounds = text_limits.resolve(Length::Shrink, Length::Shrink, Size::INFINITY); + let text_bounds = text_limits.resolve(Length::Shrink, Length::Shrink, Size::INFINITE); let text_node = layout::Node::new( text_bounds - Size::new(leading_icon_width + trailing_icon_width, 0.0), ) @@ -1266,9 +1267,9 @@ pub fn layout( } else { let limits = limits .width(width) - .height(text_input_height + padding.vertical()) + .height(text_input_height + padding.y()) .shrink(padding); - let text_bounds = limits.resolve(Length::Shrink, Length::Shrink, Size::INFINITY); + let text_bounds = limits.resolve(Length::Shrink, Length::Shrink, Size::INFINITE); let text = layout::Node::new(text_bounds).move_to(Point::new(padding.left, padding.top)); @@ -1286,7 +1287,7 @@ pub fn layout( .width(width) .shrink(padding) .height(helper_text_line_height.to_absolute(helper_text_size.into())); - let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITY); + let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITE); let state = tree.state.downcast_mut::(); let helper_text_paragraph = &mut state.helper_text; helper_text_paragraph.update(Text { @@ -1294,8 +1295,8 @@ pub fn layout( font, bounds: text_bounds, size: iced::Pixels(helper_text_size), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Center, line_height: helper_text_line_height, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::None, @@ -1332,7 +1333,7 @@ pub fn layout( #[allow(clippy::cast_possible_truncation)] pub fn update<'a, Message: Clone + 'static>( id: Option, - event: Event, + event: &Event, text_layout: Layout<'_>, edit_button_layout: Option>, cursor: mouse::Cursor, @@ -1357,7 +1358,7 @@ pub fn update<'a, Message: Clone + 'static>( layout: Layout<'_>, manage_value: bool, drag_threshold: f32, -) -> event::Status { +) { let update_cache = |state, value| { replace_paragraph( state, @@ -1420,7 +1421,8 @@ pub fn update<'a, Message: Clone + 'static>( }); } - return event::Status::Captured; + shell.capture_event(); + return; } let target = cursor_position.x - text_layout.bounds().x; @@ -1461,13 +1463,15 @@ pub fn update<'a, Message: Clone + 'static>( if cursor.is_over(selection_bounds) && (on_input.is_some() || manage_value) { state.dragging_state = Some(DraggingState::PrepareDnd(cursor_position)); - return event::Status::Captured; + shell.capture_event(); + return; } // 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; + shell.capture_event(); + return; } (None, click::Kind::Single, _) => { state.setting_selection(value, text_layout.bounds(), target); @@ -1528,7 +1532,8 @@ pub fn update<'a, Message: Clone + 'static>( state.last_click = Some(click); - return event::Status::Captured; + shell.capture_event(); + return; } else { state.unfocus(); @@ -1551,12 +1556,10 @@ pub fn update<'a, Message: Clone + 'static>( } } state.dragging_state = None; - - return if cursor.is_over(layout.bounds()) { - event::Status::Captured - } else { - event::Status::Ignored - }; + if cursor.is_over(layout.bounds()) { + shell.capture_event(); + } + return; } Event::Mouse(mouse::Event::CursorMoved { position }) | Event::Touch(touch::Event::FingerMoved { position, .. }) => { @@ -1573,7 +1576,8 @@ pub fn update<'a, Message: Clone + 'static>( .cursor .select_range(state.cursor.start(value), position); - return event::Status::Captured; + shell.capture_event(); + return; } #[cfg(feature = "wayland")] if let Some(DraggingState::PrepareDnd(start_position)) = state.dragging_state { @@ -1583,7 +1587,7 @@ pub fn update<'a, Message: Clone + 'static>( if distance >= drag_threshold { if is_secure { - return event::Status::Ignored; + return; } let input_text = state.selected_text(&value.to_string()).unwrap_or_default(); @@ -1625,7 +1629,8 @@ pub fn update<'a, Message: Clone + 'static>( state.dragging_state = Some(DraggingState::PrepareDnd(start_position)); } - return event::Status::Captured; + shell.capture_event(); + return; } } Event::Keyboard(keyboard::Event::KeyPressed { @@ -1636,11 +1641,11 @@ pub fn update<'a, Message: Clone + 'static>( .. }) => { let state = state(); - state.keyboard_modifiers = modifiers; + state.keyboard_modifiers = *modifiers; if let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) { if state.is_read_only || (!manage_value && on_input.is_none()) { - return event::Status::Ignored; + return; }; let modifiers = state.keyboard_modifiers; focus.updated_at = Instant::now(); @@ -1724,12 +1729,14 @@ pub fn update<'a, Message: Clone + 'static>( }; update_cache(state, &value); - return event::Status::Captured; + shell.capture_event(); + return; } keyboard::Key::Character("a") | keyboard::Key::Character("A") => { state.cursor.select_all(value); - return event::Status::Captured; + shell.capture_event(); + return; } _ => {} @@ -1737,9 +1744,12 @@ pub fn update<'a, Message: Clone + 'static>( } // Capture keyboard inputs that should be submitted. - if let Some(c) = text.and_then(|t| t.chars().next().filter(|c| !c.is_control())) { + if let Some(c) = text + .as_ref() + .and_then(|t| t.chars().next().filter(|c| !c.is_control())) + { if state.is_read_only || (!manage_value && on_input.is_none()) { - return event::Status::Ignored; + return; }; state.is_pasting = None; @@ -1769,7 +1779,8 @@ pub fn update<'a, Message: Clone + 'static>( update_cache(state, &value); - return event::Status::Captured; + shell.capture_event(); + return; } } @@ -1902,19 +1913,20 @@ pub fn update<'a, Message: Clone + 'static>( shell.publish(on_unfocus.clone()); } - return event::Status::Ignored; + return; }; } keyboard::Key::Named( keyboard::key::Named::ArrowUp | keyboard::key::Named::ArrowDown, ) => { - return event::Status::Ignored; + return; } _ => {} } - return event::Status::Captured; + shell.capture_event(); + return; } } Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => { @@ -1928,31 +1940,30 @@ pub fn update<'a, Message: Clone + 'static>( keyboard::Key::Named(keyboard::key::Named::Tab) | keyboard::Key::Named(keyboard::key::Named::ArrowUp) | keyboard::Key::Named(keyboard::key::Named::ArrowDown) => { - return event::Status::Ignored; + return; } _ => {} } - return event::Status::Captured; + shell.capture_event(); + return; } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { let state = state(); - state.keyboard_modifiers = modifiers; + state.keyboard_modifiers = *modifiers; } Event::Window(window::Event::RedrawRequested(now)) => { let state = state(); if let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) { - focus.now = now; + focus.now = *now; let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS - - (now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS; + - (*now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS; - shell.request_redraw(window::RedrawRequest::At( - now + Duration::from_millis(u64::try_from(millis_until_redraw).unwrap()), - )); + shell.request_redraw(); } } #[cfg(feature = "wayland")] @@ -1962,7 +1973,8 @@ pub fn update<'a, Message: Clone + 'static>( if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) { // TODO: restore value in text input state.dragging_state = None; - return event::Status::Captured; + shell.capture_event(); + return; } } #[cfg(feature = "wayland")] @@ -1974,23 +1986,23 @@ pub fn update<'a, Message: Clone + 'static>( mime_types, surface, }, - )) if rectangle == Some(dnd_id) => { + )) if *rectangle == Some(dnd_id) => { cold(); let state = state(); let is_clicked = text_layout.bounds().contains(Point { - x: x as f32, - y: y as f32, + x: *x as f32, + y: *y as f32, }); let mut accepted = false; - for m in &mime_types { + for m in mime_types { if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) { let clone = m.clone(); accepted = true; } } if accepted { - let target = x as f32 - text_layout.bounds().x; + let target = *x as f32 - text_layout.bounds().x; state.dnd_offer = DndOfferState::HandlingOffer(mime_types.clone(), DndAction::empty()); // existing logic for setting the selection @@ -2002,16 +2014,17 @@ pub fn update<'a, Message: Clone + 'static>( }; state.cursor.move_to(position.unwrap_or(0)); - return event::Status::Captured; + shell.capture_event(); + return; } } #[cfg(feature = "wayland")] Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y })) - if rectangle == Some(dnd_id) => + if *rectangle == Some(dnd_id) => { let state = state(); - let target = x as f32 - text_layout.bounds().x; + let target = *x as f32 - text_layout.bounds().x; // existing logic for setting the selection let position = if target > 0.0 { update_cache(state, value); @@ -2021,10 +2034,11 @@ pub fn update<'a, Message: Clone + 'static>( }; state.cursor.move_to(position.unwrap_or(0)); - return event::Status::Captured; + shell.capture_event(); + return; } #[cfg(feature = "wayland")] - Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if rectangle == Some(dnd_id) => { + Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if *rectangle == Some(dnd_id) => { cold(); let state = state(); if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() { @@ -2033,15 +2047,16 @@ pub fn update<'a, Message: Clone + 'static>( .find(|&&m| mime_types.iter().any(|t| t == m)) else { state.dnd_offer = DndOfferState::None; - return event::Status::Captured; + shell.capture_event(); + return; }; state.dnd_offer = DndOfferState::Dropped; } - return event::Status::Ignored; + return; } #[cfg(feature = "wayland")] - Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if Some(dnd_id) != id => {} + Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if Some(dnd_id) != *id => {} #[cfg(feature = "wayland")] Event::Dnd(DndEvent::Offer( rectangle, @@ -2057,21 +2072,24 @@ pub fn update<'a, Message: Clone + 'static>( state.dnd_offer = DndOfferState::None; } }; - return event::Status::Captured; + shell.capture_event(); + return; } #[cfg(feature = "wayland")] Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type })) - if rectangle == Some(dnd_id) => + if *rectangle == Some(dnd_id) => { cold(); let state = state(); 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; + shell.capture_event(); + return; } - let Ok(content) = String::from_utf8(data) else { - return event::Status::Captured; + let Ok(content) = String::from_utf8(data.clone()) else { + shell.capture_event(); + return; }; let mut editor = Editor::new(unsecured_value, &mut state.cursor); @@ -2091,14 +2109,13 @@ pub fn update<'a, Message: Clone + 'static>( unsecured_value }; update_cache(state, &value); - return event::Status::Captured; + shell.capture_event(); + return; } - return event::Status::Ignored; + return; } _ => {} } - - event::Status::Ignored } /// Draws the [`TextInput`] with the given [`Renderer`], overriding its @@ -2212,6 +2229,7 @@ pub fn draw<'a, Message>( color: Color::TRANSPARENT, blur_radius: 0.0, }, + snap: true, }, appearance.background, ); @@ -2228,6 +2246,7 @@ pub fn draw<'a, Message>( color: Color::TRANSPARENT, blur_radius: 0.0, }, + snap: true, }, Background::Color(Color::TRANSPARENT), ); @@ -2245,6 +2264,7 @@ pub fn draw<'a, Message>( color: Color::TRANSPARENT, blur_radius: 0.0, }, + snap: true, }, appearance.background, ); @@ -2258,8 +2278,8 @@ pub fn draw<'a, Message>( size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)), font: font.unwrap_or_else(|| renderer.default_font()), bounds: label_layout.bounds().size(), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Top, line_height, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::None, @@ -2353,6 +2373,7 @@ pub fn draw<'a, Message>( color: Color::TRANSPARENT, blur_radius: 0.0, }, + snap: true, }, text_color, )), @@ -2403,6 +2424,7 @@ pub fn draw<'a, Message>( color: Color::TRANSPARENT, blur_radius: 0.0, }, + snap: true, }, appearance.selected_fill, )), @@ -2448,8 +2470,8 @@ pub fn draw<'a, Message>( font, bounds: bounds.size(), size: iced::Pixels(size), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Center, line_height: text::LineHeight::default(), shaping: text::Shaping::Advanced, wrapping: text::Wrapping::None, @@ -2497,8 +2519,8 @@ pub fn draw<'a, Message>( size: iced::Pixels(helper_text_size), font, bounds: helper_text_layout.bounds().size(), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Top, line_height: helper_line_height, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::None, @@ -2811,6 +2833,14 @@ impl operation::TextInput for State { fn select_all(&mut self) { Self::select_all(self); } + + fn text(&self) -> &str { + todo!() + } + + fn select_range(&mut self, start: usize, end: usize) { + todo!() + } } #[inline(never)] @@ -2876,11 +2906,11 @@ fn replace_paragraph( state.value = crate::Plain::new(Text { font, line_height, - content: &value.to_string(), + content: value.to_string(), bounds, size: text_size, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Top, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::None, ellipsize: text::Ellipsize::None, diff --git a/src/widget/toaster/widget.rs b/src/widget/toaster/widget.rs index 52604592..240e4867 100644 --- a/src/widget/toaster/widget.rs +++ b/src/widget/toaster/widget.rs @@ -45,13 +45,13 @@ where } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { self.content - .as_widget() + .as_widget_mut() .layout(&mut tree.children[0], renderer, limits) } @@ -85,29 +85,29 @@ where } fn operate<'b>( - &'b self, + &'b mut self, state: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation<()>, ) { self.content - .as_widget() + .as_widget_mut() .operate(&mut state.children[0], layout, renderer, operation); } - fn on_event( + fn update( &mut self, state: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - self.content.as_widget_mut().on_event( + ) { + self.content.as_widget_mut().update( &mut state.children[0], event, layout, @@ -139,8 +139,9 @@ where fn overlay<'b>( &'b mut self, state: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { //TODO: this hides the overlay of the content during the toast @@ -149,6 +150,7 @@ where &mut state.children[0], layout, renderer, + viewport, translation, ) } else { @@ -201,7 +203,7 @@ where let node = self .element - .as_widget() + .as_widget_mut() .layout(self.state, renderer, &limits); let offset = 15.; @@ -228,16 +230,16 @@ where .draw(self.state, renderer, theme, style, layout, cursor, &bounds); } - fn on_event( + fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell, - ) -> event::Status { - self.element.as_widget_mut().on_event( + ) { + self.element.as_widget_mut().update( self.state, event, layout, @@ -253,22 +255,29 @@ where &self, layout: Layout<'_>, cursor: mouse::Cursor, - viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { - self.element - .as_widget() - .mouse_interaction(self.state, layout, cursor, viewport, renderer) + self.element.as_widget().mouse_interaction( + self.state, + layout, + cursor, + &layout.bounds(), + renderer, + ) } fn overlay<'c>( &'c mut self, - layout: Layout<'_>, + layout: Layout<'c>, renderer: &Renderer, ) -> Option> { - self.element - .as_widget_mut() - .overlay(self.state, layout, renderer, Default::default()) + self.element.as_widget_mut().overlay( + self.state, + layout, + renderer, + &layout.bounds(), + Default::default(), + ) } } diff --git a/src/widget/warning.rs b/src/widget/warning.rs index 942ffb8b..4153d647 100644 --- a/src/widget/warning.rs +++ b/src/widget/warning.rs @@ -73,5 +73,6 @@ pub fn warning_container(theme: &Theme) -> widget::container::Style { offset: iced::Vector::new(0.0, 0.0), blur_radius: 0.0, }, + snap: true, } } diff --git a/src/widget/wayland/tooltip/widget.rs b/src/widget/wayland/tooltip/widget.rs index 5194d5c7..ceb234a9 100644 --- a/src/widget/wayland/tooltip/widget.rs +++ b/src/widget/wayland/tooltip/widget.rs @@ -211,7 +211,7 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> } fn layout( - &self, + &mut self, tree: &mut Tree, renderer: &crate::Renderer, limits: &layout::Limits, @@ -224,22 +224,23 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> self.padding, |renderer, limits| { self.content - .as_widget() + .as_widget_mut() .layout(&mut tree.children[0], renderer, limits) }, ) } fn operate( - &self, + &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &crate::Renderer, operation: &mut dyn Operation<()>, ) { - operation.container(None, layout.bounds(), &mut |operation| { - self.content.as_widget().operate( - &mut tree.children[0], + operation.container(Some(&self.id), layout.bounds()); + operation.traverse(&mut |operation| { + self.content.as_widget_mut().operate( + tree, layout .children() .next() @@ -251,17 +252,17 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { let status = update( self.id.clone(), event.clone(), @@ -275,22 +276,21 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> &self.on_surface_action, || tree.state.downcast_mut::(), ); - status.merge( - self.content.as_widget_mut().on_event( - &mut tree.children[0], - event, - layout - .children() - .next() - .unwrap() - .with_virtual_offset(layout.virtual_offset()), - cursor, - renderer, - clipboard, - shell, - viewport, - ), - ) + + self.content.as_widget_mut().update( + &mut tree.children[0], + event, + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), + cursor, + renderer, + clipboard, + shell, + viewport, + ); } #[allow(clippy::too_many_lines)] @@ -359,8 +359,9 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> fn overlay<'b>( &'b mut self, tree: &'b mut Tree, - layout: Layout<'_>, + layout: Layout<'b>, renderer: &crate::Renderer, + viewport: &Rectangle, mut translation: Vector, ) -> Option> { let position = layout.bounds().position(); @@ -374,6 +375,7 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> .unwrap() .with_virtual_offset(layout.virtual_offset()), renderer, + viewport, translation, ) } @@ -451,7 +453,7 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>( on_leave: &Message, on_surface_action: &dyn Fn(crate::surface::Action) -> Message, state: impl FnOnce() -> &'a mut State, -) -> event::Status { +) { match event { Event::Touch(touch::Event::FingerLifted { .. }) => { let state = state(); @@ -461,7 +463,8 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>( shell.publish(on_leave.clone()); - return event::Status::Captured; + shell.capture_event(); + return; } } @@ -579,8 +582,6 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>( } _ => {} } - - event::Status::Ignored } #[allow(clippy::too_many_arguments)] @@ -611,6 +612,7 @@ pub fn draw( radius: styling.border_radius, }, shadow: Shadow::default(), + snap: true, }, Color::TRANSPARENT, ); @@ -632,6 +634,7 @@ pub fn draw( ..Default::default() }, shadow: Shadow::default(), + snap: true, }, Background::Color([0.0, 0.0, 0.0, 0.5].into()), ); @@ -647,6 +650,7 @@ pub fn draw( ..Default::default() }, shadow: Shadow::default(), + snap: true, }, background, ); @@ -669,6 +673,7 @@ pub fn draw( radius: styling.border_radius, }, shadow: Shadow::default(), + snap: true, }, Color::TRANSPARENT, ); diff --git a/src/widget/wrapper.rs b/src/widget/wrapper.rs index 59c0a376..73e476fa 100644 --- a/src/widget/wrapper.rs +++ b/src/widget/wrapper.rs @@ -90,7 +90,7 @@ impl Widget for RcElementWrapper { } fn layout( - &self, + &mut self, tree: &mut tree::Tree, renderer: &crate::Renderer, limits: &crate::iced_core::layout::Limits, @@ -132,30 +132,31 @@ impl Widget for RcElementWrapper { } fn operate( - &self, + &mut self, state: &mut tree::Tree, layout: crate::iced_core::Layout<'_>, renderer: &crate::Renderer, operation: &mut dyn widget::Operation, ) { - self.element.with_data(|e| { - e.as_widget().operate(state, layout, renderer, operation); + self.element.with_data_mut(|e| { + e.as_widget_mut() + .operate(state, layout, renderer, operation); }); } - fn on_event( + fn update( &mut self, state: &mut tree::Tree, - event: crate::iced::Event, + event: &crate::iced::Event, layout: crate::iced_core::Layout<'_>, cursor: crate::iced_core::mouse::Cursor, renderer: &crate::Renderer, clipboard: &mut dyn crate::iced_core::Clipboard, shell: &mut crate::iced_core::Shell<'_, M>, viewport: &Rectangle, - ) -> event::Status { + ) { self.element.with_data_mut(|e| { - e.as_widget_mut().on_event( + e.as_widget_mut().update( state, event, layout, cursor, renderer, clipboard, shell, viewport, ) }) @@ -178,15 +179,16 @@ impl Widget for RcElementWrapper { fn overlay<'a>( &'a mut self, state: &'a mut tree::Tree, - layout: crate::iced_core::Layout<'_>, + layout: crate::iced_core::Layout<'a>, renderer: &crate::Renderer, + viewport: &Rectangle, translation: crate::iced_core::Vector, ) -> Option> { assert_eq!(self.element.thread_id, thread::current().id()); Rc::get_mut(&mut self.element.data).and_then(|e| { e.get_mut() .as_widget_mut() - .overlay(state, layout, renderer, translation) + .overlay(state, layout, renderer, viewport, translation) }) } From e8d53b14ea348bd42223ddc40bd2e463f87bf401 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 19 Feb 2026 18:15:22 -0500 Subject: [PATCH 442/556] chore: various fixes and some cleanup --- Cargo.toml | 22 +- examples/applet/Cargo.toml | 2 + examples/applet/src/window.rs | 15 +- examples/application/Cargo.toml | 7 +- examples/application/src/main.rs | 9 +- src/anim.rs | 51 +++ src/app/mod.rs | 30 +- src/applet/mod.rs | 3 +- src/lib.rs | 2 + src/widget/autosize.rs | 6 +- src/widget/button/widget.rs | 1 - src/widget/cards.rs | 587 ++++++++++++++++++++++++++ src/widget/context_menu.rs | 2 +- src/widget/dnd_destination.rs | 4 +- src/widget/dnd_source.rs | 48 +-- src/widget/header_bar.rs | 6 +- src/widget/id_container.rs | 6 +- src/widget/list/column.rs | 1 + src/widget/menu/menu_bar.rs | 12 +- src/widget/menu/menu_inner.rs | 3 +- src/widget/mod.rs | 4 + src/widget/popover.rs | 23 +- src/widget/radio.rs | 2 +- src/widget/responsive_container.rs | 4 +- src/widget/segmented_button/widget.rs | 7 +- src/widget/toggler.rs | 429 ++++++++++++++++++- src/widget/wayland/tooltip/widget.rs | 2 +- 27 files changed, 1181 insertions(+), 107 deletions(-) create mode 100644 src/anim.rs create mode 100644 src/widget/cards.rs diff --git a/Cargo.toml b/Cargo.toml index 62b8ee7c..01b50733 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,27 +8,28 @@ rust-version = "1.90" name = "cosmic" [features] -# default = ["dbus-config", "multi-window", "a11y"] -default = [ "debug", +default = [ "winit", "tokio", - # "xdg-portal", - "a11y", - "wgpu", - "single-instance", - "surface-message", + "a11y", "dbus-config", "x11", "wayland", "multi-window", - "about","animated-image","autosize", "dbus-config", "pipewire", "process", "rfd", "desktop", "desktop-systemd-scope", "serde-keycode", "qr_code", "markdown", "highlighter" -] +] # default = ["dbus-config", "multi-window", "a11y"] # Accessibility support a11y = ["iced/a11y", "iced_accessibility"] # Enable about widget about = [] # Builds support for animated images -animated-image = ["dep:async-fs", "image/gif", "image/webp", "image/png", "tokio?/io-util", "tokio?/fs"] +animated-image = [ + "dep:async-fs", + "image/gif", + "image/webp", + "image/png", + "tokio?/io-util", + "tokio?/fs", +] # XXX autosize should not be used on winit windows unless dialogs autosize = [] applet = [ @@ -155,6 +156,7 @@ tracing = "0.1.44" unicode-segmentation = "1.12" url = "2.5.8" zbus = { version = "5.13.2", default-features = false, optional = true } +float-cmp = "0.10.0" # Enable DBus feature on Linux targets [target.'cfg(target_os = "linux")'.dependencies] diff --git a/examples/applet/Cargo.toml b/examples/applet/Cargo.toml index f97bff44..844ad8ff 100644 --- a/examples/applet/Cargo.toml +++ b/examples/applet/Cargo.toml @@ -13,6 +13,8 @@ env_logger = "0.10.2" log = "0.4.29" [dependencies.libcosmic] +# path = "../../" +branch = "iced-rebase" git = "https://github.com/pop-os/libcosmic" default-features = false features = ["applet-token"] diff --git a/examples/applet/src/window.rs b/examples/applet/src/window.rs index 66b2040a..547863f2 100644 --- a/examples/applet/src/window.rs +++ b/examples/applet/src/window.rs @@ -13,6 +13,7 @@ pub struct Window { core: Core, popup: Option, example_row: bool, + toggle: bool, selected: Option, } @@ -22,6 +23,7 @@ impl Default for Window { core: Core::default(), popup: None, example_row: false, + toggle: false, selected: None, } } @@ -33,6 +35,7 @@ pub enum Message { ToggleExampleRow(bool), Selected(usize), Surface(cosmic::surface::Action), + Toggle(bool), } impl cosmic::Application for Window { @@ -71,7 +74,6 @@ impl cosmic::Application for Window { Message::ToggleExampleRow(toggled) => { self.example_row = toggled; } - Message::Surface(a) => { return cosmic::task::message(cosmic::Action::Cosmic( cosmic::app::Action::Surface(a), @@ -80,6 +82,9 @@ impl cosmic::Application for Window { Message::Selected(i) => { self.selected = Some(i); } + Message::Toggle(v) => { + self.toggle = v; + } }; Task::none() } @@ -123,9 +128,9 @@ impl cosmic::Application for Window { "Example row", cosmic::widget::container( toggler(state.example_row) - .on_toggle(|value| Message::ToggleExampleRow(value)), - ) - .height(Length::Fixed(50.)), + .on_toggle(Message::ToggleExampleRow) + .width(Length::Fill), + ), )) .add(popup_dropdown( &["1", "asdf", "hello", "test"], @@ -155,7 +160,7 @@ impl cosmic::Application for Window { "oops".into() } - fn style(&self) -> Option { + fn style(&self) -> Option { Some(cosmic::applet::style()) } } diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index 35ff3d30..b1ac1242 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -8,12 +8,11 @@ default = ["wayland"] wayland = ["libcosmic/wayland"] [dependencies] -tracing = "0.1.44" -tracing-subscriber = "0.3.22" -tracing-log = "0.2.0" +env_logger = "0.11" [dependencies.libcosmic] -path = "../../" +git = "https://github.com/pop-os/libcosmic" +branch = "iced-rebase" features = [ "debug", "winit", diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index 45805579..831a47f1 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -54,8 +54,9 @@ impl widget::menu::Action for Action { /// Runs application with these settings #[rustfmt::skip] fn main() -> Result<(), Box> { - // tracing_subscriber::fmt::init(); - // let _ = tracing_log::LogTracer::init(); + + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); + let input = vec![ (Page::Page1, "🖖 Hello from libcosmic.".into()), @@ -66,9 +67,7 @@ fn main() -> Result<(), Box> { let settings = Settings::default() .size(Size::new(1024., 768.)); - - cosmic::app::run::(settings, input)?; - + cosmic::app::run::(settings, input).unwrap(); Ok(()) } diff --git a/src/anim.rs b/src/anim.rs new file mode 100644 index 00000000..3186ff2e --- /dev/null +++ b/src/anim.rs @@ -0,0 +1,51 @@ +use std::time::{Duration, Instant}; + +/// A simple linear interpolation calculation function. +/// p = `percent_complete` in decimal form +#[must_use] +pub fn lerp(start: f32, end: f32, p: f32) -> f32 { + (1.0 - p) * start + p * end +} + +/// A fast smooth interpolation calculation function. +/// p = `percent_complete` in decimal form +#[must_use] +pub fn slerp(start: f32, end: f32, p: f32) -> f32 { + let t = smootherstep(p); + (1.0 - t) * start + t * end +} + +/// utility function which maps a value [0, 1] -> [0, 1] using the smootherstep function +pub fn smootherstep(t: f32) -> f32 { + (6.0 * t.powi(5) - 15.0 * t.powi(4) + 10.0 * t.powi(3)).clamp(0.0, 1.0) +} + +#[derive(Default, Debug)] +pub struct State { + pub last_change: Option, +} + +impl State { + pub fn changed(&mut self, dur: Duration) { + let t = self.t(dur, false); + let diff = dur.mul_f32(t.abs()); + let now = Instant::now(); + self.last_change = Some(now.checked_sub(diff).unwrap_or(now)); + } + + pub fn anim_done(&mut self, dur: Duration) { + if self + .last_change + .is_some_and(|t| Instant::now().duration_since(t) > dur) + { + self.last_change = None; + } + } + + pub fn t(&self, dur: Duration, forward: bool) -> f32 { + let res = self.last_change.map_or(1., |t| { + Instant::now().duration_since(t).as_millis() as f32 / dur.as_millis() as f32 + }); + if forward { res } else { 1. - res } + } +} diff --git a/src/app/mod.rs b/src/app/mod.rs index 1287dc27..abda71c1 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -12,6 +12,7 @@ use cosmic_config::CosmicConfigEntry; pub mod context_drawer; pub use context_drawer::{ContextDrawer, context_drawer}; use iced::application::BootFn; +use iced_core::Widget; pub mod cosmic; pub mod settings; @@ -93,6 +94,7 @@ pub(crate) fn iced_settings( pub(crate) struct BootDataInner { pub flags: A::Flags, pub core: Core, + pub settings: window::Settings, } pub(crate) struct BootData(pub Rc>>>); @@ -102,8 +104,23 @@ impl BootFn, crate::Action (cosmic::Cosmic, iced::Task>) { let mut data = self.0.borrow_mut(); - let data = data.take().unwrap(); - cosmic::Cosmic::::init((data.core, data.flags)) + let mut data = data.take().unwrap(); + let mut tasks = Vec::new(); + #[cfg(feature = "multi-window")] + if data.core.main_window_id().is_some() { + let window_task = iced_runtime::task::oneshot(|channel| { + iced_runtime::Action::Window(iced_runtime::window::Action::Open( + window::Id::RESERVED, + data.settings, + channel, + )) + }); + data.core.set_main_window_id(Some(window::Id::RESERVED)); + tasks.push(window_task.discard()); + } + let (a, t) = cosmic::Cosmic::::init((data.core, data.flags)); + tasks.push(t); + (a, Task::batch(tasks)) } } /// Launch a COSMIC application with the given [`Settings`]. @@ -127,6 +144,7 @@ pub fn run(settings: Settings, flags: App::Flags) -> iced::Res BootData(Rc::new(RefCell::new(Some(BootDataInner:: { flags, core, + settings: window_settings.clone(), })))), cosmic::Cosmic::update, cosmic::Cosmic::view, @@ -147,10 +165,11 @@ pub fn run(settings: Settings, flags: App::Flags) -> iced::Res // app = app.window(window_settings); core.main_window = Some(iced_core::window::Id::RESERVED); } - let mut app = iced::daemon( + let app = iced::daemon( BootData(Rc::new(RefCell::new(Some(BootDataInner:: { flags, core, + settings: window_settings, })))), cosmic::Cosmic::update, cosmic::Cosmic::view, @@ -240,6 +259,7 @@ where BootData(Rc::new(RefCell::new(Some(BootDataInner:: { flags, core, + settings: window_settings.clone(), })))), cosmic::Cosmic::update, cosmic::Cosmic::view, @@ -263,6 +283,7 @@ where BootData(Rc::new(RefCell::new(Some(BootDataInner:: { flags, core, + settings: window_settings, })))), cosmic::Cosmic::update, cosmic::Cosmic::view, @@ -700,7 +721,7 @@ impl ApplicationExt for App { [0, 0, 0, 0] }) .into(), - ) + ); } else { //TODO: this element is added to workaround state issues widgets.push(space::horizontal().width(Length::Shrink).into()); @@ -710,6 +731,7 @@ impl ApplicationExt for App { widgets }); + let content_col = crate::widget::column::with_capacity(2) .push(content_row) .push_maybe(self.footer().map(|footer| { diff --git a/src/applet/mod.rs b/src/applet/mod.rs index ff376aab..f7fa5b62 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -392,7 +392,7 @@ impl Context { } }), ) - .width(Length::Shrink) + .width(Length::Fill) .height(Length::Shrink) .align_x(horizontal_align) .align_y(vertical_align), @@ -584,6 +584,7 @@ pub fn run(flags: App::Flags) -> iced::Result { BootData(Rc::new(RefCell::new(Some(BootDataInner:: { flags, core, + settings: window_settings, })))), cosmic::Cosmic::update, cosmic::Cosmic::view, diff --git a/src/lib.rs b/src/lib.rs index 7e61730b..1a579f96 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,8 @@ pub use apply::{Also, Apply}; pub mod action; pub use action::Action; +pub mod anim; + #[cfg(feature = "winit")] pub mod app; #[cfg(feature = "winit")] diff --git a/src/widget/autosize.rs b/src/widget/autosize.rs index 6a1e6060..937aabf9 100644 --- a/src/widget/autosize.rs +++ b/src/widget/autosize.rs @@ -107,7 +107,7 @@ where } fn diff(&mut self, tree: &mut Tree) { - tree.children[0].diff(&mut self.content); + tree.diff_children(std::slice::from_mut(&mut self.content)); } fn size(&self) -> iced_core::Size { @@ -147,7 +147,7 @@ where operation.container(Some(&self.id), layout.bounds()); operation.traverse(&mut |operation| { self.content.as_widget_mut().operate( - tree, + &mut tree.children[0], layout .children() .next() @@ -193,7 +193,7 @@ where clipboard, shell, viewport, - ) + ); } fn mouse_interaction( diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 54e29786..a4e32378 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -357,7 +357,6 @@ impl<'a, Message: 'a + Clone> Widget operation, ); }); - let state = tree.state.downcast_mut::(); } fn update( diff --git a/src/widget/cards.rs b/src/widget/cards.rs new file mode 100644 index 00000000..b8e17636 --- /dev/null +++ b/src/widget/cards.rs @@ -0,0 +1,587 @@ +//! An expandable stack of cards +use std::time::Duration; + +use self::iced_core::{ + Element, Event, Length, Size, Vector, Widget, border::Radius, id::Id, layout::Node, + renderer::Quad, widget::Tree, +}; +use crate::{ + anim, + iced_core::{self, Border, Shadow}, + widget::{ + button, + card::style::Style, + column, + icon::{self, Handle}, + row, text, + }, +}; +use float_cmp::approx_eq; +use iced::widget; +use iced_core::{widget::tree, window}; + +const ICON_SIZE: u16 = 16; +const TOP_SPACING: u16 = 4; +const VERTICAL_SPACING: f32 = 8.0; +const PADDING: u16 = 16; +const BG_CARD_VISIBLE_HEIGHT: f32 = 4.0; +const BG_CARD_BORDER_RADIUS: f32 = 8.0; +const BG_CARD_MARGIN_STEP: f32 = 8.0; + +/// get an expandable stack of cards +#[allow(clippy::too_many_arguments)] +pub fn cards<'a, Message, F, G>( + id: widget::Id, + card_inner_elements: Vec>, + on_clear_all: Message, + on_show_more: Option, + on_activate: Option, + show_more_label: &'a str, + show_less_label: &'a str, + clear_all_label: &'a str, + show_less_icon: Option, + expanded: bool, +) -> Cards<'a, Message, crate::Renderer> +where + Message: 'static + Clone, + F: 'a + Fn(bool) -> Message, + G: 'a + Fn(usize) -> Message, +{ + Cards::new( + id, + card_inner_elements, + on_clear_all, + on_show_more, + on_activate, + show_more_label, + show_less_label, + clear_all_label, + show_less_icon, + expanded, + ) +} + +impl<'a, Message, Renderer> Cards<'a, Message, Renderer> +where + Renderer: iced_core::text::Renderer, +{ + fn fully_expanded(&self, t: f32) -> bool { + self.expanded && self.elements.len() > 1 && self.can_show_more && approx_eq!(f32, t, 1.0) + } + + fn fully_unexpanded(&self, t: f32) -> bool { + self.elements.len() == 1 + || (!self.expanded && (!self.can_show_more || approx_eq!(f32, t, 0.0))) + } +} + +/// An expandable stack of cards. +#[allow(missing_debug_implementations)] +pub struct Cards<'a, Message, Renderer = crate::Renderer> +where + Renderer: iced_core::text::Renderer, +{ + id: Id, + show_less_button: Element<'a, Message, crate::Theme, Renderer>, + clear_all_button: Element<'a, Message, crate::Theme, Renderer>, + elements: Vec>, + expanded: bool, + can_show_more: bool, + width: Length, + anim_multiplier: f32, + duration: Duration, +} + +impl<'a, Message> Cards<'a, Message, crate::Renderer> +where + Message: Clone + 'static, +{ + /// Get an expandable stack of cards + #[allow(clippy::too_many_arguments)] + pub fn new( + id: widget::Id, + card_inner_elements: Vec>, + on_clear_all: Message, + on_show_more: Option, + on_activate: Option, + show_more_label: &'a str, + show_less_label: &'a str, + clear_all_label: &'a str, + show_less_icon: Option, + expanded: bool, + ) -> Self + where + F: 'a + Fn(bool) -> Message, + G: 'a + Fn(usize) -> Message, + { + let can_show_more = card_inner_elements.len() > 1 && on_show_more.is_some(); + + Self { + can_show_more, + id: Id::unique(), + show_less_button: { + let mut show_less_children = Vec::with_capacity(3); + if let Some(source) = show_less_icon { + show_less_children.push(icon::icon(source).size(ICON_SIZE).into()); + } + show_less_children.push(text::body(show_less_label).width(Length::Shrink).into()); + show_less_children.push( + icon::from_name("pan-up-symbolic") + .size(ICON_SIZE) + .icon() + .into(), + ); + + let button_content = row::with_children(show_less_children) + .align_y(iced_core::Alignment::Center) + .spacing(TOP_SPACING) + .width(Length::Shrink); + + Element::from( + button::custom(button_content) + .class(crate::theme::Button::Text) + .width(Length::Shrink) + .on_press_maybe(on_show_more.as_ref().map(|f| f(false))) + .padding([PADDING / 2, PADDING]), + ) + }, + clear_all_button: Element::from( + button::custom(text(clear_all_label)) + .class(crate::theme::Button::Text) + .width(Length::Shrink) + .on_press(on_clear_all) + .padding([PADDING / 2, PADDING]), + ), + elements: card_inner_elements + .into_iter() + .enumerate() + .map(|(i, w)| { + let custom_content = if i == 0 && !expanded && can_show_more { + column::with_capacity(2) + .push(w) + .push(text::caption(show_more_label)) + .spacing(VERTICAL_SPACING) + .align_x(iced_core::Alignment::Center) + .into() + } else { + w + }; + + let b = crate::iced::widget::button(custom_content) + .class(crate::theme::iced::Button::Card) + .padding(PADDING); + if i == 0 && !expanded && can_show_more { + b.on_press_maybe(on_show_more.as_ref().map(|f| f(true))) + } else { + b.on_press_maybe(on_activate.as_ref().map(|f| f(i))) + } + .into() + }) + // we will set the width of the container to shrink, then when laying out the top bar + // we will set the fill limit to the max of the shrink top bar width and the max shrink width of the + // cards + .collect(), + width: Length::Shrink, + anim_multiplier: 1.0, + expanded, + duration: Duration::from_millis(200), + } + } + + /// Set the width of the cards stack + #[must_use] + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + #[must_use] + /// The default animation time is 100ms, to speed up the toggle + /// animation use a value less than 1.0, and to slow down the + /// animation use a value greater than 1.0. + pub fn anim_multiplier(mut self, multiplier: f32) -> Self { + self.anim_multiplier = multiplier; + self + } + + pub fn duration(mut self, dur: Duration) -> Self { + self.duration = dur; + self + } + + pub fn id(mut self, id: Id) -> Self { + self.id = id; + self + } +} + +impl<'a, Message, Renderer> Widget for Cards<'a, Message, Renderer> +where + Message: 'a + Clone, + Renderer: 'a + iced_core::Renderer + iced_core::text::Renderer, +{ + fn children(&self) -> Vec { + [&self.show_less_button, &self.clear_all_button] + .iter() + .map(|w| Tree::new(w.as_widget())) + .chain(self.elements.iter().map(|w| Tree::new(w.as_widget()))) + .collect() + } + + fn diff(&mut self, tree: &mut Tree) { + let mut children: Vec<_> = vec![ + self.show_less_button.as_widget_mut(), + self.clear_all_button.as_widget_mut(), + ] + .into_iter() + .chain( + self.elements + .iter_mut() + .map(iced_core::Element::as_widget_mut), + ) + .collect(); + + tree.diff_children(children.as_mut_slice()); + } + + #[allow(clippy::too_many_lines)] + fn layout( + &mut self, + tree: &mut Tree, + renderer: &Renderer, + limits: &iced_core::layout::Limits, + ) -> iced_core::layout::Node { + let my_state = tree.state.downcast_ref::(); + + let mut children = Vec::with_capacity(1 + self.elements.len()); + let mut size = Size::new(0.0, 0.0); + let tree_children = &mut tree.children; + let count = self.elements.len(); + if self.elements.is_empty() { + return Node::with_children(Size::new(1., 1.), children); + } + let s = anim::smootherstep(my_state.anim.t(self.duration, self.expanded)); + let fully_expanded: bool = self.fully_expanded(s); + let fully_unexpanded: bool = self.fully_unexpanded(s); + + let show_less = &mut self.show_less_button; + let clear_all = &mut self.clear_all_button; + + let show_less_node = if self.can_show_more { + show_less + .as_widget_mut() + .layout(&mut tree_children[0], renderer, limits) + } else { + Node::new(Size::default()) + }; + let clear_all_node = + clear_all + .as_widget_mut() + .layout(&mut tree_children[1], renderer, limits); + size.width += show_less_node.size().width + clear_all_node.size().width; + + let custom_limits = limits.min_width(size.width); + for (c, t) in self.elements.iter_mut().zip(tree_children[2..].iter_mut()) { + let card_node = c.as_widget_mut().layout(t, renderer, &custom_limits); + size.width = size.width.max(card_node.size().width); + } + + if fully_expanded { + let show_less = &mut self.show_less_button; + let clear_all = &mut self.clear_all_button; + + let show_less_node = if self.can_show_more { + show_less + .as_widget_mut() + .layout(&mut tree_children[0], renderer, limits) + } else { + Node::new(Size::default()) + }; + let clear_all_node = if self.can_show_more { + let mut n = + clear_all + .as_widget_mut() + .layout(&mut tree_children[1], renderer, limits); + let clear_all_node_size = n.size(); + n = clear_all_node + .translate(Vector::new(size.width - clear_all_node_size.width, 0.0)); + size.height += show_less_node.size().height.max(n.size().height) + VERTICAL_SPACING; + n + } else { + Node::new(Size::default()) + }; + + children.push(show_less_node); + children.push(clear_all_node); + } + + let custom_limits = limits + .min_width(size.width) + .max_width(size.width) + .width(Length::Fixed(size.width)); + + for (i, (c, t)) in self + .elements + .iter_mut() + .zip(tree_children[2..].iter_mut()) + .enumerate() + { + let progress = s * size.height; + let card_node = c + .as_widget_mut() + .layout(t, renderer, &custom_limits) + .translate(Vector::new(0.0, progress)); + + size.height = size.height.max(progress + card_node.size().height); + + children.push(card_node); + + if fully_unexpanded { + let width = children.last().unwrap().bounds().width; + + // push the background card nodes + for i in 1..self.elements.len().min(3) { + // height must be 16px for 8px padding + // but we only want 4px visible + + let margin = f32::from(u8::try_from(i).unwrap()) * BG_CARD_MARGIN_STEP; + let node = + Node::new(Size::new(width - 2.0 * margin, BG_CARD_BORDER_RADIUS * 2.0)) + .translate(Vector::new( + margin, + size.height - BG_CARD_BORDER_RADIUS * 2.0 + BG_CARD_VISIBLE_HEIGHT, + )); + size.height += BG_CARD_VISIBLE_HEIGHT; + children.push(node); + } + break; + } + + if i + 1 < count { + size.height += VERTICAL_SPACING; + } + } + + Node::with_children(size, children) + } + + fn draw( + &self, + state: &iced_core::widget::Tree, + renderer: &mut Renderer, + theme: &crate::Theme, + style: &iced_core::renderer::Style, + layout: iced_core::Layout<'_>, + cursor: iced_core::mouse::Cursor, + viewport: &iced_core::Rectangle, + ) { + let my_state = state.state.downcast_ref::(); + + // there are 4 cases for drawing + // 1. empty entries list + // Nothing to draw + // 2. un-expanded + // go through the layout, draw the card, the inner card, and the bg cards + // 3. expanding / unexpanding + // go through the layout. draw each card and its inner card + // 4. expanded => + // go through the layout. draw the top bar, and do all of 3 + // cards may be hovered + // any buttons may have a hover state as well + if self.elements.is_empty() { + return; + } + + let t = my_state.anim.t(self.duration, self.expanded); + let fully_unexpanded = self.fully_unexpanded(t); + let fully_expanded = self.fully_expanded(t); + + let mut layout = layout.children(); + let mut tree_children = state.children.iter(); + + if fully_expanded { + let show_less = &self.show_less_button; + let clear_all = &self.clear_all_button; + + let show_less_layout = layout.next().unwrap(); + let clear_all_layout = layout.next().unwrap(); + + show_less.as_widget().draw( + tree_children.next().unwrap(), + renderer, + theme, + style, + show_less_layout, + cursor, + viewport, + ); + + clear_all.as_widget().draw( + tree_children.next().unwrap(), + renderer, + theme, + style, + clear_all_layout, + cursor, + viewport, + ); + } else { + _ = tree_children.next(); + _ = tree_children.next(); + } + + // Draw first to appear behind + if fully_unexpanded { + let card_layout = layout.next().unwrap(); + let appearance = Style::default(); + let bg_layout = layout.collect::>(); + for (i, layout) in (0..2).zip(bg_layout.into_iter()).rev() { + renderer.fill_quad( + Quad { + bounds: layout.bounds(), + border: Border { + radius: Radius::from([ + 0.0, + 0.0, + BG_CARD_BORDER_RADIUS, + BG_CARD_BORDER_RADIUS, + ]), + ..Default::default() + }, + shadow: Shadow::default(), + snap: true, + }, + if i == 0 { + appearance.card_1 + } else { + appearance.card_2 + }, + ); + } + self.elements[0].as_widget().draw( + tree_children.next().unwrap(), + renderer, + theme, + style, + card_layout, + cursor, + viewport, + ); + } else { + let layout = layout.collect::>(); + // draw in reverse order so later cards appear behind earlier cards + for ((inner, layout), c_state) in self + .elements + .iter() + .rev() + .zip(layout.into_iter().rev()) + .zip(tree_children.rev()) + { + inner + .as_widget() + .draw(c_state, renderer, theme, style, layout, cursor, viewport); + } + } + } + + fn update( + &mut self, + state: &mut Tree, + event: &iced_core::Event, + layout: iced_core::Layout<'_>, + cursor: iced_core::mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn iced_core::Clipboard, + shell: &mut iced_core::Shell<'_, Message>, + viewport: &iced_core::Rectangle, + ) { + if self.elements.is_empty() { + return; + } + + if let Event::Window(window::Event::RedrawRequested(_)) = event { + let state = state.state.downcast_mut::(); + + state.anim.anim_done(self.duration); + if state.anim.last_change.is_some() { + shell.request_redraw(); + shell.invalidate_layout(); + } + } + + let my_state = state.state.downcast_ref::(); + + let mut layout = layout.children(); + let mut tree_children = state.children.iter_mut(); + let t = my_state.anim.t(self.duration, self.expanded); + let fully_expanded = self.fully_expanded(t); + let fully_unexpanded = self.fully_unexpanded(t); + let show_less_state = tree_children.next(); + let clear_all_state = tree_children.next(); + + if fully_expanded { + let c_layout = layout.next().unwrap(); + let state = show_less_state.unwrap(); + self.show_less_button.as_widget_mut().update( + state, event, c_layout, cursor, renderer, clipboard, shell, viewport, + ); + + if shell.is_event_captured() { + return; + } + + let c_layout = layout.next().unwrap(); + let state = clear_all_state.unwrap(); + self.clear_all_button.as_widget_mut().update( + state, &event, c_layout, cursor, renderer, clipboard, shell, viewport, + ); + } + + if shell.is_event_captured() { + return; + } + + for ((inner, layout), c_state) in self.elements.iter_mut().zip(layout).zip(tree_children) { + inner.as_widget_mut().update( + c_state, &event, layout, cursor, renderer, clipboard, shell, viewport, + ); + if shell.is_event_captured() || fully_unexpanded { + break; + } + } + } + + fn size(&self) -> Size { + Size::new(self.width, Length::Shrink) + } + + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(State::default()) + } + + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn set_id(&mut self, id: Id) { + self.id = id; + } +} + +impl<'a, Message> From> for Element<'a, Message, crate::Theme, crate::Renderer> +where + Message: Clone + 'a, +{ + fn from(cards: Cards<'a, Message>) -> Self { + Self::new(cards) + } +} + +#[derive(Debug, Default)] +pub struct State { + anim: anim::State, +} diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 008660a7..143a78b8 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -249,7 +249,7 @@ impl Widget } fn diff(&mut self, tree: &mut Tree) { - tree.children[0].diff(self.content.as_widget_mut()); + tree.diff_children(std::slice::from_mut(&mut self.content)); let state = tree.state.downcast_mut::(); state.menu_bar_state.inner.with_data_mut(|inner| { menu_roots_diff(self.context_menu.as_mut().unwrap(), &mut inner.tree); diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index 9faa2605..b0a23fad 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -291,7 +291,7 @@ impl Widget } fn diff(&mut self, tree: &mut Tree) { - tree.children[0].diff(self.container.as_widget_mut()); + tree.diff_children(std::slice::from_mut(&mut self.container)); } fn state(&self) -> iced_core::widget::tree::State { @@ -337,7 +337,7 @@ impl Widget shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - let s = self.container.as_widget_mut().update( + self.container.as_widget_mut().update( &mut tree.children[0], event, layout, diff --git a/src/widget/dnd_source.rs b/src/widget/dnd_source.rs index c8627482..07b448a5 100644 --- a/src/widget/dnd_source.rs +++ b/src/widget/dnd_source.rs @@ -131,21 +131,25 @@ impl< ); } + #[must_use] pub fn on_start(mut self, on_start: Option) -> Self { self.on_start = on_start; self } + #[must_use] pub fn on_cancel(mut self, on_cancelled: Option) -> Self { self.on_cancelled = on_cancelled; self } + #[must_use] pub fn on_finish(mut self, on_finish: Option) -> Self { self.on_finish = on_finish; self } + #[must_use] pub fn window(mut self, window: window::Id) -> Self { self.window = Some(window); self @@ -164,7 +168,7 @@ impl iced_core::widget::tree::State { @@ -197,19 +201,15 @@ impl, viewport: &Rectangle, ) { - let ret = self.container.as_widget_mut().update( + self.container.as_widget_mut().update( &mut tree.children[0], - &event, + event, layout, cursor, renderer, @@ -241,12 +241,11 @@ impl { if let Some(position) = cursor.position() { if !state.hovered { - return ret; + return; } state.left_pressed_position = Some(position); shell.capture_event(); - return; } } mouse::Event::ButtonReleased(mouse::Button::Left) @@ -254,7 +253,6 @@ impl { if let Some(position) = cursor.position() { @@ -262,7 +260,7 @@ impl self.drag_threshold { @@ -281,16 +279,15 @@ impl return ret, + _ => (), }, Event::Dnd(DndEvent::Source(SourceEvent::Cancelled)) => { if state.is_dragging { @@ -301,7 +298,6 @@ impl { if state.is_dragging { @@ -312,11 +308,9 @@ impl return ret, + _ => (), } - ret } fn mouse_interaction( diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 695c8405..1465a9d7 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -5,7 +5,7 @@ use crate::cosmic_theme::{Density, Spacing}; use crate::{Element, theme, widget}; use apply::Apply; use derive_setters::Setters; -use iced::Length; +use iced::{Length, mouse}; use iced_core::{Vector, Widget, widget::tree}; use std::{borrow::Cow, cmp}; @@ -206,6 +206,7 @@ impl Widget ) { let child_state = &mut state.children[0]; let child_layout = layout.children().next().unwrap(); + self.header_bar_inner.as_widget_mut().update( child_state, event, @@ -215,7 +216,7 @@ impl Widget clipboard, shell, viewport, - ) + ); } fn mouse_interaction( @@ -435,6 +436,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { if let Some(message) = self.on_maximize.clone() { widget = widget.on_release(message); } + if let Some(message) = self.on_double_click.clone() { widget = widget.on_double_press(message); } diff --git a/src/widget/id_container.rs b/src/widget/id_container.rs index c8e49e04..716ee138 100644 --- a/src/widget/id_container.rs +++ b/src/widget/id_container.rs @@ -57,7 +57,7 @@ where } fn diff(&mut self, tree: &mut Tree) { - tree.children[0].diff(&mut self.content); + tree.diff_children(std::slice::from_mut(&mut self.content)); } fn size(&self) -> iced_core::Size { @@ -88,7 +88,7 @@ where operation.container(Some(&self.id), layout.bounds()); operation.traverse(&mut |operation| { self.content.as_widget_mut().operate( - tree, + &mut tree.children[0], layout .children() .next() @@ -124,7 +124,7 @@ where clipboard, shell, viewport, - ) + ); } fn mouse_interaction( diff --git a/src/widget/list/column.rs b/src/widget/list/column.rs index 49df998a..136b49ea 100644 --- a/src/widget/list/column.rs +++ b/src/widget/list/column.rs @@ -112,6 +112,7 @@ impl<'a, Message: 'static> ListColumn<'a, Message> { crate::widget::column::with_children(self.children) .spacing(self.spacing) .padding(self.padding) + .width(iced::Length::Fill) .apply(container) .padding([self.spacing, 0]) .class(self.style) diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 05fcc133..9d4b09b0 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -580,7 +580,7 @@ where &mut self.menu_roots, view_cursor, tree, - &event, + event, layout, renderer, clipboard, @@ -609,6 +609,13 @@ where }); match event { + Mouse(mouse::Event::ButtonPressed(Left)) + | Touch(touch::Event::FingerPressed { .. }) + if view_cursor.is_over(layout.bounds()) => + { + // TODO should we track that it has been pressed? + shell.capture_event(); + } Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. } | FingerLost { .. }) => { let create_popup = my_state.inner.with_data_mut(|state| { let mut create_popup = false; @@ -627,6 +634,7 @@ where ))] { let surface_action = self.on_surface_action.as_ref().unwrap(); + shell.capture_event(); shell.publish(surface_action(crate::surface::action::destroy_popup( _id, @@ -640,6 +648,7 @@ where if !create_popup { return; } + shell.capture_event(); #[cfg(all( feature = "multi-window", feature = "wayland", @@ -653,6 +662,7 @@ where Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered) if open && view_cursor.is_over(layout.bounds()) => { + shell.capture_event(); #[cfg(all( feature = "multi-window", feature = "wayland", diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index d52c929d..4f97d30e 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -1008,7 +1008,7 @@ impl Widget( menu_bounds, }; state.menu_states.push(ms); - // Hack to ensure menu opens properly shell.invalidate_layout(); diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 30b75a10..f63cdc37 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -127,6 +127,10 @@ pub use color_picker::{ColorPicker, ColorPickerModel}; #[doc(inline)] pub use iced::widget::qr_code; +mod cards; +#[doc(inline)] +pub use cards::cards; + pub mod context_drawer; #[doc(inline)] pub use context_drawer::{ContextDrawer, context_drawer}; diff --git a/src/widget/popover.rs b/src/widget/popover.rs index 951b3757..7a82cd86 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -127,7 +127,7 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let tree = content_tree_mut(tree); + let tree = &mut tree.children[0]; self.content.as_widget_mut().layout(tree, renderer, limits) } @@ -138,19 +138,9 @@ where renderer: &Renderer, operation: &mut dyn Operation, ) { - operation.container(Some(&self.id), layout.bounds()); - operation.traverse(&mut |operation| { - self.content.as_widget_mut().operate( - tree, - layout - .children() - .next() - .unwrap() - .with_virtual_offset(layout.virtual_offset()), - renderer, - operation, - ); - }); + self.content + .as_widget_mut() + .operate(content_tree_mut(tree), layout, renderer, operation); } fn update( @@ -183,7 +173,7 @@ where } self.content.as_widget_mut().update( - content_tree_mut(tree), + &mut tree.children[0], event, layout, cursor_position, @@ -265,7 +255,6 @@ where overlay_position.y = overlay_position.y.round(); translation.x += overlay_position.x; translation.y += overlay_position.y; - Some(overlay::Element::new(Box::new(Overlay { tree: &mut tree.children[1], content: popup, @@ -275,7 +264,7 @@ where }))) } else { self.content.as_widget_mut().overlay( - content_tree_mut(tree), + &mut tree.children[0], layout, renderer, viewport, diff --git a/src/widget/radio.rs b/src/widget/radio.rs index 831e9460..338c0a4e 100644 --- a/src/widget/radio.rs +++ b/src/widget/radio.rs @@ -165,7 +165,7 @@ where } fn diff(&mut self, tree: &mut Tree) { - tree.children[0].diff(&mut self.label); + tree.diff_children(std::slice::from_mut(&mut self.label)); } fn size(&self) -> Size { Size { diff --git a/src/widget/responsive_container.rs b/src/widget/responsive_container.rs index 0c7fbad3..3bb44276 100644 --- a/src/widget/responsive_container.rs +++ b/src/widget/responsive_container.rs @@ -81,7 +81,7 @@ where } fn diff(&mut self, tree: &mut Tree) { - tree.children[0].diff(&mut self.content); + tree.diff_children(std::slice::from_mut(&mut self.content)); } fn size(&self) -> iced_core::Size { @@ -131,7 +131,7 @@ where operation.container(Some(&self.id), layout.bounds()); operation.traverse(&mut |operation| { self.content.as_widget_mut().operate( - tree, + &mut tree.children[0], layout .children() .next() diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 162d1d21..f6de999e 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -2047,6 +2047,9 @@ where bounds.y = center_y; if self.model.text(key).is_some_and(|text| !text.is_empty()) { + // FIXME why has this behavior changed? Does the center alignment not work with infinite bounds now? + bounds.y -= state.paragraphs[key].min_height() / 2.; + // Draw the text for this segmented button or tab. renderer.fill_paragraph( state.paragraphs[key].raw(), @@ -2055,7 +2058,9 @@ where Rectangle { x: bounds.x, width: bounds.width, - ..original_bounds + height: original_bounds.height, + y: bounds.y, + // ..original_bounds, }, ); } diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index 65179d99..fafc6d70 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -1,17 +1,418 @@ -// Copyright 2022 System76 -// SPDX-License-Identifier: MPL-2.0 +//! Show toggle controls using togglers. -use iced::{Length, widget}; -use iced_core::text; +use std::time::{Duration, Instant}; -pub fn toggler<'a, Message, Theme: iced_widget::toggler::Catalog, Renderer>( - is_checked: bool, -) -> widget::Toggler<'a, Message, Theme, Renderer> -where - Renderer: iced_core::Renderer + text::Renderer, -{ - widget::Toggler::new(is_checked) - .size(24) - .spacing(0) - .width(Length::Shrink) +use crate::{Element, anim, iced_core::Border, iced_widget::toggler::Status}; +use iced_core::{ + Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment, event, + layout, mouse, + renderer::{self, Renderer}, + text, + widget::{self, Tree, tree}, + window, +}; +use iced_widget::Id; + +pub use crate::iced_widget::toggler::{Catalog, Style}; + +pub fn toggler<'a, Message>(is_checked: bool) -> Toggler<'a, Message> { + Toggler::new(is_checked) +} +/// A toggler widget. +#[allow(missing_debug_implementations)] +pub struct Toggler<'a, Message> { + id: Id, + is_toggled: bool, + on_toggle: Option Message + 'a>>, + label: Option, + width: Length, + size: f32, + text_size: Option, + text_line_height: text::LineHeight, + text_alignment: text::Alignment, + text_shaping: text::Shaping, + spacing: f32, + font: Option, + duration: Duration, +} + +impl<'a, Message> Toggler<'a, Message> { + /// The default size of a [`Toggler`]. + pub const DEFAULT_SIZE: f32 = 24.0; + + /// Creates a new [`Toggler`]. + /// + /// It expects: + /// * a boolean describing whether the [`Toggler`] is checked or not + /// * An optional label for the [`Toggler`] + /// * a function that will be called when the [`Toggler`] is toggled. It + /// will receive the new state of the [`Toggler`] and must produce a + /// `Message`. + pub fn new(is_toggled: bool) -> Self { + Toggler { + id: Id::unique(), + is_toggled, + on_toggle: None, + label: None, + width: Length::Fill, + size: Self::DEFAULT_SIZE, + text_size: None, + text_line_height: text::LineHeight::default(), + text_alignment: text::Alignment::Left, + text_shaping: text::Shaping::Advanced, + spacing: 0.0, + font: None, + duration: Duration::from_millis(200), + } + } + + /// Sets the size of the [`Toggler`]. + pub fn size(mut self, size: impl Into) -> Self { + self.size = size.into().0; + self + } + + /// Sets the width of the [`Toggler`]. + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Sets the text size o the [`Toggler`]. + pub fn text_size(mut self, text_size: impl Into) -> Self { + self.text_size = Some(text_size.into().0); + self + } + + /// Sets the text [`LineHeight`] of the [`Toggler`]. + pub fn text_line_height(mut self, line_height: impl Into) -> Self { + self.text_line_height = line_height.into(); + self + } + + /// Sets the horizontal alignment of the text of the [`Toggler`] + pub fn text_alignment(mut self, alignment: text::Alignment) -> Self { + self.text_alignment = alignment; + self + } + + /// Sets the [`text::Shaping`] strategy of the [`Toggler`]. + pub fn text_shaping(mut self, shaping: text::Shaping) -> Self { + self.text_shaping = shaping; + self + } + + /// Sets the spacing between the [`Toggler`] and the text. + pub fn spacing(mut self, spacing: impl Into) -> Self { + self.spacing = spacing.into().0; + self + } + + /// Sets the [`Font`] of the text of the [`Toggler`] + /// + /// [`Font`]: cosmic::iced::text::Renderer::Font + pub fn font(mut self, font: impl Into) -> Self { + self.font = Some(font.into()); + self + } + + pub fn id(mut self, id: Id) -> Self { + self.id = id; + self + } + + pub fn duration(mut self, dur: Duration) -> Self { + self.duration = dur; + self + } + + pub fn on_toggle(mut self, on_toggle: impl Fn(bool) -> Message + 'a) -> Self { + self.on_toggle = Some(Box::new(on_toggle)); + self + } + + /// Sets the label of the [`Button`]. + pub fn label(mut self, label: impl Into>) -> Self { + self.label = label.into(); + self + } +} + +impl<'a, Message> Widget for Toggler<'a, Message> { + fn size(&self) -> Size { + Size::new(self.width, Length::Shrink) + } + + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(State::default()) + } + + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn set_id(&mut self, id: Id) { + self.id = id; + } + + fn layout( + &mut self, + tree: &mut Tree, + renderer: &crate::Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.width(self.width); + + let res = next_to_each_other( + &limits, + self.spacing, + |limits| { + if let Some(label) = self.label.as_deref() { + let state = tree.state.downcast_mut::(); + let node = iced_core::widget::text::layout( + &mut state.text, + renderer, + limits, + label, + widget::text::Format { + width: self.width, + height: Length::Shrink, + line_height: self.text_line_height, + size: self.text_size.map(iced::Pixels), + font: self.font, + align_x: self.text_alignment, + align_y: alignment::Vertical::Top, + shaping: self.text_shaping, + wrapping: crate::iced_core::text::Wrapping::default(), + }, + ); + match self.width { + Length::Fill => { + let size = node.size(); + layout::Node::with_children( + Size::new(limits.width(Length::Fill).max().width, size.height), + vec![node], + ) + } + _ => node, + } + } else { + layout::Node::new(iced_core::Size::ZERO) + } + }, + |_| layout::Node::new(Size::new(48., 24.)), + ); + res + } + + fn update( + &mut self, + tree: &mut Tree, + event: &Event, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + _renderer: &crate::Renderer, + _clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, + ) { + let Some(on_toggle) = self.on_toggle.as_ref() else { + return; + }; + let state = tree.state.downcast_mut::(); + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { + let mouse_over = cursor_position.is_over(layout.bounds()); + + if mouse_over { + shell.publish((on_toggle)(!self.is_toggled)); + state.anim.changed(self.duration); + shell.capture_event(); + } + } + Event::Window(window::Event::RedrawRequested(now)) => { + state.anim.anim_done(self.duration); + if state.anim.last_change.is_some() { + shell.request_redraw(); + } + } + _ => {} + } + } + + fn mouse_interaction( + &self, + _state: &Tree, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + _viewport: &Rectangle, + _renderer: &crate::Renderer, + ) -> mouse::Interaction { + if cursor_position.is_over(layout.bounds()) { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + } + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut crate::Renderer, + theme: &crate::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + viewport: &Rectangle, + ) { + let state = tree.state.downcast_ref::(); + + let mut children = layout.children(); + let label_layout = children.next().unwrap(); + + if let Some(_label) = &self.label { + let state: &State = tree.state.downcast_ref(); + iced_widget::text::draw( + renderer, + style, + label_layout.bounds(), + state.text.raw(), + iced_widget::text::Style::default(), + viewport, + ); + } + + let toggler_layout = children.next().unwrap(); + let bounds = toggler_layout.bounds(); + + let is_mouse_over = cursor_position.is_over(bounds); + + // let style = blend_appearances( + // theme.style( + // &(), + // if is_mouse_over { + // Status::Hovered { is_toggled: false } + // } else { + // Status::Active { is_toggled: false } + // }, + // ), + // theme.style( + // &(), + // if is_mouse_over { + // Status::Hovered { is_toggled: true } + // } else { + // Status::Active { is_toggled: true } + // }, + // ), + // percent, + // ); + + let style = theme.style( + &(), + if is_mouse_over { + Status::Hovered { + is_toggled: self.is_toggled, + } + } else { + Status::Active { + is_toggled: self.is_toggled, + } + }, + ); + + let space = style.handle_margin; + + let toggler_background_bounds = Rectangle { + x: bounds.x, + y: bounds.y, + width: bounds.width, + height: bounds.height, + }; + + renderer.fill_quad( + renderer::Quad { + bounds: toggler_background_bounds, + border: Border { + radius: style.border_radius, + ..Default::default() + }, + ..renderer::Quad::default() + }, + style.background, + ); + let mut t = state.anim.t(self.duration, self.is_toggled); + + let toggler_foreground_bounds = Rectangle { + x: bounds.x + + anim::slerp( + space, + bounds.width - space - (bounds.height - (2.0 * space)), + t, + ), + + y: bounds.y + space, + width: bounds.height - (2.0 * space), + height: bounds.height - (2.0 * space), + }; + + renderer.fill_quad( + renderer::Quad { + bounds: toggler_foreground_bounds, + border: Border { + radius: style.handle_radius, + ..Default::default() + }, + ..renderer::Quad::default() + }, + style.foreground, + ); + } +} + +impl<'a, Message: 'static> From> for Element<'a, Message> { + fn from(toggler: Toggler<'a, Message>) -> Element<'a, Message> { + Element::new(toggler) + } +} + +/// Produces a [`Node`] with two children nodes one right next to each other. +pub fn next_to_each_other( + limits: &iced::Limits, + spacing: f32, + left: impl FnOnce(&iced::Limits) -> iced_core::layout::Node, + right: impl FnOnce(&iced::Limits) -> iced_core::layout::Node, +) -> iced_core::layout::Node { + let mut right_node = right(limits); + let right_size = right_node.size(); + + let left_limits = limits.shrink(Size::new(right_size.width + spacing, 0.0)); + let mut left_node = left(&left_limits); + let left_size = left_node.size(); + + let (left_y, right_y) = if left_size.height > right_size.height { + (0.0, (left_size.height - right_size.height) / 2.0) + } else { + ((right_size.height - left_size.height) / 2.0, 0.0) + }; + + left_node = left_node.move_to(iced::Point::new(0.0, left_y)); + right_node = right_node.move_to(iced::Point::new(left_size.width + spacing, right_y)); + + iced_core::layout::Node::with_children( + Size::new( + left_size.width + spacing + right_size.width, + left_size.height.max(right_size.height), + ), + vec![left_node, right_node], + ) +} + +#[derive(Debug, Default)] +pub struct State { + text: widget::text::State<::Paragraph>, + anim: anim::State, } diff --git a/src/widget/wayland/tooltip/widget.rs b/src/widget/wayland/tooltip/widget.rs index ceb234a9..b16720cd 100644 --- a/src/widget/wayland/tooltip/widget.rs +++ b/src/widget/wayland/tooltip/widget.rs @@ -240,7 +240,7 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> operation.container(Some(&self.id), layout.bounds()); operation.traverse(&mut |operation| { self.content.as_widget_mut().operate( - tree, + &mut tree.children[0], layout .children() .next() From e6fe1a68115fe8d62766cc6665c52f7bb4e4c340 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 19 Feb 2026 22:54:08 -0500 Subject: [PATCH 443/556] fix: ellipsize --- src/widget/toggler.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index fafc6d70..312b50d5 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -34,6 +34,7 @@ pub struct Toggler<'a, Message> { spacing: f32, font: Option, duration: Duration, + ellipsize: text::Ellipsize, } impl<'a, Message> Toggler<'a, Message> { @@ -63,6 +64,7 @@ impl<'a, Message> Toggler<'a, Message> { spacing: 0.0, font: None, duration: Duration::from_millis(200), + ellipsize: text::Ellipsize::None, } } @@ -108,6 +110,12 @@ impl<'a, Message> Toggler<'a, Message> { self } + /// Sets the [`text::Ellipsize`] strategy of the [`Toggler`]. + pub fn ellipsize(mut self, ellipsize: text::Ellipsize) -> Self { + self.ellipsize = ellipsize; + self + } + /// Sets the [`Font`] of the text of the [`Toggler`] /// /// [`Font`]: cosmic::iced::text::Renderer::Font @@ -188,6 +196,7 @@ impl<'a, Message> Widget for Toggler<'a, align_y: alignment::Vertical::Top, shaping: self.text_shaping, wrapping: crate::iced_core::text::Wrapping::default(), + ellipsize: self.ellipsize, }, ); match self.width { From 0d37dc69e3fae08acc14a91f6e491d7a6c5feaf6 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Sun, 22 Feb 2026 20:47:15 -0500 Subject: [PATCH 444/556] fix: applet popup width --- src/applet/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/applet/mod.rs b/src/applet/mod.rs index f7fa5b62..0cbcacab 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -392,7 +392,6 @@ impl Context { } }), ) - .width(Length::Fill) .height(Length::Shrink) .align_x(horizontal_align) .align_y(vertical_align), From 71e2c7c99eafd20cd2f11a0b8a3c4fb7d2c9eda5 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Sun, 22 Feb 2026 20:48:11 -0500 Subject: [PATCH 445/556] fix: responsive menu layout --- examples/application/Cargo.toml | 5 ++-- src/applet/mod.rs | 2 ++ src/widget/responsive_container.rs | 44 +++++++++++++++++++++------- src/widget/toggler.rs | 5 ++++ src/widget/wayland/tooltip/widget.rs | 2 +- 5 files changed, 45 insertions(+), 13 deletions(-) diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index b1ac1242..f4b62cdb 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -11,8 +11,9 @@ wayland = ["libcosmic/wayland"] env_logger = "0.11" [dependencies.libcosmic] -git = "https://github.com/pop-os/libcosmic" -branch = "iced-rebase" +# git = "https://github.com/pop-os/libcosmic" +# branch = "iced-rebase" +path = "../.." features = [ "debug", "winit", diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 0cbcacab..e18c9aad 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -234,6 +234,7 @@ impl Context { }) .width(Length::Fixed(suggested.0 as f32)) .height(Length::Fixed(suggested.1 as f32)); + dbg!(suggested); self.button_from_element(icon, symbolic) } @@ -250,6 +251,7 @@ impl Context { (applet_padding_minor_axis, applet_padding_major_axis) }; + dbg!(suggested.0 + 2 * horizontal_padding); crate::widget::button::custom(layer_container(content).center(Length::Fill)) .width(Length::Fixed((suggested.0 + 2 * horizontal_padding) as f32)) .height(Length::Fixed((suggested.1 + 2 * vertical_padding) as f32)) diff --git a/src/widget/responsive_container.rs b/src/widget/responsive_container.rs index 3bb44276..b9b6a289 100644 --- a/src/widget/responsive_container.rs +++ b/src/widget/responsive_container.rs @@ -95,7 +95,7 @@ where limits: &layout::Limits, ) -> layout::Node { let state = tree.state.downcast_mut::(); - let unrestricted_size = self.size.unwrap_or_else(|| { + let mut unrestricted_size = self.size.unwrap_or_else(|| { let node = self.content .as_widget_mut() @@ -103,21 +103,45 @@ where node.size() }); - let max_size = limits.max(); - let old_max = state.limits.max(); - state.needs_update = (unrestricted_size.width > max_size.width) - ^ (state.size.width > old_max.width) - || (unrestricted_size.height > max_size.height) ^ (state.size.height > old_max.height); - if state.needs_update { - state.limits = *limits; - state.size = unrestricted_size; - } + let cur_unrestricted_size = { + let node = + self.content + .as_widget_mut() + .layout(&mut tree.children[0], renderer, &Limits::NONE); + node.size() + }; + let max_size = limits.max(); + + let old_max = state.limits.max(); + + state.needs_update = (cur_unrestricted_size.width > max_size.width) + || (cur_unrestricted_size.width > old_max.width) + || (cur_unrestricted_size.height > max_size.height) + || (cur_unrestricted_size.height > old_max.height) + || ((unrestricted_size.width <= max_size.width) + && (unrestricted_size.height <= max_size.height) + && (unrestricted_size.width - cur_unrestricted_size.width > 1. + || unrestricted_size.height - cur_unrestricted_size.height > 1.)); + + if unrestricted_size.width < cur_unrestricted_size.width { + state.needs_update = true; + unrestricted_size.width = cur_unrestricted_size.width; + } else if unrestricted_size.height < cur_unrestricted_size.height { + state.needs_update = true; + unrestricted_size.height = cur_unrestricted_size.height; + } let node = self .content .as_widget_mut() .layout(&mut tree.children[0], renderer, limits); let size = node.size(); + + if state.needs_update { + state.limits = *limits; + state.size = unrestricted_size; + } + layout::Node::with_children(size, vec![node]) } diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index 312b50d5..2cd6a785 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -139,6 +139,11 @@ impl<'a, Message> Toggler<'a, Message> { self } + pub fn on_toggle_maybe(mut self, on_toggle: Option Message + 'a>) -> Self { + self.on_toggle = on_toggle.map(|t| Box::new(t) as _); + self + } + /// Sets the label of the [`Button`]. pub fn label(mut self, label: impl Into>) -> Self { self.label = label.into(); diff --git a/src/widget/wayland/tooltip/widget.rs b/src/widget/wayland/tooltip/widget.rs index b16720cd..7bf0991a 100644 --- a/src/widget/wayland/tooltip/widget.rs +++ b/src/widget/wayland/tooltip/widget.rs @@ -263,7 +263,7 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - let status = update( + update( self.id.clone(), event.clone(), layout, From 7554540b78e093ad1f12f293535c903f3889c350 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Sun, 22 Feb 2026 21:57:15 -0500 Subject: [PATCH 446/556] fix: update for applet widgets and grid --- src/applet/column.rs | 61 ++++++++++++++++++++------------------- src/applet/row.rs | 61 ++++++++++++++++++++------------------- src/widget/grid/widget.rs | 27 ++++++++--------- 3 files changed, 76 insertions(+), 73 deletions(-) diff --git a/src/applet/column.rs b/src/applet/column.rs index 8b3c68e9..9657b566 100644 --- a/src/applet/column.rs +++ b/src/applet/column.rs @@ -320,38 +320,25 @@ where } } - self.children + for (((i, child), state), c_layout) in self + .children .iter_mut() .enumerate() .zip(&mut tree.children) .zip(layout.children()) - .map(|(((i, child), state), c_layout)| { - let mut cursor_virtual = cursor; - if matches!( - event, - Event::Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered) - | Event::Touch( - iced_core::touch::Event::FingerMoved { .. } - | iced_core::touch::Event::FingerPressed { .. } - ) - ) && cursor.is_over(c_layout.bounds()) - { - my_state.hovered = Some(i); - return child.as_widget_mut().update( - state, - &event, - c_layout.with_virtual_offset(layout.virtual_offset()), - cursor_virtual, - renderer, - clipboard, - shell, - viewport, - ); - } else if my_state.hovered.is_some_and(|h| i != h) { - cursor_virtual = mouse::Cursor::Unavailable; - } - - child.as_widget_mut().update( + { + let mut cursor_virtual = cursor; + if matches!( + event, + Event::Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered) + | Event::Touch( + iced_core::touch::Event::FingerMoved { .. } + | iced_core::touch::Event::FingerPressed { .. } + ) + ) && cursor.is_over(c_layout.bounds()) + { + my_state.hovered = Some(i); + return child.as_widget_mut().update( state, &event, c_layout.with_virtual_offset(layout.virtual_offset()), @@ -360,8 +347,22 @@ where clipboard, shell, viewport, - ) - }); + ); + } else if my_state.hovered.is_some_and(|h| i != h) { + cursor_virtual = mouse::Cursor::Unavailable; + } + + child.as_widget_mut().update( + state, + &event, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor_virtual, + renderer, + clipboard, + shell, + viewport, + ); + } } fn mouse_interaction( diff --git a/src/applet/row.rs b/src/applet/row.rs index 2a770503..a6745d1c 100644 --- a/src/applet/row.rs +++ b/src/applet/row.rs @@ -309,39 +309,26 @@ where } } - self.children + for (((i, child), state), c_layout) in self + .children .iter_mut() .enumerate() .zip(&mut tree.children) .zip(layout.children()) - .map(|(((i, child), state), c_layout)| { - let mut cursor_virtual = cursor; + { + let mut cursor_virtual = cursor; - if matches!( - event, - Event::Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered) - | Event::Touch( - iced_core::touch::Event::FingerMoved { .. } - | iced_core::touch::Event::FingerPressed { .. } - ) - ) && cursor.is_over(c_layout.bounds()) - { - my_state.hovered = Some(i); - return child.as_widget_mut().update( - state, - &event, - c_layout.with_virtual_offset(layout.virtual_offset()), - cursor_virtual, - renderer, - clipboard, - shell, - viewport, - ); - } else if my_state.hovered.is_some_and(|h| i != h) { - cursor_virtual = mouse::Cursor::Unavailable; - } - - child.as_widget_mut().update( + if matches!( + event, + Event::Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered) + | Event::Touch( + iced_core::touch::Event::FingerMoved { .. } + | iced_core::touch::Event::FingerPressed { .. } + ) + ) && cursor.is_over(c_layout.bounds()) + { + my_state.hovered = Some(i); + return child.as_widget_mut().update( state, &event, c_layout.with_virtual_offset(layout.virtual_offset()), @@ -350,8 +337,22 @@ where clipboard, shell, viewport, - ) - }); + ); + } else if my_state.hovered.is_some_and(|h| i != h) { + cursor_virtual = mouse::Cursor::Unavailable; + } + + child.as_widget_mut().update( + state, + &event, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor_virtual, + renderer, + clipboard, + shell, + viewport, + ); + } } fn mouse_interaction( diff --git a/src/widget/grid/widget.rs b/src/widget/grid/widget.rs index f88dfc2a..e59ba90d 100644 --- a/src/widget/grid/widget.rs +++ b/src/widget/grid/widget.rs @@ -189,22 +189,23 @@ impl Widget for Grid< shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - self.children + for ((child, state), c_layout) in self + .children .iter_mut() .zip(&mut tree.children) .zip(layout.children()) - .map(|((child, state), c_layout)| { - child.as_widget_mut().update( - state, - event, - c_layout.with_virtual_offset(layout.virtual_offset()), - cursor, - renderer, - clipboard, - shell, - viewport, - ) - }); + { + child.as_widget_mut().update( + state, + event, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } } fn mouse_interaction( From 89ee66f25113dac0f8f06e734c6453323b508297 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Sun, 22 Feb 2026 22:47:28 -0500 Subject: [PATCH 447/556] fix: menu bar and flex row event handling --- src/widget/flex_row/widget.rs | 27 ++++++++++++++------------- src/widget/menu/menu_bar.rs | 28 ++++++++++++++-------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/widget/flex_row/widget.rs b/src/widget/flex_row/widget.rs index f7b90f66..b891c170 100644 --- a/src/widget/flex_row/widget.rs +++ b/src/widget/flex_row/widget.rs @@ -160,22 +160,23 @@ impl Widget for FlexR shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - self.children + for ((child, state), c_layout) in self + .children .iter_mut() .zip(&mut tree.children) .zip(layout.children()) - .map(|((child, state), c_layout)| { - child.as_widget_mut().update( - state, - event, - c_layout.with_virtual_offset(layout.virtual_offset()), - cursor, - renderer, - clipboard, - shell, - viewport, - ) - }); + { + child.as_widget_mut().update( + state, + event, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } } fn mouse_interaction( diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 9d4b09b0..7007befb 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -810,21 +810,21 @@ fn process_root_events( shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - menu_roots + for ((root, t), lo) in menu_roots .iter_mut() .zip(&mut tree.children) .zip(layout.children()) - .map(|((root, t), lo)| { - // assert!(t.tag == tree::Tag::stateless()); - root.item.update( - &mut t.children[root.index], - event, - lo, - view_cursor, - renderer, - clipboard, - shell, - viewport, - ) - }); + { + // assert!(t.tag == tree::Tag::stateless()); + root.item.update( + &mut t.children[root.index], + event, + lo, + view_cursor, + renderer, + clipboard, + shell, + viewport, + ); + } } From fb1a7d36407d863c80ee4be1e718969451697f58 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Sun, 22 Feb 2026 22:48:07 -0500 Subject: [PATCH 448/556] fix: open-dialog example --- examples/open-dialog/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/open-dialog/src/main.rs b/examples/open-dialog/src/main.rs index 10e46315..29061534 100644 --- a/examples/open-dialog/src/main.rs +++ b/examples/open-dialog/src/main.rs @@ -207,7 +207,7 @@ impl cosmic::Application for App { ); content.push( - iced::widget::vertical_space() + iced::widget::space::vertical() .height(Length::Fixed(12.0)) .into(), ); From 442ce6ad0c3c74d878d4a2d3ff467daa63275f11 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Sun, 22 Feb 2026 23:32:38 -0500 Subject: [PATCH 449/556] fix: context-menu when a popup is created and a focus event is received, we shouldn't close the popups, because it may be a focus event for a popup --- src/widget/context_menu.rs | 63 ++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 143a78b8..25953639 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -13,7 +13,7 @@ use derive_setters::Setters; use iced::touch::Finger; use iced::{Event, Vector, keyboard, window}; use iced_core::widget::{Tree, Widget, tree}; -use iced_core::{Length, Point, Size, event, mouse, touch}; +use iced_core::{Length, Point, Size, mouse, touch}; use std::collections::HashSet; use std::sync::Arc; @@ -85,6 +85,7 @@ impl ContextMenu<'_, Message> { // close existing popups state.menu_states.clear(); state.active_root.clear(); + dbg!("closing existing popups"); shell.publish(self.on_surface_action.as_ref().unwrap()(destroy_popup(id))); state.view_cursor = view_cursor; ( @@ -336,13 +337,12 @@ impl Widget .with_data(|d| !d.open && !d.active_root.is_empty()); let open = state.menu_bar_state.inner.with_data_mut(|state| { - if reset { - if let Some(popup_id) = state.popup_id.get(&self.window_id).copied() { - if let Some(handler) = self.on_surface_action.as_ref() { - shell.publish((handler)(crate::surface::Action::DestroyPopup(popup_id))); - state.reset(); - } - } + if reset + && let Some(popup_id) = state.popup_id.get(&self.window_id).copied() + && let Some(handler) = self.on_surface_action.as_ref() + { + shell.publish((handler)(crate::surface::Action::DestroyPopup(popup_id))); + state.reset(); } state.open }); @@ -356,7 +356,6 @@ impl Widget mouse::Button::Right | mouse::Button::Left, )) | Event::Touch(touch::Event::FingerPressed { .. }) - | Event::Window(window::Event::Focused) if open ) { state.menu_bar_state.inner.with_data_mut(|state| { @@ -366,15 +365,14 @@ impl Widget state.open = false; #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] - if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { - if let Some(id) = state.popup_id.remove(&self.window_id) { - { - let surface_action = self.on_surface_action.as_ref().unwrap(); - shell - .publish(surface_action(crate::surface::action::destroy_popup(id))); - } - state.view_cursor = cursor; + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) + && let Some(id) = state.popup_id.remove(&self.window_id) + { + { + let surface_action = self.on_surface_action.as_ref().unwrap(); + shell.publish(surface_action(crate::surface::action::destroy_popup(id))); } + state.view_cursor = cursor; } }); } @@ -388,7 +386,7 @@ impl Widget } Event::Touch(touch::Event::FingerLifted { id, .. }) => { - state.fingers_pressed.remove(&id); + state.fingers_pressed.remove(id); } _ => (), @@ -397,7 +395,7 @@ impl Widget // Present a context menu on a right click event. if !was_open && self.context_menu.is_some() - && (right_button_released(&event) || (touch_lifted(&event) && fingers_pressed == 2)) + && (right_button_released(event) || (touch_lifted(event) && fingers_pressed == 2)) { state.context_cursor = cursor.position().unwrap_or_default(); let state = tree.state.downcast_mut::(); @@ -412,9 +410,9 @@ impl Widget shell.capture_event(); return; - } else if !was_open && right_button_released(&event) - || (touch_lifted(&event)) - || left_button_released(&event) + } else if !was_open && right_button_released(event) + || (touch_lifted(event)) + || left_button_released(event) { state.menu_bar_state.inner.with_data_mut(|state| { was_open = true; @@ -427,16 +425,15 @@ impl Widget feature = "winit", feature = "surface-message" ))] - if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { - if let Some(id) = state.popup_id.remove(&self.window_id) { - { - let surface_action = self.on_surface_action.as_ref().unwrap(); - shell.publish(surface_action( - crate::surface::action::destroy_popup(id), - )); - } - state.view_cursor = cursor; + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) + && let Some(id) = state.popup_id.remove(&self.window_id) + { + { + let surface_action = self.on_surface_action.as_ref().unwrap(); + shell + .publish(surface_action(crate::surface::action::destroy_popup(id))); } + state.view_cursor = cursor; } }); } @@ -450,7 +447,7 @@ impl Widget clipboard, shell, viewport, - ) + ); } fn overlay<'b>( @@ -458,7 +455,7 @@ impl Widget tree: &'b mut Tree, layout: iced_core::Layout<'_>, _renderer: &crate::Renderer, - viewport: &iced::Rectangle, + _viewport: &iced::Rectangle, translation: Vector, ) -> Option> { #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] From bee2d591db0428d7fe516ed9caddcee728a27b31 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 23 Feb 2026 14:50:52 -0500 Subject: [PATCH 450/556] chore: update iced --- examples/application/Cargo.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index f4b62cdb..b1ac1242 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -11,9 +11,8 @@ wayland = ["libcosmic/wayland"] env_logger = "0.11" [dependencies.libcosmic] -# git = "https://github.com/pop-os/libcosmic" -# branch = "iced-rebase" -path = "../.." +git = "https://github.com/pop-os/libcosmic" +branch = "iced-rebase" features = [ "debug", "winit", From 904133397b1de0db13b20ad6454d34a7e82fb61f Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 25 Feb 2026 11:10:34 -0500 Subject: [PATCH 451/556] fix: toggler width fixes & cleanup --- examples/applet/src/window.rs | 3 +-- src/applet/mod.rs | 2 -- src/widget/context_menu.rs | 2 +- src/widget/mod.rs | 2 +- src/widget/settings/item.rs | 10 ++++++++-- src/widget/toggler.rs | 2 +- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/examples/applet/src/window.rs b/examples/applet/src/window.rs index 547863f2..4e05c70a 100644 --- a/examples/applet/src/window.rs +++ b/examples/applet/src/window.rs @@ -128,8 +128,7 @@ impl cosmic::Application for Window { "Example row", cosmic::widget::container( toggler(state.example_row) - .on_toggle(Message::ToggleExampleRow) - .width(Length::Fill), + .on_toggle(Message::ToggleExampleRow), ), )) .add(popup_dropdown( diff --git a/src/applet/mod.rs b/src/applet/mod.rs index e18c9aad..0cbcacab 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -234,7 +234,6 @@ impl Context { }) .width(Length::Fixed(suggested.0 as f32)) .height(Length::Fixed(suggested.1 as f32)); - dbg!(suggested); self.button_from_element(icon, symbolic) } @@ -251,7 +250,6 @@ impl Context { (applet_padding_minor_axis, applet_padding_major_axis) }; - dbg!(suggested.0 + 2 * horizontal_padding); crate::widget::button::custom(layer_container(content).center(Length::Fill)) .width(Length::Fixed((suggested.0 + 2 * horizontal_padding) as f32)) .height(Length::Fixed((suggested.1 + 2 * vertical_padding) as f32)) diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 25953639..200021c3 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -85,7 +85,7 @@ impl ContextMenu<'_, Message> { // close existing popups state.menu_states.clear(); state.active_root.clear(); - dbg!("closing existing popups"); + shell.publish(self.on_surface_action.as_ref().unwrap()(destroy_popup(id))); state.view_cursor = view_cursor; ( diff --git a/src/widget/mod.rs b/src/widget/mod.rs index f63cdc37..eae255bc 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -350,7 +350,7 @@ pub use toaster::{Toast, ToastId, Toasts, toaster}; mod toggler; #[doc(inline)] -pub use toggler::toggler; +pub use toggler::{Toggler, toggler}; #[doc(inline)] pub use tooltip::{Tooltip, tooltip}; diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index a17f2071..110ab7b7 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -41,6 +41,7 @@ pub fn item_row(children: Vec>) -> Row { row::with_children(children) .spacing(theme::spacing().space_xs) .align_y(iced::Alignment::Center) + .width(Length::Fill) } /// A settings item aligned in a flex row @@ -59,8 +60,9 @@ pub fn flex_item<'a, Message: 'static>( .wrapping(Wrapping::Word) .width(Length::Fill) .into(), - container(widget).into(), + container(widget).width(Length::Shrink).into(), ]) + .width(Length::Fill) } inner(title.into(), widget.into()) @@ -141,6 +143,10 @@ impl<'a, Message: 'static> Item<'a, Message> { is_checked: bool, message: impl Fn(bool) -> Message + 'static, ) -> Row<'a, Message> { - self.control(crate::widget::toggler(is_checked).on_toggle(message)) + self.control( + crate::widget::toggler(is_checked) + .width(Length::Shrink) + .on_toggle(message), + ) } } diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index 2cd6a785..12bb8950 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -55,7 +55,7 @@ impl<'a, Message> Toggler<'a, Message> { is_toggled, on_toggle: None, label: None, - width: Length::Fill, + width: Length::Shrink, size: Self::DEFAULT_SIZE, text_size: None, text_line_height: text::LineHeight::default(), From 0298487096e03abc6bc31eec3e8d1339530f2714 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 25 Feb 2026 13:26:38 -0500 Subject: [PATCH 452/556] fix: overlay event handling and mouse interaction --- src/app/mod.rs | 1 - src/widget/context_drawer/overlay.rs | 30 +++++++++++++++++++++++----- src/widget/context_drawer/widget.rs | 2 +- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index abda71c1..e11ed7ae 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -12,7 +12,6 @@ use cosmic_config::CosmicConfigEntry; pub mod context_drawer; pub use context_drawer::{ContextDrawer, context_drawer}; use iced::application::BootFn; -use iced_core::Widget; pub mod cosmic; pub mod settings; diff --git a/src/widget/context_drawer/overlay.rs b/src/widget/context_drawer/overlay.rs index eef9183b..39b34217 100644 --- a/src/widget/context_drawer/overlay.rs +++ b/src/widget/context_drawer/overlay.rs @@ -8,7 +8,7 @@ use iced::advanced::widget::{self, Operation}; use iced::advanced::{Clipboard, Shell}; use iced::advanced::{overlay, renderer}; use iced::{Event, Point, Size, mouse}; -use iced_core::Renderer; +use iced_core::{Renderer, touch}; pub(super) struct Overlay<'a, 'b, Message> { pub(crate) position: Point, @@ -65,7 +65,20 @@ where clipboard, shell, &layout.bounds(), - ) + ); + match event { + Event::Mouse(e) if !matches!(e, mouse::Event::CursorLeft) => { + if cursor.is_over(layout.bounds()) { + shell.capture_event(); + } + } + Event::Touch(e) if !matches!(e, touch::Event::FingerLost { .. }) => { + if cursor.is_over(layout.bounds()) { + shell.capture_event(); + } + } + _ => {} + } } fn draw( @@ -86,7 +99,7 @@ where cursor, &layout.bounds(), ); - }) + }); } fn operate( @@ -108,9 +121,16 @@ where ) -> mouse::Interaction { // TODO how to handle viewport here? let viewport = &layout.bounds(); - self.content + let interaction = self + .content .as_widget() - .mouse_interaction(self.tree, layout, cursor, viewport, renderer) + .mouse_interaction(self.tree, layout, cursor, viewport, renderer); + if let mouse::Interaction::None = interaction + && cursor.is_over(layout.bounds()) + { + return mouse::Interaction::Idle; + } + interaction } fn overlay<'c>( diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index e7ca5dab..7420738c 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -238,7 +238,7 @@ impl Widget for ContextDrawer<' clipboard, shell, viewport, - ) + ); } fn mouse_interaction( From 3d8596287c34f6151ab591acc23d30b971f7d805 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 25 Feb 2026 15:26:08 -0500 Subject: [PATCH 453/556] fix: missed event status after rebase --- src/widget/dnd_destination.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index b0a23fad..a77101b9 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -531,7 +531,8 @@ impl Widget && let Ok(s) = String::from_utf8(data[..data.len() - 1].to_vec()) { shell.publish(f(s)); - return event::Status::Captured; + shell.capture_event(); + return; } if let (Some(msg), ret) = state.on_data_received( From 89d31e988da8cf4aa1737767d7e41e6476234277 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 25 Feb 2026 17:47:58 -0500 Subject: [PATCH 454/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 73369a18..59fbf68c 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 73369a18eb4069f3f3d1916fd1e17537ee87a587 +Subproject commit 59fbf68c541758197204aa52ceca9f89d63d1611 From 0e1a9d46eb09ed2c752ad6c6467f2d3437cd25ca Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 27 Feb 2026 17:24:18 -0500 Subject: [PATCH 455/556] chore: update iced & cleanup text input --- iced | 2 +- src/widget/text_input/input.rs | 26 +++++++++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/iced b/iced index 59fbf68c..f7dc1803 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 59fbf68c541758197204aa52ceca9f89d63d1611 +Subproject commit f7dc18037113719633f450e549d9a6428b5c84b9 diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 5b6a53f3..3960cee1 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -651,11 +651,11 @@ where // if the previous state was at the end of the text, keep it there let old_value = Value::new(&old_value); - if state.is_focused() { - if let cursor::State::Index(index) = state.cursor.state(&old_value) { - if index == old_value.len() { - state.cursor.move_to(self.value.len()); - } + if state.is_focused() + && let cursor::State::Index(index) = state.cursor.state(&old_value) + { + if index == old_value.len() { + state.cursor.move_to(self.value.len()); } } @@ -935,7 +935,8 @@ where layout, self.manage_value, self.drag_threshold, - ) + self.always_active, + ); } #[inline] @@ -1358,6 +1359,7 @@ pub fn update<'a, Message: Clone + 'static>( layout: Layout<'_>, manage_value: bool, drag_threshold: f32, + always_active: bool, ) { let update_cache = |state, value| { replace_paragraph( @@ -1962,7 +1964,11 @@ pub fn update<'a, Message: Clone + 'static>( let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS - (*now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS; - + shell.request_redraw_at(window::RedrawRequest::At( + now.checked_add(Duration::from_millis(millis_until_redraw as u64)) + .unwrap_or(*now), + )); + } else if always_active { shell.request_redraw(); } } @@ -2340,11 +2346,9 @@ pub fn draw<'a, Message>( cursor::State::Index(position) => { let (text_value_width, offset) = measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, position); - let is_cursor_visible = handling_dnd_offer || ((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS) - % 2 - == 0; + .is_multiple_of(2); if is_cursor_visible { if dnd_icon { (None, 0.0) @@ -2479,7 +2483,7 @@ pub fn draw<'a, Message>( }, bounds.position(), color, - *viewport, + text_bounds, ); }; From 925cc9a39f36e09e71e29da37cfeea841cb258b4 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 2 Mar 2026 10:35:53 -0500 Subject: [PATCH 456/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index f7dc1803..0df654c1 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit f7dc18037113719633f450e549d9a6428b5c84b9 +Subproject commit 0df654c14aa811e01362275a22201a9b9eff9ae3 From 5432fee1120155248c8689f33a462f2751519060 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 2 Mar 2026 10:56:14 -0500 Subject: [PATCH 457/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 0df654c1..b479f3e8 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 0df654c14aa811e01362275a22201a9b9eff9ae3 +Subproject commit b479f3e87fd54b9e80a95cf1f4d7767f9dcfbccf From 0bfda2e28cc406411368600e112c2a813125ef2c Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 2 Mar 2026 13:36:07 -0500 Subject: [PATCH 458/556] chore: update deps and test fixes --- .github/workflows/ci.yml | 2 +- Cargo.toml | 4 +-- iced | 2 +- src/desktop.rs | 53 ++++++++++++++++++++++++---------------- 4 files changed, 36 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50a62a50..a822642e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,7 +66,7 @@ jobs: - name: Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Test features - run: cargo test --no-default-features --features "${{ matrix.features }}" + run: cargo test --no-default-features --features "${{ matrix.features }}" -- --test-threads=1 env: RUST_BACKTRACE: full diff --git a/Cargo.toml b/Cargo.toml index 01b50733..ecb84bb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ name = "cosmic" default = [ "winit", "tokio", - "a11y", + "a11y", "dbus-config", "x11", "wayland", @@ -119,7 +119,7 @@ ashpd = { version = "0.12.1", default-features = false, optional = true } async-fs = { version = "2.2", optional = true } async-std = { version = "1.13", optional = true } auto_enums = "0.8.7" -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d0e95be", optional = true } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true } jiff = "0.2" cosmic-config = { path = "cosmic-config" } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } diff --git a/iced b/iced index b479f3e8..4516691f 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit b479f3e87fd54b9e80a95cf1f4d7767f9dcfbccf +Subproject commit 4516691f3582a2a8c31f886b8e6090a235f6e72c diff --git a/src/desktop.rs b/src/desktop.rs index 0d3dbb52..fe32f286 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -416,7 +416,6 @@ fn match_exec_basename( }; let basename_lower = basename.to_ascii_lowercase(); - if normalized .iter() .any(|candidate| candidate == &basename_lower) @@ -440,8 +439,7 @@ fn fallback_entry(context: &DesktopLookupContext<'_>) -> fde::DesktopEntry { let name = context .title .as_ref() - .map(|title| title.to_string()) - .unwrap_or_else(|| context.app_id.to_string()); + .map_or_else(|| context.app_id.to_string(), |title| title.to_string()); entry.add_desktop_entry("Name".to_string(), name); entry } @@ -458,7 +456,9 @@ fn proton_or_wine_fallback( ) -> Option { let app_id = context.app_id.as_ref(); let is_proton_game = app_id == "steam_app_default"; - let is_wine_entry = app_id.ends_with(".exe"); + let is_wine_entry = std::path::Path::new(app_id) + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("exe")); if !is_proton_game && !is_wine_entry { return None; @@ -487,10 +487,6 @@ fn proton_or_wine_fallback( #[cfg(not(windows))] fn candidate_desktop_ids(context: &DesktopLookupContext<'_>) -> Vec { - const SUFFIXES: &[&str] = &[".desktop", ".Desktop", ".DESKTOP"]; - let mut ordered = Vec::new(); - let mut seen = HashSet::new(); - fn push_candidate(seen: &mut HashSet, ordered: &mut Vec, candidate: &str) { let trimmed = candidate.trim(); if trimmed.is_empty() { @@ -531,11 +527,11 @@ fn candidate_desktop_ids(context: &DesktopLookupContext<'_>) -> Vec { } } - if trimmed.contains('.') { - if let Some(last) = trimmed.rsplit('.').next() { - if last.len() >= 2 { - push_candidate(seen, ordered, last); - } + if trimmed.contains('.') + && let Some(last) = trimmed.rsplit('.').next() + { + if last.len() >= 2 { + push_candidate(seen, ordered, last); } } @@ -546,13 +542,20 @@ fn candidate_desktop_ids(context: &DesktopLookupContext<'_>) -> Vec { push_candidate(seen, ordered, &trimmed.replace('_', "-")); } - for token in trimmed.split(|c: char| matches!(c, '.' | '-' | '_' | '@' | ' ')) { + for token in + trimmed.split(|c: char| matches!(c, '.' | '-' | '_' | '@') || c.is_whitespace()) + { if token.len() >= 2 && token != trimmed { push_candidate(seen, ordered, token); } } } + const SUFFIXES: &[&str] = &[".desktop", ".Desktop", ".DESKTOP"]; + + let mut ordered = Vec::new(); + let mut seen = HashSet::new(); + add_variants( &mut seen, &mut ordered, @@ -915,12 +918,20 @@ mod tests { let candidates = candidate_desktop_ids(&ctx); assert_eq!(candidates.first().unwrap(), "com.example.App.desktop"); - assert!(candidates.contains(&"com.example.App".to_string())); - assert!(candidates.contains(&"com-example-App".to_string())); - assert!(candidates.contains(&"com_example_App".to_string())); - assert!(candidates.contains(&"Example App".to_string())); - assert!(candidates.contains(&"Example".to_string())); - assert!(candidates.contains(&"App".to_string())); + for test in [ + "com.example.App", + "com-example-App", + "com_example_App", + "Example App", + "Example", + "App", + ] { + assert!( + candidates + .iter() + .any(|c| c.to_ascii_lowercase() == test.to_ascii_lowercase()), + ); + } } #[test] @@ -985,7 +996,7 @@ Icon=vmware-workstation\n\ let resolved = resolve_desktop_entry(&mut cache, &ctx, &DesktopResolveOptions::default()); - assert_eq!(resolved.id(), "vmware-workstation.desktop"); + assert_eq!(resolved.id(), "vmware-workstation"); } #[test] From 976e0e214f90aafae5913099475f4695e6b2841d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 3 Mar 2026 01:45:04 -0500 Subject: [PATCH 459/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 4516691f..14cefe03 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 4516691f3582a2a8c31f886b8e6090a235f6e72c +Subproject commit 14cefe034e57a189f53a10939b94d2b1d1cfdad3 From 8795c506fa9817faba87c5d0088c7a83aaab6c72 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 4 Mar 2026 10:44:42 -0500 Subject: [PATCH 460/556] chore: update iced should fix responsive widgets --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 14cefe03..40b6bfe9 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 14cefe034e57a189f53a10939b94d2b1d1cfdad3 +Subproject commit 40b6bfe9cabcaa932584f30f0710f8f69d6eb95d From ad65416551975cded91007b491b46556a45e059a Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 4 Mar 2026 13:12:28 -0500 Subject: [PATCH 461/556] fix: resize border --- iced | 2 +- src/app/mod.rs | 2 +- src/applet/mod.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/iced b/iced index 40b6bfe9..fb1d5b2e 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 40b6bfe9cabcaa932584f30f0710f8f69d6eb95d +Subproject commit fb1d5b2ed88f8e56b8637b777cedb135a04098d4 diff --git a/src/app/mod.rs b/src/app/mod.rs index e11ed7ae..e137042e 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -72,7 +72,7 @@ pub(crate) fn iced_settings( core.exit_on_main_window_closed = exit_on_close; if let Some(border_size) = settings.resizable { - // window_settings.resize_border = border_size as u32; + window_settings.resize_border = border_size as u32; window_settings.resizable = true; } window_settings.decorations = !settings.client_decorations; diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 0cbcacab..a3f5228b 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -569,7 +569,7 @@ pub fn run(flags: App::Flags) -> iced::Result { window_settings.decorations = false; window_settings.exit_on_close_request = true; window_settings.resizable = false; - // window_settings.resize_border = 0; + window_settings.resize_border = 0; // TODO make multi-window not mandatory From 1810bedfa5db1d2d8587ec9104bb3b527d9166f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Mar=C3=ADn?= <62134857+mariinkys@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:07:26 +0100 Subject: [PATCH 462/556] fix(navbar): fill height of panel instead of shrinking --- src/app/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index e137042e..b36ec4f6 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -385,9 +385,8 @@ where .on_context(|id| crate::Action::Cosmic(Action::NavBarContext(id))) .context_menu(self.nav_context_menu(self.core().nav_bar_context())) .into_container() - // XXX both must be shrink to avoid flex layout from ignoring it .width(iced::Length::Shrink) - .height(iced::Length::Shrink); + .height(iced::Length::Fill); if !self.core().is_condensed() { nav = nav.max_width(280); From 197049945935f0e0c2d1e42e5c0ff136dd4146ce Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 5 Mar 2026 15:38:28 -0500 Subject: [PATCH 463/556] fix: capture mouse motion and mouse interactions in overlay --- src/widget/dropdown/multi/widget.rs | 2 +- src/widget/menu/menu_inner.rs | 338 +++++++++--------- src/widget/segmented_button/widget.rs | 482 +++++++++++++------------- src/widget/toaster/widget.rs | 2 +- 4 files changed, 417 insertions(+), 407 deletions(-) diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index a46c6dcc..779c6d00 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -135,7 +135,7 @@ impl<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static> self.on_selected.as_ref(), self.selections, || tree.state.downcast_mut::>(), - ) + ); } fn mouse_interaction( diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 4f97d30e..d23a1599 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -585,9 +585,9 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { Cow::Borrowed(_) => panic!(), Cow::Owned(o) => o.as_mut_slice(), }; - let menu_status = process_menu_events( + process_menu_events( self, - &event, + event, view_cursor, renderer, clipboard, @@ -629,8 +629,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { if !self.is_overlay && !view_cursor.is_over(viewport) { return None; } - - let (new_root, status) = process_overlay_events( + let new_root = process_overlay_events( self, renderer, viewport_size, @@ -641,6 +640,10 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { shell, ); + if self.is_overlay && view_cursor.is_over(viewport) { + shell.capture_event(); + } + return new_root; } @@ -680,24 +683,23 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { feature = "winit", feature = "surface-message" ))] - if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { - if let Some(handler) = self.on_surface_action.as_ref() { - let mut root = self.window_id; - let mut depth = self.depth; - while let Some(parent) = - state.popup_id.iter().find(|(_, v)| **v == root) - { - // parent of root popup is the window, so we stop. - if depth == 0 { - break; - } - root = *parent.0; - depth = depth.saturating_sub(1); + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) + && let Some(handler) = self.on_surface_action.as_ref() + { + let mut root = self.window_id; + let mut depth = self.depth; + while let Some(parent) = + state.popup_id.iter().find(|(_, v)| **v == root) + { + // parent of root popup is the window, so we stop. + if depth == 0 { + break; } - shell.publish((handler)(crate::surface::Action::DestroyPopup( - root, - ))); + root = *parent.0; + depth = depth.saturating_sub(1); } + shell + .publish((handler)(crate::surface::Action::DestroyPopup(root))); } state.reset(); @@ -708,7 +710,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { if self.bar_bounds.contains(overlay_cursor) { state.reset(); } - }) + }); } _ => {} @@ -804,26 +806,25 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { let menu_color = styling.background; r.fill_quad(menu_quad, menu_color); // draw path hightlight - if let (true, Some(active)) = (draw_path, ms.index) { - if let Some(active_layout) = children_layout + if let (true, Some(active)) = (draw_path, ms.index) + && let Some(active_layout) = children_layout .children() .nth(active.saturating_sub(start_index)) - { - let path_quad = renderer::Quad { - bounds: active_layout - .bounds() - .intersection(&viewport) - .unwrap_or_default(), - border: Border { - radius: styling.menu_border_radius.into(), - ..Default::default() - }, - shadow: Shadow::default(), - snap: true, - }; + { + let path_quad = renderer::Quad { + bounds: active_layout + .bounds() + .intersection(&viewport) + .unwrap_or_default(), + border: Border { + radius: styling.menu_border_radius.into(), + ..Default::default() + }, + shadow: Shadow::default(), + snap: true, + }; - r.fill_quad(path_quad, styling.path); - } + r.fill_quad(path_quad, styling.path); } if start_index < menu_roots.len() { // draw item @@ -894,6 +895,19 @@ impl overlay::Overlay, + cursor: mouse::Cursor, + _renderer: &crate::Renderer, + ) -> mouse::Interaction { + if cursor.is_over(layout.bounds()) { + mouse::Interaction::Idle + } else { + mouse::Interaction::None + } + } } impl Widget @@ -948,73 +962,74 @@ impl Widget Widget, + cursor: mouse::Cursor, + _viewport: &Rectangle, + _renderer: &crate::Renderer, + ) -> mouse::Interaction { + if cursor.is_over(layout.bounds()) { + mouse::Interaction::Idle + } else { + mouse::Interaction::None } } } @@ -1331,7 +1358,7 @@ fn process_menu_events( shell, &Rectangle::default(), ); - }) + }); } #[allow(unused_results, clippy::too_many_lines, clippy::too_many_arguments)] @@ -1343,12 +1370,11 @@ fn process_overlay_events( view_cursor: Cursor, overlay_cursor: Point, cross_offset: f32, - _shell: &mut Shell<'_, Message>, -) -> (Option<(usize, MenuState)>, event::Status) + shell: &mut Shell<'_, Message>, +) -> Option<(usize, MenuState)> where Message: std::clone::Clone, { - use event::Status::{Captured, Ignored}; /* if no active root || pressed: return @@ -1431,8 +1457,8 @@ where state.open = false; } } - - return (new_menu_root, Captured); + shell.capture_event(); + return new_menu_root; }; let last_menu_bounds = &last_menu_state.menu_bounds; @@ -1446,7 +1472,8 @@ where { last_menu_state.index = None; - return (new_menu_root, Captured); + shell.capture_event(); + return new_menu_root; } // calc new index @@ -1461,7 +1488,7 @@ where }; if state.pressed { - return (new_menu_root, Ignored); + return new_menu_root; } let roots = active_root.iter().skip(1).fold( &menu.menu_roots[active_root[0]].children, @@ -1494,7 +1521,7 @@ where if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove { if let Some(id) = state.popup_id.remove(&menu.window_id) { state.active_root.truncate(menu.depth + 1); - _shell.publish((menu.on_surface_action.as_ref().unwrap())({ + shell.publish((menu.on_surface_action.as_ref().unwrap())({ crate::surface::action::destroy_popup(id) })); } @@ -1555,7 +1582,8 @@ where state.menu_states.truncate(menu.depth + 1); } - (new_menu_root, Captured) + shell.capture_event(); + new_menu_root }) } diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index f6de999e..857d6371 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -20,7 +20,7 @@ use iced::clipboard::mime::AllowedMimeTypes; use iced::touch::Finger; use iced::{ Alignment, Background, Color, Event, Length, Padding, Rectangle, Size, Task, Vector, alignment, - event, keyboard, mouse, touch, window, + keyboard, mouse, touch, window, }; use iced_core::mouse::ScrollDelta; use iced_core::text::{self, Ellipsize, LineHeight, Renderer as TextRenderer, Shaping, Wrapping}; @@ -36,7 +36,6 @@ use std::collections::HashSet; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; -use std::mem; use std::time::{Duration, Instant}; thread_local! { @@ -609,27 +608,26 @@ where .text .get(button) .zip(state.paragraphs.entry(button)) + && !text.is_empty() { - if !text.is_empty() { - icon_spacing = f32::from(self.button_spacing); - let paragraph = entry.or_insert_with(|| { - crate::Plain::new(Text { - content: text.to_string(), // TODO should we just use String at this point? - size: iced::Pixels(self.font_size), - bounds: Size::INFINITE, - font, - align_x: text::Alignment::Left, - align_y: alignment::Vertical::Center, - shaping: Shaping::Advanced, - wrapping: Wrapping::default(), - ellipsize: Ellipsize::default(), - line_height: self.line_height, - }) - }); + icon_spacing = f32::from(self.button_spacing); + let paragraph = entry.or_insert_with(|| { + crate::Plain::new(Text { + content: text.to_string(), // TODO should we just use String at this point? + size: iced::Pixels(self.font_size), + bounds: Size::INFINITE, + font, + align_x: text::Alignment::Left, + align_y: alignment::Vertical::Center, + shaping: Shaping::Advanced, + wrapping: Wrapping::default(), + ellipsize: Ellipsize::default(), + line_height: self.line_height, + }) + }); - let size = paragraph.min_bounds(); - width += size.width; - } + let size = paragraph.min_bounds(); + width += size.width; } // Add indent to measurement if found. @@ -895,10 +893,10 @@ where } // Unfocus if another segmented control was focused. - if let Some(f) = state.focused.as_ref() { - if f.updated_at != LAST_FOCUS_UPDATE.with(|f| f.get()) { - state.unfocus(); - } + if let Some(f) = state.focused.as_ref() + && f.updated_at != LAST_FOCUS_UPDATE.with(|f| f.get()) + { + state.unfocus(); } } @@ -1162,6 +1160,9 @@ where None:: Message>, on_drop, ); + if matches!(ret, iced::event::Status::Captured) { + shell.capture_event(); + } if let Some(msg) = maybe_msg { log::trace!( target: TAB_REORDER_LOG_TARGET, @@ -1200,9 +1201,8 @@ where } Event::Touch(touch::Event::FingerLifted { id, .. }) => { - state.fingers_pressed.remove(&id); + state.fingers_pressed.remove(id); } - _ => (), } @@ -1301,27 +1301,26 @@ where Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) ) && !over_close_button + && let Some(position) = cursor_position.position() { - if let Some(position) = cursor_position.position() { - state.tab_drag_candidate = Some(TabDragCandidate { - entity: key, - bounds, - origin: position, - }); - if let Some(tab_drag) = self.tab_drag.as_ref() { - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "tab drag candidate entity={:?} origin=({:.2},{:.2}) bounds=({:.2},{:.2},{:.2},{:.2}) threshold={}", - key, - position.x, - position.y, - bounds.x, - bounds.y, - bounds.width, - bounds.height, - tab_drag.threshold - ); - } + state.tab_drag_candidate = Some(TabDragCandidate { + entity: key, + bounds, + origin: position, + }); + if let Some(tab_drag) = self.tab_drag.as_ref() { + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "tab drag candidate entity={:?} origin=({:.2},{:.2}) bounds=({:.2},{:.2},{:.2},{:.2}) threshold={}", + key, + position.x, + position.y, + bounds.x, + bounds.y, + bounds.width, + bounds.height, + tab_drag.threshold + ); } } @@ -1330,40 +1329,35 @@ where } if let Some(on_activate) = self.on_activate.as_ref() { - if is_pressed(&event) { + if is_pressed(event) { state.pressed_item = Some(Item::Tab(key)); - } else if is_lifted(&event) { - if self.button_is_pressed(state, key) { - shell.publish(on_activate(key)); - state.set_focused(); - state.focused_item = Item::Tab(key); - state.pressed_item = None; - shell.capture_event(); - return; - } + } else if is_lifted(&event) && self.button_is_pressed(state, key) { + shell.publish(on_activate(key)); + state.set_focused(); + state.focused_item = Item::Tab(key); + state.pressed_item = None; + shell.capture_event(); + return; } } // Present a context menu on a right click event. - if self.context_menu.is_some() { - if let Some(on_context) = self.on_context.as_ref() { - if right_button_released(&event) - || (touch_lifted(&event) && fingers_pressed == 2) - { - state.show_context = Some(key); - state.context_cursor = - cursor_position.position().unwrap_or_default(); + if self.context_menu.is_some() + && let Some(on_context) = self.on_context.as_ref() + && (right_button_released(&event) + || (touch_lifted(&event) && fingers_pressed == 2)) + { + state.show_context = Some(key); + state.context_cursor = cursor_position.position().unwrap_or_default(); - state.menu_state.inner.with_data_mut(|data| { - data.open = true; - data.view_cursor = cursor_position; - }); + state.menu_state.inner.with_data_mut(|data| { + data.open = true; + data.view_cursor = cursor_position; + }); - shell.publish(on_context(key)); - shell.capture_event(); - return; - } - } + shell.publish(on_context(key)); + shell.capture_event(); + return; } if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) = @@ -1385,57 +1379,56 @@ where } } - if self.scrollable_focus { - if let Some(on_activate) = self.on_activate.as_ref() { - if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event { - let current = Instant::now(); + if self.scrollable_focus + && let Some(on_activate) = self.on_activate.as_ref() + && let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event + { + let current = Instant::now(); - // Permit successive scroll wheel events only after a given delay. - if state.wheel_timestamp.is_none_or(|previous| { - current.duration_since(previous) > Duration::from_millis(250) - }) { - state.wheel_timestamp = Some(current); + // Permit successive scroll wheel events only after a given delay. + if state.wheel_timestamp.is_none_or(|previous| { + current.duration_since(previous) > Duration::from_millis(250) + }) { + state.wheel_timestamp = Some(current); - match delta { - ScrollDelta::Lines { y, .. } | ScrollDelta::Pixels { y, .. } => { - let mut activate_key = None; + match delta { + ScrollDelta::Lines { y, .. } | ScrollDelta::Pixels { y, .. } => { + let mut activate_key = None; - if *y < 0.0 { - let mut prev_key = Entity::null(); + if *y < 0.0 { + let mut prev_key = Entity::null(); - for key in self.model.order.iter().copied() { - if self.model.is_active(key) && !prev_key.is_null() { - activate_key = Some(prev_key); - } + for key in self.model.order.iter().copied() { + if self.model.is_active(key) && !prev_key.is_null() { + activate_key = Some(prev_key); + } + if self.model.is_enabled(key) { + prev_key = key; + } + } + } else if *y > 0.0 { + let mut buttons = self.model.order.iter().copied(); + while let Some(key) = buttons.next() { + if self.model.is_active(key) { + for key in buttons { if self.model.is_enabled(key) { - prev_key = key; - } - } - } else if *y > 0.0 { - let mut buttons = self.model.order.iter().copied(); - while let Some(key) = buttons.next() { - if self.model.is_active(key) { - for key in buttons { - if self.model.is_enabled(key) { - activate_key = Some(key); - break; - } - } + activate_key = Some(key); break; } } - } - - if let Some(key) = activate_key { - shell.publish(on_activate(key)); - state.set_focused(); - state.focused_item = Item::Tab(key); - shell.capture_event(); - return; + break; } } } + + if let Some(key) = activate_key { + shell.publish(on_activate(key)); + state.set_focused(); + state.focused_item = Item::Tab(key); + shell.capture_event(); + return; + } } } } @@ -1460,31 +1453,27 @@ where if let (Some(tab_drag), Some(candidate)) = (self.tab_drag.as_ref(), state.tab_drag_candidate) + && let Event::Mouse(mouse::Event::CursorMoved { .. }) = event + && let Some(position) = cursor_position.position() + && position.distance(candidate.origin) >= tab_drag.threshold + && let Some(candidate) = state.tab_drag_candidate.take() { - if let Event::Mouse(mouse::Event::CursorMoved { .. }) = event { - if let Some(position) = cursor_position.position() { - if position.distance(candidate.origin) >= tab_drag.threshold { - if let Some(candidate) = state.tab_drag_candidate.take() { - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "tab drag threshold met entity={:?} distance={:.2} threshold={}", - candidate.entity, - position.distance(candidate.origin), - tab_drag.threshold - ); - if self.start_tab_drag( - state, - candidate.entity, - candidate.bounds, - position, - clipboard, - ) { - shell.capture_event(); - return; - } - } - } - } + log::trace!( + target: TAB_REORDER_LOG_TARGET, + "tab drag threshold met entity={:?} distance={:.2} threshold={}", + candidate.entity, + position.distance(candidate.origin), + tab_drag.threshold + ); + if self.start_tab_drag( + state, + candidate.entity, + candidate.bounds, + position, + clipboard, + ) { + shell.capture_event(); + return; } } @@ -1504,55 +1493,53 @@ where { state.focused_visible = true; return if *modifiers == keyboard::Modifiers::SHIFT { - self.focus_previous(state, shell) + self.focus_previous(state, shell); } else if modifiers.is_empty() { - self.focus_next(state, shell) + self.focus_next(state, shell); }; } - if let Some(on_activate) = self.on_activate.as_ref() { - if let Event::Keyboard(keyboard::Event::KeyReleased { + if let Some(on_activate) = self.on_activate.as_ref() + && let Event::Keyboard(keyboard::Event::KeyReleased { key: keyboard::Key::Named(keyboard::key::Named::Enter), .. }) = event - { - match state.focused_item { - Item::Tab(entity) => { - shell.publish(on_activate(entity)); - } - - Item::PrevButton => { - if self.prev_tab_sensitive(state) { - state.buttons_offset -= 1; - - // If the change would cause it to be insensitive, focus the first tab. - if !self.prev_tab_sensitive(state) { - if let Some(first) = self.first_tab(state) { - state.focused_item = Item::Tab(first); - } - } - } - } - - Item::NextButton => { - if self.next_tab_sensitive(state) { - state.buttons_offset += 1; - - // If the change would cause it to be insensitive, focus the last tab. - if !self.next_tab_sensitive(state) { - if let Some(last) = self.last_tab(state) { - state.focused_item = Item::Tab(last); - } - } - } - } - - Item::None | Item::Set => (), + { + match state.focused_item { + Item::Tab(entity) => { + shell.publish(on_activate(entity)); } - shell.capture_event(); - return; + Item::PrevButton => { + if self.prev_tab_sensitive(state) { + state.buttons_offset -= 1; + + // If the change would cause it to be insensitive, focus the first tab. + if !self.prev_tab_sensitive(state) + && let Some(first) = self.first_tab(state) + { + state.focused_item = Item::Tab(first); + } + } + } + + Item::NextButton => { + if self.next_tab_sensitive(state) { + state.buttons_offset += 1; + + // If the change would cause it to be insensitive, focus the last tab. + if !self.next_tab_sensitive(state) + && let Some(last) = self.last_tab(state) + { + state.focused_item = Item::Tab(last); + } + } + } + + Item::None | Item::Set => (), } + + shell.capture_event(); } } } @@ -1794,22 +1781,22 @@ where let original_bounds = bounds; let center_y = bounds.center_y(); - if show_drop_hint_marker { - if matches!( + if show_drop_hint_marker + && matches!( drop_hint_marker, Some(DropHint { entity, side: DropSide::Before }) if entity == key - ) { - draw_drop_indicator( - renderer, - original_bounds, - DropSide::Before, - Self::VERTICAL, - appearance.active.text_color, - ); - } + ) + { + draw_drop_indicator( + renderer, + original_bounds, + DropSide::Before, + Self::VERTICAL, + appearance.active.text_color, + ); } let menu_open = || { @@ -1882,41 +1869,41 @@ where let mut indent_padding = 0.0; // Adjust bounds by indent - if let Some(indent) = self.model.indent(key) { - if indent > 0 { - let adjustment = f32::from(indent) * f32::from(self.indent_spacing); - bounds.x += adjustment; - bounds.width -= adjustment; + if let Some(indent) = self.model.indent(key) + && indent > 0 + { + let adjustment = f32::from(indent) * f32::from(self.indent_spacing); + bounds.x += adjustment; + bounds.width -= adjustment; - // Draw indent line - if let crate::theme::SegmentedButton::FileNav = self.style { - if indent > 1 { - indent_padding = 7.0; + // Draw indent line + if let crate::theme::SegmentedButton::FileNav = self.style + && indent > 1 + { + indent_padding = 7.0; - for level in 1..indent { - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: (level as f32) - .mul_add(-(self.indent_spacing as f32), bounds.x) - + indent_padding, - width: 1.0, - ..bounds - }, - border: Border { - radius: rad_0.into(), - ..Default::default() - }, - shadow: Shadow::default(), - snap: true, - }, - divider_background, - ); - } - - indent_padding += 4.0; - } + for level in 1..indent { + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: (level as f32) + .mul_add(-(self.indent_spacing as f32), bounds.x) + + indent_padding, + width: 1.0, + ..bounds + }, + border: Border { + radius: rad_0.into(), + ..Default::default() + }, + shadow: Shadow::default(), + snap: true, + }, + divider_background, + ); } + + indent_padding += 4.0; } } @@ -1990,40 +1977,35 @@ where bounds.x += offset; } else { // Draw the selection indicator if widget is a segmented selection, and the item is selected. - if key_is_active { - if let crate::theme::SegmentedButton::Control = self.style { - let mut image_bounds = bounds; - image_bounds.y = center_y - 8.0; + if key_is_active && let crate::theme::SegmentedButton::Control = self.style { + let mut image_bounds = bounds; + image_bounds.y = center_y - 8.0; - draw_icon::( - renderer, - theme, - style, - cursor, - viewport, - status_appearance.text_color, - Rectangle { - width: 16.0, - height: 16.0, - ..image_bounds - }, - crate::widget::icon( - match crate::widget::common::object_select().data() { - crate::iced_core::svg::Data::Bytes(bytes) => { - crate::widget::icon::from_svg_bytes(bytes.as_ref()) - .symbolic(true) - } - crate::iced_core::svg::Data::Path(path) => { - crate::widget::icon::from_path(path.clone()) - } - }, - ), - ); + draw_icon::( + renderer, + theme, + style, + cursor, + viewport, + status_appearance.text_color, + Rectangle { + width: 16.0, + height: 16.0, + ..image_bounds + }, + crate::widget::icon(match crate::widget::common::object_select().data() { + crate::iced_core::svg::Data::Bytes(bytes) => { + crate::widget::icon::from_svg_bytes(bytes.as_ref()).symbolic(true) + } + crate::iced_core::svg::Data::Path(path) => { + crate::widget::icon::from_path(path.clone()) + } + }), + ); - let offset = 16.0 + f32::from(self.button_spacing); + let offset = 16.0 + f32::from(self.button_spacing); - bounds.x += offset; - } + bounds.x += offset; } } diff --git a/src/widget/toaster/widget.rs b/src/widget/toaster/widget.rs index 240e4867..de47a9bd 100644 --- a/src/widget/toaster/widget.rs +++ b/src/widget/toaster/widget.rs @@ -248,7 +248,7 @@ where clipboard, shell, &layout.bounds(), - ) + ); } fn mouse_interaction( From 14a5d0c0ba60b95e5b244414da041df36148edc8 Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Fri, 6 Mar 2026 11:55:53 -0500 Subject: [PATCH 464/556] fix(iced): reversed scroll direction --- examples/applet/Cargo.toml | 2 -- examples/application/Cargo.toml | 1 - iced | 2 +- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/applet/Cargo.toml b/examples/applet/Cargo.toml index 844ad8ff..f97bff44 100644 --- a/examples/applet/Cargo.toml +++ b/examples/applet/Cargo.toml @@ -13,8 +13,6 @@ env_logger = "0.10.2" log = "0.4.29" [dependencies.libcosmic] -# path = "../../" -branch = "iced-rebase" git = "https://github.com/pop-os/libcosmic" default-features = false features = ["applet-token"] diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index b1ac1242..c842c79f 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -12,7 +12,6 @@ env_logger = "0.11" [dependencies.libcosmic] git = "https://github.com/pop-os/libcosmic" -branch = "iced-rebase" features = [ "debug", "winit", diff --git a/iced b/iced index fb1d5b2e..b22d363f 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit fb1d5b2ed88f8e56b8637b777cedb135a04098d4 +Subproject commit b22d363f2c7d6485a3eddc6a54b2a652b7ded916 From 79f8337634071ac4fc32e063a5ecfc173dda4c6c Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Fri, 6 Mar 2026 13:21:34 -0500 Subject: [PATCH 465/556] fix(iced): space key is now handled differently in iced-winit --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index b22d363f..02149769 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit b22d363f2c7d6485a3eddc6a54b2a652b7ded916 +Subproject commit 02149769ec61d485ddc0bf4f07e98f0ec700420f From 3d2c018cd1df25697fa2825dbc5d040cff39389a Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Fri, 6 Mar 2026 14:37:56 -0500 Subject: [PATCH 466/556] fix(dnd_source): rely on current cursor position for hover state --- iced | 2 +- src/widget/dnd_source.rs | 51 +++++++++++++++++----------------------- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/iced b/iced index 02149769..ac24bbe8 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 02149769ec61d485ddc0bf4f07e98f0ec700420f +Subproject commit ac24bbe80dd16ea586b8a0b5816066e3ba1b48fa diff --git a/src/widget/dnd_source.rs b/src/widget/dnd_source.rs index 07b448a5..25900a66 100644 --- a/src/widget/dnd_source.rs +++ b/src/widget/dnd_source.rs @@ -240,7 +240,7 @@ impl match mouse_event { mouse::Event::ButtonPressed(mouse::Button::Left) => { if let Some(position) = cursor.position() { - if !state.hovered { + if !cursor.is_over(layout.bounds()) { return; } @@ -256,33 +256,27 @@ impl { if let Some(position) = cursor.position() { - if state.hovered { - // We ignore motion if we do not possess drag content by now. - if self.drag_content.is_none() { - state.left_pressed_position = None; - return; + // We ignore motion if we do not possess drag content by now. + if self.drag_content.is_none() { + state.left_pressed_position = None; + return; + } + if let Some(left_pressed_position) = state.left_pressed_position + && position.distance(left_pressed_position) > self.drag_threshold + { + if let Some(on_start) = self.on_start.as_ref() { + shell.publish(on_start.clone()); } - if let Some(left_pressed_position) = state.left_pressed_position { - if position.distance(left_pressed_position) > self.drag_threshold { - if let Some(on_start) = self.on_start.as_ref() { - shell.publish(on_start.clone()) - } - let offset = Vector::new( - left_pressed_position.x - layout.bounds().x, - left_pressed_position.y - layout.bounds().y, - ); - self.start_dnd(clipboard, state.cached_bounds, offset); - state.is_dragging = true; - state.left_pressed_position = None; - } - } - if !cursor.is_over(layout.bounds()) { - state.hovered = false; - - return; - } - } else if cursor.is_over(layout.bounds()) { - state.hovered = true; + let offset = Vector::new( + left_pressed_position.x - layout.bounds().x, + left_pressed_position.y - layout.bounds().y, + ); + self.start_dnd(clipboard, state.cached_bounds, offset); + state.is_dragging = true; + state.left_pressed_position = None; + } + if !cursor.is_over(layout.bounds()) { + return; } shell.capture_event(); } @@ -296,7 +290,6 @@ impl { @@ -306,7 +299,6 @@ impl (), @@ -422,7 +414,6 @@ impl< /// Local state of the [`MouseListener`]. #[derive(Debug, Default)] struct State { - hovered: bool, left_pressed_position: Option, is_dragging: bool, cached_bounds: Rectangle, From 03d0171bbe36664b4749c2b70297a288f35df59f Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 6 Mar 2026 16:45:19 -0500 Subject: [PATCH 467/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index ac24bbe8..5f97135c 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit ac24bbe80dd16ea586b8a0b5816066e3ba1b48fa +Subproject commit 5f97135c3dd558cde27334a534df6f0b55ab02fa From 5eec82061592fdb5749045b71b1b2c3dbe24ee49 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sun, 8 Mar 2026 10:09:55 +0100 Subject: [PATCH 468/556] i18n: translation updates from weblate Co-authored-by: Aman Alam Co-authored-by: Ettore Atalan Co-authored-by: Hosted Weblate Co-authored-by: Vilius Paliokas Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/de/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/lt/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/pa/ Translation: Pop OS/libcosmic --- i18n/de/libcosmic.ftl | 6 ++++-- i18n/lt/libcosmic.ftl | 21 ++++++++++++++------- i18n/pa/libcosmic.ftl | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/i18n/de/libcosmic.ftl b/i18n/de/libcosmic.ftl index 1f17c924..238000f5 100644 --- a/i18n/de/libcosmic.ftl +++ b/i18n/de/libcosmic.ftl @@ -21,8 +21,8 @@ september = September { $year } october = Oktober { $year } november = November { $year } december = Dezember { $year } -monday = Mo -tuesday = Di +monday = Montag +tuesday = Dienstag wednesday = Mittwoch thursday = Donnerstag friday = Freitag @@ -33,3 +33,5 @@ thu = Do fri = Fr sat = Sa sun = So +tue = Di +mon = Mo diff --git a/i18n/lt/libcosmic.ftl b/i18n/lt/libcosmic.ftl index 6472cbd3..097b3219 100644 --- a/i18n/lt/libcosmic.ftl +++ b/i18n/lt/libcosmic.ftl @@ -2,26 +2,33 @@ february = Vasaris { $year } close = Uždaryti documenters = Dokumentuotojai november = Lapkritis { $year } -friday = Penk -tuesday = Antr +friday = Penktadienis +tuesday = Antradienis may = Gegužė { $year } -wednesday = Treč +wednesday = Trečiadienis april = Balandis { $year } -monday = Pirm +monday = Pirmadienis translators = Vertėjai artists = Menininkai license = Licencija december = Gruodis { $year } -sunday = Sekm +sunday = Sekmadienis links = Nuorodos march = Kovas { $year } june = Birželis { $year } -saturday = Šešt +saturday = Šeštadienis august = Rugpjūtis { $year } developers = Kūrėjai july = Liepa { $year } -thursday = Ketv +thursday = Ketvirtadienis september = Rugsėjis { $year } designers = Dizaineriai october = Spalis { $year } january = Sausis { $year } +mon = Pirm +tue = Antr +wed = Treč +thu = Ketv +fri = Penkt +sat = Šešt +sun = Sekm diff --git a/i18n/pa/libcosmic.ftl b/i18n/pa/libcosmic.ftl index e69de29b..83d82608 100644 --- a/i18n/pa/libcosmic.ftl +++ b/i18n/pa/libcosmic.ftl @@ -0,0 +1,34 @@ +close = ਬੰਦ ਕਰੋ +license = ਲਸੰਸ +links = ਲਿੰਕ +developers = ਡਿਵੈਲਪਰ +designers = ਡਿਜ਼ਾਇਨਰ +artists = ਕਲਾਕਾਰ +translators = ਅਨੁਵਾਦਕ +documenters = ਦਸਤਾਵੇਜ਼ ਤਿਆਰ ਕਰਤਾ +january = ਜਨਵਰੀ { $year } +february = ਫਰਵਰੀ { $year } +march = ਮਾਰਚ { $year } +april = ਅਪਰੈਲ { $year } +may = ਮਈ { $year } +june = ਜੂਨ { $year } +july = ਜੁਲਾਈ { $year } +august = ਅਗਸਤ { $year } +september = ਸਤੰਬਰ { $year } +october = ਅਕਤੂਬਰ { $year } +november = ਨਵੰਬਰ { $year } +december = ਦਸੰਬਰ { $year } +monday = ਸੋਮਵਾਰ +mon = ਸੋਮ +tuesday = ਮੰਗਲਵਾਰ +tue = ਮੰਗਲ +wednesday = ਬੁੱਧਵਾਰ +wed = ਬੁੱਧ +thursday = ਵੀਰਵਾਰ +thu = ਵੀਰ +friday = ਸ਼ੁੱਕਰਵਾਰ +fri = ਸ਼ੁੱਕਰ +saturday = ਸ਼ਨਿੱਚਰਵਾਰ +sat = ਸ਼ਨਿੱਚਰ +sunday = ਐਤਵਾਰ +sun = ਐਤ From 4b92ee5f80dbc3cc6d64cf1411ade88ded8ff741 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 9 Mar 2026 16:15:03 -0400 Subject: [PATCH 469/556] chore: update iced includes fix for virtual offsets --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 5f97135c..99bc4551 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 5f97135c3dd558cde27334a534df6f0b55ab02fa +Subproject commit 99bc45511804ce94ddba880d4913e983a5f64a7f From 26f40869313f0843c436c0d8a59a05703330d571 Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Tue, 10 Mar 2026 12:33:00 -0400 Subject: [PATCH 470/556] fix(iced): fix touch event handling --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 99bc4551..1e419b2b 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 99bc45511804ce94ddba880d4913e983a5f64a7f +Subproject commit 1e419b2bc6ba5bdbb7923b1798f5292815a8c2c3 From 242fe6c4ac2229f4d1aa233e09d34234883941d0 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 11 Mar 2026 10:15:30 -0400 Subject: [PATCH 471/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 1e419b2b..d8315860 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 1e419b2bc6ba5bdbb7923b1798f5292815a8c2c3 +Subproject commit d8315860b378536dc5b2fe821b9da54934d96ff3 From b4533e3a5621edfff6cdc0c064bca61555f832b1 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 11 Mar 2026 10:38:51 -0400 Subject: [PATCH 472/556] chore: update deps --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index d8315860..f0899a2a 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit d8315860b378536dc5b2fe821b9da54934d96ff3 +Subproject commit f0899a2a8192ed66f9d9bf00e3643194820239e6 From ce9e8b520579f8d70627093ebee74eb07c41055e Mon Sep 17 00:00:00 2001 From: Dryadxon <81884588+Dryadxon@users.noreply.github.com> Date: Wed, 11 Mar 2026 00:15:14 +0100 Subject: [PATCH 473/556] fix(flex_row): layout::resolve swap align_items with justify_items --- src/widget/flex_row/widget.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/flex_row/widget.rs b/src/widget/flex_row/widget.rs index b891c170..0b2e6e13 100644 --- a/src/widget/flex_row/widget.rs +++ b/src/widget/flex_row/widget.rs @@ -119,8 +119,8 @@ impl Widget for FlexR f32::from(self.column_spacing), f32::from(self.row_spacing), self.min_item_width, - self.align_items, self.justify_items, + self.align_items, self.justify_content, &mut tree.children, ) From 1dc9aa37ed8b8670217f7b9f82d40b3476204ad1 Mon Sep 17 00:00:00 2001 From: Dryadxon <81884588+Dryadxon@users.noreply.github.com> Date: Wed, 11 Mar 2026 17:25:02 +0100 Subject: [PATCH 474/556] feat(flex_row): re-export JustifyItems --- src/widget/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/mod.rs b/src/widget/mod.rs index eae255bc..73004597 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -259,7 +259,7 @@ pub use id_container::{IdContainer, id_container}; #[cfg(feature = "animated-image")] pub mod frames; -pub use taffy::JustifyContent; +pub use taffy::{JustifyContent, JustifyItems}; pub mod list; #[doc(inline)] From 01e5593741c7aa7eedf0692c6087ee3fb97feeb8 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 11 Mar 2026 22:35:31 -0400 Subject: [PATCH 475/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index f0899a2a..88f3b00d 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit f0899a2a8192ed66f9d9bf00e3643194820239e6 +Subproject commit 88f3b00d9625a3dd08ebaadb328fd119957fcd85 From c52ef976500c270b1f9b5fe488dbe5e153022ad3 Mon Sep 17 00:00:00 2001 From: Jonathan Wingrove Date: Sat, 14 Mar 2026 22:07:58 +0000 Subject: [PATCH 476/556] fix(table): Use on_item_mb_double for double-click handler instead of on_item_mb_left --- src/widget/table/widget/compact.rs | 2 +- src/widget/table/widget/standard.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widget/table/widget/compact.rs b/src/widget/table/widget/compact.rs index 85b5cfce..db71a1af 100644 --- a/src/widget/table/widget/compact.rs +++ b/src/widget/table/widget/compact.rs @@ -145,7 +145,7 @@ where }) // Double click .apply(|mouse_area| { - if let Some(ref on_item_mb) = val.on_item_mb_left { + if let Some(ref on_item_mb) = val.on_item_mb_double { mouse_area.on_double_click((on_item_mb)(entity)) } else { mouse_area diff --git a/src/widget/table/widget/standard.rs b/src/widget/table/widget/standard.rs index 79107074..1fa611f3 100644 --- a/src/widget/table/widget/standard.rs +++ b/src/widget/table/widget/standard.rs @@ -206,7 +206,7 @@ where }) // Double click .apply(|mouse_area| { - if let Some(ref on_item_mb) = val.on_item_mb_left { + if let Some(ref on_item_mb) = val.on_item_mb_double { mouse_area.on_double_click((on_item_mb)(entity)) } else { mouse_area From 12cc536cd54d2b4c99f4cf8803beb260ec40dc63 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 16 Mar 2026 14:24:46 -0400 Subject: [PATCH 477/556] chore: update iced fix for tiny-skia rotation --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 88f3b00d..d79181f4 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 88f3b00d9625a3dd08ebaadb328fd119957fcd85 +Subproject commit d79181f44325e63e35ef9e9653543b4bc09976bb From 9602dfd2f12b667e0afacdccd0e403d8152dde5a Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 16 Mar 2026 15:59:34 -0400 Subject: [PATCH 478/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index d79181f4..7491547d 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit d79181f44325e63e35ef9e9653543b4bc09976bb +Subproject commit 7491547d7078c8bad54cf350b1276c7f32e50df5 From adb6e304052857392b596f5b1f9732af2955a0d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Tue, 17 Mar 2026 16:23:31 +0100 Subject: [PATCH 479/556] feat(header_bar): use custom widget for layout --- src/app/mod.rs | 6 +- src/widget/header_bar.rs | 434 +++++++++++++++++++-------------------- 2 files changed, 213 insertions(+), 227 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index b36ec4f6..47900107 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -742,9 +742,6 @@ impl ApplicationExt for App { })); let content: Element<_> = if content_container { content_col - .apply(container) - .width(iced::Length::Fill) - .height(iced::Length::Fill) .apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_content_container"))) .into() } else { @@ -772,8 +769,7 @@ impl ApplicationExt for App { .title(&core.window.header_title) .on_drag(crate::Action::Cosmic(Action::Drag)) .on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu)) - .on_double_click(crate::Action::Cosmic(Action::Maximize)) - .is_condensed(is_condensed); + .on_double_click(crate::Action::Cosmic(Action::Maximize)); if self.nav_model().is_some() { let toggle = crate::widget::nav_bar_toggle() diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 1465a9d7..9ab6ff15 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -5,9 +5,8 @@ use crate::cosmic_theme::{Density, Spacing}; use crate::{Element, theme, widget}; use apply::Apply; use derive_setters::Setters; -use iced::{Length, mouse}; -use iced_core::{Vector, Widget, widget::tree}; -use std::{borrow::Cow, cmp}; +use iced_core::{Length, Size, Vector, Widget, layout, text, widget::tree}; +use std::borrow::Cow; #[must_use] pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { @@ -27,7 +26,6 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { sharp_corners: false, is_ssd: false, on_double_click: None, - is_condensed: false, transparent: false, } } @@ -91,9 +89,6 @@ pub struct HeaderBar<'a, Message> { /// HeaderBar used for server-side decorations is_ssd: bool, - /// Whether the headerbar should be compact - is_condensed: bool, - /// Whether the headerbar should be transparent transparent: bool, } @@ -126,48 +121,120 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { self.end.push(widget.into()); self } - - /// Build the widget - #[must_use] - #[inline] - pub fn build(self) -> HeaderBarWidget<'a, Message> { - HeaderBarWidget { - header_bar_inner: self.view(), - } - } } pub struct HeaderBarWidget<'a, Message> { - header_bar_inner: Element<'a, Message>, + start: Element<'a, Message>, + center: Option>, + end: Element<'a, Message>, } -impl Widget - for HeaderBarWidget<'_, Message> +impl<'a, Message> HeaderBarWidget<'a, Message> { + pub fn new( + start: Element<'a, Message>, + center: Option>, + end: Element<'a, Message>, + ) -> Self { + Self { start, center, end } + } + + fn elems(&self) -> impl Iterator> { + std::iter::once(&self.start) + .chain(std::iter::once(&self.end)) + .chain(self.center.as_ref()) + } + + fn elems_mut(&mut self) -> impl Iterator> { + std::iter::once(&mut self.start) + .chain(std::iter::once(&mut self.end)) + .chain(self.center.as_mut()) + } +} + +impl<'a, Message: Clone + 'static> Widget + for HeaderBarWidget<'a, Message> { fn diff(&mut self, tree: &mut tree::Tree) { - tree.diff_children(&mut [&mut self.header_bar_inner]); + if let Some(center) = &mut self.center { + tree.diff_children(&mut [&mut self.start, &mut self.end, center]); + } else { + tree.diff_children(&mut [&mut self.start, &mut self.end]); + } } fn children(&self) -> Vec { - vec![tree::Tree::new(&self.header_bar_inner)] + self.elems().map(tree::Tree::new).collect() } - fn size(&self) -> iced_core::Size { - self.header_bar_inner.as_widget().size() + fn size(&self) -> Size { + Size { + width: Length::Fill, + height: Length::Shrink, + } } fn layout( &mut self, tree: &mut tree::Tree, renderer: &crate::Renderer, - limits: &iced_core::layout::Limits, - ) -> iced_core::layout::Node { - let child_tree = &mut tree.children[0]; - let child = self - .header_bar_inner - .as_widget_mut() - .layout(child_tree, renderer, limits); - iced_core::layout::Node::with_children(child.size(), vec![child]) + limits: &layout::Limits, + ) -> layout::Node { + let width = limits.max().width; + let height = limits.max().height; + let gap = 8.0; + + let end_node = + self.end + .as_widget_mut() + .layout(&mut tree.children[1], renderer, &limits.loose()); + let end_width = end_node.size().width; + + let start_available = (width - end_width - gap).max(0.0); + let start_node = self.start.as_widget_mut().layout( + &mut tree.children[0], + renderer, + &layout::Limits::new(Size::ZERO, Size::new(start_available, height)), + ); + let start_width = start_node.size().width; + + let (center_node, center_x) = if let Some(center) = &mut self.center { + let slot_start = start_width + gap; + let slot_end = (width - end_width - gap).max(slot_start); + let slot_width = slot_end - slot_start; + // this instead of `node.size().width` prevents center jitter as text ellipsizes + let natural_width = center + .as_widget_mut() + .layout(&mut tree.children[2], renderer, &limits.loose()) + .size() + .width; + + let node = center.as_widget_mut().layout( + &mut tree.children[2], + renderer, + &layout::Limits::new(Size::ZERO, Size::new(slot_width, height)), + ); + + let ideal_x = (width - natural_width) / 2.0; + let max_x = (width - end_width - gap - natural_width).max(slot_start); + let center_x = ideal_x.clamp(slot_start, max_x); + (Some(node), center_x) + } else { + (None, 0.0) + }; + + let vcenter = |node: layout::Node, x: f32| -> layout::Node { + let dy = ((height - node.size().height) / 2.0).max(0.0); + node.translate(Vector::new(x, dy)) + }; + + let mut child_nodes = Vec::with_capacity(3); + child_nodes.push(vcenter(start_node, 0.0)); + child_nodes.push(vcenter(end_node, width - end_width)); + if let Some(cn) = center_node { + child_nodes.push(vcenter(cn, center_x)); + } + + layout::Node::with_children(Size::new(width, height), child_nodes) } fn draw( @@ -180,17 +247,10 @@ impl Widget cursor: iced_core::mouse::Cursor, viewport: &iced_core::Rectangle, ) { - let layout_children = layout.children().next().unwrap(); - let state_children = &tree.children[0]; - self.header_bar_inner.as_widget().draw( - state_children, - renderer, - theme, - style, - layout_children, - cursor, - viewport, - ); + for ((e, s), l) in self.elems().zip(&tree.children).zip(layout.children()) { + e.as_widget() + .draw(s, renderer, theme, style, l, cursor, viewport); + } } fn update( @@ -204,19 +264,14 @@ impl Widget shell: &mut iced_core::Shell<'_, Message>, viewport: &iced_core::Rectangle, ) { - let child_state = &mut state.children[0]; - let child_layout = layout.children().next().unwrap(); - - self.header_bar_inner.as_widget_mut().update( - child_state, - event, - child_layout, - cursor, - renderer, - clipboard, - shell, - viewport, - ); + for ((e, s), l) in self + .elems_mut() + .zip(&mut state.children) + .zip(layout.children()) + { + e.as_widget_mut() + .update(s, event, l, cursor, renderer, clipboard, shell, viewport); + } } fn mouse_interaction( @@ -227,15 +282,15 @@ impl Widget viewport: &iced_core::Rectangle, renderer: &crate::Renderer, ) -> iced_core::mouse::Interaction { - let child_tree = &state.children[0]; - let child_layout = layout.children().next().unwrap(); - self.header_bar_inner.as_widget().mouse_interaction( - child_tree, - child_layout, - cursor, - viewport, - renderer, - ) + self.elems() + .zip(&state.children) + .zip(layout.children()) + .map(|((e, s), l)| { + e.as_widget() + .mouse_interaction(s, l, cursor, viewport, renderer) + }) + .max() + .unwrap_or(iced_core::mouse::Interaction::None) } fn operate( @@ -245,14 +300,13 @@ impl Widget renderer: &crate::Renderer, operation: &mut dyn iced_core::widget::Operation<()>, ) { - let child_tree = &mut state.children[0]; - let child_layout = layout.children().next().unwrap(); - self.header_bar_inner.as_widget_mut().operate( - child_tree, - child_layout, - renderer, - operation, - ); + for ((e, s), l) in self + .elems_mut() + .zip(&mut state.children) + .zip(layout.children()) + { + e.as_widget_mut().operate(s, l, renderer, operation); + } } fn overlay<'b>( @@ -263,15 +317,27 @@ impl Widget viewport: &iced_core::Rectangle, translation: Vector, ) -> Option> { - let child_tree = &mut state.children[0]; - let child_layout = layout.children().next().unwrap(); - self.header_bar_inner.as_widget_mut().overlay( - child_tree, - child_layout, - renderer, - viewport, - translation, - ) + let mut layouts = layout.children(); + let mut try_overlay = |elem: &'b mut Element<'a, Message>, + state: &'b mut tree::Tree| + -> Option< + iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>, + > { + elem.as_widget_mut() + .overlay(state, layouts.next()?, renderer, viewport, translation) + }; + + if let Some(center) = &mut self.center { + let (start_slice, end_center) = state.children.split_at_mut(1); + let (end_slice, center_slice) = end_center.split_at_mut(1); + try_overlay(&mut self.start, &mut start_slice[0]) + .or_else(|| try_overlay(&mut self.end, &mut end_slice[0])) + .or_else(|| try_overlay(center, &mut center_slice[0])) + } else { + let (start_slice, end_slice) = state.children.split_at_mut(1); + try_overlay(&mut self.start, &mut start_slice[0]) + .or_else(|| try_overlay(&mut self.end, &mut end_slice[0])) + } } fn drag_destinations( @@ -281,15 +347,9 @@ impl Widget renderer: &crate::Renderer, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { - if let Some((child_tree, child_layout)) = - state.children.iter().zip(layout.children()).next() - { - self.header_bar_inner.as_widget().drag_destinations( - child_tree, - child_layout, - renderer, - dnd_rectangles, - ); + for ((e, s), l) in self.elems().zip(&state.children).zip(layout.children()) { + e.as_widget() + .drag_destinations(s, l, renderer, dnd_rectangles); } } @@ -301,16 +361,22 @@ impl Widget state: &tree::Tree, p: iced::mouse::Cursor, ) -> iced_accessibility::A11yTree { - let c_layout = layout.children().next().unwrap(); - let c_state = &state.children[0]; - self.header_bar_inner - .as_widget() - .a11y_nodes(c_layout, c_state, p) + iced_accessibility::A11yTree::join( + self.elems() + .zip(&state.children) + .zip(layout.children()) + .map(|((e, s), l)| e.as_widget().a11y_nodes(l, s, p)), + ) + } +} + +impl<'a, Message: Clone + 'static> From> for Element<'a, Message> { + fn from(w: HeaderBarWidget<'a, Message>) -> Self { + Element::new(w) } } impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { - #[allow(clippy::too_many_lines)] /// Converts the headerbar builder into an Iced element. pub fn view(mut self) -> Element<'a, Message> { let Spacing { @@ -324,154 +390,85 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { let center = std::mem::take(&mut self.center); let mut end = std::mem::take(&mut self.end); - let window_control_cnt = self.on_close.is_some() as usize - + self.on_maximize.is_some() as usize - + self.on_minimize.is_some() as usize; // Also packs the window controls at the very end. - end.push(self.window_controls()); + end.push(self.window_controls(space_xxs)); - // Center content depending on window border - let padding = match self.density.unwrap_or_else(crate::config::header_size) { - Density::Compact => { - if self.maximized { - [4, 8, 4, 8] - } else { - [3, 7, 4, 7] - } - } - _ => { - if self.maximized { - [8, 8, 8, 8] - } else { - [7, 7, 8, 7] - } + let padding = if self.is_ssd { + [0, 8, 0, 8] + } else { + match ( + self.density.unwrap_or_else(crate::config::header_size), + // Center content depending on window border + self.maximized, + ) { + (Density::Compact, true) => [4, 8, 4, 8], + (Density::Compact, false) => [3, 7, 4, 7], + (_, true) => [8, 8, 8, 8], + (_, false) => [7, 7, 8, 7], } }; - let acc_count = |v: &[Element<'a, Message>]| { - v.iter().fold(0, |acc, e| { - acc + match e.as_widget().size().width { - Length::Fixed(w) if w > 30. => (w / 30.0).ceil() as usize, - _ => 1, - } - }) - }; - - let left_len = acc_count(&start); - let right_len = acc_count(&end); - - let portion = ((left_len.max(right_len + window_control_cnt) as f32 - / center.len().max(1) as f32) - .round() as u16) - .max(1); - let (left_portion, right_portion) = - if center.is_empty() && (self.title.is_empty() || self.is_condensed) { - let left_to_right_ratio = left_len as f32 / right_len.max(1) as f32; - let right_to_left_ratio = right_len as f32 / left_len.max(1) as f32; - if right_to_left_ratio > 2. || left_len < 1 { - (1, 2) - } else if left_to_right_ratio > 2. || right_len < 1 { - (2, 1) - } else { - (left_len as u16, (right_len + window_control_cnt) as u16) - } - } else { - (portion, portion) - }; - let title_portion = cmp::max(left_portion, right_portion) * 2; - // Creates the headerbar widget. - let mut widget = widget::row::with_capacity(3) - // If elements exist in the start region, append them here. - .push( - widget::row::with_children(start) + let start = widget::row::with_children(start) + .spacing(space_xxxs) + .align_y(iced::Alignment::Center) + .into(); + let center = if !center.is_empty() { + Some( + widget::row::with_children(center) .spacing(space_xxxs) .align_y(iced::Alignment::Center) - .apply(widget::container) - .align_x(iced::Alignment::Start) - .width(Length::FillPortion(left_portion)), + .into(), ) - // If elements exist in the center region, use them here. - // This will otherwise use the title as a widget if a title was defined. - .push_maybe(if !center.is_empty() { - Some( - widget::row::with_children(center) - .spacing(space_xxxs) - .align_y(iced::Alignment::Center) - .apply(widget::container) - .center_x(Length::Fill) - .into(), - ) - } else if !self.title.is_empty() && !self.is_condensed { - Some(self.title_widget(title_portion)) - } else { - None - }) - .push( - widget::row::with_children(end) - .spacing(space_xxs) - .align_y(iced::Alignment::Center) - .apply(widget::container) - .align_x(iced::Alignment::End) - .width(Length::FillPortion(right_portion)), + } else if !self.title.is_empty() { + Some( + widget::text::heading(self.title) + .wrapping(text::Wrapping::None) + .ellipsize(text::Ellipsize::End(text::EllipsizeHeightLimit::Lines(1))) + .into(), ) + } else { + None + }; + let end = widget::row::with_children(end) + .spacing(space_xxs) .align_y(iced::Alignment::Center) - .height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32)) - .padding(if self.is_ssd { [0, 8, 0, 8] } else { padding }) - .spacing(8) + .into(); + + let mut widget = HeaderBarWidget::new(start, center, end) .apply(widget::container) .class(crate::theme::Container::HeaderBar { focused: self.focused, sharp_corners: self.sharp_corners, transparent: self.transparent, }) - .center_y(Length::Shrink) + .height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32)) + .padding(padding) .apply(widget::mouse_area); - // Assigns a message to emit when the headerbar is dragged. - if let Some(message) = self.on_drag.clone() { + if let Some(message) = self.on_drag { widget = widget.on_drag(message); } - - // Assigns a message to emit when the headerbar is double-clicked. - if let Some(message) = self.on_maximize.clone() { + if let Some(message) = self.on_maximize { widget = widget.on_release(message); } - - if let Some(message) = self.on_double_click.clone() { + if let Some(message) = self.on_double_click { widget = widget.on_double_press(message); } - if let Some(message) = self.on_right_click.clone() { + if let Some(message) = self.on_right_click { widget = widget.on_right_press(message); } widget.into() } - fn title_widget(&mut self, title_portion: u16) -> Element<'a, Message> { - let mut title = Cow::default(); - std::mem::swap(&mut title, &mut self.title); - - widget::text::heading(title) - .wrapping(iced_core::text::Wrapping::None) - .ellipsize(iced_core::text::Ellipsize::End( - iced_core::text::EllipsizeHeightLimit::Lines(1), - )) - .apply(widget::container) - .center(Length::FillPortion(title_portion)) - .into() - } - /// Creates the widget for window controls. - fn window_controls(&mut self) -> Element<'a, Message> { + fn window_controls(&mut self, spacing: u16) -> Element<'a, Message> { macro_rules! icon { ($name:expr, $size:expr, $on_press:expr) => {{ - let icon = { - widget::icon::from_name($name) - .apply(widget::button::icon) - .padding(8) - }; - - icon.class(crate::theme::Button::HeaderBar) + widget::icon::from_name($name) + .apply(widget::button::icon) + .padding(8) + .class(crate::theme::Button::HeaderBar) .selected(self.focused) .icon_size($size) .on_press($on_press) @@ -482,7 +479,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .push_maybe( self.on_minimize .take() - .map(|m: Message| icon!("window-minimize-symbolic", 16, m)), + .map(|m| icon!("window-minimize-symbolic", 16, m)), ) .push_maybe(self.on_maximize.take().map(|m| { if self.maximized { @@ -496,21 +493,14 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .take() .map(|m| icon!("window-close-symbolic", 16, m)), ) - .spacing(theme::spacing().space_xxs) - .apply(widget::container) - .center_y(Length::Fill) + .spacing(spacing) + .align_y(iced::Alignment::Center) .into() } } impl<'a, Message: Clone + 'static> From> for Element<'a, Message> { fn from(headerbar: HeaderBar<'a, Message>) -> Self { - Element::new(headerbar.build()) - } -} - -impl<'a, Message: Clone + 'static> From> for Element<'a, Message> { - fn from(headerbar: HeaderBarWidget<'a, Message>) -> Self { - Element::new(headerbar) + headerbar.view() } } From 0bb006c5bbf7eb89491891d45bfc8f21f8eb1305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Tue, 17 Mar 2026 17:13:46 +0100 Subject: [PATCH 480/556] fix(header_bar): add vertical SSD padding Prevents SSDs from having a gap after the rebase. --- src/widget/header_bar.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 9ab6ff15..11b00e09 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -394,7 +394,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { end.push(self.window_controls(space_xxs)); let padding = if self.is_ssd { - [0, 8, 0, 8] + [2, 8, 2, 8] } else { match ( self.density.unwrap_or_else(crate::config::header_size), From c7ac9cfd31c8c5095d46f9322adc3e7c3208c94e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 17 Mar 2026 15:18:09 -0400 Subject: [PATCH 481/556] fix: if not in bounds, return default mouse interaction --- src/widget/segmented_button/widget.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 857d6371..059d8387 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -1596,7 +1596,7 @@ where } } - iced_core::mouse::Interaction::Idle + iced_core::mouse::Interaction::default() } #[allow(clippy::too_many_lines)] From 6c6d16d34a3572b96eabb40a86872c230d0cdd93 Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Wed, 18 Mar 2026 10:53:09 -0400 Subject: [PATCH 482/556] fix(iced): scaling issue in the cosmic-greeter lock screen --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 7491547d..2d412482 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 7491547d7078c8bad54cf350b1276c7f32e50df5 +Subproject commit 2d412482884ff36b30aeca656c8c43043a9f3e20 From 54bcb9ec128e86b222a3435b7c90b8c660b769bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Wed, 18 Mar 2026 15:54:07 +0100 Subject: [PATCH 483/556] chore: update dependencies and examples --- Cargo.toml | 19 ++++----- cosmic-config/Cargo.toml | 8 ++-- cosmic-theme/Cargo.toml | 2 +- examples/cosmic/src/window/bluetooth.rs | 5 ++- examples/cosmic/src/window/demo.rs | 40 +++++++++--------- examples/cosmic/src/window/desktop.rs | 41 +++++++++---------- .../cosmic/src/window/system_and_accounts.rs | 9 ++-- src/app/cosmic.rs | 4 +- src/widget/header_bar.rs | 29 ++++++------- src/widget/settings/section.rs | 6 --- 10 files changed, 76 insertions(+), 87 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ecb84bb5..23483a1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,10 +115,10 @@ x11 = ["iced/x11", "iced_winit/x11"] [dependencies] apply = "0.3.0" -ashpd = { version = "0.12.1", default-features = false, optional = true } +ashpd = { version = "0.12.3", default-features = false, optional = true } async-fs = { version = "2.2", optional = true } async-std = { version = "1.13", optional = true } -auto_enums = "0.8.7" +auto_enums = "0.8.8" cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true } jiff = "0.2" cosmic-config = { path = "cosmic-config" } @@ -131,17 +131,16 @@ i18n-embed = { version = "0.16.0", features = [ i18n-embed-fl = "0.10" rust-embed = "8.11.0" css-color = "0.2.8" -derive_setters = "0.1.8" +derive_setters = "0.1.9" futures = "0.3" -image = { version = "0.25.9", default-features = false, features = [ +image = { version = "0.25.10", default-features = false, features = [ "jpeg", "png", ] } -libc = { version = "0.2.180", optional = true } +libc = { version = "0.2.183", optional = true } log = "0.4" mime = { version = "0.3.17", optional = true } palette = "0.7.6" -raw-window-handle = "0.6" rfd = { version = "0.16.0", default-features = false, features = [ "xdg-portal", ], optional = true } @@ -151,18 +150,18 @@ slotmap = "1.1.1" smol = { version = "2.0.2", optional = true } thiserror = "2.0.18" taffy = { version = "0.9.2", features = ["grid"] } -tokio = { version = "1.49.0", optional = true } +tokio = { version = "1.50.0", optional = true } tracing = "0.1.44" unicode-segmentation = "1.12" url = "2.5.8" -zbus = { version = "5.13.2", default-features = false, optional = true } +zbus = { version = "5.14.0", default-features = false, optional = true } float-cmp = "0.10.0" # Enable DBus feature on Linux targets [target.'cfg(target_os = "linux")'.dependencies] cosmic-config = { path = "cosmic-config", features = ["dbus"] } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" } -zbus = { version = "5.13.2", default-features = false } +zbus = { version = "5.14.0", default-features = false } [target.'cfg(unix)'.dependencies] freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } @@ -242,4 +241,4 @@ exclude = ["iced"] dirs = "6.0.0" [dev-dependencies] -tempfile = "3.24.0" +tempfile = "3.27.0" diff --git a/cosmic-config/Cargo.toml b/cosmic-config/Cargo.toml index 6103c15e..0a7653e0 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -11,9 +11,9 @@ subscription = ["iced_futures"] [dependencies] cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } -zbus = { version = "5.13.2", default-features = false, optional = true } +zbus = { version = "5.14.0", default-features = false, optional = true } atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" } -calloop = { version = "0.14.3", optional = true } +calloop = { version = "0.14.4", optional = true } notify = "8.2.0" ron = "0.12.0" serde = "1.0.228" @@ -22,7 +22,7 @@ iced = { path = "../iced/", default-features = false, optional = true } iced_futures = { path = "../iced/futures/", default-features = false, optional = true } futures-util = { version = "0.3", optional = true } dirs.workspace = true -tokio = { version = "1.49", optional = true, features = ["time"] } +tokio = { version = "1.50", optional = true, features = ["time"] } async-std = { version = "1.13", optional = true } tracing = "0.1" @@ -30,4 +30,4 @@ tracing = "0.1" xdg = "3.0" [target.'cfg(windows)'.dependencies] -known-folders = "1.4.0" +known-folders = "1.4.2" diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index 80f4805d..1d64912a 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -22,7 +22,7 @@ serde_json = { version = "1.0.149", optional = true, features = [ "preserve_order", ] } ron = "0.12.0" -csscolorparser = { version = "0.8.1", features = ["serde"] } +csscolorparser = { version = "0.8.3", features = ["serde"] } cosmic-config = { path = "../cosmic-config/", default-features = false, features = [ "subscription", "macro", diff --git a/examples/cosmic/src/window/bluetooth.rs b/examples/cosmic/src/window/bluetooth.rs index 44fe7d6c..1b5892f6 100644 --- a/examples/cosmic/src/window/bluetooth.rs +++ b/examples/cosmic/src/window/bluetooth.rs @@ -28,13 +28,14 @@ impl State { column!( list_column().add(settings::item( "Bluetooth", - toggler(None, self.enabled, Message::Enable) + toggler(self.enabled).on_toggle(Message::Enable) )), text("Now visible as \"TODO\", just kidding") ) .spacing(8) .into(), - settings::view_section("Devices") + settings::section() + .title("Devices") .add(settings::item("No devices found", text(""))) .into(), ]) diff --git a/examples/cosmic/src/window/demo.rs b/examples/cosmic/src/window/demo.rs index 9ca84ef7..0d31fa93 100644 --- a/examples/cosmic/src/window/demo.rs +++ b/examples/cosmic/src/window/demo.rs @@ -258,12 +258,13 @@ impl State { match self.tab_bar.active_data() { None => panic!("no tab is active"), Some(DemoView::TabA) => settings::view_column(vec![ - settings::view_section("Debug") + settings::section() + .title("Debug") .add(settings::item("Debug theme", choose_theme)) .add(settings::item("Debug icon theme", choose_icon_theme)) .add(settings::item( "Debug layout", - toggler(None, window.debug, Message::Debug), + toggler(window.debug).on_toggle(Message::Debug), )) .add(settings::item( "Scaling Factor", @@ -276,10 +277,11 @@ impl State { .into(), ])) .into(), - settings::view_section("Controls") + settings::section() + .title("Controls") .add(settings::item( "Toggler", - toggler(None, self.toggler_value, Message::TogglerToggled), + toggler(self.toggler_value).on_toggle(Message::TogglerToggled), )) .add(settings::item( "Pick List (TODO)", @@ -299,15 +301,13 @@ impl State { .add(settings::item( "Progress", progress_bar(0.0..=100.0, self.slider_value) - .width(Length::Fixed(250.0)) - .height(Length::Fixed(4.0)), + .length(Length::Fixed(250.0)) + .girth(Length::Fixed(4.0)), )) - .add(settings::item_row(vec![checkbox( - "Checkbox", - self.checkbox_value, - Message::CheckboxToggled, - ) - .into()])) + .add(settings::item_row(vec![checkbox(self.checkbox_value) + .label("Checkbox") + .on_toggle(Message::CheckboxToggled) + .into()])) .add(settings::item( format!( "Spin Button (Range {}:{})", @@ -354,8 +354,7 @@ impl State { .width(Length::Shrink) .on_activate(Message::MultiSelection) .apply(container) - .center_x() - .width(Length::Fill) + .center_x(Length::Fill) .into(), text("Vertical With Spacing").into(), cosmic::iced::widget::row(vec![ @@ -424,13 +423,12 @@ impl State { ]) .padding(0) .into(), - Some(DemoView::TabC) => { - settings::view_column(vec![settings::view_section("Tab C") - .add(text("Nothing here yet").width(Length::Fill)) - .into()]) - .padding(0) - .into() - } + Some(DemoView::TabC) => settings::view_column(vec![settings::section() + .title("Tab C") + .add(text("Nothing here yet").width(Length::Fill)) + .into()]) + .padding(0) + .into(), }, container(text("Background container with some text").size(24)) .layer(cosmic_theme::Layer::Background) diff --git a/examples/cosmic/src/window/desktop.rs b/examples/cosmic/src/window/desktop.rs index 4fa726d8..46a4e5b8 100644 --- a/examples/cosmic/src/window/desktop.rs +++ b/examples/cosmic/src/window/desktop.rs @@ -147,7 +147,8 @@ impl State { fn view_desktop_options<'a>(&'a self, window: &'a Window) -> Element<'a, Message> { settings::view_column(vec![ window.parent_page_button(DesktopPage::DesktopOptions), - settings::view_section("Super Key Action") + settings::section() + .title("Super Key Action") .add(settings::item("Launcher", horizontal_space(Length::Fill))) .add(settings::item("Workspaces", horizontal_space(Length::Fill))) .add(settings::item( @@ -155,38 +156,34 @@ impl State { horizontal_space(Length::Fill), )) .into(), - settings::view_section("Hot Corner") + settings::section() + .title("Hot Corner") .add(settings::item( "Enable top-left hot corner for Workspaces", - toggler(None, self.top_left_hot_corner, Message::TopLeftHotCorner), + toggler(self.top_left_hot_corner).on_toggle(Message::TopLeftHotCorner), )) .into(), - settings::view_section("Top Panel") + settings::section() + .title("Top Panel") .add(settings::item( "Show Workspaces Button", - toggler( - None, - self.show_workspaces_button, - Message::ShowWorkspacesButton, - ), + toggler(self.show_workspaces_button).on_toggle(Message::ShowWorkspacesButton), )) .add(settings::item( "Show Applications Button", - toggler( - None, - self.show_applications_button, - Message::ShowApplicationsButton, - ), + toggler(self.show_applications_button) + .on_toggle(Message::ShowApplicationsButton), )) .into(), - settings::view_section("Window Controls") + settings::section() + .title("Window Controls") .add(settings::item( "Show Minimize Button", - toggler(None, self.show_minimize_button, Message::ShowMinimizeButton), + toggler(self.show_minimize_button).on_toggle(Message::ShowMinimizeButton), )) .add(settings::item( "Show Maximize Button", - toggler(None, self.show_maximize_button, Message::ShowMaximizeButton), + toggler(self.show_maximize_button).on_toggle(Message::ShowMaximizeButton), )) .into(), ]) @@ -245,12 +242,12 @@ impl State { list_column() .add(settings::item( "Same background on all displays", - toggler(None, self.same_background, Message::SameBackground), + toggler(self.same_background).on_toggle(Message::SameBackground), )) .add(settings::item("Background fit", text("TODO"))) .add(settings::item( "Slideshow", - toggler(None, self.slideshow, Message::Slideshow), + toggler(self.slideshow).on_toggle(Message::Slideshow), )) .into(), column(image_column).spacing(16).into(), @@ -261,7 +258,8 @@ impl State { fn view_desktop_workspaces<'a>(&'a self, window: &'a Window) -> Element<'a, Message> { settings::view_column(vec![ window.parent_page_button(DesktopPage::Wallpaper), - settings::view_section("Workspace Behavior") + settings::section() + .title("Workspace Behavior") .add(settings::item( "Dynamic workspaces", horizontal_space(Length::Fill), @@ -271,7 +269,8 @@ impl State { horizontal_space(Length::Fill), )) .into(), - settings::view_section("Multi-monitor Behavior") + settings::section() + .title("Multi-monitor Behavior") .add(settings::item( "Workspaces Span Displays", horizontal_space(Length::Fill), diff --git a/examples/cosmic/src/window/system_and_accounts.rs b/examples/cosmic/src/window/system_and_accounts.rs index e42e643c..ed1bd004 100644 --- a/examples/cosmic/src/window/system_and_accounts.rs +++ b/examples/cosmic/src/window/system_and_accounts.rs @@ -69,14 +69,16 @@ impl State { list_column() .add(settings::item("Device name", text("TODO"))) .into(), - settings::view_section("Hardware") + settings::section() + .title("Hardware") .add(settings::item("Hardware model", text("TODO"))) .add(settings::item("Memory", text("TODO"))) .add(settings::item("Processor", text("TODO"))) .add(settings::item("Graphics", text("TODO"))) .add(settings::item("Disk Capacity", text("TODO"))) .into(), - settings::view_section("Operating System") + settings::section() + .title("Operating System") .add(settings::item("Operating system", text("TODO"))) .add(settings::item( "Operating system architecture", @@ -85,7 +87,8 @@ impl State { .add(settings::item("Desktop environment", text("TODO"))) .add(settings::item("Windowing system", text("TODO"))) .into(), - settings::view_section("Related settings") + settings::section() + .title("Related settings") .add(settings::item("Get support", text("TODO"))) .into(), ]) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index edd7b157..9566403a 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -49,8 +49,8 @@ pub fn windowing_system() -> Option { WINDOWING_SYSTEM.get().copied() } -fn init_windowing_system(handle: raw_window_handle::WindowHandle) -> crate::Action { - let raw: &raw_window_handle::RawWindowHandle = handle.as_ref(); +fn init_windowing_system(handle: window::raw_window_handle::WindowHandle) -> crate::Action { + let raw = handle.as_ref(); let system = match raw { window::raw_window_handle::RawWindowHandle::UiKit(_) => WindowingSystem::UiKit, window::raw_window_handle::RawWindowHandle::AppKit(_) => WindowingSystem::AppKit, diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 11b00e09..1c0ca2c0 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -197,7 +197,16 @@ impl<'a, Message: Clone + 'static> Widget layout::Node { + let dy = ((height - node.size().height) / 2.0).max(0.0); + node.translate(Vector::new(x, dy)) + }; + + let mut child_nodes = Vec::with_capacity(3); + child_nodes.push(vcenter(start_node, 0.0)); + child_nodes.push(vcenter(end_node, width - end_width)); + + if let Some(center) = &mut self.center { let slot_start = start_width + gap; let slot_end = (width - end_width - gap).max(slot_start); let slot_width = slot_end - slot_start; @@ -217,21 +226,8 @@ impl<'a, Message: Clone + 'static> Widget layout::Node { - let dy = ((height - node.size().height) / 2.0).max(0.0); - node.translate(Vector::new(x, dy)) - }; - - let mut child_nodes = Vec::with_capacity(3); - child_nodes.push(vcenter(start_node, 0.0)); - child_nodes.push(vcenter(end_node, width - end_width)); - if let Some(cn) = center_node { - child_nodes.push(vcenter(cn, center_x)); + child_nodes.push(vcenter(node, center_x)) } layout::Node::with_children(Size::new(width, height), child_nodes) @@ -398,8 +394,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { } else { match ( self.density.unwrap_or_else(crate::config::header_size), - // Center content depending on window border - self.maximized, + self.maximized, // window border handling ) { (Density::Compact, true) => [4, 8, 4, 8], (Density::Compact, false) => [3, 7, 4, 7], diff --git a/src/widget/settings/section.rs b/src/widget/settings/section.rs index 899826dc..ab95b5ad 100644 --- a/src/widget/settings/section.rs +++ b/src/widget/settings/section.rs @@ -5,12 +5,6 @@ use crate::Element; use crate::widget::{ListColumn, column, text}; use std::borrow::Cow; -/// A section within a settings view column. -#[deprecated(note = "use `settings::section().title()` instead")] -pub fn view_section<'a, Message: 'static>(title: impl Into>) -> Section<'a, Message> { - section().title(title) -} - /// A section within a settings view column. pub fn section<'a, Message: 'static>() -> Section<'a, Message> { with_column(ListColumn::default()) From 3da55e807440a99f6ed62edc2e7a84ca4be9b844 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Tue, 17 Mar 2026 16:45:39 -0600 Subject: [PATCH 484/556] fix(flex_row): calculate height based on nodes --- src/widget/flex_row/layout.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/widget/flex_row/layout.rs b/src/widget/flex_row/layout.rs index ae0c28d6..166b47f4 100644 --- a/src/widget/flex_row/layout.rs +++ b/src/widget/flex_row/layout.rs @@ -162,9 +162,14 @@ pub fn resolve( }); }); + let actual_height = nodes + .iter() + .map(|node| node.bounds().y + node.bounds().height) + .fold(0.0f32, f32::max); + let size = Size { width: flex_layout.content_size.width, - height: flex_layout.content_size.height, + height: actual_height.max(flex_layout.content_size.height), }; Node::with_children(size, nodes) From 36cba695d2e4b12e4172ed8855811f6bf96223f6 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 19 Mar 2026 18:25:11 -0400 Subject: [PATCH 485/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 2d412482..a3a434ac 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 2d412482884ff36b30aeca656c8c43043a9f3e20 +Subproject commit a3a434ac924cb0d8f0c30ff704a01f01031c7fbb From 7a5676242259c1c743387b7a23df12bd8be1e53f Mon Sep 17 00:00:00 2001 From: Hojjat Date: Fri, 20 Mar 2026 14:33:40 -0600 Subject: [PATCH 486/556] fix: restore width and height fill for app content --- src/app/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/mod.rs b/src/app/mod.rs index 47900107..5c0e95e4 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -742,6 +742,8 @@ impl ApplicationExt for App { })); let content: Element<_> = if content_container { content_col + .width(iced::Length::Fill) + .height(iced::Length::Fill) .apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_content_container"))) .into() } else { From dc3ebaa38e6b09c5f9489d2dabc7dd31012caf40 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Thu, 19 Mar 2026 12:18:13 -0600 Subject: [PATCH 487/556] feat(segmented_button): add ellipsize support --- src/widget/segmented_button/horizontal.rs | 12 +++++ src/widget/segmented_button/vertical.rs | 9 +++- src/widget/segmented_button/widget.rs | 53 +++++++++++++++++++++-- 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/widget/segmented_button/horizontal.rs b/src/widget/segmented_button/horizontal.rs index 3e46dd5e..5fd67649 100644 --- a/src/widget/segmented_button/horizontal.rs +++ b/src/widget/segmented_button/horizontal.rs @@ -213,6 +213,18 @@ where state.buttons_offset = num - state.buttons_visible; } + // Resize paragraph bounds so that text ellipsis can take effect. + if !matches!(self.width, Length::Shrink) || state.collapsed { + let num = state.buttons_visible.max(1) as f32; + let spacing = f32::from(self.spacing); + let mut width_offset = 0.0; + if state.collapsed { + width_offset = f32::from(self.button_height) * 2.0; + } + let button_width = ((num).mul_add(-spacing, size.width - width_offset) + spacing) / num; + self.resize_paragraphs(state, button_width); + } + size } } diff --git a/src/widget/segmented_button/vertical.rs b/src/widget/segmented_button/vertical.rs index 7963e9c8..5458cd0a 100644 --- a/src/widget/segmented_button/vertical.rs +++ b/src/widget/segmented_button/vertical.rs @@ -117,10 +117,15 @@ where height += item_height; } - limits.height(Length::Fixed(height)).resolve( + let size = limits.height(Length::Fixed(height)).resolve( self.width, self.height, Size::new(width, height), - ) + ); + + // Resize paragraph bounds so that text ellipsis can take effect. + self.resize_paragraphs(state, size.width); + + size } } diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 059d8387..bdce1324 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -156,6 +156,8 @@ where pub(super) spacing: u16, /// LineHeight of the font. pub(super) line_height: LineHeight, + /// Ellipsize strategy for button text. + pub(super) ellipsize: Ellipsize, /// Style to draw the widget in. #[setters(into)] pub(super) style: Style, @@ -223,6 +225,7 @@ where width: Length::Fill, spacing: 0, line_height: LineHeight::default(), + ellipsize: Ellipsize::default(), style: Style::default(), context_menu: None, on_activate: None, @@ -275,7 +278,7 @@ where shaping: Shaping::Advanced, wrapping: Wrapping::None, line_height: self.line_height, - ellipsize: Ellipsize::default(), + ellipsize: self.ellipsize, }; paragraph.update(text); } else { @@ -289,7 +292,7 @@ where shaping: Shaping::Advanced, wrapping: Wrapping::None, line_height: self.line_height, - ellipsize: Ellipsize::default(), + ellipsize: self.ellipsize, }; state.paragraphs.insert(key, crate::Plain::new(text)); } @@ -621,7 +624,7 @@ where align_y: alignment::Vertical::Center, shaping: Shaping::Advanced, wrapping: Wrapping::default(), - ellipsize: Ellipsize::default(), + ellipsize: self.ellipsize, line_height: self.line_height, }) }); @@ -657,6 +660,50 @@ where (width, f32::from(self.button_height)) } + /// Resizes paragraph bounds based on the actual available button width so that + /// text ellipsis can take effect. Call this after `variant_layout` has populated + /// `state.internal_layout` with final button sizes. + pub(super) fn resize_paragraphs(&self, state: &mut LocalState, available_width: f32) { + if matches!(self.ellipsize, Ellipsize::None) { + return; + } + + for (nth, key) in self.model.order.iter().copied().enumerate() { + if self.model.text(key).is_some_and(|text| !text.is_empty()) { + let mut non_text_width = + f32::from(self.button_padding[0]) + f32::from(self.button_padding[2]); + + if let Some(icon) = self.model.icon(key) { + non_text_width += f32::from(icon.size) + f32::from(self.button_spacing); + } else if self.model.is_active(key) { + if let crate::theme::SegmentedButton::Control = self.style { + non_text_width += 16.0 + f32::from(self.button_spacing); + } + } + + if self.model.is_closable(key) { + non_text_width += + f32::from(self.close_icon.size) + f32::from(self.button_spacing); + } + + let text_width = (available_width - non_text_width).max(0.0); + + if let Some(paragraph) = state.paragraphs.get_mut(key) { + paragraph.resize(Size::new(text_width, f32::INFINITY)); + + // Update internal_layout actual content width so that + // button_alignment centering uses the ellipsized size. + let content_width = paragraph.min_bounds().width + non_text_width + - f32::from(self.button_padding[0]) + - f32::from(self.button_padding[2]); + if let Some(entry) = state.internal_layout.get_mut(nth) { + entry.1.width = content_width; + } + } + } + } + } + pub(super) fn max_button_dimensions( &self, state: &mut LocalState, From c804d3851d28ec4ecea38a430fc66d7858af6ce1 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Fri, 20 Mar 2026 15:07:11 -0600 Subject: [PATCH 488/556] fix: don't ever draw glyphs outside of the bounds --- src/widget/segmented_button/widget.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index bdce1324..76c74f3b 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -1985,7 +1985,9 @@ where // Align contents of the button to the requested `button_alignment`. { - let actual_width = state.internal_layout[nth].1.width; + // Avoid shifting content outside the left edge when the measured content is + // wider than the available button bounds (for example, non-ellipsized text). + let actual_width = state.internal_layout[nth].1.width.min(bounds.width); let offset = match self.button_alignment { Alignment::Start => None, From 141261b9bfdae30bdfd96feaf57d8ae6a48db55f Mon Sep 17 00:00:00 2001 From: Hojjat Date: Fri, 20 Mar 2026 16:25:10 -0600 Subject: [PATCH 489/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index a3a434ac..70f54c99 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit a3a434ac924cb0d8f0c30ff704a01f01031c7fbb +Subproject commit 70f54c994acb17aa247284366edc630d8514e23d From d7fd880ac6e3ea03b421541837d654bd036437ea Mon Sep 17 00:00:00 2001 From: Frederic Laing Date: Mon, 23 Mar 2026 01:11:11 +0100 Subject: [PATCH 490/556] fix(toggler): add touch input support --- src/widget/toggler.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index 12bb8950..9d31ca1e 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -7,7 +7,7 @@ use iced_core::{ Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment, event, layout, mouse, renderer::{self, Renderer}, - text, + text, touch, widget::{self, Tree, tree}, window, }; @@ -239,7 +239,8 @@ impl<'a, Message> Widget for Toggler<'a, }; let state = tree.state.downcast_mut::(); match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { let mouse_over = cursor_position.is_over(layout.bounds()); if mouse_over { From 8e439c842ccc37a5df0821141c61766aef10c53e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 23 Mar 2026 20:17:53 -0400 Subject: [PATCH 491/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 70f54c99..f59d5354 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 70f54c994acb17aa247284366edc630d8514e23d +Subproject commit f59d5354bfc433d636c6987a60b61bc8f7a25d68 From adb3e341fc35282c0cee108b73740dcb28d4efe1 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 25 Mar 2026 12:04:00 -0400 Subject: [PATCH 492/556] fix(theme): bright colors for success, warn, destructive --- cosmic-theme/src/model/theme.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 8e1cd9f7..5db0f32c 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -986,19 +986,19 @@ impl ThemeBuilder { let success = if let Some(success) = success { success.into_color() } else { - palette.as_ref().accent_green + palette.as_ref().bright_green }; let warning = if let Some(warning) = warning { warning.into_color() } else { - palette.as_ref().accent_yellow + palette.as_ref().bright_orange }; let destructive = if let Some(destructive) = destructive { destructive.into_color() } else { - palette.as_ref().accent_red + palette.as_ref().bright_red }; let text_steps_array = text_tint.map(|c| steps(c, NonZeroUsize::new(100).unwrap())); From 763f0da64cea86422150f522b6f0503653529a2e Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Thu, 26 Mar 2026 17:19:39 -0400 Subject: [PATCH 493/556] fix(iced): RTL text fix --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index f59d5354..a11b8282 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit f59d5354bfc433d636c6987a60b61bc8f7a25d68 +Subproject commit a11b828280ccded9dd2c5d52fb4c71dc9a999e3d From a38a6f5d73294441f6ee9f141dffb541a83a8fb0 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Thu, 26 Mar 2026 18:02:10 -0600 Subject: [PATCH 494/556] fix(ci): install dependencies --- .github/workflows/pages.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 46d53ad2..4229839e 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -15,6 +15,8 @@ jobs: uses: actions/checkout@v3 with: submodules: recursive + - name: System dependencies + run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev - name: Build documentation run: cargo doc --verbose --features tokio,winit - name: Deploy documentation From e63f3196e2ae7e9a581829675d61c3e32ce1a194 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 27 Mar 2026 15:06:22 -0400 Subject: [PATCH 495/556] fix: MenuActive path highlight --- src/widget/menu/menu_inner.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index d23a1599..596e148e 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -765,7 +765,13 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { PathHighlight::OmitActive => { !indices.is_empty() && i < indices.len() - 1 } - PathHighlight::MenuActive => self.depth == state.active_root.len() - 1, + PathHighlight::MenuActive => { + !indices.is_empty() + && i < indices.len() + && menu_roots.len() > indices[i] + && (i < indices.len() - 1 + || !menu_roots[indices[i]].children.is_empty()) + } }); // react only to the last menu From 254c13cfc486833bf24dabf35038dd5991b1862d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 27 Mar 2026 14:37:35 -0400 Subject: [PATCH 496/556] fix: ellipsize text in menu items --- src/widget/menu/menu_tree.rs | 46 ++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index bd182b9c..047df0ed 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -252,9 +252,18 @@ 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).into(), + widget::text(l) + .ellipsize(iced_core::text::Ellipsize::Middle( + iced_core::text::EllipsizeHeightLimit::Lines(1), + )) + .into(), widget::space::horizontal().into(), - widget::text(key).class(key_class).into(), + widget::text(key) + .class(key_class) + .ellipsize(iced_core::text::Ellipsize::Middle( + iced_core::text::EllipsizeHeightLimit::Lines(1), + )) + .into(), ]; if let Some(icon) = icon { @@ -275,9 +284,18 @@ pub fn menu_items< let key = find_key(&action, key_binds); let mut items = vec![ - widget::text(l).into(), + widget::text(l) + .ellipsize(iced_core::text::Ellipsize::Middle( + iced_core::text::EllipsizeHeightLimit::Lines(1), + )) + .into(), widget::space::horizontal().into(), - widget::text(key).class(key_class).into(), + widget::text(key) + .ellipsize(iced_core::text::Ellipsize::Middle( + iced_core::text::EllipsizeHeightLimit::Lines(1), + )) + .class(key_class) + .into(), ]; if let Some(icon) = icon { @@ -312,9 +330,19 @@ pub fn menu_items< .into() }, widget::space::horizontal().width(spacing.space_xxs).into(), - widget::text(label).align_x(iced::Alignment::Start).into(), + widget::text(label) + .ellipsize(iced_core::text::Ellipsize::Middle( + iced_core::text::EllipsizeHeightLimit::Lines(1), + )) + .align_x(iced::Alignment::Start) + .into(), widget::space::horizontal().into(), - widget::text(key).class(key_class).into(), + widget::text(key) + .class(key_class) + .ellipsize(iced_core::text::Ellipsize::Middle( + iced_core::text::EllipsizeHeightLimit::Lines(1), + )) + .into(), ]; if let Some(icon) = icon { @@ -335,7 +363,11 @@ pub fn menu_items< trees.push(MenuTree::::with_children( RcElementWrapper::new(crate::Element::from( menu_button::<'static, _>(vec![ - widget::text(l.clone()).into(), + widget::text(l.clone()) + .ellipsize(iced_core::text::Ellipsize::Middle( + iced_core::text::EllipsizeHeightLimit::Lines(1), + )) + .into(), widget::space::horizontal().into(), widget::icon::from_name("pan-end-symbolic") .size(16) From 380b341bdc57c28b8e46da13a1baf4ec996ea6e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dtalo=20Dell=20Areti?= Date: Thu, 19 Feb 2026 13:18:02 -0300 Subject: [PATCH 497/556] feat(text_input): add select_range method and Task function --- src/widget/text_input/input.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 3960cee1..43db6a4d 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -1125,6 +1125,14 @@ pub fn select_all(id: Id) -> Task { task::effect(Action::widget(operation::text_input::select_all(id))) } +/// Produces a [`Task`] that selects a range of the content of the [`TextInput`] with the given +/// [`Id`]. +pub fn select_range(id: Id, start: usize, end: usize) -> Task { + task::effect(Action::widget(operation::text_input::select_range( + id, start, end, + ))) +} + /// Computes the layout of a [`TextInput`]. #[allow(clippy::cast_precision_loss)] #[allow(clippy::too_many_arguments)] @@ -2782,6 +2790,12 @@ impl State { self.cursor.select_range(0, usize::MAX); } + /// Selects a range of the content of the [`TextInput`]. + #[inline] + pub fn select_range(&mut self, start: usize, end: usize) { + self.cursor.select_range(start, end); + } + pub(super) fn setting_selection(&mut self, value: &Value, bounds: Rectangle, target: f32) { let position = if target > 0.0 { find_cursor_position(bounds, value, self, target) @@ -2842,8 +2856,9 @@ impl operation::TextInput for State { todo!() } + #[inline] fn select_range(&mut self, start: usize, end: usize) { - todo!() + Self::select_range(self, start, end); } } From 413e63f62a84ee9833eb13fa33ff44b27280f12a Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 30 Mar 2026 18:51:33 -0400 Subject: [PATCH 498/556] chore: update features and feature gates --- Cargo.toml | 13 +++++-- iced | 2 +- src/app/action.rs | 6 +-- src/app/cosmic.rs | 63 ++++++++++++++++--------------- src/app/settings.rs | 4 +- src/core.rs | 8 ++-- src/lib.rs | 2 +- src/surface/action.rs | 18 ++++----- src/theme/style/mod.rs | 4 +- src/widget/autosize.rs | 2 +- src/widget/context_menu.rs | 36 +++++++++++++++--- src/widget/dropdown/mod.rs | 2 +- src/widget/dropdown/widget.rs | 22 +++++------ src/widget/menu/menu_bar.rs | 27 +++++++++++-- src/widget/menu/menu_inner.rs | 8 +++- src/widget/mod.rs | 2 +- src/widget/responsive_menu_bar.rs | 4 +- src/widget/text_input/input.rs | 38 +++++++++---------- 18 files changed, 159 insertions(+), 102 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 23483a1d..35d048ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ default = [ "a11y", "dbus-config", "x11", - "wayland", + "iced-wayland", "multi-window", ] # default = ["dbus-config", "multi-window", "a11y"] # Accessibility support @@ -80,15 +80,20 @@ tokio = [ ] # Tokio async runtime # Wayland window support -wayland = [ +iced-wayland = [ "ashpd?/wayland", "autosize", - "iced_runtime/wayland", "iced/wayland", "iced_winit/wayland", - "cctk", "surface-message", ] +wayland = [ + "iced-wayland", + "iced_runtime/cctk", + "iced_winit/cctk", + "iced/cctk", + "dep:cctk", +] surface-message = [] # multi-window support multi-window = [] diff --git a/iced b/iced index a11b8282..1fdd24ab 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit a11b828280ccded9dd2c5d52fb4c71dc9a999e3d +Subproject commit 1fdd24ab995a4d65ba83cc1957e992b57cc37fcd diff --git a/src/app/action.rs b/src/app/action.rs index 05fc7cbe..fb982acb 100644 --- a/src/app/action.rs +++ b/src/app/action.rs @@ -5,7 +5,7 @@ use crate::surface; use crate::theme::Theme; use crate::widget::nav_bar; use crate::{config::CosmicTk, keyboard_nav}; -#[cfg(feature = "wayland")] +#[cfg(all(feature = "wayland", target_os = "linux"))] use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use cosmic_theme::ThemeMode; @@ -69,10 +69,10 @@ pub enum Action { /// Updates the tracked window geometry. WindowResize(iced::window::Id, f32, f32), /// Tracks updates to window state. - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] WindowState(iced::window::Id, WindowState), /// Capabilities the window manager supports - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] WmCapabilities(iced::window::Id, WindowManagerCapabilities), #[cfg(feature = "xdg-portal")] DesktopSettings(crate::theme::portal::Desktop), diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 9566403a..b732eee9 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -8,16 +8,16 @@ use std::sync::Arc; use super::{Action, Application, ApplicationExt, Subscription}; use crate::theme::{THEME, Theme, ThemeType}; use crate::{Core, Element, keyboard_nav}; -#[cfg(feature = "wayland")] +#[cfg(all(feature = "wayland", target_os = "linux"))] use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use cosmic_theme::ThemeMode; -#[cfg(not(any(feature = "multi-window", feature = "wayland")))] +#[cfg(not(any(feature = "multi-window", feature = "wayland", target_os = "linux")))] use iced::Application as IcedApplication; -#[cfg(feature = "wayland")] +#[cfg(all(feature = "wayland", target_os = "linux"))] use iced::event::wayland; use iced::{Task, theme, window}; use iced_futures::event::listen_with; -#[cfg(feature = "wayland")] +#[cfg(all(feature = "wayland", target_os = "linux"))] use iced_winit::SurfaceIdWrapper; use palette::color_difference::EuclideanDistance; @@ -83,7 +83,7 @@ fn init_windowing_system(handle: window::raw_window_handle::WindowHandle) -> #[derive(Default)] pub struct Cosmic { pub app: App, - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] pub surface_views: HashMap< window::Id, ( @@ -138,7 +138,7 @@ where ) -> iced::Task> { #[cfg(feature = "surface-message")] match _surface_message { - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] crate::surface::Action::AppSubsurface(settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() @@ -168,7 +168,7 @@ where iced_winit::commands::subsurface::get_subsurface(settings(&mut self.app)) } } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] crate::surface::Action::Subsurface(settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() @@ -196,7 +196,7 @@ where iced_winit::commands::subsurface::get_subsurface(settings()) } } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] crate::surface::Action::AppPopup(settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() @@ -225,15 +225,15 @@ where iced_winit::commands::popup::get_popup(settings(&mut self.app)) } } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] crate::surface::Action::DestroyPopup(id) => { iced_winit::commands::popup::destroy_popup(id) } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] crate::surface::Action::DestroySubsurface(id) => { iced_winit::commands::subsurface::destroy_subsurface(id) } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] crate::surface::Action::DestroyWindow(id) => iced::window::close(id), crate::surface::Action::ResponsiveMenuBar { menu_bar, @@ -244,7 +244,7 @@ where core.menu_bars.insert(menu_bar, (limits, size)); iced::Task::none() } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] crate::surface::Action::Popup(settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() @@ -271,7 +271,7 @@ where iced_winit::commands::popup::get_popup(settings()) } } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] crate::surface::Action::AppWindow(id, settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| { s.downcast:: iced::window::Settings + Send + Sync>>() @@ -310,7 +310,7 @@ where .discard() } } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] crate::surface::Action::Window(id, settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| { s.downcast:: iced::window::Settings + Send + Sync>>() @@ -430,7 +430,7 @@ where } iced::Event::Window(window::Event::Focused) => return Some(Action::Focus(id)), iced::Event::Window(window::Event::Unfocused) => return Some(Action::Unfocus(id)), - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => { match event { wayland::Event::Popup(wayland::PopupEvent::Done, _, id) @@ -443,7 +443,7 @@ where ) => { return Some(Action::SuggestedBounds(b)); } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] wayland::Event::Window(iced::event::wayland::WindowEvent::WindowState( s, )) => { @@ -560,7 +560,7 @@ where #[cfg(feature = "multi-window")] pub fn view(&self, id: window::Id) -> Element<'_, crate::Action> { - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] if let Some((_, _, v)) = self.surface_views.get(&id) { return v(&self.app); } @@ -611,7 +611,7 @@ impl Cosmic { fn cosmic_update(&mut self, message: Action) -> iced::Task> { match message { Action::WindowMaximized(id, maximized) => { - #[cfg(not(feature = "wayland"))] + #[cfg(not(all(feature = "wayland", target_os = "linux")))] if self .app .core() @@ -641,7 +641,7 @@ impl Cosmic { }); } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] Action::WindowState(id, state) => { if self .app @@ -693,7 +693,7 @@ impl Cosmic { } } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] Action::WmCapabilities(id, capabilities) => { if self .app @@ -800,7 +800,7 @@ impl Cosmic { new_theme.theme_type.prefer_dark(prefer_dark); cosmic_theme.set_theme(new_theme.theme_type); - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] if self.app.core().sync_window_border_radii_to_theme() { use iced_runtime::platform_specific::wayland::CornerRadius; use iced_winit::platform_specific::commands::corner_radius::corner_radius; @@ -946,7 +946,7 @@ impl Cosmic { // Only apply update if the theme is set to load a system theme if let ThemeType::System { .. } = cosmic_theme.theme_type { cosmic_theme.set_theme(new_theme.theme_type); - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] if self.app.core().sync_window_border_radii_to_theme() { use iced_runtime::platform_specific::wayland::CornerRadius; use iced_winit::platform_specific::commands::corner_radius::corner_radius; @@ -1040,7 +1040,7 @@ impl Cosmic { // Unminimize window before requesting to activate it. let mut task = iced_runtime::window::minimize(id, false); - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] { task = task.chain( iced_winit::platform_specific::commands::activation::activate( @@ -1051,7 +1051,7 @@ impl Cosmic { ) } - #[cfg(not(feature = "wayland"))] + #[cfg(not(all(feature = "wayland", target_os = "linux")))] { task = task.chain(iced_runtime::window::gain_focus(id)); } @@ -1068,7 +1068,7 @@ impl Cosmic { *v == 0 }) { self.opened_surfaces.remove(&id); - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] self.surface_views.remove(&id); self.tracked_windows.remove(&id); } @@ -1190,7 +1190,8 @@ impl Cosmic { #[cfg(all( feature = "wayland", feature = "multi-window", - feature = "surface-message" + feature = "surface-message", + target_os = "linux" ))] if let Some(( parent, @@ -1235,7 +1236,7 @@ impl Cosmic { core.applet.suggested_bounds = b; } Action::Opened(id) => { - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] if self.app.core().sync_window_border_radii_to_theme() { use iced_runtime::platform_specific::wayland::CornerRadius; use iced_winit::platform_specific::commands::corner_radius::corner_radius; @@ -1284,14 +1285,14 @@ impl Cosmic { pub fn new(app: App) -> Self { Self { app, - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] surface_views: HashMap::new(), tracked_windows: HashSet::new(), opened_surfaces: HashMap::new(), } } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] /// Create a subsurface pub fn get_subsurface( &mut self, @@ -1314,7 +1315,7 @@ impl Cosmic { get_subsurface(settings) } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] /// Create a subsurface pub fn get_popup( &mut self, @@ -1336,7 +1337,7 @@ impl Cosmic { get_popup(settings) } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] /// Create a window surface pub fn get_window( &mut self, diff --git a/src/app/settings.rs b/src/app/settings.rs index 926181e1..5c903f09 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -16,7 +16,7 @@ pub struct Settings { pub(crate) antialiasing: bool, /// Autosize the window to fit its contents - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] pub(crate) autosize: bool, /// Set the application to not create a main window @@ -80,7 +80,7 @@ impl Default for Settings { fn default() -> Self { Self { antialiasing: true, - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] autosize: false, no_main_window: false, client_decorations: true, diff --git a/src/core.rs b/src/core.rs index 4d50e764..970a5351 100644 --- a/src/core.rs +++ b/src/core.rs @@ -99,7 +99,7 @@ pub struct Core { pub(crate) menu_bars: HashMap, - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] pub(crate) sync_window_border_radii_to_theme: bool, } @@ -159,7 +159,7 @@ impl Default for Core { main_window: None, exit_on_main_window_closed: true, menu_bars: HashMap::new(), - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] sync_window_border_radii_to_theme: true, } } @@ -493,12 +493,12 @@ impl Core { } // TODO should we emit tasks setting the corner radius or unsetting it if this is changed? - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] pub fn set_sync_window_border_radii_to_theme(&mut self, sync: bool) { self.sync_window_border_radii_to_theme = sync; } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] pub fn sync_window_border_radii_to_theme(&self) -> bool { self.sync_window_border_radii_to_theme } diff --git a/src/lib.rs b/src/lib.rs index 1a579f96..aa3b7db2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,7 +100,7 @@ pub(crate) mod malloc; #[cfg(all(feature = "process", not(windows)))] pub mod process; -#[cfg(feature = "wayland")] +#[cfg(all(feature = "wayland", target_os = "linux"))] pub use cctk; pub mod surface; diff --git a/src/surface/action.rs b/src/surface/action.rs index 3a078ca3..50e2b4a9 100644 --- a/src/surface/action.rs +++ b/src/surface/action.rs @@ -9,25 +9,25 @@ use iced::window; use std::{any::Any, sync::Arc}; /// Used to produce a destroy popup message from within a widget. -#[cfg(feature = "wayland")] +#[cfg(all(feature = "wayland", target_os = "linux"))] #[must_use] pub fn destroy_popup(id: iced_core::window::Id) -> Action { Action::DestroyPopup(id) } -#[cfg(feature = "wayland")] +#[cfg(all(feature = "wayland", target_os = "linux"))] #[must_use] pub fn destroy_subsurface(id: iced_core::window::Id) -> Action { Action::DestroySubsurface(id) } -#[cfg(feature = "wayland")] +#[cfg(all(feature = "wayland", target_os = "linux"))] #[must_use] pub fn destroy_window(id: iced_core::window::Id) -> Action { Action::DestroyWindow(id) } -#[cfg(all(feature = "wayland", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[must_use] pub fn app_window( settings: impl Fn(&mut App) -> window::Settings + Send + Sync + 'static, @@ -60,7 +60,7 @@ pub fn app_window( } /// Used to create a window message from within a widget. -#[cfg(all(feature = "wayland", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[must_use] pub fn simple_window( settings: impl Fn() -> window::Settings + Send + Sync + 'static, @@ -92,7 +92,7 @@ pub fn simple_window( ) } -#[cfg(all(feature = "wayland", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[must_use] pub fn app_popup( settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings @@ -126,7 +126,7 @@ pub fn app_popup( } /// Used to create a subsurface message from within a widget. -#[cfg(all(feature = "wayland", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[must_use] pub fn simple_subsurface( settings: impl Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings @@ -155,7 +155,7 @@ pub fn simple_subsurface( } /// Used to create a popup message from within a widget. -#[cfg(all(feature = "wayland", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[must_use] pub fn simple_popup( settings: impl Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings @@ -186,7 +186,7 @@ pub fn simple_popup( ) } -#[cfg(all(feature = "wayland", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[must_use] pub fn subsurface( settings: impl Fn( diff --git a/src/theme/style/mod.rs b/src/theme/style/mod.rs index a187374c..bc648a73 100644 --- a/src/theme/style/mod.rs +++ b/src/theme/style/mod.rs @@ -32,7 +32,7 @@ mod text_input; #[doc(inline)] pub use self::text_input::TextInput; -#[cfg(all(feature = "wayland", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] pub mod tooltip; -#[cfg(all(feature = "wayland", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] pub use tooltip::Tooltip; diff --git a/src/widget/autosize.rs b/src/widget/autosize.rs index 937aabf9..69fd9c83 100644 --- a/src/widget/autosize.rs +++ b/src/widget/autosize.rs @@ -170,7 +170,7 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] if matches!( event, Event::PlatformSpecific(event::PlatformSpecific::Wayland( diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 200021c3..918d4da2 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -3,7 +3,12 @@ //! A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation. -#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] +#[cfg(all( + feature = "wayland", + target_os = "linux", + feature = "winit", + feature = "surface-message" +))] use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; use crate::widget::menu::{ self, CloseCondition, Direction, ItemHeight, ItemWidth, MenuBarState, PathHighlight, @@ -59,7 +64,12 @@ pub struct ContextMenu<'a, Message> { } impl ContextMenu<'_, Message> { - #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + #[cfg(all( + feature = "wayland", + target_os = "linux", + feature = "winit", + feature = "surface-message" + ))] #[allow(clippy::too_many_lines)] fn create_popup( &mut self, @@ -364,7 +374,12 @@ impl Widget state.active_root.clear(); state.open = false; - #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + #[cfg(all( + feature = "wayland", + target_os = "linux", + feature = "winit", + feature = "surface-message" + ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && let Some(id) = state.popup_id.remove(&self.window_id) { @@ -403,7 +418,12 @@ impl Widget state.open = true; state.view_cursor = cursor; }); - #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + #[cfg(all( + feature = "wayland", + target_os = "linux", + feature = "winit", + feature = "surface-message" + ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { self.create_popup(layout, cursor, renderer, shell, viewport, state); } @@ -422,6 +442,7 @@ impl Widget #[cfg(all( feature = "wayland", + target_os = "linux", feature = "winit", feature = "surface-message" ))] @@ -458,7 +479,12 @@ impl Widget _viewport: &iced::Rectangle, translation: Vector, ) -> Option> { - #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] + #[cfg(all( + feature = "wayland", + target_os = "linux", + feature = "winit", + feature = "surface-message" + ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && self.window_id != window::Id::NONE && self.on_surface_action.is_some() diff --git a/src/widget/dropdown/mod.rs b/src/widget/dropdown/mod.rs index b2d3fbed..b5fd4c06 100644 --- a/src/widget/dropdown/mod.rs +++ b/src/widget/dropdown/mod.rs @@ -50,7 +50,7 @@ pub fn popup_dropdown< let dropdown: Dropdown<'_, S, Message, AppMessage> = Dropdown::new(selections.into(), selected, on_selected); - #[cfg(all(feature = "winit", feature = "wayland"))] + #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] let dropdown = dropdown.with_popup(_parent_id, _on_surface_action, _map_action); dropdown diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index b6244c07..2ff9c92f 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -60,7 +60,7 @@ where action_map: Option AppMessage + 'static + Send + Sync>>, #[setters(strip_option)] window_id: Option, - #[cfg(all(feature = "winit", feature = "wayland"))] + #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, } @@ -96,14 +96,14 @@ where text_line_height: text::LineHeight::Relative(1.2), font: None, window_id: None, - #[cfg(all(feature = "winit", feature = "wayland"))] + #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), on_surface_action: None, action_map: None, } } - #[cfg(all(feature = "winit", feature = "wayland"))] + #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] /// Handle dropdown requests for popup creation. /// Intended to be used with [`crate::app::message::get_popup`] pub fn with_popup( @@ -154,7 +154,7 @@ where self } - #[cfg(all(feature = "winit", feature = "wayland"))] + #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] pub fn with_positioner( mut self, positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, @@ -268,7 +268,7 @@ where layout, cursor, shell, - #[cfg(all(feature = "winit", feature = "wayland"))] + #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] self.positioner.clone(), self.on_selected.clone(), self.selected, @@ -346,7 +346,7 @@ where viewport: &Rectangle, translation: Vector, ) -> Option> { - #[cfg(all(feature = "winit", feature = "wayland"))] + #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] if self.window_id.is_some() || self.on_surface_action.is_some() { return None; } @@ -545,7 +545,7 @@ pub fn update< layout: Layout<'_>, cursor: mouse::Cursor, shell: &mut Shell<'_, Message>, - #[cfg(all(feature = "winit", feature = "wayland"))] + #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, on_selected: Arc Message + Send + Sync + 'static>, selected: Option, @@ -571,7 +571,7 @@ pub fn update< *hovered_guard = selected; let id = window::Id::unique(); state.popup_id = id; - #[cfg(all(feature = "winit", feature = "wayland"))] + #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] if let Some(((on_surface_action, parent), action_map)) = on_surface_action .as_ref() .zip(_window_id) @@ -658,7 +658,7 @@ pub fn update< state.close_operation = false; state.is_open.store(false, Ordering::SeqCst); if is_open { - #[cfg(all(feature = "winit", feature = "wayland"))] + #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] if let Some(ref on_close) = on_surface_action { shell.publish(on_close(surface::action::destroy_popup(state.popup_id))); } @@ -681,7 +681,7 @@ pub fn update< // Event wasn't processed by overlay, so cursor was clicked either outside it's // bounds or on the drop-down, either way we close the overlay. state.is_open.store(false, Ordering::Relaxed); - #[cfg(all(feature = "winit", feature = "wayland"))] + #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] if let Some(on_close) = on_surface_action { shell.publish(on_close(surface::action::destroy_popup(state.popup_id))); } @@ -726,7 +726,7 @@ pub fn mouse_interaction(layout: Layout<'_>, cursor: mouse::Cursor) -> mouse::In } } -#[cfg(all(feature = "winit", feature = "wayland"))] +#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] /// Returns the current menu widget of a [`Dropdown`]. #[allow(clippy::too_many_arguments)] pub fn menu_widget< diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 7007befb..981446e8 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -12,6 +12,7 @@ use super::{ #[cfg(all( feature = "multi-window", feature = "wayland", + target_os = "linux", feature = "winit", feature = "surface-message" ))] @@ -195,7 +196,12 @@ pub struct MenuBar { menu_roots: Vec>, style: ::Style, window_id: window::Id, - #[cfg(all(feature = "multi-window", feature = "wayland", feature = "winit"))] + #[cfg(all( + feature = "multi-window", + feature = "wayland", + feature = "winit", + target_os = "linux" + ))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, pub(crate) on_surface_action: Option Message + Send + Sync + 'static>>, @@ -230,7 +236,12 @@ where menu_roots, style: ::Style::default(), window_id: window::Id::NONE, - #[cfg(all(feature = "multi-window", feature = "wayland", feature = "winit"))] + #[cfg(all( + feature = "multi-window", + feature = "wayland", + feature = "winit", + target_os = "linux" + ))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), on_surface_action: None, } @@ -324,7 +335,12 @@ where self } - #[cfg(all(feature = "multi-window", feature = "wayland", feature = "winit"))] + #[cfg(all( + feature = "multi-window", + feature = "wayland", + feature = "winit", + target_os = "linux" + ))] pub fn with_positioner( mut self, positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, @@ -359,6 +375,7 @@ where #[cfg(all( feature = "multi-window", feature = "wayland", + target_os = "linux", feature = "winit", feature = "surface-message" ))] @@ -629,6 +646,7 @@ where state.open = false; #[cfg(all( feature = "wayland", + target_os = "linux", feature = "winit", feature = "surface-message" ))] @@ -652,6 +670,7 @@ where #[cfg(all( feature = "multi-window", feature = "wayland", + target_os = "linux", feature = "winit", feature = "surface-message" ))] @@ -666,6 +685,7 @@ where #[cfg(all( feature = "multi-window", feature = "wayland", + target_os = "linux", feature = "winit", feature = "surface-message" ))] @@ -748,6 +768,7 @@ where #[cfg(all( feature = "multi-window", feature = "wayland", + target_os = "linux", feature = "winit", feature = "surface-message" ))] diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 596e148e..74afe60f 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -7,6 +7,7 @@ use super::{menu_bar::MenuBarState, menu_tree::MenuTree}; #[cfg(all( feature = "multi-window", feature = "wayland", + target_os = "linux", feature = "winit", feature = "surface-message" ))] @@ -680,6 +681,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { #[cfg(all( feature = "multi-window", feature = "wayland", + target_os = "linux", feature = "winit", feature = "surface-message" ))] @@ -966,7 +968,8 @@ impl Widget( #[cfg(all( feature = "multi-window", feature = "wayland", + target_os = "linux", feature = "winit", feature = "surface-message" ))] @@ -1523,7 +1527,7 @@ where .as_ref() .is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty()); - #[cfg(all(feature = "multi-window", feature = "wayland", feature = "winit", feature = "surface-message"))] + #[cfg(all(feature = "multi-window", feature = "wayland",target_os = "linux", feature = "winit", feature = "surface-message"))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove { if let Some(id) = state.popup_id.remove(&menu.window_id) { state.active_root.truncate(menu.depth + 1); diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 73004597..0f607240 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -355,7 +355,7 @@ pub use toggler::{Toggler, toggler}; #[doc(inline)] pub use tooltip::{Tooltip, tooltip}; -#[cfg(all(feature = "wayland", feature = "winit"))] +#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] pub mod wayland; pub mod tooltip { diff --git a/src/widget/responsive_menu_bar.rs b/src/widget/responsive_menu_bar.rs index 5f855260..b5dd556d 100644 --- a/src/widget/responsive_menu_bar.rs +++ b/src/widget/responsive_menu_bar.rs @@ -25,7 +25,7 @@ impl Default for ResponsiveMenuBar { fn default() -> ResponsiveMenuBar { ResponsiveMenuBar { collapsed_item_width: { - #[cfg(all(feature = "winit", feature = "wayland"))] + #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] if matches!( crate::app::cosmic::WINDOWING_SYSTEM.get(), Some(crate::app::cosmic::WindowingSystem::Wayland) @@ -34,7 +34,7 @@ impl Default for ResponsiveMenuBar { } else { ItemWidth::Static(84) } - #[cfg(not(all(feature = "winit", feature = "wayland")))] + #[cfg(not(all(feature = "winit", feature = "wayland", target_os = "linux")))] { ItemWidth::Static(84) } diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 43db6a4d..8f6fb329 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -513,7 +513,7 @@ where } /// Sets the start dnd handler of the [`TextInput`]. - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] pub fn on_start_dnd(mut self, on_start_dnd: impl Fn(State) -> Message + 'a) -> Self { self.on_create_dnd_source = Some(Box::new(on_start_dnd)); self @@ -1445,7 +1445,7 @@ pub fn update<'a, Message: Clone + 'static>( click.kind(), state.cursor().state(value), ) { - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] (None, click::Kind::Single, cursor::State::Selection { start, end }) => { let left = start.min(end); let right = end.max(start); @@ -1556,7 +1556,7 @@ pub fn update<'a, Message: Clone + 'static>( | Event::Touch(touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }) => { cold(); let state = state(); - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] if matches!(state.dragging_state, Some(DraggingState::PrepareDnd(_))) { // clear selection and place cursor at click position update_cache(state, value); @@ -1589,7 +1589,7 @@ pub fn update<'a, Message: Clone + 'static>( shell.capture_event(); return; } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] 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)) @@ -1980,7 +1980,7 @@ pub fn update<'a, Message: Clone + 'static>( shell.request_redraw(); } } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => { cold(); let state = state(); @@ -1991,7 +1991,7 @@ pub fn update<'a, Message: Clone + 'static>( return; } } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer( rectangle, OfferEvent::Enter { @@ -2032,7 +2032,7 @@ pub fn update<'a, Message: Clone + 'static>( return; } } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y })) if *rectangle == Some(dnd_id) => { @@ -2051,7 +2051,7 @@ pub fn update<'a, Message: Clone + 'static>( shell.capture_event(); return; } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if *rectangle == Some(dnd_id) => { cold(); let state = state(); @@ -2069,9 +2069,9 @@ pub fn update<'a, Message: Clone + 'static>( return; } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if Some(dnd_id) != *id => {} - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer( rectangle, OfferEvent::Leave | OfferEvent::LeaveDestination, @@ -2089,7 +2089,7 @@ pub fn update<'a, Message: Clone + 'static>( shell.capture_event(); return; } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type })) if *rectangle == Some(dnd_id) => { @@ -2336,9 +2336,9 @@ pub fn draw<'a, Message>( let actual_width = text_width.max(text_bounds.width); let radius_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0.into(); - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] let handling_dnd_offer = !matches!(state.dnd_offer, DndOfferState::None); - #[cfg(not(feature = "wayland"))] + #[cfg(not(all(feature = "wayland", target_os = "linux")))] let handling_dnd_offer = false; let (cursor, offset) = if let Some(focus) = state.is_focused.filter(|f| f.focused).or_else(|| { @@ -2567,7 +2567,7 @@ pub fn mouse_interaction( #[derive(Debug, Clone)] pub struct TextInputString(pub String); -#[cfg(feature = "wayland")] +#[cfg(all(feature = "wayland", target_os = "linux"))] impl AsMimeTypes for TextInputString { fn available(&self) -> Cow<'static, [String]> { Cow::Owned( @@ -2591,13 +2591,13 @@ impl AsMimeTypes for TextInputString { #[derive(Debug, Clone, PartialEq)] pub(crate) enum DraggingState { Selection, - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] PrepareDnd(Point), - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] Dnd(DndAction, String), } -#[cfg(feature = "wayland")] +#[cfg(all(feature = "wayland", target_os = "linux"))] #[derive(Debug, Default, Clone)] pub(crate) enum DndOfferState { #[default] @@ -2606,7 +2606,7 @@ pub(crate) enum DndOfferState { Dropped, } #[derive(Debug, Default, Clone)] -#[cfg(not(feature = "wayland"))] +#[cfg(not(all(feature = "wayland", target_os = "linux")))] pub(crate) struct DndOfferState; /// The state of a [`TextInput`]. @@ -2680,7 +2680,7 @@ impl State { } } - #[cfg(feature = "wayland")] + #[cfg(all(feature = "wayland", target_os = "linux"))] /// Returns the current value of the dragged text in the [`TextInput`]. #[must_use] pub fn dragged_text(&self) -> Option { From f06d15ae35204cb3bcef5a3188b5ec59a1cc9bfd Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Tue, 31 Mar 2026 16:02:52 +0100 Subject: [PATCH 499/556] feat(cosmic-theme): produce QPalette ini for more compatibility --- cosmic-theme/src/output/mod.rs | 4 + cosmic-theme/src/output/qt56ct_output.rs | 281 ++++++++++++++++++++++- cosmic-theme/src/output/qt_output.rs | 38 +-- 3 files changed, 303 insertions(+), 20 deletions(-) diff --git a/cosmic-theme/src/output/mod.rs b/cosmic-theme/src/output/mod.rs index b2474dc1..19f7bc5b 100644 --- a/cosmic-theme/src/output/mod.rs +++ b/cosmic-theme/src/output/mod.rs @@ -46,8 +46,10 @@ impl Theme { pub fn write_exports(&self) -> Result<(), OutputError> { let gtk_res = self.write_gtk4(); let qt_res = self.write_qt(); + let qt56ct_res = self.write_qt56ct(); gtk_res?; qt_res?; + qt56ct_res?; Ok(()) } @@ -56,8 +58,10 @@ impl Theme { pub fn reset_exports() -> Result<(), OutputError> { let gtk_res = Theme::reset_gtk(); let qt_res = Theme::reset_qt(); + let qt56ct_res = Theme::reset_qt56ct(); gtk_res?; qt_res?; + qt56ct_res?; Ok(()) } } diff --git a/cosmic-theme/src/output/qt56ct_output.rs b/cosmic-theme/src/output/qt56ct_output.rs index 552e7fec..eccfc846 100644 --- a/cosmic-theme/src/output/qt56ct_output.rs +++ b/cosmic-theme/src/output/qt56ct_output.rs @@ -1,8 +1,11 @@ use crate::Theme; use configparser::ini::Ini; +use palette::{Mix, Srgba, WithAlpha, blend::Compose, rgb::Rgba}; use std::{ fs::{self, File}, + io::Write, path::PathBuf, + vec, }; use super::{OutputError, qt_settings_ini_style}; @@ -15,7 +18,117 @@ impl Theme { /// Increment this value when changes to qt{5,6}ct.conf are needed. /// If the config's version is outdated, we update several sections. /// Otherwise, only the light/dark mode is updated. - const COSMIC_QT_VERSION: u64 = 1; + const COSMIC_QT_VERSION: u64 = 2; + + /// Produces a QPalette ini file for qt5ct and qt6ct. + /// + /// Example file: https://github.com/trialuser02/qt6ct/blob/master/colors/airy.conf + #[must_use] + #[cold] + pub fn as_qpalette(&self) -> String { + let lightest = if self.is_dark { + self.background.on + } else { + self.background.base + }; + let darkest = if self.is_dark { + self.background.base + } else { + self.background.on + }; + let active = QPaletteGroup { + window_text: self.background.on, + button: self.button.base, + light: self.button.base.mix(lightest, 0.1), + midlight: self.button.base.mix(lightest, 0.05), + dark: self.button.base.mix(darkest, 0.1), + mid: self.button.base.mix(darkest, 0.05), + text: self.background.component.on, + bright_text: lightest, + button_text: self.button.on, + base: self.background.component.base, + window: self.background.base, + shadow: darkest, + // selection colors are swapped to fix menu bar contrast + highlight: self.background.component.selected_text, + highlighted_text: self.background.component.selected, + link: self.link_button.on, + link_visited: self.link_button.on.mix(self.secondary.component.base, 0.2), + alternate_base: self.background.base.mix(self.accent.base, 0.05), + no_role: self.background.component.disabled, + tool_tip_base: self.background.component.base, + tool_tip_text: self.background.component.on, + placeholder_text: self.background.component.on.with_alpha(0.5), + }; + let inactive = QPaletteGroup { + window_text: active.window_text.with_alpha(0.8), + text: active.text.with_alpha(0.8), + highlighted_text: active.highlighted_text.with_alpha(0.8), + tool_tip_text: active.tool_tip_text.with_alpha(0.8), + ..active + }; + let disabled = QPaletteGroup { + button: self.button.disabled, + text: self.background.component.on_disabled, + button_text: self.button.on_disabled, + base: self.background.component.disabled, + highlighted_text: active.highlighted_text.with_alpha(0.5), + link: self.link_button.on_disabled, + link_visited: self + .link_button + .on_disabled + .mix(self.secondary.component.disabled, 0.2), + alternate_base: self.background.base.mix(self.accent.disabled, 0.05), + tool_tip_base: self.background.component.disabled, + tool_tip_text: self.background.component.on_disabled, + placeholder_text: self.background.component.on_disabled.with_alpha(0.5), + ..inactive + }; + + format!( + r#"# GENERATED BY COSMIC + +[ColorScheme] +active_colors={} +disabled_colors={} +inactive_colors={} +"#, + active.as_list(), + disabled.as_list(), + inactive.as_list(), + ) + } + + /// Writes the QPalette ini files to: + /// - `~/.config/qt6ct/colors/` + /// - `~/.config/qt5ct/colors/` + #[cold] + pub fn write_qt56ct(&self) -> Result<(), OutputError> { + let qpalette = self.as_qpalette(); + let qt5ct_res = self.write_ct("qt5ct", &qpalette); + let qt6ct_res = self.write_ct("qt6ct", &qpalette); + qt5ct_res?; + qt6ct_res?; + Ok(()) + } + #[must_use] + #[cold] + fn write_ct(&self, ct: &str, qpalette: &str) -> Result<(), OutputError> { + let file_path = Self::get_qpalette_path(ct, self.is_dark)?; + let tmp_file_path = file_path.with_extension("conf.new"); + + let mut tmp_file = File::create(&tmp_file_path).map_err(OutputError::Io)?; + let res = tmp_file + .write_all(qpalette.as_bytes()) + .and_then(|_| tmp_file.flush()) + .and_then(|_| std::fs::rename(&tmp_file_path, file_path)); + if let Err(e) = res { + _ = std::fs::remove_file(&tmp_file_path); + return Err(OutputError::Io(e)); + } + + Ok(()) + } /// Edits qt{5,6}ct.conf to use COSMIC styles if needed. #[cold] @@ -39,7 +152,7 @@ impl Theme { .map_err(OutputError::Ini)? .unwrap_or_default(); - let color_scheme_path = Self::get_qt_colors_path(is_dark)?; + let color_scheme_path = Self::get_qpalette_path(ct, is_dark)?; let icon_theme = if is_dark { "breeze-dark" } else { "breeze" }; ini.set( @@ -91,11 +204,48 @@ impl Theme { Ok(()) } + /// Reset the applied qt56ct config by removing COSMIC-specific entries from the config file. + #[cold] + pub fn reset_qt56ct() -> Result<(), OutputError> { + let qt5ct_res = Self::reset_ct("qt5ct"); + let qt6ct_res = Self::reset_ct("qt6ct"); + qt5ct_res?; + qt6ct_res?; + Ok(()) + } + #[must_use] + #[cold] + fn reset_ct(ct: &str) -> Result<(), OutputError> { + let path = Self::get_conf_path(ct)?; + let file_content = fs::read_to_string(&path).map_err(OutputError::Io)?; + let mut ini = Ini::new_cs(); + ini.read(file_content).map_err(OutputError::Ini)?; + + let old_version = ini + .getuint("Appearance", "cosmic_qt_version") + .map_err(OutputError::Ini)? + .unwrap_or_default(); + if old_version == 0 { + return Ok(()); + } + + ini.remove_key("Appearance", "cosmic_qt_version"); + ini.remove_key("Appearance", "color_scheme_path"); + ini.remove_key("Appearance", "icon_theme"); + + ini.pretty_write(path, &qt_settings_ini_style()) + .map_err(OutputError::Io)?; + Ok(()) + } + /// Returns the file paths of the form `~/.config/ct/ct.conf`: /// e.g. `~/.config/qt6ct/qt6ct.conf`. /// /// The file and its parent directory are created if they don't exist. + #[cold] fn get_conf_path(ct: &str) -> Result { + assert!(ct == "qt5ct" || ct == "qt6ct"); + let Some(mut config_dir) = dirs::config_dir() else { return Err(OutputError::MissingConfigDir); }; @@ -111,4 +261,131 @@ impl Theme { Ok(file_path) } + + /// Gets a path like `~/.config/qt6ct/colors/CosmicDark.conf` + /// + /// Its parent directory is created if it doesn't exist. + #[cold] + fn get_qpalette_path(ct: &str, is_dark: bool) -> Result { + assert!(ct == "qt5ct" || ct == "qt6ct"); + + let Some(mut config_dir) = dirs::config_dir() else { + return Err(OutputError::MissingConfigDir); + }; + config_dir.push(&ct); + config_dir.push("colors"); + if !config_dir.exists() { + fs::create_dir_all(&config_dir).map_err(OutputError::Io)?; + } + + let file_name = if is_dark { + "CosmicDark.conf" + } else { + "CosmicLight.conf" + }; + + Ok(config_dir.join(file_name)) + } +} + +/// Defines the different symbolic color roles used in current GUIs. +/// +/// qt5ct and qt6ct consume this as a list of colors, ordered by ColorRole: +/// - https://doc.qt.io/qt-6/qpalette.html#ColorRole-enum +/// - https://doc.qt.io/archives/qt-5.15/qpalette.html#ColorRole-enum +struct QPaletteGroup { + /// A general foreground color. + window_text: Srgba, + /// The general button background color. + button: Srgba, + /// Lighter than [button] color, used mostly for 3D bevel and shadow effects. + light: Srgba, + /// Between [button] and [light], used mostly for 3D bevel and shadow effects. + midlight: Srgba, + /// Darker than [button], used mostly for 3D bevel and shadow effects. + dark: Srgba, + /// Between [button] and [dark], used mostly for 3D bevel and shadow effects. + mid: Srgba, + /// The foreground color used with [base]. + text: Srgba, + /// A text color that is very different from [window_text], and contrasts well with e.g. [dark]. + /// Typically used for text that needs to be drawn where [text] or [window_text] would give poor contrast, such as on pressed push buttons. + bright_text: Srgba, + /// A foreground color used with the [button] color. + button_text: Srgba, + /// Used mostly as the background color for text entry widgets, but can also be used for other painting - + /// such as the background of combobox drop down lists and toolbar handles. + base: Srgba, + /// A general background color. + window: Srgba, + /// A very dark color, used mostly for 3D bevel and shadow effects. + /// Opaque black by default. + shadow: Srgba, + /// A color to indicate a selected item or the current item. + highlight: Srgba, + /// A text color that contrasts with [highlight]. + highlighted_text: Srgba, + /// A text color used for unvisited hyperlinks. + link: Srgba, + /// A text color used for already visited hyperlinks. + link_visited: Srgba, + /// Used as the alternate background color in views with alternating row colors. + alternate_base: Srgba, + /// No role; this special role is often used to indicate that a role has not been assigned. + no_role: Srgba, + /// Used as the background color for QToolTip and QWhatsThis. + /// Tool tips use the inactive color group of QPalette, because tool tips are not active windows. + tool_tip_base: Srgba, + /// Used as the foreground color for QToolTip and QWhatsThis. + /// Tool tips use the inactive color group of QPalette, because tool tips are not active windows. + tool_tip_text: Srgba, + /// Used as the placeholder color for various text input widgets. + placeholder_text: Srgba, + // /// [accent] only exists since Qt 6.6. Including it here breaks qt5ct. + // /// When omitted, it defaults to [highlight]. + // accent: Srgba, +} + +impl QPaletteGroup { + /// Returns a comma-separated list of the colors as hex codes. + /// E.g. `#ff000000, #ffdcdcdc, ...` + /// + /// Any transparent colors are flattened with [base] to avoid issues with + /// the Fusion style. + fn as_list(&self) -> String { + let colors = vec![ + to_argb_hex(self.window_text.over(self.base)), + to_argb_hex(self.button.over(self.base)), + to_argb_hex(self.light.over(self.base)), + to_argb_hex(self.midlight.over(self.base)), + to_argb_hex(self.dark.over(self.base)), + to_argb_hex(self.mid.over(self.base)), + to_argb_hex(self.text.over(self.base)), + to_argb_hex(self.bright_text.over(self.base)), + to_argb_hex(self.button_text.over(self.base)), + to_argb_hex(self.base.over(self.base)), + to_argb_hex(self.window.over(self.base)), + to_argb_hex(self.shadow.over(self.base)), + to_argb_hex(self.highlight.over(self.base)), + to_argb_hex(self.highlighted_text.over(self.base)), + to_argb_hex(self.link.over(self.base)), + to_argb_hex(self.link_visited.over(self.base)), + to_argb_hex(self.alternate_base.over(self.base)), + to_argb_hex(self.no_role.over(self.base)), + to_argb_hex(self.tool_tip_base.over(self.base)), + to_argb_hex(self.tool_tip_text.over(self.base)), + to_argb_hex(self.placeholder_text.over(self.base)), + ]; + colors.join(", ") + } +} + +/// Converts a color to a hex string in the format `#AARRGGBB`. +/// Do not use [to_hex] since that uses the format `RRGGBBAA`. +fn to_argb_hex(c: Srgba) -> String { + let c_u8: Rgba = c.into_format(); + format!( + "#{:02x}{:02x}{:02x}{:02x}", + c_u8.alpha, c_u8.red, c_u8.green, c_u8.blue + ) } diff --git a/cosmic-theme/src/output/qt_output.rs b/cosmic-theme/src/output/qt_output.rs index 9bca3d18..86f7ac13 100644 --- a/cosmic-theme/src/output/qt_output.rs +++ b/cosmic-theme/src/output/qt_output.rs @@ -14,10 +14,11 @@ impl Theme { /// Produces a color scheme ini file for Qt. /// /// Some high-level documentation for this file can be found at: - /// https://web.archive.org/web/20250402234329/https://docs.kde.org/stable5/en/plasma-workspace/kcontrol/colors/ + /// - https://api.kde.org/kcolorscheme.html + /// - https://web.archive.org/web/20250402234329/https://docs.kde.org/stable5/en/plasma-workspace/kcontrol/colors/ #[must_use] #[cold] - pub fn as_qt(&self) -> String { + pub fn as_kcolorscheme(&self) -> String { // Usually, disabled elements will have strongly reduced contrast and are often notably darker or lighter let disabled_color_effects = IniColorEffects { color: self.button.disabled, @@ -41,7 +42,7 @@ impl Theme { let bg = self.background.base; // the background container - let view_colors = IniColors { + let window_colors = IniColors { background_alternate: bg.mix(self.accent.base, 0.05), background_normal: bg, decoration_focus: self.accent_text_color(), @@ -56,16 +57,17 @@ impl Theme { foreground_visited: self.accent_text_color(), }; // components inside the background container - let window_colors = IniColors { + let view_colors = IniColors { background_alternate: self.background.component.base.mix(self.accent.base, 0.05), background_normal: self.background.component.base, - ..view_colors + ..window_colors }; // selected text and items let selection_colors = { - let selected = self.background.component.selected; - let selected_text = self.background.component.selected_text; + // selection colors are swapped to fix menu bar contrast + let selected = self.background.component.selected_text; + let selected_text = self.background.component.selected; IniColors { background_alternate: selected.mix(bg, 0.5), background_normal: selected, @@ -116,10 +118,10 @@ impl Theme { }; // headers in cosmic don't have a background - let header_colors = &view_colors; - let header_colors_inactive = &view_colors; + let header_colors = &window_colors; + let header_colors_inactive = &window_colors; // tool tips, "What's This" tips, and similar elements - let tooltip_colors = &window_colors; + let tooltip_colors = &view_colors; let general_color_scheme = if self.is_dark { "CosmicDark" @@ -198,7 +200,7 @@ widgetStyle=qt6ct-style format_ini_colors(&tooltip_colors, bg), format_ini_colors(&view_colors, bg), format_ini_colors(&window_colors, bg), - format_ini_wm_colors(&view_colors, self.is_dark), + format_ini_wm_colors(&window_colors, self.is_dark), ) } @@ -212,14 +214,14 @@ widgetStyle=qt6ct-style /// Returns an `OutputError` if there is an error writing the colors file. #[cold] pub fn write_qt(&self) -> Result<(), OutputError> { - let colors = self.as_qt(); - let file_path = Self::get_qt_colors_path(self.is_dark)?; + let kcolorscheme = self.as_kcolorscheme(); + let file_path = Self::get_kcolorscheme_path(self.is_dark)?; let tmp_file_path = file_path.with_extension("colors.new"); // Write to tmp_file_path first, then move it to file_path let mut tmp_file = File::create(&tmp_file_path).map_err(OutputError::Io)?; let res = tmp_file - .write_all(colors.as_bytes()) + .write_all(kcolorscheme.as_bytes()) .and_then(|_| tmp_file.flush()) .and_then(|_| std::fs::rename(&tmp_file_path, file_path)); if let Err(e) = res { @@ -245,7 +247,7 @@ widgetStyle=qt6ct-style let kdeglobals_file = config_dir.join("kdeglobals"); let mut kdeglobals_ini = Self::read_ini(&kdeglobals_file)?; - let src_file = Self::get_qt_colors_path(is_dark)?; + let src_file = Self::get_kcolorscheme_path(is_dark)?; let src_ini = Self::read_ini(&src_file)?; Self::backup_non_cosmic_kdeglobals(&kdeglobals_ini, &kdeglobals_file) @@ -288,7 +290,7 @@ widgetStyle=qt6ct-style } let is_dark = false; // doesn't matter since we're only reading keys - let src_file = Self::get_qt_colors_path(is_dark)?; + let src_file = Self::get_kcolorscheme_path(is_dark)?; let src_ini = Self::read_ini(&src_file)?; for (section, key_value) in src_ini.get_map_ref() { @@ -303,8 +305,8 @@ widgetStyle=qt6ct-style Ok(()) } - /// Gets a path like `~/.config/color-schemes/CosmicDark.colors` - pub fn get_qt_colors_path(is_dark: bool) -> Result { + /// Gets a path like `~/.local/share/color-schemes/CosmicDark.colors` + fn get_kcolorscheme_path(is_dark: bool) -> Result { let Some(mut data_dir) = dirs::data_dir() else { return Err(OutputError::MissingDataDir); }; From 1433b89e407a2f2676ceec1090224b7e27f155f7 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 31 Mar 2026 14:58:46 -0400 Subject: [PATCH 500/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 1fdd24ab..be453292 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 1fdd24ab995a4d65ba83cc1957e992b57cc37fcd +Subproject commit be453292c69f3bf103b93ea27e38f57386450085 From 4541c6a275bd90f14b91bbce875212825702c9dd Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 31 Mar 2026 15:09:07 -0400 Subject: [PATCH 501/556] fix: example deps --- examples/applet/Cargo.toml | 2 +- examples/application/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/applet/Cargo.toml b/examples/applet/Cargo.toml index f97bff44..13eff684 100644 --- a/examples/applet/Cargo.toml +++ b/examples/applet/Cargo.toml @@ -13,6 +13,6 @@ env_logger = "0.10.2" log = "0.4.29" [dependencies.libcosmic] -git = "https://github.com/pop-os/libcosmic" +path = "../../" default-features = false features = ["applet-token"] diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index c842c79f..bc037ec0 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -11,7 +11,7 @@ wayland = ["libcosmic/wayland"] env_logger = "0.11" [dependencies.libcosmic] -git = "https://github.com/pop-os/libcosmic" +path = "../../" features = [ "debug", "winit", From d631f9d6d789304a1f01806cf2c1a0c5e93df58a Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Wed, 1 Apr 2026 17:21:27 -0400 Subject: [PATCH 502/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index be453292..e4da5002 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit be453292c69f3bf103b93ea27e38f57386450085 +Subproject commit e4da5002ae4e9d68cc4ac777ed77b4a225659440 From 8b52592f2d6c0915d11a96af25915698e351800e Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Wed, 1 Apr 2026 17:11:42 +0100 Subject: [PATCH 503/556] ci: test cosmic-theme --- .github/workflows/ci.yml | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a822642e..7897eb01 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,16 +33,17 @@ jobs: strategy: fail-fast: false matrix: - features: - - "" # for cosmic-comp, don't remove! - - 'winit_debug' - - 'winit_tokio' - - winit - - winit_wgpu - - wayland - - applet - - desktop,smol - - desktop,tokio + test_args: + - --no-default-features --features "" # for cosmic-comp, don't remove! + - --no-default-features --features "winit_debug" + - --no-default-features --features "winit_tokio" + - --no-default-features --features "winit" + - --no-default-features --features "winit_wgpu" + - --no-default-features --features "wayland" + - --no-default-features --features "applet" + - --no-default-features --features "desktop,smol" + - --no-default-features --features "desktop,tokio" + - -p cosmic-theme runs-on: ubuntu-22.04 steps: - name: Checkout sources @@ -66,7 +67,7 @@ jobs: - name: Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Test features - run: cargo test --no-default-features --features "${{ matrix.features }}" -- --test-threads=1 + run: cargo test ${{ matrix.test_args }} -- --test-threads=1 env: RUST_BACKTRACE: full @@ -103,7 +104,7 @@ jobs: run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev - name: Rust toolchain uses: dtolnay/rust-toolchain@stable - - name: Test example + - name: Check example run: cargo check -p "${{ matrix.examples }}" env: RUST_BACKTRACE: full From 672f9047a2666eee371ae11082800aafd1d51dd8 Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Wed, 1 Apr 2026 17:02:14 +0100 Subject: [PATCH 504/556] test: use almost::zero instead of almost::equal as per documentation "Do not use this to compare a value with a constant zero. Instead, for this you should use almost::zero." --- cosmic-theme/src/steps.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cosmic-theme/src/steps.rs b/cosmic-theme/src/steps.rs index 143cf532..00a002c9 100644 --- a/cosmic-theme/src/steps.rs +++ b/cosmic-theme/src/steps.rs @@ -145,7 +145,6 @@ pub fn is_valid_srgb(c: Srgba) -> bool { #[cfg(test)] mod tests { - use almost::equal; use palette::{OklabHue, Srgba}; use super::{is_valid_srgb, oklch_to_srgba_nearest_chroma}; @@ -173,16 +172,16 @@ mod tests { fn test_conversion_boundaries() { let c1 = palette::Oklcha::new(0.0, 0.288, OklabHue::from_degrees(0.0), 1.0); let srgb = oklch_to_srgba_nearest_chroma(c1); - equal(srgb.red, 0.0); - equal(srgb.blue, 0.0); - equal(srgb.green, 0.0); + almost::zero(srgb.red); + almost::zero(srgb.blue); + almost::zero(srgb.green); let c1 = palette::Oklcha::new(1.0, 0.288, OklabHue::from_degrees(0.0), 1.0); let srgb = oklch_to_srgba_nearest_chroma(c1); - equal(srgb.red, 1.0); - equal(srgb.blue, 1.0); - equal(srgb.green, 1.0); + almost::equal(srgb.red, 1.0); + almost::equal(srgb.blue, 1.0); + almost::equal(srgb.green, 1.0); } #[test] From e86304cf3ffaf9ea4a6f60c87898d993b4196942 Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Wed, 1 Apr 2026 16:59:38 +0100 Subject: [PATCH 505/556] ref: use assert_eq not assert This way, the test log can show the expected and actual result if it fails. thread 'steps::tests::test_conversion_fallback_colors' (61338) panicked at cosmic-theme/src/steps.rs:213:9: assertion `left == right` failed left: 102 right: 103 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace --- cosmic-theme/src/steps.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/cosmic-theme/src/steps.rs b/cosmic-theme/src/steps.rs index 00a002c9..4c3ab3d7 100644 --- a/cosmic-theme/src/steps.rs +++ b/cosmic-theme/src/steps.rs @@ -188,41 +188,41 @@ mod tests { fn test_conversion_colors() { let c1 = palette::Oklcha::new(0.4608, 0.11111, OklabHue::new(57.31), 1.0); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::(); - assert!(srgb.red == 133); - assert!(srgb.green == 69); - assert!(srgb.blue == 0); + assert_eq!(srgb.red, 133); + assert_eq!(srgb.green, 69); + assert_eq!(srgb.blue, 0); let c1 = palette::Oklcha::new(0.30, 0.08, OklabHue::new(35.0), 1.0); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::(); - assert!(srgb.red == 78); - assert!(srgb.green == 27); - assert!(srgb.blue == 15); + assert_eq!(srgb.red, 78); + assert_eq!(srgb.green, 27); + assert_eq!(srgb.blue, 15); let c1 = palette::Oklcha::new(0.757, 0.146, OklabHue::new(301.2), 1.0); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::(); - assert!(srgb.red == 192); - assert!(srgb.green == 153); - assert!(srgb.blue == 253); + assert_eq!(srgb.red, 192); + assert_eq!(srgb.green, 153); + assert_eq!(srgb.blue, 253); } #[test] fn test_conversion_fallback_colors() { let c1 = palette::Oklcha::new(0.70, 0.284, OklabHue::new(35.0), 1.0); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::(); - assert!(srgb.red == 255); - assert!(srgb.green == 103); - assert!(srgb.blue == 65); + assert_eq!(srgb.red, 255); + assert_eq!(srgb.green, 103); + assert_eq!(srgb.blue, 65); let c1 = palette::Oklcha::new(0.757, 0.239, OklabHue::new(301.2), 1.0); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::(); - assert!(srgb.red == 193); - assert!(srgb.green == 152); - assert!(srgb.blue == 255); + assert_eq!(srgb.red, 193); + assert_eq!(srgb.green, 152); + assert_eq!(srgb.blue, 255); let c1 = palette::Oklcha::new(0.163, 0.333, OklabHue::new(141.0), 1.0); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::(); - assert!(srgb.red == 1); - assert!(srgb.green == 19); - assert!(srgb.blue == 0); + assert_eq!(srgb.red, 1); + assert_eq!(srgb.green, 19); + assert_eq!(srgb.blue, 0); } } From f734ccbbdeb68a5844465b2bb36a9052b54c288b Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Wed, 1 Apr 2026 17:17:19 +0100 Subject: [PATCH 506/556] test: fix expected color value --- cosmic-theme/src/steps.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cosmic-theme/src/steps.rs b/cosmic-theme/src/steps.rs index 4c3ab3d7..6ebf1015 100644 --- a/cosmic-theme/src/steps.rs +++ b/cosmic-theme/src/steps.rs @@ -210,7 +210,7 @@ mod tests { let c1 = palette::Oklcha::new(0.70, 0.284, OklabHue::new(35.0), 1.0); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::(); assert_eq!(srgb.red, 255); - assert_eq!(srgb.green, 103); + assert_eq!(srgb.green, 102); assert_eq!(srgb.blue, 65); let c1 = palette::Oklcha::new(0.757, 0.239, OklabHue::new(301.2), 1.0); From 39e8300d90f7aab7a8b28e216d6631985d2de801 Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Wed, 1 Apr 2026 16:43:32 +0100 Subject: [PATCH 507/556] test: snapshots of kcolorscheme and qpalette AI disclosure: I asked GitHub Copilot (Claude Haiku 4.5) "What's the best way to add tests for my recently merged qt theming contributions?" It suggested the insta crate for golden testing the output strings as well as some unit tests. I implemented it myself. --- cosmic-theme/Cargo.toml | 7 + cosmic-theme/src/output/qt56ct_output.rs | 24 +++ cosmic-theme/src/output/qt_output.rs | 41 +++++ ..._output__tests__dark_default_qpalette.snap | 10 ++ ...output__tests__light_default_qpalette.snap | 10 ++ ...put__tests__dark_default_kcolorscheme.snap | 157 ++++++++++++++++++ ...ut__tests__light_default_kcolorscheme.snap | 157 ++++++++++++++++++ 7 files changed, 406 insertions(+) create mode 100644 cosmic-theme/src/output/snapshots/cosmic_theme__output__qt56ct_output__tests__dark_default_qpalette.snap create mode 100644 cosmic-theme/src/output/snapshots/cosmic_theme__output__qt56ct_output__tests__light_default_qpalette.snap create mode 100644 cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__dark_default_kcolorscheme.snap create mode 100644 cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index 1d64912a..7e408d8d 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -30,3 +30,10 @@ cosmic-config = { path = "../cosmic-config/", default-features = false, features configparser = "3.1.0" dirs.workspace = true thiserror = "2.0.18" + +[dev-dependencies] +insta = "1.47.2" + +[profile.dev.package] +insta.opt-level = 3 +similar.opt-level = 3 diff --git a/cosmic-theme/src/output/qt56ct_output.rs b/cosmic-theme/src/output/qt56ct_output.rs index eccfc846..43a45470 100644 --- a/cosmic-theme/src/output/qt56ct_output.rs +++ b/cosmic-theme/src/output/qt56ct_output.rs @@ -389,3 +389,27 @@ fn to_argb_hex(c: Srgba) -> String { c_u8.alpha, c_u8.red, c_u8.green, c_u8.blue ) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_color_to_argb_hex() { + let color = Srgba::new(0x33, 0x55, 0x77, 0xff); + let argb = to_argb_hex(color.into()); + assert_eq!(argb, "#ff335577"); + } + + #[test] + fn test_light_default_qpalette() { + let light_default_qpalette = Theme::light_default().as_qpalette(); + insta::assert_snapshot!(light_default_qpalette); + } + + #[test] + fn test_dark_default_qpalette() { + let dark_default_qpalette = Theme::dark_default().as_qpalette(); + insta::assert_snapshot!(dark_default_qpalette); + } +} diff --git a/cosmic-theme/src/output/qt_output.rs b/cosmic-theme/src/output/qt_output.rs index 86f7ac13..cd66e865 100644 --- a/cosmic-theme/src/output/qt_output.rs +++ b/cosmic-theme/src/output/qt_output.rs @@ -522,3 +522,44 @@ impl ColorEffect { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_opaque_color_to_rgb() { + let color = Srgba::new(30.0 / 255.0, 50.0 / 255.0, 70.0 / 255.0, 1.0); + let bg = Srgba::new(1.0, 1.0, 1.0, 1.0); + let result = to_rgb(color, bg); + assert_eq!(result, "30,50,70"); + } + + #[test] + fn test_transparent_color_to_rgb() { + let color = Srgba::new(0.0, 0.0, 0.0, 0.0); + let bg = Srgba::new(1.0, 1.0, 1.0, 1.0); + let result = to_rgb(color, bg); + assert_eq!(result, "255,255,255"); + } + + #[test] + fn test_translucent_color_to_rgb() { + let color = Srgba::new(0.0, 0.0, 0.0, 0.9); + let bg = Srgba::new(1.0, 1.0, 1.0, 1.0); + let result = to_rgb(color, bg); + assert_eq!(result, "26,26,26"); + } + + #[test] + fn test_light_default_kcolorscheme() { + let light_default_kcolorscheme = Theme::light_default().as_kcolorscheme(); + insta::assert_snapshot!(light_default_kcolorscheme); + } + + #[test] + fn test_dark_default_kcolorscheme() { + let dark_default_kcolorscheme = Theme::dark_default().as_kcolorscheme(); + insta::assert_snapshot!(dark_default_kcolorscheme); + } +} diff --git a/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt56ct_output__tests__dark_default_qpalette.snap b/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt56ct_output__tests__dark_default_qpalette.snap new file mode 100644 index 00000000..15746fd0 --- /dev/null +++ b/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt56ct_output__tests__dark_default_qpalette.snap @@ -0,0 +1,10 @@ +--- +source: cosmic-theme/src/output/qt56ct_output.rs +expression: dark_default_qpalette +--- +# GENERATED BY COSMIC + +[ColorScheme] +active_colors=#ffe7e7e7, #ff4a4a4a, #ff555555, #ff505050, #ff4f4f4f, #ff4d4d4d, #ffc0c0c0, #ffe7e7e7, #ffc0c0c0, #ff2e2e2e, #ff1b1b1b, #ff1b1b1b, #ff63d0df, #ff434343, #ff63d0df, #ff5bb2be, #ff1f2425, #ff2e2e2e, #ff2e2e2e, #ffc0c0c0, #ff777777 +disabled_colors=#e6d3d3d3, #8f474747, #a9696969, #a4626262, #a95f5f5f, #a45d5d5d, #d2a1a1a1, #ffe7e7e7, #d2a1a1a1, #bf2e2e2e, #ff1b1b1b, #ff1b1b1b, #ff63d0df, #bf3c3c3c, #bf30555a, #bf324f53, #ff1f2425, #bf2e2e2e, #bf2e2e2e, #d2a1a1a1, #bf909090 +inactive_colors=#ffc2c2c2, #ff4a4a4a, #ff555555, #ff505050, #ff4f4f4f, #ff4d4d4d, #ffa3a3a3, #ffe7e7e7, #ffc0c0c0, #ff2e2e2e, #ff1b1b1b, #ff1b1b1b, #ff63d0df, #ff3f3f3f, #ff63d0df, #ff5bb2be, #ff1f2425, #ff2e2e2e, #ff2e2e2e, #ffa3a3a3, #ff777777 diff --git a/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt56ct_output__tests__light_default_qpalette.snap b/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt56ct_output__tests__light_default_qpalette.snap new file mode 100644 index 00000000..c79b2c55 --- /dev/null +++ b/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt56ct_output__tests__light_default_qpalette.snap @@ -0,0 +1,10 @@ +--- +source: cosmic-theme/src/output/qt56ct_output.rs +expression: light_default_qpalette +--- +# GENERATED BY COSMIC + +[ColorScheme] +active_colors=#ff121212, #ffc3c3c3, #ffbababa, #ffbebebe, #ffb3b3b3, #ffbbbbbb, #ff272727, #ffd7d7d7, #ff272727, #fff5f5f5, #ffd7d7d7, #ff121212, #ff00525a, #fff6f6f6, #ff00525a, #ff317379, #ffccd0d1, #fff5f5f5, #fff5f5f5, #ff272727, #ff8e8e8e +disabled_colors=#e62b2b2b, #8fc9c9c9, #a99b9b9b, #a4a0a0a0, #a9929292, #a49b9b9b, #d2535353, #ffd7d7d7, #d2535353, #bff5f5f5, #ffd7d7d7, #ff121212, #ff00525a, #bff6f6f6, #bf526d70, #bf72888a, #ffccd0d1, #bff5f5f5, #bff5f5f5, #d2535353, #bf6c6c6c +inactive_colors=#ff3f3f3f, #ffc3c3c3, #ffbababa, #ffbebebe, #ffb3b3b3, #ffbbbbbb, #ff505050, #ffd7d7d7, #ff272727, #fff5f5f5, #ffd7d7d7, #ff121212, #ff00525a, #fff6f6f6, #ff00525a, #ff317379, #ffccd0d1, #fff5f5f5, #fff5f5f5, #ff505050, #ff8e8e8e diff --git a/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__dark_default_kcolorscheme.snap b/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__dark_default_kcolorscheme.snap new file mode 100644 index 00000000..c50f95dc --- /dev/null +++ b/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__dark_default_kcolorscheme.snap @@ -0,0 +1,157 @@ +--- +source: cosmic-theme/src/output/qt_output.rs +expression: dark_default_kcolorscheme +--- +# GENERATED BY COSMIC + +[ColorEffects:Disabled] +Color=43,43,43 +ColorAmount=0 +ColorEffect=0 +ContrastAmount=0.65 +ContrastEffect=1 +IntensityAmount=0.1 +IntensityEffect=2 + +[ColorEffects:Inactive] +ChangeSelectionColor=false +Enable=false +Color=27,27,27 +ColorAmount=0.025 +ColorEffect=2 +ContrastAmount=0.1 +ContrastEffect=2 +IntensityAmount=0 +IntensityEffect=0 + +[Colors:Button] +BackgroundAlternate=99,208,223 +BackgroundNormal=60,60,60 +DecorationFocus=99,208,223 +DecorationHover=99,208,223 +ForegroundActive=99,208,223 +ForegroundInactive=211,211,211 +ForegroundLink=99,208,223 +ForegroundNegative=255,160,154 +ForegroundNeutral=255,163,125 +ForegroundNormal=231,231,231 +ForegroundPositive=94,219,140 +ForegroundVisited=99,208,223 + +[Colors:Complementary] +BackgroundAlternate=99,208,223 +BackgroundNormal=27,27,27 +DecorationFocus=99,208,223 +DecorationHover=99,208,223 +ForegroundActive=99,208,223 +ForegroundInactive=211,211,211 +ForegroundLink=99,208,223 +ForegroundNegative=255,160,154 +ForegroundNeutral=255,163,125 +ForegroundNormal=231,231,231 +ForegroundPositive=94,219,140 +ForegroundVisited=99,208,223 + +[Colors:Header] +BackgroundAlternate=31,36,37 +BackgroundNormal=27,27,27 +DecorationFocus=99,208,223 +DecorationHover=99,208,223 +ForegroundActive=99,208,223 +ForegroundInactive=211,211,211 +ForegroundLink=99,208,223 +ForegroundNegative=255,160,154 +ForegroundNeutral=255,163,125 +ForegroundNormal=231,231,231 +ForegroundPositive=94,219,140 +ForegroundVisited=99,208,223 + +[Colors:Header][Inactive] +BackgroundAlternate=31,36,37 +BackgroundNormal=27,27,27 +DecorationFocus=99,208,223 +DecorationHover=99,208,223 +ForegroundActive=99,208,223 +ForegroundInactive=211,211,211 +ForegroundLink=99,208,223 +ForegroundNegative=255,160,154 +ForegroundNeutral=255,163,125 +ForegroundNormal=231,231,231 +ForegroundPositive=94,219,140 +ForegroundVisited=99,208,223 + +[Colors:Selection] +BackgroundAlternate=63,118,125 +BackgroundNormal=99,208,223 +DecorationFocus=99,208,223 +DecorationHover=99,208,223 +ForegroundActive=67,67,67 +ForegroundInactive=83,138,145 +ForegroundLink=27,27,27 +ForegroundNegative=255,160,154 +ForegroundNeutral=255,163,125 +ForegroundNormal=67,67,67 +ForegroundPositive=94,219,140 +ForegroundVisited=99,208,223 + +[Colors:Tooltip] +BackgroundAlternate=49,55,55 +BackgroundNormal=46,46,46 +DecorationFocus=99,208,223 +DecorationHover=99,208,223 +ForegroundActive=99,208,223 +ForegroundInactive=211,211,211 +ForegroundLink=99,208,223 +ForegroundNegative=255,160,154 +ForegroundNeutral=255,163,125 +ForegroundNormal=231,231,231 +ForegroundPositive=94,219,140 +ForegroundVisited=99,208,223 + +[Colors:View] +BackgroundAlternate=49,55,55 +BackgroundNormal=46,46,46 +DecorationFocus=99,208,223 +DecorationHover=99,208,223 +ForegroundActive=99,208,223 +ForegroundInactive=211,211,211 +ForegroundLink=99,208,223 +ForegroundNegative=255,160,154 +ForegroundNeutral=255,163,125 +ForegroundNormal=231,231,231 +ForegroundPositive=94,219,140 +ForegroundVisited=99,208,223 + +[Colors:Window] +BackgroundAlternate=31,36,37 +BackgroundNormal=27,27,27 +DecorationFocus=99,208,223 +DecorationHover=99,208,223 +ForegroundActive=99,208,223 +ForegroundInactive=211,211,211 +ForegroundLink=99,208,223 +ForegroundNegative=255,160,154 +ForegroundNeutral=255,163,125 +ForegroundNormal=231,231,231 +ForegroundPositive=94,219,140 +ForegroundVisited=99,208,223 + +[General] +ColorScheme=CosmicDark +Name=COSMIC Dark +shadeSortColumn=true + +[Icons] +Theme=breeze-dark + +[KDE] +contrast=4 +widgetStyle=qt6ct-style + +[WM] +activeBackground=27,27,27 +activeBlend=99,208,223 +activeForeground=99,208,223 +inactiveBackground=27,27,27 +inactiveBlend=99,208,223 +inactiveForeground=99,208,223 diff --git a/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap b/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap new file mode 100644 index 00000000..40aacf01 --- /dev/null +++ b/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap @@ -0,0 +1,157 @@ +--- +source: cosmic-theme/src/output/qt_output.rs +expression: light_default_kcolorscheme +--- +# GENERATED BY COSMIC + +[ColorEffects:Disabled] +Color=194,194,194 +ColorAmount=0 +ColorEffect=0 +ContrastAmount=0.65 +ContrastEffect=1 +IntensityAmount=0.1 +IntensityEffect=2 + +[ColorEffects:Inactive] +ChangeSelectionColor=false +Enable=false +Color=215,215,215 +ColorAmount=0.025 +ColorEffect=2 +ContrastAmount=0.1 +ContrastEffect=2 +IntensityAmount=0 +IntensityEffect=0 + +[Colors:Button] +BackgroundAlternate=0,82,90 +BackgroundNormal=173,173,173 +DecorationFocus=0,82,90 +DecorationHover=0,82,90 +ForegroundActive=0,82,90 +ForegroundInactive=38,38,38 +ForegroundLink=0,82,90 +ForegroundNegative=137,4,24 +ForegroundNeutral=121,44,0 +ForegroundNormal=18,18,18 +ForegroundPositive=0,87,44 +ForegroundVisited=0,82,90 + +[Colors:Complementary] +BackgroundAlternate=24,85,41 +BackgroundNormal=203,221,173 +DecorationFocus=24,85,41 +DecorationHover=24,85,41 +ForegroundActive=24,85,41 +ForegroundInactive=34,36,31 +ForegroundLink=24,85,41 +ForegroundNegative=120,41,46 +ForegroundNeutral=83,72,0 +ForegroundNormal=16,16,16 +ForegroundPositive=24,85,41 +ForegroundVisited=24,85,41 + +[Colors:Header] +BackgroundAlternate=204,208,209 +BackgroundNormal=215,215,215 +DecorationFocus=0,82,90 +DecorationHover=0,82,90 +ForegroundActive=0,82,90 +ForegroundInactive=38,38,38 +ForegroundLink=0,82,90 +ForegroundNegative=137,4,24 +ForegroundNeutral=121,44,0 +ForegroundNormal=18,18,18 +ForegroundPositive=0,87,44 +ForegroundVisited=0,82,90 + +[Colors:Header][Inactive] +BackgroundAlternate=204,208,209 +BackgroundNormal=215,215,215 +DecorationFocus=0,82,90 +DecorationHover=0,82,90 +ForegroundActive=0,82,90 +ForegroundInactive=38,38,38 +ForegroundLink=0,82,90 +ForegroundNegative=137,4,24 +ForegroundNeutral=121,44,0 +ForegroundNormal=18,18,18 +ForegroundPositive=0,87,44 +ForegroundVisited=0,82,90 + +[Colors:Selection] +BackgroundAlternate=108,149,152 +BackgroundNormal=0,82,90 +DecorationFocus=0,82,90 +DecorationHover=0,82,90 +ForegroundActive=246,246,246 +ForegroundInactive=123,164,168 +ForegroundLink=215,215,215 +ForegroundNegative=137,4,24 +ForegroundNeutral=121,44,0 +ForegroundNormal=246,246,246 +ForegroundPositive=0,87,44 +ForegroundVisited=0,82,90 + +[Colors:Tooltip] +BackgroundAlternate=233,237,237 +BackgroundNormal=245,245,245 +DecorationFocus=0,82,90 +DecorationHover=0,82,90 +ForegroundActive=0,82,90 +ForegroundInactive=38,38,38 +ForegroundLink=0,82,90 +ForegroundNegative=137,4,24 +ForegroundNeutral=121,44,0 +ForegroundNormal=18,18,18 +ForegroundPositive=0,87,44 +ForegroundVisited=0,82,90 + +[Colors:View] +BackgroundAlternate=233,237,237 +BackgroundNormal=245,245,245 +DecorationFocus=0,82,90 +DecorationHover=0,82,90 +ForegroundActive=0,82,90 +ForegroundInactive=38,38,38 +ForegroundLink=0,82,90 +ForegroundNegative=137,4,24 +ForegroundNeutral=121,44,0 +ForegroundNormal=18,18,18 +ForegroundPositive=0,87,44 +ForegroundVisited=0,82,90 + +[Colors:Window] +BackgroundAlternate=204,208,209 +BackgroundNormal=215,215,215 +DecorationFocus=0,82,90 +DecorationHover=0,82,90 +ForegroundActive=0,82,90 +ForegroundInactive=38,38,38 +ForegroundLink=0,82,90 +ForegroundNegative=137,4,24 +ForegroundNeutral=121,44,0 +ForegroundNormal=18,18,18 +ForegroundPositive=0,87,44 +ForegroundVisited=0,82,90 + +[General] +ColorScheme=CosmicLight +Name=COSMIC Light +shadeSortColumn=true + +[Icons] +Theme=breeze + +[KDE] +contrast=4 +widgetStyle=qt6ct-style + +[WM] +activeBackground=215,215,215 +activeBlend=215,215,215 +activeForeground=0,82,90 +inactiveBackground=215,215,215 +inactiveBlend=215,215,215 +inactiveForeground=0,82,90 From 9a72fe6c2da372ea940e0669ea713b56e7311133 Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Wed, 1 Apr 2026 17:49:54 +0100 Subject: [PATCH 508/556] fix: complementary should be dark not light --- cosmic-theme/src/output/qt_output.rs | 2 +- ...ut__tests__light_default_kcolorscheme.snap | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cosmic-theme/src/output/qt_output.rs b/cosmic-theme/src/output/qt_output.rs index cd66e865..5b369719 100644 --- a/cosmic-theme/src/output/qt_output.rs +++ b/cosmic-theme/src/output/qt_output.rs @@ -95,7 +95,7 @@ impl Theme { let dark = if self.is_dark { self.clone() } else { - Theme::light_config() + Theme::dark_config() .ok() .as_ref() .and_then(|conf| Theme::get_entry(conf).ok()) diff --git a/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap b/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap index 40aacf01..12c511fa 100644 --- a/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap +++ b/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap @@ -39,18 +39,18 @@ ForegroundPositive=0,87,44 ForegroundVisited=0,82,90 [Colors:Complementary] -BackgroundAlternate=24,85,41 -BackgroundNormal=203,221,173 -DecorationFocus=24,85,41 -DecorationHover=24,85,41 -ForegroundActive=24,85,41 -ForegroundInactive=34,36,31 -ForegroundLink=24,85,41 -ForegroundNegative=120,41,46 -ForegroundNeutral=83,72,0 -ForegroundNormal=16,16,16 -ForegroundPositive=24,85,41 -ForegroundVisited=24,85,41 +BackgroundAlternate=129,196,88 +BackgroundNormal=12,17,6 +DecorationFocus=129,196,88 +DecorationHover=129,196,88 +ForegroundActive=129,196,88 +ForegroundInactive=191,198,186 +ForegroundLink=129,196,88 +ForegroundNegative=253,161,160 +ForegroundNeutral=247,224,98 +ForegroundNormal=211,218,206 +ForegroundPositive=146,207,156 +ForegroundVisited=129,196,88 [Colors:Header] BackgroundAlternate=204,208,209 From c33455e9ad7e1d748f755766b6e5688c90f5f602 Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Wed, 1 Apr 2026 18:01:16 +0100 Subject: [PATCH 509/556] test: use default dark theme, not real system theme --- cosmic-theme/src/output/qt_output.rs | 3 +++ ...ut__tests__light_default_kcolorscheme.snap | 24 +++++++++---------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/cosmic-theme/src/output/qt_output.rs b/cosmic-theme/src/output/qt_output.rs index 5b369719..d42d553b 100644 --- a/cosmic-theme/src/output/qt_output.rs +++ b/cosmic-theme/src/output/qt_output.rs @@ -94,6 +94,9 @@ impl Theme { let complementary_colors = { let dark = if self.is_dark { self.clone() + } else if cfg!(test) { + // For reproducible results in tests, use the default dark theme + Theme::dark_default() } else { Theme::dark_config() .ok() diff --git a/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap b/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap index 12c511fa..ae2bcb66 100644 --- a/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap +++ b/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap @@ -39,18 +39,18 @@ ForegroundPositive=0,87,44 ForegroundVisited=0,82,90 [Colors:Complementary] -BackgroundAlternate=129,196,88 -BackgroundNormal=12,17,6 -DecorationFocus=129,196,88 -DecorationHover=129,196,88 -ForegroundActive=129,196,88 -ForegroundInactive=191,198,186 -ForegroundLink=129,196,88 -ForegroundNegative=253,161,160 -ForegroundNeutral=247,224,98 -ForegroundNormal=211,218,206 -ForegroundPositive=146,207,156 -ForegroundVisited=129,196,88 +BackgroundAlternate=99,208,223 +BackgroundNormal=27,27,27 +DecorationFocus=99,208,223 +DecorationHover=99,208,223 +ForegroundActive=99,208,223 +ForegroundInactive=211,211,211 +ForegroundLink=99,208,223 +ForegroundNegative=255,160,154 +ForegroundNeutral=255,163,125 +ForegroundNormal=231,231,231 +ForegroundPositive=94,219,140 +ForegroundVisited=99,208,223 [Colors:Header] BackgroundAlternate=204,208,209 From 2299fba69b0116d7dc970895a78f24ebe40746a8 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Wed, 1 Apr 2026 11:44:58 -0600 Subject: [PATCH 510/556] fix(text_input): RTL text cursor and highlight fixes --- src/widget/text_input/cursor.rs | 41 ++- src/widget/text_input/input.rs | 448 +++++++++++++++++++++----------- src/widget/text_input/value.rs | 33 ++- 3 files changed, 365 insertions(+), 157 deletions(-) diff --git a/src/widget/text_input/cursor.rs b/src/widget/text_input/cursor.rs index 42f52da1..3ffb535c 100644 --- a/src/widget/text_input/cursor.rs +++ b/src/widget/text_input/cursor.rs @@ -3,16 +3,19 @@ // SPDX-License-Identifier: MIT //! Track the cursor of a text input. +use iced_core::text::Affinity; + use super::value::Value; /// The cursor of a text input. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct Cursor { state: State, + affinity: Affinity, } /// The state of a [`Cursor`]. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum State { /// Cursor without a selection Index(usize), @@ -31,6 +34,7 @@ impl Default for Cursor { fn default() -> Self { Self { state: State::Index(0), + affinity: Affinity::Before, } } } @@ -193,4 +197,37 @@ impl Cursor { State::Selection { start, end } => start.max(end), } } + + /// Returns the current cursor [`Affinity`]. + #[must_use] + pub fn affinity(&self) -> Affinity { + self.affinity + } + + /// Sets the cursor [`Affinity`]. + pub fn set_affinity(&mut self, affinity: Affinity) { + self.affinity = affinity; + } + + /// Moves the cursor in a visual direction, accounting for RTL text. + /// + /// `forward` = `true` is visually rightward. + pub fn move_visual(&mut self, forward: bool, by_words: bool, rtl: bool, value: &Value) { + match (forward ^ rtl, by_words) { + (true, false) => self.move_right(value), + (true, true) => self.move_right_by_words(value), + (false, false) => self.move_left(value), + (false, true) => self.move_left_by_words(value), + } + } + + /// Extends the selection in a visual direction, accounting for RTL text. + pub fn select_visual(&mut self, forward: bool, by_words: bool, rtl: bool, value: &Value) { + match (forward ^ rtl, by_words) { + (true, false) => self.select_right(value), + (true, true) => self.select_right_by_words(value), + (false, false) => self.select_left(value), + (false, true) => self.select_left_by_words(value), + } + } } diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 8f6fb329..ffb08c8b 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -937,6 +937,18 @@ where self.drag_threshold, self.always_active, ); + + let state = tree.state.downcast_mut::(); + let value = if self.is_secure { + self.value.secure() + } else { + self.value.clone() + }; + state.scroll_offset = offset( + text_layout.children().next().unwrap().bounds(), + &value, + state, + ); } #[inline] @@ -1435,7 +1447,17 @@ pub fn update<'a, Message: Clone + 'static>( return; } - let target = cursor_position.x - text_layout.bounds().x; + let target = { + let text_bounds = text_layout.bounds(); + + let alignment_offset = alignment_offset( + text_bounds.width, + state.value.raw().min_width(), + effective_alignment(state.value.raw()), + ); + + cursor_position.x - text_bounds.x - alignment_offset + }; let click = mouse::Click::new(cursor_position, mouse::Button::Left, state.last_click); @@ -1454,17 +1476,30 @@ pub fn update<'a, Message: Clone + 'static>( state.value.raw(), text_layout.bounds(), left, + value, + state.cursor.affinity(), + state.scroll_offset, ); let (right_position, _right_offset) = measure_cursor_and_scroll_offset( state.value.raw(), text_layout.bounds(), right, + value, + state.cursor.affinity(), + state.scroll_offset, ); - let width = right_position - left_position; + let selection_start = left_position.min(right_position); + let width = (right_position - left_position).abs(); + let alignment_offset = alignment_offset( + text_layout.bounds().width, + state.value.raw().min_width(), + effective_alignment(state.value.raw()), + ); let selection_bounds = Rectangle { - x: text_layout.bounds().x + left_position, + x: text_layout.bounds().x + alignment_offset + selection_start + - state.scroll_offset, y: text_layout.bounds().y, width, height: text_layout.bounds().height, @@ -1492,10 +1527,11 @@ pub fn update<'a, Message: Clone + 'static>( if is_secure { state.cursor.select_all(value); } else { - let position = + let (position, affinity) = find_cursor_position(text_layout.bounds(), value, state, target) - .unwrap_or(0); + .unwrap_or((0, text::Affinity::Before)); + state.cursor.set_affinity(affinity); state.cursor.select_range( value.previous_start_of_word(position), value.next_end_of_word(position), @@ -1561,7 +1597,17 @@ pub fn update<'a, Message: Clone + 'static>( // 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; + let target = { + let text_bounds = text_layout.bounds(); + + let alignment_offset = alignment_offset( + text_bounds.width, + state.value.raw().min_width(), + effective_alignment(state.value.raw()), + ); + + position.x - text_bounds.x - alignment_offset + }; state.setting_selection(value, text_layout.bounds(), target); } } @@ -1576,12 +1622,24 @@ pub fn update<'a, Message: Clone + 'static>( let state = state(); if matches!(state.dragging_state, Some(DraggingState::Selection)) { - let target = position.x - text_layout.bounds().x; + let target = { + let text_bounds = text_layout.bounds(); + + let alignment_offset = alignment_offset( + text_bounds.width, + state.value.raw().min_width(), + effective_alignment(state.value.raw()), + ); + + position.x - text_bounds.x - alignment_offset + }; update_cache(state, value); - let position = - find_cursor_position(text_layout.bounds(), value, state, target).unwrap_or(0); + let (position, affinity) = + find_cursor_position(text_layout.bounds(), value, state, target) + .unwrap_or((0, text::Affinity::Before)); + state.cursor.set_affinity(affinity); state .cursor .select_range(state.cursor.start(value), position); @@ -1860,29 +1918,23 @@ pub fn update<'a, Message: Clone + 'static>( update_cache(state, &value); } keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => { - if platform::is_jump_modifier_pressed(modifiers) && !is_secure { - if modifiers.shift() { - state.cursor.select_left_by_words(value); - } else { - state.cursor.move_left_by_words(value); - } - } else if modifiers.shift() { - state.cursor.select_left(value); + let rtl = state.value.raw().is_rtl(0).unwrap_or(false); + let by_words = platform::is_jump_modifier_pressed(modifiers) && !is_secure; + + if modifiers.shift() { + state.cursor.select_visual(false, by_words, rtl, value); } else { - state.cursor.move_left(value); + state.cursor.move_visual(false, by_words, rtl, value); } } keyboard::Key::Named(keyboard::key::Named::ArrowRight) => { - if platform::is_jump_modifier_pressed(modifiers) && !is_secure { - if modifiers.shift() { - state.cursor.select_right_by_words(value); - } else { - state.cursor.move_right_by_words(value); - } - } else if modifiers.shift() { - state.cursor.select_right(value); + let rtl = state.value.raw().is_rtl(0).unwrap_or(false); + let by_words = platform::is_jump_modifier_pressed(modifiers) && !is_secure; + + if modifiers.shift() { + state.cursor.select_visual(true, by_words, rtl, value); } else { - state.cursor.move_right(value); + state.cursor.move_visual(true, by_words, rtl, value); } } keyboard::Key::Named(keyboard::key::Named::Home) => { @@ -2016,18 +2068,27 @@ pub fn update<'a, Message: Clone + 'static>( } } if accepted { - let target = *x as f32 - text_layout.bounds().x; + let target = { + let text_bounds = text_layout.bounds(); + + let alignment_offset = alignment_offset( + text_bounds.width, + state.value.raw().min_width(), + effective_alignment(state.value.raw()), + ); + + *x as f32 - text_bounds.x - alignment_offset + }; state.dnd_offer = DndOfferState::HandlingOffer(mime_types.clone(), DndAction::empty()); // existing logic for setting the selection - let position = if target > 0.0 { - update_cache(state, value); + update_cache(state, value); + let (position, affinity) = find_cursor_position(text_layout.bounds(), value, state, target) - } else { - None - }; + .unwrap_or((0, text::Affinity::Before)); - state.cursor.move_to(position.unwrap_or(0)); + state.cursor.set_affinity(affinity); + state.cursor.move_to(position); shell.capture_event(); return; } @@ -2038,16 +2099,25 @@ pub fn update<'a, Message: Clone + 'static>( { let state = state(); - let target = *x as f32 - text_layout.bounds().x; - // existing logic for setting the selection - let position = if target > 0.0 { - update_cache(state, value); - find_cursor_position(text_layout.bounds(), value, state, target) - } else { - None - }; + let target = { + let text_bounds = text_layout.bounds(); - state.cursor.move_to(position.unwrap_or(0)); + let alignment_offset = alignment_offset( + text_bounds.width, + state.value.raw().min_width(), + effective_alignment(state.value.raw()), + ); + + *x as f32 - text_bounds.x - alignment_offset + }; + // existing logic for setting the selection + update_cache(state, value); + let (position, affinity) = + find_cursor_position(text_layout.bounds(), value, state, target) + .unwrap_or((0, text::Affinity::Before)); + + state.cursor.set_affinity(affinity); + state.cursor.move_to(position); shell.capture_event(); return; } @@ -2340,7 +2410,7 @@ pub fn draw<'a, Message>( let handling_dnd_offer = !matches!(state.dnd_offer, DndOfferState::None); #[cfg(not(all(feature = "wayland", target_os = "linux")))] let handling_dnd_offer = false; - let (cursor, offset) = if let Some(focus) = + let (cursors, offset, is_selecting) = if let Some(focus) = state.is_focused.filter(|f| f.focused).or_else(|| { let now = Instant::now(); handling_dnd_offer.then_some(Focus { @@ -2352,78 +2422,26 @@ pub fn draw<'a, Message>( }) { match state.cursor.state(value) { cursor::State::Index(position) => { - let (text_value_width, offset) = - measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, position); + let (text_value_width, _) = measure_cursor_and_scroll_offset( + state.value.raw(), + text_bounds, + position, + value, + state.cursor.affinity(), + state.scroll_offset, + ); let is_cursor_visible = handling_dnd_offer || ((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS) .is_multiple_of(2); - if is_cursor_visible { - if dnd_icon { - (None, 0.0) - } else { - ( - Some(( - renderer::Quad { - bounds: Rectangle { - x: text_bounds.x + text_value_width - offset - + if text_value_width < 0. { - actual_width - } else { - 0. - }, - y: text_bounds.y, - width: 1.0, - height: text_bounds.height, - }, - border: Border { - width: 0.0, - color: Color::TRANSPARENT, - radius: radius_0, - }, - shadow: Shadow { - offset: Vector::ZERO, - color: Color::TRANSPARENT, - blur_radius: 0.0, - }, - snap: true, - }, - text_color, - )), - offset, - ) - } - } else { - (None, offset) - } - } - cursor::State::Selection { start, end } => { - let left = start.min(end); - let right = end.max(start); - let value_paragraph = &state.value; - let (left_position, left_offset) = - measure_cursor_and_scroll_offset(value_paragraph.raw(), text_bounds, left); - - let (right_position, right_offset) = - measure_cursor_and_scroll_offset(value_paragraph.raw(), text_bounds, right); - - let width = right_position - left_position; - if dnd_icon { - (None, 0.0) - } else { + if is_cursor_visible && !dnd_icon { ( - Some(( + vec![( renderer::Quad { bounds: Rectangle { - x: text_bounds.x - + left_position - + if left_position < 0. || right_position < 0. { - actual_width - } else { - 0. - }, + x: (text_bounds.x + text_value_width).floor(), y: text_bounds.y, - width, + width: 1.0, height: text_bounds.height, }, border: Border { @@ -2438,30 +2456,101 @@ pub fn draw<'a, Message>( }, snap: true, }, - appearance.selected_fill, - )), - if end == right { - right_offset - } else { - left_offset - }, + text_color, + )], + state.scroll_offset, + false, ) + } else { + ( + Vec::<(renderer::Quad, Color)>::new(), + if dnd_icon { 0.0 } else { state.scroll_offset }, + false, + ) + } + } + cursor::State::Selection { start, end } => { + let left = start.min(end); + let right = end.max(start); + + if dnd_icon { + (Vec::<(renderer::Quad, Color)>::new(), 0.0, true) + } else { + let lo_byte = value.byte_index_at_grapheme(left); + let hi_byte = value.byte_index_at_grapheme(right); + + let rects = state.value.raw().highlight( + 0, + (lo_byte, text::Affinity::After), + (hi_byte, text::Affinity::Before), + ); + + let cursors: Vec<(renderer::Quad, Color)> = rects + .into_iter() + .map(|r| { + ( + renderer::Quad { + bounds: Rectangle { + x: text_bounds.x + r.x, + y: text_bounds.y, + width: r.width, + height: text_bounds.height, + }, + border: Border { + width: 0.0, + color: Color::TRANSPARENT, + radius: radius_0, + }, + shadow: Shadow { + offset: Vector::ZERO, + color: Color::TRANSPARENT, + blur_radius: 0.0, + }, + snap: true, + }, + appearance.selected_fill, + ) + }) + .collect(); + + (cursors, state.scroll_offset, true) } } } } else { - (None, 0.0) + let unfocused_offset = match effective_alignment(state.value.raw()) { + alignment::Horizontal::Right => { + (state.value.raw().min_width() - text_bounds.width).max(0.0) + } + _ => 0.0, + }; + + ( + Vec::<(renderer::Quad, Color)>::new(), + unfocused_offset, + false, + ) }; let render = |renderer: &mut crate::Renderer| { - if let Some((cursor, color)) = cursor { - renderer.fill_quad(cursor, color); + let alignment_offset = alignment_offset( + text_bounds.width, + state.value.raw().min_width(), + effective_alignment(state.value.raw()), + ); + + if !cursors.is_empty() { + renderer.with_translation(Vector::new(alignment_offset - offset, 0.0), |renderer| { + for (quad, color) in &cursors { + renderer.fill_quad(*quad, *color); + } + }); } else { renderer.with_translation(Vector::ZERO, |_| {}); } let bounds = Rectangle { - x: text_bounds.x - offset, + x: text_bounds.x + alignment_offset - offset, y: text_bounds.center_y(), width: actual_width, ..text_bounds @@ -2482,7 +2571,7 @@ pub fn draw<'a, Message>( font, bounds: bounds.size(), size: iced::Pixels(size), - align_x: text::Alignment::Left, + align_x: text::Alignment::Default, align_y: alignment::Vertical::Center, line_height: text::LineHeight::default(), shaping: text::Shaping::Advanced, @@ -2495,7 +2584,11 @@ pub fn draw<'a, Message>( ); }; - renderer.with_layer(text_bounds, render); + if is_selecting { + renderer.with_layer(bounds, render); + } else { + render(renderer); + } let trailing_icon_tree = children.get(child_index); @@ -2630,7 +2723,7 @@ pub struct State { last_click: Option, cursor: Cursor, keyboard_modifiers: keyboard::Modifiers, - // TODO: Add stateful horizontal scrolling offset + scroll_offset: f32, } #[derive(Debug, Clone, Copy)] @@ -2709,6 +2802,7 @@ impl State { last_click: None, cursor: Cursor::default(), keyboard_modifiers: keyboard::Modifiers::default(), + scroll_offset: 0.0, dirty: false, } } @@ -2797,13 +2891,11 @@ impl State { } pub(super) fn setting_selection(&mut self, value: &Value, bounds: Rectangle, target: f32) { - let position = if target > 0.0 { - find_cursor_position(bounds, value, self, target) - } else { - None - }; + let (position, affinity) = find_cursor_position(bounds, value, self, target) + .unwrap_or((0, text::Affinity::Before)); - self.cursor.move_to(position.unwrap_or(0)); + self.cursor.set_affinity(affinity); + self.cursor.move_to(position); self.dragging_state = Some(DraggingState::Selection); } } @@ -2867,14 +2959,33 @@ fn measure_cursor_and_scroll_offset( paragraph: &impl text::Paragraph, text_bounds: Rectangle, cursor_index: usize, + value: &Value, + affinity: text::Affinity, + current_offset: f32, ) -> (f32, f32) { - let grapheme_position = paragraph - .grapheme_position(0, cursor_index) + let byte_index = value.byte_index_at_grapheme(cursor_index); + let position = paragraph + .cursor_position(0, byte_index, affinity) .unwrap_or(Point::ORIGIN); - let offset = ((grapheme_position.x + 5.0) - text_bounds.width).max(0.0); + // The visible window in paragraph coordinates is: + // [current_offset, current_offset + text_bounds.width] + // Keep the cursor visible with a 5px margin on each side. + let offset = if position.x > current_offset + text_bounds.width - 5.0 { + // Cursor past right edge of visible window → scroll left + (position.x + 5.0) - text_bounds.width + } else if position.x < current_offset + 5.0 { + // Cursor past left edge of visible window → scroll right + position.x - 5.0 + } else { + // Cursor is within visible window → keep current scroll + current_offset + }; - (grapheme_position.x, offset) + let max_offset = (paragraph.min_width() - text_bounds.width).max(0.0); + let offset = offset.clamp(0.0, max_offset); + + (position.x, offset) } /// Computes the position of the text cursor at the given X coordinate of @@ -2885,23 +2996,23 @@ fn find_cursor_position( value: &Value, state: &State, x: f32, -) -> Option { - let offset = offset(text_bounds, value, state); - let value = value.to_string(); +) -> Option<(usize, text::Affinity)> { + let value_str = value.to_string(); - let char_offset = state - .value - .raw() - .hit_test(Point::new(x + offset, text_bounds.height / 2.0)) - .map(text::Hit::cursor)?; + let hit = state.value.raw().hit_test(Point::new( + x + state.scroll_offset, + text_bounds.height / 2.0, + ))?; + let char_offset = hit.cursor(); + let affinity = hit.affinity(); - Some( - unicode_segmentation::UnicodeSegmentation::graphemes( - &value[..char_offset.min(value.len())], - true, - ) - .count(), + let grapheme_count = unicode_segmentation::UnicodeSegmentation::graphemes( + &value_str[..char_offset.min(value_str.len())], + true, ) + .count(); + + Some((grapheme_count, affinity)) } #[inline(never)] @@ -2928,7 +3039,7 @@ fn replace_paragraph( content: value.to_string(), bounds, size: text_size, - align_x: text::Alignment::Left, + align_x: text::Alignment::Default, align_y: alignment::Vertical::Top, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::None, @@ -2961,11 +3072,48 @@ fn offset(text_bounds: Rectangle, value: &Value, state: &State) -> f32 { cursor::State::Selection { end, .. } => end, }; - let (_, offset) = - measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, focus_position); + let (_, offset) = measure_cursor_and_scroll_offset( + state.value.raw(), + text_bounds, + focus_position, + value, + state.cursor().affinity(), + state.scroll_offset, + ); offset } else { - 0.0 + match effective_alignment(state.value.raw()) { + alignment::Horizontal::Right => { + (state.value.raw().min_width() - text_bounds.width).max(0.0) + } + _ => 0.0, + } + } +} + +#[inline(never)] +fn alignment_offset( + text_bounds_width: f32, + text_min_width: f32, + alignment: alignment::Horizontal, +) -> f32 { + if text_min_width > text_bounds_width { + 0.0 + } else { + match alignment { + alignment::Horizontal::Left => 0.0, + alignment::Horizontal::Center => (text_bounds_width - text_min_width) / 2.0, + alignment::Horizontal::Right => text_bounds_width - text_min_width, + } + } +} + +#[inline(never)] +fn effective_alignment(paragraph: &impl text::Paragraph) -> alignment::Horizontal { + if paragraph.is_rtl(0).unwrap_or(false) { + alignment::Horizontal::Right + } else { + alignment::Horizontal::Left } } diff --git a/src/widget/text_input/value.rs b/src/widget/text_input/value.rs index 900aac0f..9faff4ac 100644 --- a/src/widget/text_input/value.rs +++ b/src/widget/text_input/value.rs @@ -132,11 +132,34 @@ impl Value { graphemes: std::iter::repeat_n(String::from("•"), self.graphemes.len()).collect(), } } -} -impl ToString for Value { - #[inline] - fn to_string(&self) -> String { - self.graphemes.concat() + /// Converts a grapheme index to a byte index in the underlying string. + #[must_use] + pub fn byte_index_at_grapheme(&self, grapheme_index: usize) -> usize { + self.graphemes[..grapheme_index.min(self.graphemes.len())] + .iter() + .map(|g| g.len()) + .sum() + } + + /// Converts a byte index to a grapheme index. + #[must_use] + pub fn grapheme_index_at_byte(&self, byte_index: usize) -> usize { + let mut bytes = 0; + for (i, g) in self.graphemes.iter().enumerate() { + if bytes >= byte_index { + return i; + } + bytes += g.len(); + } + + self.graphemes.len() + } +} + +impl std::fmt::Display for Value { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.graphemes.concat()) } } From e1738d2ea7c3a2df2584a7cf7f098681c2b34c86 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Wed, 1 Apr 2026 11:49:12 -0600 Subject: [PATCH 511/556] fix(text_input): keyboard shortcuts when keyboard is a different language Matches what Iced does --- src/widget/text_input/input.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index ffb08c8b..a86e4b6e 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -1719,13 +1719,10 @@ pub fn update<'a, Message: Clone + 'static>( focus.updated_at = Instant::now(); LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at)); - // Check if Ctrl+A/C/V/X was pressed. - if state.keyboard_modifiers == keyboard::Modifiers::COMMAND - || state.keyboard_modifiers - == keyboard::Modifiers::COMMAND | keyboard::Modifiers::CAPS_LOCK - { - match key.as_ref() { - keyboard::Key::Character("c") | keyboard::Key::Character("C") => { + // Check if Ctrl/Command+A/C/V/X was pressed. + if state.keyboard_modifiers.command() { + match key.to_latin(*physical_key) { + Some('c') => { if !is_secure { if let Some((start, end)) = state.cursor.selection(value) { clipboard.write( @@ -1737,7 +1734,7 @@ pub fn update<'a, Message: Clone + 'static>( } // XXX if we want to allow cutting of secure text, we need to // update the cache and decide which value to cut - keyboard::Key::Character("x") | keyboard::Key::Character("X") => { + Some('x') => { if !is_secure { if let Some((start, end)) = state.cursor.selection(value) { clipboard.write( @@ -1756,7 +1753,7 @@ pub fn update<'a, Message: Clone + 'static>( } } } - keyboard::Key::Character("v") | keyboard::Key::Character("V") => { + Some('v') => { let content = if let Some(content) = state.is_pasting.take() { content } else { @@ -1801,7 +1798,7 @@ pub fn update<'a, Message: Clone + 'static>( return; } - keyboard::Key::Character("a") | keyboard::Key::Character("A") => { + Some('a') => { state.cursor.select_all(value); shell.capture_event(); return; From 22661fd76459a279fc5837ed61abb56866e2f988 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Wed, 1 Apr 2026 15:25:10 -0600 Subject: [PATCH 512/556] chore: udpate iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index e4da5002..84f32108 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit e4da5002ae4e9d68cc4ac777ed77b4a225659440 +Subproject commit 84f3210819c03f5393fe4dcc404ab9532b941c70 From aef328238fcdaaa17e05f67fc53615ce64a547e0 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Wed, 1 Apr 2026 13:30:36 -0600 Subject: [PATCH 513/556] fix(editable): the UX is closer to design now This fixes the unresponsive trailing icon and changes the behavior to be closer to the UI/UX design. --- src/widget/text_input/input.rs | 146 ++++++++++++++++++++++++--------- 1 file changed, 108 insertions(+), 38 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index a86e4b6e..2c788235 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -66,18 +66,20 @@ pub fn editable_input<'a, Message: Clone + 'static>( editing: bool, on_toggle_edit: impl Fn(bool) -> Message + 'a, ) -> TextInput<'a, Message> { - let icon = crate::widget::icon::from_name(if editing { - "edit-clear-symbolic" - } else { - "edit-symbolic" - }); - + // The trailing icon is a placeholder; diff() rebuilds it reactively + // based on the current is_read_only state and value content. TextInput::new(placeholder, text) .style(crate::theme::TextInput::EditableText) .editable() .editing(editing) .on_toggle_edit(on_toggle_edit) - .trailing_icon(icon.size(16).into()) + .trailing_icon( + crate::widget::icon::from_name("edit-symbolic") + .size(16) + .apply(crate::widget::container) + .padding(8) + .into(), + ) } /// Creates a new search [`TextInput`]. @@ -666,7 +668,36 @@ where } } - self.is_read_only = state.is_read_only; + if self.is_editable_variant { + if !state.is_focused() { + // Not yet interacted, use the widget's value + state.is_read_only = self.is_read_only; + } else { + // Already interacted, use the state + self.is_read_only = state.is_read_only; + } + + let editing = !self.is_read_only; + let icon_name = if editing { + if self.value.is_empty() { + "window-close-symbolic" + } else { + "edit-clear-symbolic" + } + } else { + "edit-symbolic" + }; + + self.trailing_icon = Some( + crate::widget::icon::from_name(icon_name) + .size(16) + .apply(crate::widget::container) + .padding(8) + .into(), + ); + } else { + self.is_read_only = state.is_read_only; + } // Stop pasting if input becomes disabled if !self.manage_value && self.on_input.is_none() { @@ -855,9 +886,6 @@ where if !state.is_read_only && state.is_focused.is_some_and(|f| !f.focused) { state.is_read_only = true; shell.publish((on_edit)(false)); - } else if state.is_focused() && state.is_read_only { - state.is_read_only = false; - shell.publish((on_edit)(true)); } else if let Some(f) = state.is_focused.as_mut().filter(|f| f.needs_update) { // TODO do we want to just move this to on_focus or on_unfocus for all inputs? f.needs_update = false; @@ -1018,9 +1046,7 @@ where index += 1; } - if let (Some(trailing_icon), Some(tree)) = - (self.trailing_icon.as_ref(), state.children.get(index)) - { + if self.trailing_icon.is_some() { let mut children = layout.children(); children.next(); // skip if there is no leading icon @@ -1030,13 +1056,21 @@ where let trailing_icon_layout = children.next().unwrap(); if cursor_position.is_over(trailing_icon_layout.bounds()) { - return trailing_icon.as_widget().mouse_interaction( - tree, - layout, - cursor_position, - viewport, - renderer, - ); + if self.is_editable_variant { + return mouse::Interaction::Pointer; + } + + if let Some((trailing_icon, tree)) = + self.trailing_icon.as_ref().zip(state.children.get(index)) + { + return trailing_icon.as_widget().mouse_interaction( + tree, + layout, + cursor_position, + viewport, + renderer, + ); + } } } let mut children = layout.children(); @@ -1426,21 +1460,54 @@ pub fn update<'a, Message: Clone + 'static>( && edit_button_layout.is_some_and(|l| cursor.is_over(l.bounds())) { if is_editable_variant { - state.is_read_only = !state.is_read_only; - state.move_cursor_to_end(); + let has_content = !unsecured_value.is_empty(); + let is_editing = !state.is_read_only; - if let Some(on_toggle_edit) = on_toggle_edit { - shell.publish(on_toggle_edit(!state.is_read_only)); + if is_editing && has_content { + if let Some(on_input) = on_input { + shell.publish((on_input)(String::new())); + } + + if manage_value { + *unsecured_value = Value::new(""); + state.tracked_value = unsecured_value.clone(); + + let cleared_value = if is_secure { + unsecured_value.secure() + } else { + unsecured_value.clone() + }; + + update_cache(state, &cleared_value); + } + + state.move_cursor_to_end(); + } else if is_editing { + // Close: toggle back to read-only and unfocus. + state.is_read_only = true; + state.unfocus(); + + if let Some(on_toggle_edit) = on_toggle_edit { + shell.publish(on_toggle_edit(false)); + } + } else { + // Edit: toggle to editing, select all, and focus. + state.is_read_only = false; + state.cursor.select_range(0, value.len()); + + if let Some(on_toggle_edit) = on_toggle_edit { + shell.publish(on_toggle_edit(true)); + } + + let now = Instant::now(); + LAST_FOCUS_UPDATE.with(|x| x.set(now)); + state.is_focused = Some(Focus { + updated_at: now, + now, + focused: true, + needs_update: false, + }); } - - let now = Instant::now(); - LAST_FOCUS_UPDATE.with(|x| x.set(now)); - state.is_focused = Some(Focus { - updated_at: now, - now, - focused: true, - needs_update: false, - }); } shell.capture_event(); @@ -1550,15 +1617,18 @@ pub fn update<'a, Message: Clone + 'static>( } // Focus on click of the text input, and ensure that the input is writable. - if !state.is_focused() - && matches!(state.dragging_state, None | Some(DraggingState::Selection)) + if matches!(state.dragging_state, None | Some(DraggingState::Selection)) + && (!state.is_focused() || (is_editable_variant && state.is_read_only)) { - if let Some(on_focus) = on_focus { - shell.publish(on_focus.clone()); + if !state.is_focused() { + if let Some(on_focus) = on_focus { + shell.publish(on_focus.clone()); + } } if state.is_read_only { state.is_read_only = false; + state.cursor.select_range(0, value.len()); if let Some(on_toggle_edit) = on_toggle_edit { let message = (on_toggle_edit)(true); shell.publish(message); From 0ba668eb52908e5b10f65971d7a4b6395dc194ae Mon Sep 17 00:00:00 2001 From: TobyDig <53296459+TobyDig@users.noreply.github.com> Date: Thu, 2 Apr 2026 08:32:36 +1100 Subject: [PATCH 514/556] fix(desktop): use `-e` argument for spawning desktop entries with a terminal --- src/desktop.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/desktop.rs b/src/desktop.rs index fe32f286..98ce7d4b 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -789,7 +789,7 @@ pub async fn spawn_desktop_exec( }) .unwrap_or_else(|| String::from("cosmic-term")); - term_exec = format!("{term} -- {}", exec.as_ref()); + term_exec = format!("{term} -e {}", exec.as_ref()); &term_exec } else { exec.as_ref() From f6eb314606f77adfa7199338a89b17f3d17d136c Mon Sep 17 00:00:00 2001 From: KENZ Date: Thu, 2 Apr 2026 07:35:57 +0900 Subject: [PATCH 515/556] feat(text_input): minimal IME support for COSMIC specific text widgets --- src/widget/text_input/input.rs | 104 ++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 2c788235..cd93a7d7 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -22,10 +22,11 @@ use iced::Limits; use iced::clipboard::dnd::{DndAction, DndEvent, OfferEvent, SourceEvent}; use iced::clipboard::mime::AsMimeTypes; use iced_core::event::{self, Event}; +use iced_core::input_method::{self, InputMethod, Preedit}; use iced_core::mouse::{self, click}; use iced_core::overlay::Group; use iced_core::renderer::{self, Renderer as CoreRenderer}; -use iced_core::text::{self, Paragraph, Renderer, Text}; +use iced_core::text::{self, Affinity, Paragraph, Renderer, Text}; use iced_core::time::{Duration, Instant}; use iced_core::touch; use iced_core::widget::Id; @@ -2083,6 +2084,66 @@ pub fn update<'a, Message: Clone + 'static>( state.keyboard_modifiers = *modifiers; } + Event::InputMethod(event) => { + let state = state(); + + match event { + input_method::Event::Opened | input_method::Event::Closed => { + state.preedit = matches!(event, input_method::Event::Opened) + .then(input_method::Preedit::new); + shell.capture_event(); + return; + } + input_method::Event::Preedit(content, selection) => { + if state.is_focused.is_some() { + state.preedit = Some(input_method::Preedit { + content: content.to_owned(), + selection: selection.clone(), + text_size: Some(size.into()), + }); + shell.capture_event(); + return; + } + } + input_method::Event::Commit(text) => { + let Some(focus) = &mut state.is_focused else { + return; + }; + let Some(on_input) = on_input else { + return; + }; + if state.is_read_only { + return; + } + + focus.updated_at = Instant::now(); + LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at)); + + let mut editor = Editor::new(unsecured_value, &mut state.cursor); + editor.paste(Value::new(&text)); + + let contents = editor.contents(); + let unsecured_value = Value::new(&contents); + let message = if let Some(paste) = &on_paste { + (paste)(contents) + } else { + (on_input)(contents) + }; + shell.publish(message); + + state.is_pasting = None; + let value = if is_secure { + unsecured_value.secure() + } else { + unsecured_value + }; + + update_cache(state, &value); + shell.capture_event(); + return; + } + } + } Event::Window(window::Event::RedrawRequested(now)) => { let state = state(); @@ -2095,6 +2156,8 @@ pub fn update<'a, Message: Clone + 'static>( now.checked_add(Duration::from_millis(millis_until_redraw as u64)) .unwrap_or(*now), )); + + shell.request_input_method(&input_method(state, text_layout, unsecured_value)); } else if always_active { shell.request_redraw(); } @@ -2269,6 +2332,43 @@ pub fn update<'a, Message: Clone + 'static>( } } +fn input_method<'b>( + state: &'b State, + text_layout: Layout<'_>, + value: &Value, +) -> InputMethod<&'b str> { + if state.is_focused() { + } else { + return InputMethod::Disabled; + }; + + let text_bounds = text_layout.bounds(); + let cursor_index = match state.cursor.state(value) { + cursor::State::Index(position) => position, + cursor::State::Selection { start, end } => start.min(end), + }; + let (cursor, offset) = measure_cursor_and_scroll_offset( + state.value.raw(), + text_bounds, + cursor_index, + value, + state.cursor.affinity(), + state.scroll_offset, + ); + InputMethod::Enabled { + cursor: Rectangle::new( + Point::new(text_bounds.x + cursor - offset, text_bounds.y), + Size::new(1.0, text_bounds.height), + ), + purpose: if state.is_secure { + input_method::Purpose::Secure + } else { + input_method::Purpose::Normal + }, + preedit: state.preedit.as_ref().map(input_method::Preedit::as_ref), + } +} + /// Draws the [`TextInput`] with the given [`Renderer`], overriding its /// [`Value`] if provided. /// @@ -2789,6 +2889,7 @@ pub struct State { is_pasting: Option, last_click: Option, cursor: Cursor, + preedit: Option, keyboard_modifiers: keyboard::Modifiers, scroll_offset: f32, } @@ -2868,6 +2969,7 @@ impl State { is_pasting: None, last_click: None, cursor: Cursor::default(), + preedit: None, keyboard_modifiers: keyboard::Modifiers::default(), scroll_offset: 0.0, dirty: false, From 12be83a8ef58019a1bd31ead100040244bd05f16 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Wed, 1 Apr 2026 20:12:12 -0600 Subject: [PATCH 516/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 84f32108..42e3afb5 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 84f3210819c03f5393fe4dcc404ab9532b941c70 +Subproject commit 42e3afb5686eff08c78c9292bb83c36d5c8f5146 From 61e5d882ae877f39b3389e17da48f516fdcc4582 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Thu, 26 Mar 2026 19:27:50 -0600 Subject: [PATCH 517/556] fix(ci): only document libcosmic, no dependency --- .github/workflows/pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 4229839e..e48570ba 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -18,7 +18,7 @@ jobs: - name: System dependencies run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev - name: Build documentation - run: cargo doc --verbose --features tokio,winit + run: cargo doc --no-deps --verbose --features tokio,winit - name: Deploy documentation uses: peaceiris/actions-gh-pages@v3 with: From 7a02c9a296c10469d9061391657f71aa33b3936b Mon Sep 17 00:00:00 2001 From: GroobleDierne Date: Fri, 30 Jan 2026 23:33:52 +0100 Subject: [PATCH 518/556] fix(color palette): avoid duplicates --- src/widget/color_picker/mod.rs | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index d484bb62..318e943b 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -4,7 +4,6 @@ //! Widgets for selecting colors with a color picker. use std::borrow::Cow; -use std::iter; use std::rc::Rc; use std::sync::LazyLock; use std::sync::atomic::{AtomicBool, Ordering}; @@ -93,8 +92,6 @@ pub struct ColorPickerModel { #[setters(skip)] active_color: palette::Hsv, #[setters(skip)] - save_next: Option, - #[setters(skip)] input_color: String, #[setters(skip)] applied_color: Option, @@ -128,7 +125,6 @@ impl ColorPickerModel { .insert(move |b| b.text(rgb.clone())) .build(), active_color: hsv, - save_next: None, input_color: color_to_string(hsv, true), applied_color: initial, fallback_color, @@ -159,22 +155,26 @@ impl ColorPickerModel { ) } + fn update_recent_colors(&mut self, new_color: Color) { + if let Some(pos) = self.recent_colors.iter().position(|c| *c == new_color) { + self.recent_colors.remove(pos); + } + self.recent_colors.insert(0, new_color); + self.recent_colors.truncate(MAX_RECENT); + } + pub fn update(&mut self, update: ColorPickerUpdate) -> Task { match update { ColorPickerUpdate::ActiveColor(c) => { self.must_clear_cache.store(true, Ordering::SeqCst); self.input_color = color_to_string(c, self.is_hex()); - if let Some(to_save) = self.save_next.take() { - self.recent_colors.insert(0, to_save); - self.recent_colors.truncate(MAX_RECENT); - } self.active_color = c; self.copied_at = None; } - ColorPickerUpdate::AppliedColor => { + ColorPickerUpdate::AppliedColor | ColorPickerUpdate::ActionFinished => { let srgb = palette::Srgb::from_color(self.active_color); if let Some(applied_color) = self.applied_color.take() { - self.recent_colors.push(applied_color); + self.update_recent_colors(applied_color); } self.applied_color = Some(Color::from(srgb)); self.active = false; @@ -215,21 +215,12 @@ impl ColorPickerModel { palette::Hsv::from_color(palette::Srgb::new(c.red, c.green, c.blue)); } } - ColorPickerUpdate::ActionFinished => { - let srgb = palette::Srgb::from_color(self.active_color); - if let Some(applied_color) = self.applied_color.take() { - self.recent_colors.push(applied_color); - } - self.applied_color = Some(Color::from(srgb)); - self.active = false; - self.save_next = Some(Color::from(srgb)); - } ColorPickerUpdate::ToggleColorPicker => { self.must_clear_cache.store(true, Ordering::SeqCst); self.active = !self.active; self.copied_at = None; } - }; + } Task::none() } @@ -395,7 +386,8 @@ where text_input("", self.input_color) .on_input(move |s| on_update(ColorPickerUpdate::Input(s))) .on_paste(move |s| on_update(ColorPickerUpdate::Input(s))) - .on_submit(move |_| on_update(ColorPickerUpdate::AppliedColor)) + .on_submit(move |_| on_update(ColorPickerUpdate::ActionFinished)) + // .on_unfocus(on_update(ColorPickerUpdate::ActionFinished)) Somehow this is called even when the field wasn't previously focused .leading_icon( color_button( None, From 24464908f6503e0f7923357a19a578151f18f50a Mon Sep 17 00:00:00 2001 From: Hojjat Date: Thu, 2 Apr 2026 18:15:41 -0600 Subject: [PATCH 519/556] fix: buttons are focusable again --- src/widget/button/widget.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index a4e32378..4acf3f2d 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -357,6 +357,8 @@ impl<'a, Message: 'a + Clone> Widget operation, ); }); + let state = tree.state.downcast_mut::(); + operation.focusable(Some(&self.id), layout.bounds(), state); } fn update( From 97a805e5a184c122364e47d7eceac763987fb491 Mon Sep 17 00:00:00 2001 From: Hendrik Hamerlinck Date: Wed, 11 Feb 2026 22:34:22 +0100 Subject: [PATCH 520/556] feat(applets): add destroy tooltip popup action This commit adds a new surface action to explicitly destroy the tooltip popup on `TOOLTIP_WINDOW_ID`, allowing proper cleanup when minimizing applets. --- src/app/cosmic.rs | 11 +++++++++++ src/applet/mod.rs | 2 +- src/surface/mod.rs | 3 +++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index b732eee9..030ed041 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -230,6 +230,17 @@ where iced_winit::commands::popup::destroy_popup(id) } #[cfg(all(feature = "wayland", target_os = "linux"))] + crate::surface::Action::DestroyTooltipPopup => { + #[cfg(feature = "applet")] + { + iced_winit::commands::popup::destroy_popup(*crate::applet::TOOLTIP_WINDOW_ID) + } + #[cfg(not(feature = "applet"))] + { + Task::none() + } + } + #[cfg(all(feature = "wayland", target_os = "linux"))] crate::surface::Action::DestroySubsurface(id) => { iced_winit::commands::subsurface::destroy_subsurface(id) } diff --git a/src/applet/mod.rs b/src/applet/mod.rs index a3f5228b..a7fc4069 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -42,7 +42,7 @@ static AUTOSIZE_ID: LazyLock = static AUTOSIZE_MAIN_ID: LazyLock = LazyLock::new(|| iced::id::Id::new("cosmic-applet-autosize-main")); static TOOLTIP_ID: LazyLock = LazyLock::new(|| iced::id::Id::new("subsurface")); -static TOOLTIP_WINDOW_ID: LazyLock = LazyLock::new(window::Id::unique); +pub(crate) static TOOLTIP_WINDOW_ID: LazyLock = LazyLock::new(window::Id::unique); #[derive(Debug, Clone)] pub struct Context { diff --git a/src/surface/mod.rs b/src/surface/mod.rs index 4598ac7c..0dad6459 100644 --- a/src/surface/mod.rs +++ b/src/surface/mod.rs @@ -36,6 +36,8 @@ pub enum Action { ), /// Destroy a subsurface with a view function DestroyPopup(iced::window::Id), + /// Destroys the global tooltip popup subsurface + DestroyTooltipPopup, /// Create a window with a view function accepting the App as a parameter AppWindow( @@ -85,6 +87,7 @@ impl std::fmt::Debug for Action { } Self::Popup(arg0, arg1) => f.debug_tuple("Popup").field(arg0).field(arg1).finish(), Self::DestroyPopup(arg0) => f.debug_tuple("DestroyPopup").field(arg0).finish(), + Self::DestroyTooltipPopup => f.debug_tuple("DestroyTooltipPopup").finish(), Self::ResponsiveMenuBar { menu_bar, limits, From b0f4e931f2c1d7d30e4fd0dee8f5b45b9b5038f9 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 3 Apr 2026 08:25:01 -0400 Subject: [PATCH 521/556] fix: font issues some fonts are not falling back when a glyph is missing for a selected font and weight --- Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 35d048ee..bdbc141b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ name = "cosmic" [features] default = [ + "advanced-shaping", "winit", "tokio", "a11y", @@ -16,7 +17,8 @@ default = [ "x11", "iced-wayland", "multi-window", -] # default = ["dbus-config", "multi-window", "a11y"] +] +advanced-shaping = ["iced/advanced-shaping"] # Accessibility support a11y = ["iced/a11y", "iced_accessibility"] # Enable about widget From cdd825b953b528b19907e185957f8bc203514d3d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 3 Apr 2026 08:25:12 -0400 Subject: [PATCH 522/556] fix: update iced softbuffer released version doesn't support transparency yet --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 42e3afb5..2d4ede15 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 42e3afb5686eff08c78c9292bb83c36d5c8f5146 +Subproject commit 2d4ede1597860db0bfaccfbc0166ee89ac353fc2 From 34219d1fd4171fb14ea7b4f6f572f9cbd4952150 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 3 Apr 2026 14:12:58 -0400 Subject: [PATCH 523/556] chore: wgpu cctk feature for wayland --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index bdbc141b..78922132 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,6 +93,7 @@ wayland = [ "iced-wayland", "iced_runtime/cctk", "iced_winit/cctk", + "iced_wgpu/cctk", "iced/cctk", "dep:cctk", ] From a9e0671075093ef417a9bae8d0ec39ac44a8c035 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 Apr 2026 03:58:26 +0200 Subject: [PATCH 524/556] fix(segmented_button): hover text style --- src/widget/segmented_button/widget.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 76c74f3b..203fbc2e 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -218,7 +218,7 @@ where maximum_button_width: u16::MAX, indent_spacing: 16, font_active: crate::font::semibold(), - font_hovered: crate::font::semibold(), + font_hovered: crate::font::default(), font_inactive: crate::font::default(), font_size: 14.0, height: Length::Shrink, From fdf3369cea2f772aabb5f7c4e5cdf6406780f6ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Wed, 1 Apr 2026 23:24:53 +0200 Subject: [PATCH 525/556] chore: re-export iced row and column This removes the custom row and column implementations and uses the iced ones directly. --- examples/about/src/main.rs | 2 +- examples/application/src/main.rs | 4 +- examples/calendar/src/main.rs | 6 +-- examples/image-button/src/main.rs | 2 +- examples/subscriptions/src/main.rs | 2 +- examples/text-input/src/main.rs | 4 +- src/ext.rs | 66 ------------------------ src/widget/about.rs | 56 +++++++++++--------- src/widget/button/icon.rs | 7 +-- src/widget/calendar.rs | 2 +- src/widget/context_menu.rs | 2 +- src/widget/header_bar.rs | 74 ++++++++++++--------------- src/widget/list/column.rs | 2 +- src/widget/mod.rs | 65 +++-------------------- src/widget/segmented_button/widget.rs | 6 +-- src/widget/settings/item.rs | 12 ++--- src/widget/settings/mod.rs | 4 +- src/widget/table/widget/compact.rs | 6 +-- src/widget/table/widget/standard.rs | 4 +- src/widget/toaster/mod.rs | 4 +- 20 files changed, 103 insertions(+), 227 deletions(-) diff --git a/examples/about/src/main.rs b/examples/about/src/main.rs index 50f25da4..c25a9b9a 100644 --- a/examples/about/src/main.rs +++ b/examples/about/src/main.rs @@ -132,7 +132,7 @@ impl cosmic::Application for App { fn view(&self) -> Element<'_, Self::Message> { let show_about_button = widget::button::text("Show about").on_press(Message::ToggleAbout); let centered = cosmic::widget::container( - widget::column() + widget::column::with_capacity(1) .push(show_about_button) .width(Length::Fill) .height(Length::Shrink) diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index 831a47f1..53f1c28e 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -54,7 +54,7 @@ impl widget::menu::Action for Action { /// Runs application with these settings #[rustfmt::skip] fn main() -> Result<(), Box> { - + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); @@ -190,7 +190,7 @@ impl cosmic::Application for App { .map_or("No page selected", String::as_str); let centered = widget::container( - widget::column() + widget::column::with_capacity(5) .push(widget::text::body(page_content)) .push( widget::text_input::text_input("", &self.input_1) diff --git a/examples/calendar/src/main.rs b/examples/calendar/src/main.rs index 240684c6..494087d1 100644 --- a/examples/calendar/src/main.rs +++ b/examples/calendar/src/main.rs @@ -85,8 +85,6 @@ impl cosmic::Application for App { /// Creates a view after each update. fn view(&self) -> Element<'_, Self::Message> { - let mut content = cosmic::widget::column().spacing(12); - let calendar = cosmic::widget::calendar( &self.calendar_model, |date| Message::DateSelected(date), @@ -95,9 +93,7 @@ impl cosmic::Application for App { Weekday::Sunday, ); - content = content.push(calendar); - - let centered = cosmic::widget::container(content) + let centered = cosmic::widget::container(calendar) .width(iced::Length::Fill) .height(iced::Length::Shrink) .align_x(iced::Alignment::Center) diff --git a/examples/image-button/src/main.rs b/examples/image-button/src/main.rs index 0ac906ca..c68c7070 100644 --- a/examples/image-button/src/main.rs +++ b/examples/image-button/src/main.rs @@ -80,7 +80,7 @@ impl cosmic::Application for App { /// Creates a view after each update. fn view(&self) -> Element<'_, Self::Message> { - let mut content = cosmic::widget::column().spacing(12); + let mut content = cosmic::widget::column::with_capacity(self.images.len()).spacing(12); for (id, image) in self.images.iter().enumerate() { content = content.push( diff --git a/examples/subscriptions/src/main.rs b/examples/subscriptions/src/main.rs index 47bd3772..17e630aa 100644 --- a/examples/subscriptions/src/main.rs +++ b/examples/subscriptions/src/main.rs @@ -64,7 +64,7 @@ impl cosmic::Application for App { /// Creates a view after each update. fn view(&self) -> Element<'_, Self::Message> { - widget::row().into() + widget::Row::new().into() } } diff --git a/examples/text-input/src/main.rs b/examples/text-input/src/main.rs index ea99666c..c17fcd5c 100644 --- a/examples/text-input/src/main.rs +++ b/examples/text-input/src/main.rs @@ -99,7 +99,9 @@ impl cosmic::Application for App { let inline = cosmic::widget::inline_input("", &self.input).on_input(Message::Input); - let column = cosmic::widget::column().push(editable).push(inline); + let column = cosmic::widget::column::with_capacity(2) + .push(editable) + .push(inline); let centered = cosmic::widget::container(column.width(200)) .width(iced::Length::Fill) diff --git a/src/ext.rs b/src/ext.rs index c85e6e86..8eb749e5 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -19,72 +19,6 @@ impl ElementExt for crate::Element<'_, Message> { } } -/// Additional methods for the [`Column`] and [`Row`] widgets. -pub trait CollectionWidget<'a, Message: 'a>: - Widget -where - Self: Sized, -{ - /// Moves all the elements of `other` into `self`, leaving `other` empty. - #[must_use] - fn append(self, other: &mut Vec) -> Self - where - E: Into>; - - /// Appends all elements in an iterator to the widget. - #[must_use] - fn extend(mut self, iterator: impl Iterator) -> Self - where - E: Into>, - { - for item in iterator { - self = self.push(item.into()); - } - - self - } - - /// Pushes an element into the widget. - #[must_use] - fn push(self, element: impl Into>) -> Self; - - /// Conditionally pushes an element to the widget. - #[must_use] - fn push_maybe(self, element: Option>>) -> Self { - if let Some(element) = element { - self.push(element.into()) - } else { - self - } - } -} - -impl<'a, Message: 'a> CollectionWidget<'a, Message> for crate::widget::Column<'a, Message> { - fn append(self, other: &mut Vec) -> Self - where - E: Into>, - { - self.extend(other.drain(..).map(Into::into)) - } - - fn push(self, element: impl Into>) -> Self { - self.push(element) - } -} - -impl<'a, Message: 'a> CollectionWidget<'a, Message> for crate::widget::Row<'a, Message> { - fn append(self, other: &mut Vec) -> Self - where - E: Into>, - { - self.extend(other.drain(..).map(Into::into)) - } - - fn push(self, element: impl Into>) -> Self { - self.push(element) - } -} - pub trait ColorExt { /// Combines color with background to create appearance of transparency. #[must_use] diff --git a/src/widget/about.rs b/src/widget/about.rs index ba88e03a..148af02a 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -47,32 +47,40 @@ pub struct About { fn add_contributors(contributors: Vec<(&str, &str)>) -> Vec<(String, String)> { contributors .into_iter() - .map(|(name, email)| (name.to_string(), format!("mailto:{email}"))) + .map(|(name, email)| (name.into(), format!("mailto:{email}"))) .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 { - 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." - ); + /// Artists who contributed to the application. + pub fn artists(mut self, contributors: impl Into>) -> Self { + self.artists = add_contributors(contributors.into()); + self + } + + /// Designers who contributed to the application. + pub fn designers(mut self, contributors: impl Into>) -> Self { + self.designers = add_contributors(contributors.into()); + self + } + + /// Developers who contributed to the application. + pub fn developers(mut self, contributors: impl Into>) -> Self { + self.developers = add_contributors(contributors.into()); + self + } + + /// Documenters who contributed to the application. + pub fn documenters(mut self, contributors: impl Into>) -> Self { + self.documenters = add_contributors(contributors.into()); + self + } + + /// Translators who contributed to the application. + pub fn translators(mut self, contributors: impl Into>) -> Self { + self.translators = add_contributors(contributors.into()); + self + } /// Links associated with the application. pub fn links, V: Into>( @@ -97,7 +105,7 @@ pub fn about<'a, Message: Clone + 'static>( } = crate::theme::spacing(); let section_button = |name: &'a str, url: &'a str| -> Element<'a, Message> { - widget::row() + widget::row::with_capacity(3) .push(widget::text(name)) .push(space::horizontal()) .push_maybe( @@ -158,7 +166,7 @@ pub fn about<'a, Message: Clone + 'static>( let copyright = about.copyright.as_ref().map(widget::text::body); let comments = about.comments.as_ref().map(widget::text::body); - widget::column() + widget::column::with_capacity(10) .push_maybe(header) .push_maybe(links_section) .push_maybe(developers_section) diff --git a/src/widget/button/icon.rs b/src/widget/button/icon.rs index edb54272..04d2bdd5 100644 --- a/src/widget/button/icon.rs +++ b/src/widget/button/icon.rs @@ -3,10 +3,7 @@ use super::{Builder, ButtonClass}; use crate::Element; -use crate::widget::{ - icon::{self, Handle}, - tooltip, -}; +use crate::widget::{icon::Handle, tooltip}; use apply::Apply; use iced_core::{Alignment, Length, Padding, font::Weight, text::LineHeight, widget::Id}; use std::borrow::Cow; @@ -133,7 +130,7 @@ impl Button<'_, Message> { } impl<'a, Message: Clone + 'static> From> for Element<'a, Message> { - fn from(mut builder: Button<'a, Message>) -> Element<'a, Message> { + fn from(builder: Button<'a, Message>) -> Element<'a, Message> { let mut content = Vec::with_capacity(2); content.push( diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index 7c09d39c..19758472 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -212,7 +212,7 @@ where let content_list = column::with_children([ row::with_children([ - column().push(date).push(day).into(), + column([date.into(), day.into()]).into(), crate::widget::space::horizontal() .width(Length::Fill) .into(), diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 918d4da2..3f35f04a 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -32,7 +32,7 @@ pub fn context_menu<'a, Message: 'static + Clone>( content: content.into(), context_menu: context_menu.map(|menus| { vec![menu::Tree::with_children( - crate::Element::from(crate::widget::row::<'static, Message>()), + crate::Element::from(crate::widget::Row::new()), menus, )] }), diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 1c0ca2c0..a772f7d2 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -243,10 +243,13 @@ impl<'a, Message: Clone + 'static> Widget Widget, viewport: &iced_core::Rectangle, ) { - for ((e, s), l) in self - .elems_mut() + self.elems_mut() .zip(&mut state.children) .zip(layout.children()) - { - e.as_widget_mut() - .update(s, event, l, cursor, renderer, clipboard, shell, viewport); - } + .for_each(|((e, s), l)| { + e.as_widget_mut() + .update(s, event, l, cursor, renderer, clipboard, shell, viewport); + }); } fn mouse_interaction( @@ -296,13 +298,12 @@ impl<'a, Message: Clone + 'static> Widget, ) { - for ((e, s), l) in self - .elems_mut() + self.elems_mut() .zip(&mut state.children) .zip(layout.children()) - { - e.as_widget_mut().operate(s, l, renderer, operation); - } + .for_each(|((e, s), l)| { + e.as_widget_mut().operate(s, l, renderer, operation); + }); } fn overlay<'b>( @@ -313,27 +314,13 @@ impl<'a, Message: Clone + 'static> Widget Option> { - let mut layouts = layout.children(); - let mut try_overlay = |elem: &'b mut Element<'a, Message>, - state: &'b mut tree::Tree| - -> Option< - iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>, - > { - elem.as_widget_mut() - .overlay(state, layouts.next()?, renderer, viewport, translation) - }; - - if let Some(center) = &mut self.center { - let (start_slice, end_center) = state.children.split_at_mut(1); - let (end_slice, center_slice) = end_center.split_at_mut(1); - try_overlay(&mut self.start, &mut start_slice[0]) - .or_else(|| try_overlay(&mut self.end, &mut end_slice[0])) - .or_else(|| try_overlay(center, &mut center_slice[0])) - } else { - let (start_slice, end_slice) = state.children.split_at_mut(1); - try_overlay(&mut self.start, &mut start_slice[0]) - .or_else(|| try_overlay(&mut self.end, &mut end_slice[0])) - } + self.elems_mut() + .zip(&mut state.children) + .zip(layout.children()) + .find_map(|((e, s), l)| { + e.as_widget_mut() + .overlay(s, l, renderer, viewport, translation) + }) } fn drag_destinations( @@ -343,10 +330,13 @@ impl<'a, Message: Clone + 'static> Widget HeaderBar<'a, Message> { let mut widget = HeaderBarWidget::new(start, center, end) .apply(widget::container) - .class(crate::theme::Container::HeaderBar { + .class(theme::Container::HeaderBar { focused: self.focused, sharp_corners: self.sharp_corners, transparent: self.transparent, @@ -463,7 +453,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { widget::icon::from_name($name) .apply(widget::button::icon) .padding(8) - .class(crate::theme::Button::HeaderBar) + .class(theme::Button::HeaderBar) .selected(self.focused) .icon_size($size) .on_press($on_press) diff --git a/src/widget/list/column.rs b/src/widget/list/column.rs index 136b49ea..945b9140 100644 --- a/src/widget/list/column.rs +++ b/src/widget/list/column.rs @@ -63,7 +63,7 @@ impl<'a, Message: 'static> ListColumn<'a, Message> { } // Ensure a minimum height of 32. - let list_item = iced::widget::row![ + let list_item = crate::widget::row![ container(item).align_y(iced::Alignment::Center), vertical().height(iced::Length::Fixed(32.)) ] diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 0f607240..ef212dab 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -24,7 +24,7 @@ //! .on_press(Message::LaunchUrl(REPOSITORY)) //! .padding(0); //! -//! let content = widget::column() +//! let content = widget::column::with_capacity(3) //! .push(widget::icon::from_name("my-app-icon")) //! .push(widget::text::title3("My App Name")) //! .push(link) @@ -53,6 +53,9 @@ pub use iced::widget::{Canvas, canvas}; #[doc(inline)] pub use iced::widget::{Checkbox, checkbox}; +#[doc(inline)] +pub use iced::widget::{Column, column}; + #[doc(inline)] pub use iced::widget::{ComboBox, combo_box}; @@ -80,6 +83,9 @@ pub use iced::widget::{ProgressBar, progress_bar}; #[doc(inline)] pub use iced::widget::{Responsive, responsive}; +#[doc(inline)] +pub use iced::widget::{Row, row}; + #[doc(inline)] pub use iced::widget::{Slider, VerticalSlider, slider, vertical_slider}; @@ -135,34 +141,6 @@ pub mod context_drawer; #[doc(inline)] pub use context_drawer::{ContextDrawer, context_drawer}; -#[doc(inline)] -pub use column::{Column, column}; -pub mod column { - //! A container which aligns its children in a column. - - pub type Column<'a, Message> = iced::widget::Column<'a, Message, crate::Theme, crate::Renderer>; - - #[must_use] - /// A container which aligns its children in a column. - pub fn column<'a, Message>() -> Column<'a, Message> { - Column::new() - } - - #[must_use] - /// A pre-allocated [`column`]. - pub fn with_capacity<'a, Message>(capacity: usize) -> Column<'a, Message> { - Column::with_capacity(capacity) - } - - #[must_use] - /// 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) - } -} - pub mod layer_container; #[doc(inline)] pub use layer_container::{LayerContainer, layer_container}; @@ -287,35 +265,6 @@ pub mod rectangle_tracker; #[doc(inline)] pub use rectangle_tracker::{RectangleTracker, rectangle_tracking_container}; -#[doc(inline)] -pub use row::{Row, row}; - -pub mod row { - //! A container which aligns its children in a row. - - pub type Row<'a, Message> = iced::widget::Row<'a, Message, crate::Theme, crate::Renderer>; - - #[must_use] - /// A container which aligns its children in a row. - pub fn row<'a, Message>() -> Row<'a, Message> { - Row::new() - } - - #[must_use] - /// A pre-allocated [`row`]. - pub fn with_capacity<'a, Message>(capacity: usize) -> Row<'a, Message> { - Row::with_capacity(capacity) - } - - #[must_use] - /// 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) - } -} - pub mod scrollable; #[doc(inline)] pub use scrollable::scrollable; diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 203fbc2e..b9d1000e 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -305,7 +305,7 @@ where { self.context_menu = context_menu.map(|menus| { vec![menu::Tree::with_children( - crate::Element::from(crate::widget::row::<'static, Message>()), + crate::Element::from(crate::widget::Row::new()), menus, )] }); @@ -1481,7 +1481,7 @@ where } } } else { - if let Item::Tab(key) = std::mem::replace(&mut state.hovered, Item::None) { + if let Item::Tab(_key) = std::mem::replace(&mut state.hovered, Item::None) { for key in self.model.order.iter().copied() { self.update_entity_paragraph(state, key); } @@ -2139,7 +2139,7 @@ where tree: &'b mut Tree, layout: iced_core::Layout<'b>, _renderer: &Renderer, - viewport: &iced_core::Rectangle, + _viewport: &iced_core::Rectangle, translation: Vector, ) -> Option> { let state = tree.state.downcast_mut::(); diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index 110ab7b7..349d93d8 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use crate::{ - Element, theme, + Element, Theme, theme, widget::{FlexRow, Row, column, container, flex_row, row, text}, }; use derive_setters::Setters; @@ -18,12 +18,12 @@ use taffy::AlignContent; pub fn item<'a, Message: 'static>( title: impl Into> + 'a, widget: impl Into> + 'a, -) -> Row<'a, Message> { +) -> Row<'a, Message, Theme> { #[inline(never)] fn inner<'a, Message: 'static>( title: Cow<'a, str>, widget: Element<'a, Message>, - ) -> Row<'a, Message> { + ) -> Row<'a, Message, Theme> { item_row(vec![ text(title).wrapping(Wrapping::Word).into(), space::horizontal().into(), @@ -37,7 +37,7 @@ pub fn item<'a, Message: 'static>( /// A settings item aligned in a row #[must_use] #[allow(clippy::module_name_repetitions)] -pub fn item_row(children: Vec>) -> Row { +pub fn item_row(children: Vec>) -> Row { row::with_children(children) .spacing(theme::spacing().space_xs) .align_y(iced::Alignment::Center) @@ -105,7 +105,7 @@ pub struct Item<'a, Message> { impl<'a, Message: 'static> Item<'a, Message> { /// Assigns a control to the item. - pub fn control(self, widget: impl Into>) -> Row<'a, Message> { + pub fn control(self, widget: impl Into>) -> Row<'a, Message, Theme> { item_row(self.control_(widget.into())) } @@ -142,7 +142,7 @@ impl<'a, Message: 'static> Item<'a, Message> { self, is_checked: bool, message: impl Fn(bool) -> Message + 'static, - ) -> Row<'a, Message> { + ) -> Row<'a, Message, Theme> { self.control( crate::widget::toggler(is_checked) .width(Length::Shrink) diff --git a/src/widget/settings/mod.rs b/src/widget/settings/mod.rs index 597d9bdd..79d81697 100644 --- a/src/widget/settings/mod.rs +++ b/src/widget/settings/mod.rs @@ -8,10 +8,10 @@ pub use self::item::{flex_item, flex_item_row, item, item_row}; pub use self::section::{Section, section}; use crate::widget::{Column, column}; -use crate::{Element, theme}; +use crate::{Element, Theme, theme}; /// A column with a predefined style for creating a settings panel #[must_use] -pub fn view_column(children: Vec>) -> Column { +pub fn view_column(children: Vec>) -> Column { column::with_children(children).spacing(theme::spacing().space_m) } diff --git a/src/widget/table/widget/compact.rs b/src/widget/table/widget/compact.rs index db71a1af..65ac9058 100644 --- a/src/widget/table/widget/compact.rs +++ b/src/widget/table/widget/compact.rs @@ -65,7 +65,7 @@ where let selected = val.model.is_active(entity); let context_menu = (val.item_context_builder)(item); - widget::column() + widget::column::with_capacity(2) .spacing(val.item_spacing) .push( widget::divider::horizontal::default() @@ -73,7 +73,7 @@ where .padding(val.divider_padding), ) .push( - widget::row() + widget::row::with_capacity(2) .spacing(space_xxxs) .align_y(Alignment::Center) .push_maybe( @@ -81,7 +81,7 @@ where .map(|icon| icon.size(val.icon_size)), ) .push( - widget::column() + widget::column::with_capacity(2) .push(widget::text::body(item.get_text(Category::default()))) .push({ let mut elements = val diff --git a/src/widget/table/widget/standard.rs b/src/widget/table/widget/standard.rs index 1fa611f3..9ab76c9d 100644 --- a/src/widget/table/widget/standard.rs +++ b/src/widget/table/widget/standard.rs @@ -99,7 +99,7 @@ where }; // Build the category header - widget::row() + widget::row::with_capacity(2) .spacing(val.icon_spacing) .push(widget::text::heading(category.to_string())) .push_maybe(match sort_state { @@ -152,7 +152,7 @@ where categories .iter() .map(|category| { - widget::row() + widget::row::with_capacity(2) .spacing(val.icon_spacing) .push_maybe( item.get_icon(*category) diff --git a/src/widget/toaster/mod.rs b/src/widget/toaster/mod.rs index efd93a9d..bafaa9f9 100644 --- a/src/widget/toaster/mod.rs +++ b/src/widget/toaster/mod.rs @@ -34,10 +34,10 @@ pub fn toaster<'a, Message: Clone + 'static>( } = theme.cosmic().spacing; let make_toast = move |(id, toast): (ToastId, &'a Toast)| { - let row = row() + let row = row::with_capacity(2) .push(text(&toast.message)) .push( - row() + row::with_capacity(2) .push_maybe(toast.action.as_ref().map(|action| { button::text(&action.description).on_press((action.message)(id)) })) From 1d01054993862f615adb029379307cd5501c79f7 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Fri, 3 Apr 2026 17:05:24 -0600 Subject: [PATCH 526/556] chore: update iced pulls in fixes for cycling focus --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 2d4ede15..ed9ad80e 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 2d4ede1597860db0bfaccfbc0166ee89ac353fc2 +Subproject commit ed9ad80e18fdaa442a60f9cfce5b8841e19e9ef3 From 8e3672a7dd6aa2fb8d663b1379fa80afdd1ab75b Mon Sep 17 00:00:00 2001 From: KENZ Date: Sun, 5 Apr 2026 14:33:23 +0900 Subject: [PATCH 527/556] fix: focus detecting in IME logic --- src/widget/text_input/input.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index cd93a7d7..12fd731b 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -2095,7 +2095,7 @@ pub fn update<'a, Message: Clone + 'static>( return; } input_method::Event::Preedit(content, selection) => { - if state.is_focused.is_some() { + if state.is_focused() { state.preedit = Some(input_method::Preedit { content: content.to_owned(), selection: selection.clone(), @@ -2106,7 +2106,7 @@ pub fn update<'a, Message: Clone + 'static>( } } input_method::Event::Commit(text) => { - let Some(focus) = &mut state.is_focused else { + let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) else { return; }; let Some(on_input) = on_input else { @@ -2337,8 +2337,7 @@ fn input_method<'b>( text_layout: Layout<'_>, value: &Value, ) -> InputMethod<&'b str> { - if state.is_focused() { - } else { + if !state.is_focused() { return InputMethod::Disabled; }; From ab3eedd0f2e2ed7de9108ba6728261d8bae9e48d Mon Sep 17 00:00:00 2001 From: Hojjat Date: Mon, 6 Apr 2026 09:40:43 -0600 Subject: [PATCH 528/556] chore: update iced This pulls in the fix in cosmic-text to fallback to the default SansSerif if there are missing glyphs in basic shaping. Also removes advanced-shaping from the default features list. --- Cargo.toml | 1 - iced | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 78922132..83fe90f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ name = "cosmic" [features] default = [ - "advanced-shaping", "winit", "tokio", "a11y", diff --git a/iced b/iced index ed9ad80e..7fd263d9 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit ed9ad80e18fdaa442a60f9cfce5b8841e19e9ef3 +Subproject commit 7fd263d99e6ae1b07e51f25bda3367f7463806b1 From 9aa87cd66b94b3d7d4dc2047e9c85b93f968d1d0 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 6 Apr 2026 18:16:32 -0400 Subject: [PATCH 529/556] fix(segmented_button): active font for context menu & prioritize active font over hover --- src/widget/segmented_button/widget.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index b9d1000e..a2efdfb8 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -246,12 +246,13 @@ where fn update_entity_paragraph(&mut self, state: &mut LocalState, key: Entity) { if let Some(text) = self.model.text.get(key) { - let font = if self.button_is_focused(state, key) { + let font = if self.button_is_focused(state, key) + || state.show_context == Some(key) + || self.model.is_active(key) + { self.font_active - } else if state.show_context == Some(key) || self.button_is_hovered(state, key) { + } else if self.button_is_hovered(state, key) { self.font_hovered - } else if self.model.is_active(key) { - self.font_active } else { self.font_inactive }; From 1f87cbc88320e540db408d65b763a4bed675b93e Mon Sep 17 00:00:00 2001 From: Hojjat Date: Mon, 6 Apr 2026 23:04:49 -0600 Subject: [PATCH 530/556] fix: do not allow cursor or keyboard activity when popup is open traps Tab from escaping, and won't allow elements in the background to react to hover --- src/widget/popover.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/widget/popover.rs b/src/widget/popover.rs index 7a82cd86..af5370a8 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -138,6 +138,10 @@ where renderer: &Renderer, operation: &mut dyn Operation, ) { + // Skip operating on background content, prevents Tab from escaping + if self.modal && self.popup.is_some() { + return; + } self.content .as_widget_mut() .operate(content_tree_mut(tree), layout, renderer, operation); @@ -172,11 +176,17 @@ where } } + // Hide cursor from background content when modal popup is active + let cursor = if self.modal && self.popup.is_some() { + mouse::Cursor::Unavailable + } else { + cursor_position + }; self.content.as_widget_mut().update( &mut tree.children[0], event, layout, - cursor_position, + cursor, renderer, clipboard, shell, @@ -214,13 +224,19 @@ where cursor_position: mouse::Cursor, viewport: &Rectangle, ) { + // Hide cursor from background content when a modal popup is active + let cursor = if self.modal && self.popup.is_some() { + mouse::Cursor::Unavailable + } else { + cursor_position + }; self.content.as_widget().draw( content_tree(tree), renderer, theme, renderer_style, layout, - cursor_position, + cursor, viewport, ); } From 724351727a191516ca1b2f2f90a00b7d211c7e1f Mon Sep 17 00:00:00 2001 From: Hojjat Date: Mon, 6 Apr 2026 22:56:18 -0600 Subject: [PATCH 531/556] feat: select until char and double click select delimiter adds a feature to select from the start of the sentence until the last occurrence of a character. This can be used to select until the extension in cosmic-files save dialog or rename pop up. Also, it adds a feature to select until the last occurrence of a character on double-click. --- src/widget/text_input/input.rs | 45 +++++++++++++++++++++++++++++++--- src/widget/text_input/value.rs | 8 ++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 12fd731b..806ceda0 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -188,6 +188,7 @@ pub struct TextInput<'a, Message> { is_editable_variant: bool, is_read_only: bool, select_on_focus: bool, + double_click_select_delimiter: Option, font: Option<::Font>, width: Length, padding: Padding, @@ -238,6 +239,7 @@ where is_editable_variant: false, is_read_only: false, select_on_focus: false, + double_click_select_delimiter: None, font: None, width: Length::Fill, padding: spacing.into(), @@ -343,6 +345,17 @@ where self } + /// Sets a delimiter character for double-click selection behavior. + /// + /// When set, double-clicking before the last occurrence of this character + /// selects from the start to that character. Double-clicking after the + /// delimiter uses normal word selection. + #[inline] + pub const fn double_click_select_delimiter(mut self, delimiter: char) -> Self { + self.double_click_select_delimiter = Some(delimiter); + self + } + /// Emits a message when an unfocused text input has been focused by click. /// /// This will not trigger if the input was focused externally by the application. @@ -598,6 +611,7 @@ where self.value = state.tracked_value.clone(); // std::mem::swap(&mut state.tracked_value, &mut self.value); } + state.double_click_select_delimiter = self.double_click_select_delimiter; // Unfocus text input if it becomes disabled if self.on_input.is_none() && !self.manage_value { state.last_click = None; @@ -1180,6 +1194,14 @@ pub fn select_range(id: Id, start: usize, end: usize) -> Task< ))) } +/// Produces a [`Task`] that selects from the front to the last occurrence of the given character +/// in the [`TextInput`] with the given [`Id`], or selects all if not found. +pub fn select_until_last(id: Id, value: &str, ch: char) -> Task { + let v = Value::new(value); + let end = v.rfind_char(ch).unwrap_or(v.len()); + select_range(id, 0, end) +} + /// Computes the layout of a [`TextInput`]. #[allow(clippy::cast_precision_loss)] #[allow(clippy::too_many_arguments)] @@ -1600,10 +1622,23 @@ pub fn update<'a, Message: Clone + 'static>( .unwrap_or((0, text::Affinity::Before)); state.cursor.set_affinity(affinity); - state.cursor.select_range( - value.previous_start_of_word(position), - value.next_end_of_word(position), - ); + + if let Some(delimiter) = state.double_click_select_delimiter { + if let Some(delim_pos) = value.rfind_char(delimiter) { + if position <= delim_pos { + state.cursor.select_range(0, delim_pos); + } else { + state.cursor.select_range(delim_pos + 1, value.len()); + } + } else { + state.cursor.select_all(value); + } + } else { + state.cursor.select_range( + value.previous_start_of_word(position), + value.next_end_of_word(position), + ); + } } state.dragging_state = Some(DraggingState::Selection); } @@ -2882,6 +2917,7 @@ pub struct State { pub is_read_only: bool, pub emit_unfocus: bool, select_on_focus: bool, + double_click_select_delimiter: Option, is_focused: Option, dragging_state: Option, dnd_offer: DndOfferState, @@ -2963,6 +2999,7 @@ impl State { emit_unfocus: false, is_focused: None, select_on_focus: false, + double_click_select_delimiter: None, dragging_state: None, dnd_offer: DndOfferState::default(), is_pasting: None, diff --git a/src/widget/text_input/value.rs b/src/widget/text_input/value.rs index 9faff4ac..3f7b8d73 100644 --- a/src/widget/text_input/value.rs +++ b/src/widget/text_input/value.rs @@ -142,6 +142,14 @@ impl Value { .sum() } + /// Returns the grapheme index of the last occurrence of the given character, + /// searching from the end. + #[must_use] + pub fn rfind_char(&self, ch: char) -> Option { + let needle = ch.to_string(); + self.graphemes.iter().rposition(|g| g == &needle) + } + /// Converts a byte index to a grapheme index. #[must_use] pub fn grapheme_index_at_byte(&self, byte_index: usize) -> usize { From b963fbfea9a94316dd1a0d99e84a5116cf696853 Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Tue, 7 Apr 2026 11:02:58 -0400 Subject: [PATCH 532/556] feat(widget): progress bars --- examples/application/src/main.rs | 50 +++ src/widget/mod.rs | 9 +- src/widget/progress_bar/circular.rs | 453 ++++++++++++++++++++++++++ src/widget/progress_bar/linear.rs | 306 +++++++++++++++++ src/widget/progress_bar/mod.rs | 11 + src/widget/progress_bar/style.rs | 105 ++++++ src/widget/segmented_button/widget.rs | 9 +- 7 files changed, 935 insertions(+), 8 deletions(-) create mode 100644 src/widget/progress_bar/circular.rs create mode 100644 src/widget/progress_bar/linear.rs create mode 100644 src/widget/progress_bar/mod.rs create mode 100644 src/widget/progress_bar/style.rs diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index 53f1c28e..bceece6e 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -82,6 +82,7 @@ pub enum Message { Hi, Hi2, Hi3, + Tick, } /// The [`App`] stores application-specific state. @@ -92,6 +93,7 @@ pub struct App { input_2: String, hidden: bool, keybinds: HashMap, + progress: f32, } /// Implement [`cosmic::Application`] to integrate with COSMIC. @@ -133,6 +135,7 @@ impl cosmic::Application for App { input_2: String::new(), hidden: true, keybinds: HashMap::new(), + progress: 0.0, }; let command = app.update_title(); @@ -178,10 +181,17 @@ impl cosmic::Application for App { Message::Hi3 => { dbg!("hi 3"); } + Message::Tick => { + self.progress = (self.progress + 0.01) % 1.0; + } } Task::none() } + fn subscription(&self) -> iced::Subscription { + iced::time::every(std::time::Duration::from_millis(64)).map(|_| Message::Tick) + } + /// Creates a view after each update. fn view(&self) -> Element<'_, Self::Message> { let page_content = self @@ -212,6 +222,46 @@ impl cosmic::Application for App { .on_input(Message::Input2) .on_clear(Message::Ignore), ) + .push(widget::progress_bar::circular::Circular::new().size(50.0)) + .push( + widget::progress_bar::linear::Linear::new() + .girth(10.0) + .width(Length::Fill), + ) + .push( + widget::progress_bar::circular::Circular::new() + .bar_height(10.0) + .size(50.0) + .progress(self.progress), + ) + .push( + widget::progress_bar::linear::Linear::new() + .girth(10.0) + .progress(self.progress) + .width(Length::Fill), + ) + .push( + widget::progress_bar::circular::Circular::new() + .size(50.0) + .progress(0.0), + ) + .push( + widget::progress_bar::linear::Linear::new() + .girth(10.0) + .progress(0.0) + .width(Length::Fill), + ) + .push( + widget::progress_bar::circular::Circular::new() + .size(50.0) + .progress(1.0), + ) + .push( + widget::progress_bar::linear::Linear::new() + .girth(10.0) + .progress(1.0) + .width(Length::Fill), + ) .spacing(cosmic::theme::spacing().space_s) .width(Length::Fill) .height(Length::Shrink) diff --git a/src/widget/mod.rs b/src/widget/mod.rs index ef212dab..7dcfa233 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -77,9 +77,6 @@ pub use iced::widget::{MouseArea, mouse_area}; #[doc(inline)] pub use iced::widget::{PaneGrid, pane_grid}; -#[doc(inline)] -pub use iced::widget::{ProgressBar, progress_bar}; - #[doc(inline)] pub use iced::widget::{Responsive, responsive}; @@ -257,6 +254,12 @@ pub mod popover; #[doc(inline)] pub use popover::{Popover, popover}; +pub mod progress_bar; +#[doc(inline)] +pub use progress_bar::{ + circular, circular::Circular, circular_progress, linear, linear::Linear, linear_progress, style, +}; + pub mod radio; #[doc(inline)] pub use radio::{Radio, radio}; diff --git a/src/widget/progress_bar/circular.rs b/src/widget/progress_bar/circular.rs new file mode 100644 index 00000000..7e8177d6 --- /dev/null +++ b/src/widget/progress_bar/circular.rs @@ -0,0 +1,453 @@ +//! Show a circular progress indicator. +use super::style::StyleSheet; +use crate::anim::smootherstep; +use iced::advanced::layout; +use iced::advanced::renderer; +use iced::advanced::widget::tree::{self, Tree}; +use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; +use iced::mouse; +use iced::time::Instant; +use iced::widget::canvas; +use iced::window; +use iced::{Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector}; + +use std::f32::consts::PI; +use std::time::Duration; + +const MIN_ANGLE: Radians = Radians(PI / 8.0); +const WRAP_ANGLE: Radians = Radians(2.0 * PI - PI / 4.0); +const BASE_ROTATION_SPEED: u32 = u32::MAX / 80; + +#[must_use] +pub struct Circular +where + Theme: StyleSheet, +{ + size: f32, + bar_height: f32, + style: ::Style, + cycle_duration: Duration, + rotation_duration: Duration, + progress: Option, +} + +impl Circular +where + Theme: StyleSheet, +{ + /// Creates a new [`Circular`] with the given content. + pub fn new() -> Self { + Circular { + size: 40.0, + bar_height: 4.0, + style: ::Style::default(), + cycle_duration: Duration::from_millis(1500), + rotation_duration: Duration::from_secs(2), + progress: None, + } + } + + /// Sets the size of the [`Circular`]. + pub fn size(mut self, size: f32) -> Self { + self.size = size; + self + } + + /// Sets the bar height of the [`Circular`]. + pub fn bar_height(mut self, bar_height: f32) -> Self { + self.bar_height = bar_height; + self + } + + /// Sets the style variant of this [`Circular`]. + pub fn style(mut self, style: ::Style) -> Self { + self.style = style; + self + } + + /// Sets the cycle duration of this [`Circular`]. + pub fn cycle_duration(mut self, duration: Duration) -> Self { + self.cycle_duration = duration / 2; + self + } + + /// Sets the base rotation duration of this [`Circular`]. This is the duration that a full + /// rotation would take if the cycle rotation were set to 0.0 (no expanding or contracting) + pub fn rotation_duration(mut self, duration: Duration) -> Self { + self.rotation_duration = duration; + self + } + + /// Override the default behavior by providing a determinate progress value between `0.0` and `1.0`. + pub fn progress(mut self, progress: f32) -> Self { + self.progress = Some(progress.clamp(0.0, 1.0)); + self + } +} + +impl Default for Circular +where + Theme: StyleSheet, +{ + fn default() -> Self { + Self::new() + } +} + +#[derive(Clone, Copy)] +enum Animation { + Expanding { + start: Instant, + progress: f32, + rotation: u32, + last: Instant, + }, + Contracting { + start: Instant, + progress: f32, + rotation: u32, + last: Instant, + }, +} + +impl Default for Animation { + fn default() -> Self { + Self::Expanding { + start: Instant::now(), + progress: 0.0, + rotation: 0, + last: Instant::now(), + } + } +} + +impl Animation { + fn next(&self, additional_rotation: u32, now: Instant) -> Self { + match self { + Self::Expanding { rotation, .. } => Self::Contracting { + start: now, + progress: 0.0, + rotation: rotation.wrapping_add(additional_rotation), + last: now, + }, + Self::Contracting { rotation, .. } => Self::Expanding { + start: now, + progress: 0.0, + rotation: rotation.wrapping_add(BASE_ROTATION_SPEED.wrapping_add( + (f64::from(WRAP_ANGLE / (2.0 * Radians::PI)) * f64::from(u32::MAX)) as u32, + )), + last: now, + }, + } + } + + fn start(&self) -> Instant { + match self { + Self::Expanding { start, .. } | Self::Contracting { start, .. } => *start, + } + } + + fn last(&self) -> Instant { + match self { + Self::Expanding { last, .. } | Self::Contracting { last, .. } => *last, + } + } + + fn timed_transition( + &self, + cycle_duration: Duration, + rotation_duration: Duration, + now: Instant, + ) -> Self { + let elapsed = now.duration_since(self.start()); + let additional_rotation = ((now - self.last()).as_secs_f32() + / rotation_duration.as_secs_f32() + * (u32::MAX) as f32) as u32; + + match elapsed { + elapsed if elapsed > cycle_duration => self.next(additional_rotation, now), + _ => self.with_elapsed(cycle_duration, additional_rotation, elapsed, now), + } + } + + fn with_elapsed( + &self, + cycle_duration: Duration, + additional_rotation: u32, + elapsed: Duration, + now: Instant, + ) -> Self { + let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32(); + match self { + Self::Expanding { + start, rotation, .. + } => Self::Expanding { + start: *start, + progress, + rotation: rotation.wrapping_add(additional_rotation), + last: now, + }, + Self::Contracting { + start, rotation, .. + } => Self::Contracting { + start: *start, + progress, + rotation: rotation.wrapping_add(additional_rotation), + last: now, + }, + } + } + + fn rotation(&self) -> f32 { + match self { + Self::Expanding { rotation, .. } | Self::Contracting { rotation, .. } => { + *rotation as f32 / u32::MAX as f32 + } + } + } +} + +#[derive(Default)] +struct State { + animation: Animation, + cache: canvas::Cache, + progress: Option, +} + +impl Widget for Circular +where + Message: Clone, + Theme: StyleSheet, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(State::default()) + } + + fn size(&self) -> Size { + Size { + width: Length::Fixed(self.size), + height: Length::Fixed(self.size), + } + } + + fn layout( + &mut self, + _tree: &mut Tree, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + layout::atomic(limits, self.size, self.size) + } + + fn update( + &mut self, + tree: &mut Tree, + event: &Event, + _layout: Layout<'_>, + _cursor: mouse::Cursor, + _renderer: &Renderer, + _clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, + ) { + let state = tree.state.downcast_mut::(); + if self.progress.is_some() { + if !float_cmp::approx_eq!( + f32, + state.progress.unwrap_or_default(), + self.progress.unwrap_or_default() + ) { + state.progress = self.progress; + state.cache.clear(); + } + return; + } + if let Event::Window(window::Event::RedrawRequested(now)) = event { + state.animation = + state + .animation + .timed_transition(self.cycle_duration, self.rotation_duration, *now); + + state.cache.clear(); + shell.request_redraw(); + } + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + _style: &renderer::Style, + layout: Layout<'_>, + _cursor: mouse::Cursor, + _viewport: &Rectangle, + ) { + use advanced::Renderer as _; + + let state = tree.state.downcast_ref::(); + let bounds = layout.bounds(); + let custom_style = + ::appearance(theme, &self.style, self.progress.is_some(), true); + + let geometry = state.cache.draw(renderer, bounds.size(), |frame| { + let track_radius = frame.width() / 2.0 - self.bar_height; + let track_path = canvas::Path::circle(frame.center(), track_radius); + + frame.stroke( + &track_path, + canvas::Stroke::default() + .with_color(custom_style.track_color) + .with_width(self.bar_height), + ); + + if let Some(progress) = self.progress { + // outer border + if let Some(border_color) = custom_style.border_color { + let border_path = + canvas::Path::circle(frame.center(), track_radius + self.bar_height / 2.0); + + frame.stroke( + &border_path, + canvas::Stroke::default() + .with_color(border_color) + .with_width(1.0), + ); + } + + // inner border + if let Some(border_color) = custom_style.border_color { + let border_path = + canvas::Path::circle(frame.center(), track_radius - self.bar_height / 2.0); + + frame.stroke( + &border_path, + canvas::Stroke::default() + .with_color(border_color) + .with_width(1.0), + ); + } + + // bar + let mut builder = canvas::path::Builder::new(); + + builder.arc(canvas::path::Arc { + center: frame.center(), + radius: track_radius, + start_angle: Radians(-PI / 2.0), + end_angle: Radians(-PI / 2.0 + progress * 2.0 * PI), + }); + + let bar_path = builder.build(); + + frame.stroke( + &bar_path, + canvas::Stroke::default() + .with_color(custom_style.bar_color) + .with_width(self.bar_height), + ); + + let mut builder = canvas::path::Builder::new(); + + // get center of end of arc for rounded cap + let end_angle = -PI / 2.0 + progress * 2.0 * PI; + let end_center = + frame.center() + Vector::new(end_angle.cos(), end_angle.sin()) * track_radius; + builder.arc(canvas::path::Arc { + center: end_center, + radius: self.bar_height / 2.0, + start_angle: Radians(end_angle), + end_angle: Radians(end_angle + PI), + }); + + // get center of start of arc for rounded cap + let start_angle = -PI / 2.0; + let start_center = frame.center() + + Vector::new(start_angle.cos(), start_angle.sin()) * track_radius; + builder.arc(canvas::path::Arc { + center: start_center, + radius: self.bar_height / 2.0, + start_angle: Radians(start_angle - PI), + end_angle: Radians(start_angle), + }); + + let cap_path = builder.build(); + frame.fill(&cap_path, custom_style.bar_color); + } else { + let mut builder = canvas::path::Builder::new(); + + let start = Radians(state.animation.rotation() * 2.0 * PI); + let (start_angle, end_angle) = match state.animation { + Animation::Expanding { progress, .. } => ( + start, + start + MIN_ANGLE + WRAP_ANGLE * (smootherstep(progress)), + ), + Animation::Contracting { progress, .. } => ( + start + WRAP_ANGLE * (smootherstep(progress)), + start + MIN_ANGLE + WRAP_ANGLE, + ), + }; + builder.arc(canvas::path::Arc { + center: frame.center(), + radius: track_radius, + start_angle, + end_angle, + }); + + let bar_path = builder.build(); + + frame.stroke( + &bar_path, + canvas::Stroke::default() + .with_color(custom_style.bar_color) + .with_width(self.bar_height), + ); + + let mut builder = canvas::path::Builder::new(); + + // get center of end of arc for rounded cap + let end_center = frame.center() + + Vector::new(end_angle.0.cos(), end_angle.0.sin()) * track_radius; + builder.arc(canvas::path::Arc { + center: end_center, + radius: self.bar_height / 2.0, + start_angle: Radians(end_angle.0), + end_angle: Radians(end_angle.0 + PI), + }); + + // get center of start of arc for rounded cap + let start_center = frame.center() + + Vector::new(start_angle.0.cos(), start_angle.0.sin()) * track_radius; + builder.arc(canvas::path::Arc { + center: start_center, + radius: self.bar_height / 2.0, + start_angle: Radians(start_angle.0 - PI), + end_angle: Radians(start_angle.0), + }); + + let cap_path = builder.build(); + frame.fill(&cap_path, custom_style.bar_color); + } + }); + + renderer.with_translation(Vector::new(bounds.x, bounds.y), |renderer| { + use iced::advanced::graphics::geometry::Renderer as _; + + renderer.draw_geometry(geometry); + }); + } +} + +impl<'a, Message, Theme> From> for Element<'a, Message, Theme, Renderer> +where + Message: Clone + 'a, + Theme: StyleSheet + 'a, +{ + fn from(circular: Circular) -> Self { + Self::new(circular) + } +} diff --git a/src/widget/progress_bar/linear.rs b/src/widget/progress_bar/linear.rs new file mode 100644 index 00000000..226b2b5f --- /dev/null +++ b/src/widget/progress_bar/linear.rs @@ -0,0 +1,306 @@ +//! Show a linear progress indicator. +use iced::advanced::layout; +use iced::advanced::renderer::{self, Quad}; +use iced::advanced::widget::tree::{self, Tree}; +use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; +use iced::mouse; +use iced::time::Instant; +use iced::window; +use iced::{Background, Element, Event, Length, Rectangle, Size}; + +use crate::anim::smootherstep; + +use super::style::StyleSheet; + +use std::time::Duration; + +#[must_use] +pub struct Linear +where + Theme: StyleSheet, +{ + width: Length, + girth: Length, + style: Theme::Style, + cycle_duration: Duration, + progress: Option, +} + +impl Linear +where + Theme: StyleSheet, +{ + /// Creates a new [`Linear`] with the given content. + pub fn new() -> Self { + Linear { + width: Length::Fixed(100.0), + girth: Length::Fixed(4.0), + style: Theme::Style::default(), + cycle_duration: Duration::from_millis(1500), + progress: None, + } + } + + /// Sets the width of the [`Linear`]. + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Sets the girth of the [`Linear`]. + pub fn girth(mut self, girth: impl Into) -> Self { + self.girth = girth.into(); + self + } + + /// Sets the style variant of this [`Linear`]. + pub fn style(mut self, style: impl Into) -> Self { + self.style = style.into(); + self + } + + /// Sets the cycle duration of this [`Linear`]. + pub fn cycle_duration(mut self, duration: Duration) -> Self { + self.cycle_duration = duration / 2; + self + } + + /// Override the default behavior by providing a determinate progress value between `0.0` and `1.0`. + pub fn progress(mut self, progress: f32) -> Self { + self.progress = Some(progress.clamp(0.0, 1.0)); + self + } +} + +impl Default for Linear +where + Theme: StyleSheet, +{ + fn default() -> Self { + Self::new() + } +} + +#[derive(Clone, Copy)] +enum State { + Expanding { start: Instant, progress: f32 }, + Contracting { start: Instant, progress: f32 }, +} + +impl Default for State { + fn default() -> Self { + Self::Expanding { + start: Instant::now(), + progress: 0.0, + } + } +} + +impl State { + fn next(&self, now: Instant) -> Self { + match self { + Self::Expanding { .. } => Self::Contracting { + start: now, + progress: 0.0, + }, + Self::Contracting { .. } => Self::Expanding { + start: now, + progress: 0.0, + }, + } + } + + fn start(&self) -> Instant { + match self { + Self::Expanding { start, .. } | Self::Contracting { start, .. } => *start, + } + } + + fn timed_transition(&self, cycle_duration: Duration, now: Instant) -> Self { + let elapsed = now.duration_since(self.start()); + + match elapsed { + elapsed if elapsed > cycle_duration => self.next(now), + _ => self.with_elapsed(cycle_duration, elapsed), + } + } + + fn with_elapsed(&self, cycle_duration: Duration, elapsed: Duration) -> Self { + let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32(); + match self { + Self::Expanding { start, .. } => Self::Expanding { + start: *start, + progress, + }, + Self::Contracting { start, .. } => Self::Contracting { + start: *start, + progress, + }, + } + } +} + +impl Widget for Linear +where + Message: Clone, + Theme: StyleSheet, + Renderer: advanced::Renderer, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(State::default()) + } + + fn size(&self) -> Size { + Size { + width: self.width, + height: self.girth, + } + } + + fn layout( + &mut self, + _tree: &mut Tree, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + layout::atomic(limits, self.width, self.girth) + } + + fn update( + &mut self, + tree: &mut Tree, + event: &Event, + _layout: Layout<'_>, + _cursor: mouse::Cursor, + _renderer: &Renderer, + _clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, + ) { + if self.progress.is_some() { + return; + } + + let state = tree.state.downcast_mut::(); + + if let Event::Window(window::Event::RedrawRequested(now)) = event { + *state = state.timed_transition(self.cycle_duration, *now); + + shell.request_redraw(); + } + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + _style: &renderer::Style, + layout: Layout<'_>, + _cursor: mouse::Cursor, + _viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + let custom_style = theme.appearance(&self.style, self.progress.is_some(), false); + let state = tree.state.downcast_ref::(); + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x, + y: bounds.y, + width: bounds.width, + height: bounds.height, + }, + border: iced::Border { + width: if custom_style.border_color.is_some() { + 1.0 + } else { + 0.0 + }, + color: custom_style.border_color.unwrap_or(custom_style.bar_color), + radius: custom_style.border_radius.into(), + }, + snap: true, + ..renderer::Quad::default() + }, + Background::Color(custom_style.track_color), + ); + + if let Some(progress) = self.progress { + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x, + y: bounds.y, + width: progress * bounds.width, + height: bounds.height, + }, + border: iced::Border { + width: 0., + color: iced::Color::TRANSPARENT, + radius: custom_style.border_radius.into(), + }, + snap: true, + ..renderer::Quad::default() + }, + Background::Color(custom_style.bar_color), + ); + } else { + match state { + State::Expanding { progress, .. } => renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x, + y: bounds.y, + width: smootherstep(*progress) * bounds.width, + height: bounds.height, + }, + border: iced::Border { + width: 0., + color: iced::Color::TRANSPARENT, + radius: custom_style.border_radius.into(), + }, + snap: true, + ..renderer::Quad::default() + }, + Background::Color(custom_style.bar_color), + ), + + State::Contracting { progress, .. } => renderer.fill_quad( + Quad { + bounds: Rectangle { + x: bounds.x + smootherstep(*progress) * bounds.width, + y: bounds.y, + width: (1.0 - smootherstep(*progress)) * bounds.width, + height: bounds.height, + }, + border: iced::Border { + width: 0., + color: iced::Color::TRANSPARENT, + radius: custom_style.border_radius.into(), + }, + snap: true, + ..renderer::Quad::default() + }, + Background::Color(custom_style.bar_color), + ), + } + } + } +} + +impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> +where + Message: Clone + 'a, + Theme: StyleSheet + 'a, + Renderer: iced::advanced::Renderer + 'a, +{ + fn from(linear: Linear) -> Self { + Self::new(linear) + } +} diff --git a/src/widget/progress_bar/mod.rs b/src/widget/progress_bar/mod.rs new file mode 100644 index 00000000..c1230961 --- /dev/null +++ b/src/widget/progress_bar/mod.rs @@ -0,0 +1,11 @@ +pub mod circular; +pub mod linear; +pub mod style; + +pub fn circular_progress() -> circular::Circular { + circular::Circular::new() +} + +pub fn linear_progress() -> linear::Linear { + linear::Linear::new() +} diff --git a/src/widget/progress_bar/style.rs b/src/widget/progress_bar/style.rs new file mode 100644 index 00000000..db2fe64d --- /dev/null +++ b/src/widget/progress_bar/style.rs @@ -0,0 +1,105 @@ +use iced::Color; + +#[derive(Debug, Clone, Copy)] +pub struct Appearance { + /// The track [`Color`] of the progress indicator. + pub track_color: Color, + /// The bar [`Color`] of the progress indicator. + pub bar_color: Color, + /// The border [`Color`] of the progress indicator. + pub border_color: Option, + /// The border radius of the progress indicator. + pub border_radius: f32, +} + +impl std::default::Default for Appearance { + fn default() -> Self { + Self { + track_color: Color::TRANSPARENT, + bar_color: Color::BLACK, + border_color: None, + border_radius: 0.0, + } + } +} + +/// A set of rules that dictate the style of an indicator. +pub trait StyleSheet { + /// The supported style of the [`StyleSheet`]. + type Style: Default; + + /// Produces the active [`Appearance`] of a indicator. + fn appearance( + &self, + style: &Self::Style, + is_determinate: bool, + is_circular: bool, + ) -> Appearance; +} + +impl StyleSheet for iced::Theme { + type Style = (); + + fn appearance( + &self, + _style: &Self::Style, + _is_determinate: bool, + _is_circular: bool, + ) -> Appearance { + let palette = self.extended_palette(); + + Appearance { + track_color: palette.background.weak.color, + bar_color: palette.primary.base.color, + border_color: None, + border_radius: 0.0, + } + } +} + +impl StyleSheet for crate::Theme { + type Style = (); + + fn appearance( + &self, + _style: &Self::Style, + is_determinate: bool, + is_circular: bool, + ) -> Appearance { + let cur = self.current_container(); + let mut cur_divider = cur.divider; + cur_divider.alpha = 0.5; + let theme = self.cosmic(); + + let (mut track_color, bar_color) = if theme.is_dark && theme.is_high_contrast { + ( + theme.palette.neutral_6.into(), + theme.accent_text_color().into(), + ) + } else if theme.is_dark { + (theme.palette.neutral_5.into(), theme.accent_color().into()) + } else if theme.is_high_contrast { + ( + theme.palette.neutral_4.into(), + theme.accent_text_color().into(), + ) + } else { + (theme.palette.neutral_3.into(), theme.accent_color().into()) + }; + + if !is_determinate && is_circular { + track_color = Color::TRANSPARENT; + } + + Appearance { + track_color, + bar_color, + border_color: if is_determinate && theme.is_high_contrast { + Some(cur_divider.into()) + } else { + None + }, + border_radius: theme.corner_radii.radius_xl[0], + } + } +} diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index a2efdfb8..5d862e9f 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -262,10 +262,10 @@ where font.hash(&mut hasher); let text_hash = hasher.finish(); - if let Some(prev_hash) = state.text_hashes.insert(key, text_hash) { - if prev_hash == text_hash { - return; - } + if let Some(prev_hash) = state.text_hashes.insert(key, text_hash) + && prev_hash == text_hash + { + return; } if let Some(paragraph) = state.paragraphs.get_mut(key) { @@ -928,7 +928,6 @@ where fn diff(&mut self, tree: &mut Tree) { let state = tree.state.downcast_mut::(); - for key in self.model.order.iter().copied() { self.update_entity_paragraph(state, key); } From d9121d6f0dfff4116eea096459151d051befe1da Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 7 Apr 2026 15:37:13 -0400 Subject: [PATCH 533/556] refactor: better helpers for the progress_bar --- src/widget/mod.rs | 3 ++- src/widget/progress_bar/mod.rs | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 7dcfa233..f442b0da 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -257,7 +257,8 @@ pub use popover::{Popover, popover}; pub mod progress_bar; #[doc(inline)] pub use progress_bar::{ - circular, circular::Circular, circular_progress, linear, linear::Linear, linear_progress, style, + circular, circular::Circular, determinate_circular, determinate_linear, indeterminate_circular, + indeterminate_linear, linear, linear::Linear, style, }; pub mod radio; diff --git a/src/widget/progress_bar/mod.rs b/src/widget/progress_bar/mod.rs index c1230961..ea069ffc 100644 --- a/src/widget/progress_bar/mod.rs +++ b/src/widget/progress_bar/mod.rs @@ -2,10 +2,22 @@ pub mod circular; pub mod linear; pub mod style; -pub fn circular_progress() -> circular::Circular { +/// A spinner / throbber widget that can be used to indicate that some operation is in progress. +pub fn indeterminate_circular() -> circular::Circular { circular::Circular::new() } -pub fn linear_progress() -> linear::Linear { +/// A linear throbber widget that can be used to indicate that some operation is in progress. +pub fn indeterminate_linear() -> linear::Linear { linear::Linear::new() } + +/// A circular progress spinner widget that can be used to indicate the progress of some operation. +pub fn determinate_circular(progress: f32) -> circular::Circular { + circular::Circular::new().progress(progress) +} + +/// A linear progress bar widget that can be used to indicate the progress of some operation. +pub fn determinate_linear(progress: f32) -> linear::Linear { + linear::Linear::new().progress(progress) +} From 5d1dfc4c54eba5d1037293fc0bc2ebfdc78eaab3 Mon Sep 17 00:00:00 2001 From: Adam Cosner <160804448+Adam-Cosner@users.noreply.github.com> Date: Wed, 8 Apr 2026 01:12:10 +0000 Subject: [PATCH 534/556] refactor!: remove `cosmic::iced_*` re-exports --- examples/applet/src/window.rs | 4 +-- examples/context-menu/src/main.rs | 2 +- examples/menu/src/main.rs | 6 ++--- examples/multi-window/src/window.rs | 4 +-- examples/nav-context/src/main.rs | 2 +- examples/open-dialog/src/main.rs | 2 +- examples/table-view/src/main.rs | 2 +- src/applet/mod.rs | 16 ++++++------ src/applet/token/subscription.rs | 2 +- src/lib.rs | 23 ----------------- src/widget/calendar.rs | 2 +- src/widget/cards.rs | 9 +++---- src/widget/dnd_destination.rs | 29 ++++++++++----------- src/widget/dnd_source.rs | 18 +++++++------- src/widget/menu/menu_tree.rs | 2 +- src/widget/segmented_button/widget.rs | 6 ++--- src/widget/toggler.rs | 12 ++++----- src/widget/wrapper.rs | 36 +++++++++++++-------------- 18 files changed, 77 insertions(+), 100 deletions(-) diff --git a/examples/applet/src/window.rs b/examples/applet/src/window.rs index 4e05c70a..22903eac 100644 --- a/examples/applet/src/window.rs +++ b/examples/applet/src/window.rs @@ -1,8 +1,8 @@ use cosmic::app::{Core, Task}; +use cosmic::iced::core::window; use cosmic::iced::window::Id; use cosmic::iced::{Length, Rectangle}; -use cosmic::iced_runtime::core::window; use cosmic::surface::action::{app_popup, destroy_popup}; use cosmic::widget::{dropdown::popup_dropdown, list_column, settings, toggler}; use cosmic::Element; @@ -159,7 +159,7 @@ impl cosmic::Application for Window { "oops".into() } - fn style(&self) -> Option { + fn style(&self) -> Option { Some(cosmic::applet::style()) } } diff --git a/examples/context-menu/src/main.rs b/examples/context-menu/src/main.rs index db66ba1b..e5ca5878 100644 --- a/examples/context-menu/src/main.rs +++ b/examples/context-menu/src/main.rs @@ -4,7 +4,7 @@ //! Application API example use cosmic::app::{Core, Settings, Task}; -use cosmic::iced_core::Size; +use cosmic::iced::Size; use cosmic::widget::menu; use cosmic::{executor, iced, ApplicationExt, Element}; use std::collections::HashMap; diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs index 8b5a1cb7..da0c3231 100644 --- a/examples/menu/src/main.rs +++ b/examples/menu/src/main.rs @@ -7,10 +7,10 @@ use std::collections::HashMap; use std::{env, process}; use cosmic::app::{Core, Settings, Task}; +use cosmic::iced::alignment::{Horizontal, Vertical}; +use cosmic::iced::keyboard::Key; use cosmic::iced::window; -use cosmic::iced_core::alignment::{Horizontal, Vertical}; -use cosmic::iced_core::keyboard::Key; -use cosmic::iced_core::{Length, Size}; +use cosmic::iced::{Length, Size}; use cosmic::widget::menu::action::MenuAction; use cosmic::widget::menu::key_bind::KeyBind; use cosmic::widget::menu::key_bind::Modifier; diff --git a/examples/multi-window/src/window.rs b/examples/multi-window/src/window.rs index 74ab5386..754a0d86 100644 --- a/examples/multi-window/src/window.rs +++ b/examples/multi-window/src/window.rs @@ -2,9 +2,9 @@ use std::collections::HashMap; use cosmic::{ app::Core, + iced::core::{id, Alignment, Length, Point}, + iced::widget::{column, container, scrollable, text}, iced::{self, event, window, Subscription}, - iced_core::{id, Alignment, Length, Point}, - iced_widget::{column, container, scrollable, text}, prelude::*, widget::{button, header_bar}, }; diff --git a/examples/nav-context/src/main.rs b/examples/nav-context/src/main.rs index fdfb90f9..1992066f 100644 --- a/examples/nav-context/src/main.rs +++ b/examples/nav-context/src/main.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use cosmic::app::{Core, Settings, Task}; -use cosmic::iced_core::Size; +use cosmic::iced::Size; use cosmic::widget::{menu, nav_bar}; use cosmic::{executor, iced, ApplicationExt, Element}; diff --git a/examples/open-dialog/src/main.rs b/examples/open-dialog/src/main.rs index 29061534..b4b5343f 100644 --- a/examples/open-dialog/src/main.rs +++ b/examples/open-dialog/src/main.rs @@ -6,7 +6,7 @@ use apply::Apply; use cosmic::app::{Core, Settings, Task}; use cosmic::dialog::file_chooser::{self, FileFilter}; -use cosmic::iced_core::Length; +use cosmic::iced::Length; use cosmic::widget::button; use cosmic::{executor, iced, ApplicationExt, Element}; use std::sync::Arc; diff --git a/examples/table-view/src/main.rs b/examples/table-view/src/main.rs index bbd9cf5b..d2478429 100644 --- a/examples/table-view/src/main.rs +++ b/examples/table-view/src/main.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use chrono::Datelike; use cosmic::app::{Core, Settings, Task}; -use cosmic::iced_core::Size; +use cosmic::iced::Size; use cosmic::prelude::*; use cosmic::widget::table; use cosmic::widget::{self, nav_bar}; diff --git a/src/applet/mod.rs b/src/applet/mod.rs index a7fc4069..48721e1c 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -6,13 +6,6 @@ use crate::{ Application, Element, Renderer, app::iced_settings, cctk::sctk, - iced::{ - self, Color, Length, Limits, Rectangle, - alignment::{Alignment, Horizontal, Vertical}, - widget::Container, - window, - }, - iced_widget, theme::{self, Button, THEME, system_dark, system_light}, widget::{ self, @@ -24,8 +17,15 @@ use crate::{ space::vertical, }, }; + pub use cosmic_panel_config; use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize}; +use iced::{ + self, Color, Length, Limits, Rectangle, + alignment::{Alignment, Horizontal, Vertical}, + widget::Container, + window, +}; use iced_core::{Padding, Shadow}; use iced_runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner}; use iced_widget::Text; @@ -226,7 +226,7 @@ impl Context { let symbolic = icon.symbolic; let icon = widget::icon(icon) .class(if symbolic { - theme::Svg::Custom(Rc::new(|theme| crate::iced_widget::svg::Style { + theme::Svg::Custom(Rc::new(|theme| iced_widget::svg::Style { color: Some(theme.cosmic().background.on.into()), })) } else { diff --git a/src/applet/token/subscription.rs b/src/applet/token/subscription.rs index 82763303..07c528ea 100644 --- a/src/applet/token/subscription.rs +++ b/src/applet/token/subscription.rs @@ -1,11 +1,11 @@ use crate::iced; -use crate::iced_futures::futures; use cctk::sctk::reexports::calloop; use futures::{ SinkExt, StreamExt, channel::mpsc::{UnboundedReceiver, unbounded}, }; use iced::Subscription; +use iced_futures::futures; use iced_futures::stream; use std::{fmt::Debug, hash::Hash, thread::JoinHandle}; diff --git a/src/lib.rs b/src/lib.rs index aa3b7db2..e04f1609 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,29 +66,6 @@ pub mod font; #[doc(inline)] pub use iced; -#[doc(inline)] -pub use iced_core; - -#[doc(inline)] -pub use iced_futures; - -#[doc(inline)] -pub use iced_renderer; - -#[doc(inline)] -pub use iced_runtime; - -#[doc(inline)] -pub use iced_widget; - -#[doc(inline)] -#[cfg(feature = "winit")] -pub use iced_winit; - -#[doc(inline)] -#[cfg(feature = "wgpu")] -pub use iced_wgpu; - pub mod icon_theme; pub mod keyboard_nav; diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index 19758472..91c601d3 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -4,10 +4,10 @@ //! A widget that displays an interactive calendar. use crate::fl; -use crate::iced_core::{Alignment, Length}; use crate::widget::{button, column, grid, icon, row, text}; use apply::Apply; use iced::alignment::Vertical; +use iced_core::{Alignment, Length}; use jiff::{ ToSpan, civil::{Date, Weekday}, diff --git a/src/widget/cards.rs b/src/widget/cards.rs index b8e17636..66267a73 100644 --- a/src/widget/cards.rs +++ b/src/widget/cards.rs @@ -1,13 +1,8 @@ //! An expandable stack of cards use std::time::Duration; -use self::iced_core::{ - Element, Event, Length, Size, Vector, Widget, border::Radius, id::Id, layout::Node, - renderer::Quad, widget::Tree, -}; use crate::{ anim, - iced_core::{self, Border, Shadow}, widget::{ button, card::style::Style, @@ -18,6 +13,10 @@ use crate::{ }; use float_cmp::approx_eq; use iced::widget; +use iced_core::{ + Border, Element, Event, Length, Shadow, Size, Vector, Widget, border::Radius, id::Id, + layout::Node, renderer::Quad, widget::Tree, +}; use iced_core::{widget::tree, window}; const ICON_SIZE: u16 = 16; diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index a77101b9..10bf7a8b 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -7,23 +7,24 @@ use iced::Vector; use crate::{ Element, - iced::{ - Event, Length, Rectangle, - clipboard::{ - dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent}, - mime::AllowedMimeTypes, - }, - event, - id::Internal, - mouse, overlay, - }, - iced_core::{ - self, Clipboard, Shell, layout, - widget::{Tree, tree}, - }, widget::{Id, Widget}, }; +use iced::{ + Event, Length, Rectangle, + clipboard::{ + dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent}, + mime::AllowedMimeTypes, + }, + event, + id::Internal, + mouse, overlay, +}; +use iced_core::{ + self, Clipboard, Shell, layout, + widget::{Tree, tree}, +}; + pub fn dnd_destination<'a, Message: 'static>( child: impl Into>, mimes: Vec>, diff --git a/src/widget/dnd_source.rs b/src/widget/dnd_source.rs index 25900a66..980723e3 100644 --- a/src/widget/dnd_source.rs +++ b/src/widget/dnd_source.rs @@ -4,17 +4,17 @@ use iced_core::{widget::Operation, window}; use crate::{ Element, - iced::{ - Event, Length, Point, Rectangle, Vector, - clipboard::dnd::{DndAction, DndEvent, SourceEvent}, - event, mouse, overlay, - }, - iced_core::{ - self, Clipboard, Shell, layout, renderer, - widget::{Tree, tree}, - }, widget::{Id, Widget, container}, }; +use iced::{ + Event, Length, Point, Rectangle, Vector, + clipboard::dnd::{DndAction, DndEvent, SourceEvent}, + event, mouse, overlay, +}; +use iced_core::{ + self, Clipboard, Shell, layout, renderer, + widget::{Tree, tree}, +}; pub fn dnd_source< 'a, diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index 047df0ed..41cf1dff 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -9,11 +9,11 @@ use std::rc::Rc; use iced::advanced::widget::text::Style as TextStyle; use iced_widget::core::{Element, renderer}; -use crate::iced_core::{Alignment, Length}; use crate::widget::menu::action::MenuAction; use crate::widget::menu::key_bind::KeyBind; use crate::widget::{Button, RcElementWrapper, icon}; use crate::{theme, widget}; +use iced_core::{Alignment, Length}; /// Nested menu is essentially a tree of items, a menu is a collection of items /// a menu itself can also be an item of another menu. diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 5d862e9f..44ca8574 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -3,7 +3,6 @@ use super::model::{Entity, Model, Selectable}; use super::{InsertPosition, ReorderEvent}; -use crate::iced_core::id::Internal; use crate::theme::{SegmentedButton as Style, THEME}; use crate::widget::dnd_destination::DragId; use crate::widget::menu::{ @@ -22,6 +21,7 @@ use iced::{ Alignment, Background, Color, Event, Length, Padding, Rectangle, Size, Task, Vector, alignment, keyboard, mouse, touch, window, }; +use iced_core::id::Internal; use iced_core::mouse::ScrollDelta; use iced_core::text::{self, Ellipsize, LineHeight, Renderer as TextRenderer, Shaping, Wrapping}; use iced_core::widget::operation::Focusable; @@ -2043,10 +2043,10 @@ where ..image_bounds }, crate::widget::icon(match crate::widget::common::object_select().data() { - crate::iced_core::svg::Data::Bytes(bytes) => { + iced_core::svg::Data::Bytes(bytes) => { crate::widget::icon::from_svg_bytes(bytes.as_ref()).symbolic(true) } - crate::iced_core::svg::Data::Path(path) => { + iced_core::svg::Data::Path(path) => { crate::widget::icon::from_path(path.clone()) } }), diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index 9d31ca1e..05371a17 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -2,18 +2,18 @@ use std::time::{Duration, Instant}; -use crate::{Element, anim, iced_core::Border, iced_widget::toggler::Status}; +use crate::{Element, anim}; use iced_core::{ - Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment, event, - layout, mouse, + Border, Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment, + event, layout, mouse, renderer::{self, Renderer}, text, touch, widget::{self, Tree, tree}, window, }; -use iced_widget::Id; +use iced_widget::{Id, toggler::Status}; -pub use crate::iced_widget::toggler::{Catalog, Style}; +pub use iced_widget::toggler::{Catalog, Style}; pub fn toggler<'a, Message>(is_checked: bool) -> Toggler<'a, Message> { Toggler::new(is_checked) @@ -200,7 +200,7 @@ impl<'a, Message> Widget for Toggler<'a, align_x: self.text_alignment, align_y: alignment::Vertical::Top, shaping: self.text_shaping, - wrapping: crate::iced_core::text::Wrapping::default(), + wrapping: iced_core::text::Wrapping::default(), ellipsize: self.ellipsize, }, ); diff --git a/src/widget/wrapper.rs b/src/widget/wrapper.rs index 73e476fa..133f9b87 100644 --- a/src/widget/wrapper.rs +++ b/src/widget/wrapper.rs @@ -93,8 +93,8 @@ impl Widget for RcElementWrapper { &mut self, tree: &mut tree::Tree, renderer: &crate::Renderer, - limits: &crate::iced_core::layout::Limits, - ) -> crate::iced_core::layout::Node { + limits: &iced_core::layout::Limits, + ) -> iced_core::layout::Node { self.element .with_data_mut(|e| e.as_widget_mut().layout(tree, renderer, limits)) } @@ -104,9 +104,9 @@ impl Widget for RcElementWrapper { tree: &tree::Tree, renderer: &mut crate::Renderer, theme: &crate::Theme, - style: &crate::iced_core::renderer::Style, - layout: crate::iced_core::Layout<'_>, - cursor: crate::iced_core::mouse::Cursor, + style: &iced_core::renderer::Style, + layout: iced_core::Layout<'_>, + cursor: iced_core::mouse::Cursor, viewport: &Rectangle, ) { self.element.with_data(move |e| { @@ -134,7 +134,7 @@ impl Widget for RcElementWrapper { fn operate( &mut self, state: &mut tree::Tree, - layout: crate::iced_core::Layout<'_>, + layout: iced_core::Layout<'_>, renderer: &crate::Renderer, operation: &mut dyn widget::Operation, ) { @@ -148,11 +148,11 @@ impl Widget for RcElementWrapper { &mut self, state: &mut tree::Tree, event: &crate::iced::Event, - layout: crate::iced_core::Layout<'_>, - cursor: crate::iced_core::mouse::Cursor, + layout: iced_core::Layout<'_>, + cursor: iced_core::mouse::Cursor, renderer: &crate::Renderer, - clipboard: &mut dyn crate::iced_core::Clipboard, - shell: &mut crate::iced_core::Shell<'_, M>, + clipboard: &mut dyn iced_core::Clipboard, + shell: &mut iced_core::Shell<'_, M>, viewport: &Rectangle, ) { self.element.with_data_mut(|e| { @@ -165,11 +165,11 @@ impl Widget for RcElementWrapper { fn mouse_interaction( &self, state: &tree::Tree, - layout: crate::iced_core::Layout<'_>, - cursor: crate::iced_core::mouse::Cursor, + layout: iced_core::Layout<'_>, + cursor: iced_core::mouse::Cursor, viewport: &Rectangle, renderer: &crate::Renderer, - ) -> crate::iced_core::mouse::Interaction { + ) -> iced_core::mouse::Interaction { self.element.with_data(|e| { e.as_widget() .mouse_interaction(state, layout, cursor, viewport, renderer) @@ -179,11 +179,11 @@ impl Widget for RcElementWrapper { fn overlay<'a>( &'a mut self, state: &'a mut tree::Tree, - layout: crate::iced_core::Layout<'a>, + layout: iced_core::Layout<'a>, renderer: &crate::Renderer, viewport: &Rectangle, - translation: crate::iced_core::Vector, - ) -> Option> { + translation: iced_core::Vector, + ) -> Option> { assert_eq!(self.element.thread_id, thread::current().id()); Rc::get_mut(&mut self.element.data).and_then(|e| { e.get_mut() @@ -203,9 +203,9 @@ impl Widget for RcElementWrapper { fn drag_destinations( &self, state: &tree::Tree, - layout: crate::iced_core::Layout<'_>, + layout: iced_core::Layout<'_>, renderer: &crate::Renderer, - dnd_rectangles: &mut crate::iced_core::clipboard::DndDestinationRectangles, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { self.element.with_data_mut(|e| { e.as_widget_mut() From e5955b568de23b653963cfc8a0ed444633e1795b Mon Sep 17 00:00:00 2001 From: Adam Cosner Date: Tue, 7 Apr 2026 22:12:36 -0400 Subject: [PATCH 535/556] ci: Updated pages.yml workflow Use nightly channel to enable docs generating feature badges, plus enabled more features in the docs build, and building the cctk docs also --- .github/workflows/pages.yml | 33 ++++++++++++++++++--------------- src/lib.rs | 1 + 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index e48570ba..419c99d0 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -7,21 +7,24 @@ on: jobs: pages: - runs-on: ubuntu-latest steps: - - name: Checkout sources - uses: actions/checkout@v3 - with: - submodules: recursive - - name: System dependencies - run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev - - name: Build documentation - run: cargo doc --no-deps --verbose --features tokio,winit - - name: Deploy documentation - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./target/doc - force_orphan: true + - name: Checkout sources + uses: actions/checkout@v3 + with: + submodules: recursive + - name: Install Rust nightly + uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2025-07-31 + - name: System dependencies + run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev + - name: Build documentation + run: RUSTDOCFLAGS="--cfg docsrs" cargo +nightly-2025-07-31 doc --no-deps -p cosmic-client-toolkit -p libcosmic --verbose --features tokio,winit,wayland,process,desktop,single-instance + - name: Deploy documentation + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./target/doc + force_orphan: true diff --git a/src/lib.rs b/src/lib.rs index e04f1609..f3873443 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ #![allow(clippy::module_name_repetitions)] #![cfg_attr(target_os = "redox", feature(lazy_cell))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] /// Recommended default imports. pub mod prelude { From 12d2233c6b5f0315e3feb99705d9046f38729a94 Mon Sep 17 00:00:00 2001 From: Adam Cosner Date: Tue, 7 Apr 2026 22:25:25 -0400 Subject: [PATCH 536/556] fix(ci): Added an inline doc to cctk reexport --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index f3873443..02623799 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ pub(crate) mod malloc; #[cfg(all(feature = "process", not(windows)))] pub mod process; +#[doc(inline)] #[cfg(all(feature = "wayland", target_os = "linux"))] pub use cctk; From 6df3f76a33f55de94670e16d1c7e57a1de2fe7f3 Mon Sep 17 00:00:00 2001 From: Adam Cosner Date: Tue, 7 Apr 2026 22:50:13 -0400 Subject: [PATCH 537/556] ci: Added a few more enabled dependency docs --- .github/workflows/pages.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 419c99d0..a15b99b5 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -21,7 +21,15 @@ jobs: - name: System dependencies run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev - name: Build documentation - run: RUSTDOCFLAGS="--cfg docsrs" cargo +nightly-2025-07-31 doc --no-deps -p cosmic-client-toolkit -p libcosmic --verbose --features tokio,winit,wayland,process,desktop,single-instance + run: RUSTDOCFLAGS="--cfg docsrs" \ + cargo +nightly-2025-07-31 doc --no-deps \ + -p cosmic-client-toolkit \ + -p cosmic-protocols \ + -p smithay-client-toolkit \ + -p wayland-protocols \ + -p wayland-client \ + -p libcosmic \ + --verbose --features tokio,winit,wayland,desktop,single-instance,applet,xdg-portal,multi-window - name: Deploy documentation uses: peaceiris/actions-gh-pages@v3 with: From 77b37f22466ddb581407e0f683c733cf8b7e6891 Mon Sep 17 00:00:00 2001 From: Adam Cosner Date: Tue, 7 Apr 2026 23:01:10 -0400 Subject: [PATCH 538/556] fix(ci) removed the smithay and wayland protocol docs builds --- .github/workflows/pages.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index a15b99b5..34e4d0cf 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -25,9 +25,6 @@ jobs: cargo +nightly-2025-07-31 doc --no-deps \ -p cosmic-client-toolkit \ -p cosmic-protocols \ - -p smithay-client-toolkit \ - -p wayland-protocols \ - -p wayland-client \ -p libcosmic \ --verbose --features tokio,winit,wayland,desktop,single-instance,applet,xdg-portal,multi-window - name: Deploy documentation From c7093beca323e555f169fe65ac1118e925bd4e75 Mon Sep 17 00:00:00 2001 From: Adam Cosner Date: Wed, 8 Apr 2026 01:22:23 -0400 Subject: [PATCH 539/556] fix(ci): cargo now running properly --- .github/workflows/pages.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 34e4d0cf..3e3a042e 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -21,7 +21,8 @@ jobs: - name: System dependencies run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev - name: Build documentation - run: RUSTDOCFLAGS="--cfg docsrs" \ + run: | + RUSTDOCFLAGS="--cfg docsrs" \ cargo +nightly-2025-07-31 doc --no-deps \ -p cosmic-client-toolkit \ -p cosmic-protocols \ From 47ab72be502378d18b931f0e10e8ba94619dd607 Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Wed, 8 Apr 2026 01:38:18 -0400 Subject: [PATCH 540/556] fix!(progress_bar): remove unused generic Message type --- src/widget/progress_bar/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/widget/progress_bar/mod.rs b/src/widget/progress_bar/mod.rs index ea069ffc..4e277b0a 100644 --- a/src/widget/progress_bar/mod.rs +++ b/src/widget/progress_bar/mod.rs @@ -3,21 +3,21 @@ pub mod linear; pub mod style; /// A spinner / throbber widget that can be used to indicate that some operation is in progress. -pub fn indeterminate_circular() -> circular::Circular { +pub fn indeterminate_circular() -> circular::Circular { circular::Circular::new() } /// A linear throbber widget that can be used to indicate that some operation is in progress. -pub fn indeterminate_linear() -> linear::Linear { +pub fn indeterminate_linear() -> linear::Linear { linear::Linear::new() } /// A circular progress spinner widget that can be used to indicate the progress of some operation. -pub fn determinate_circular(progress: f32) -> circular::Circular { +pub fn determinate_circular(progress: f32) -> circular::Circular { circular::Circular::new().progress(progress) } /// A linear progress bar widget that can be used to indicate the progress of some operation. -pub fn determinate_linear(progress: f32) -> linear::Linear { +pub fn determinate_linear(progress: f32) -> linear::Linear { linear::Linear::new().progress(progress) } From a44cff8011d81209e18de86f24da248c88b5a28d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 8 Apr 2026 09:58:20 -0400 Subject: [PATCH 541/556] fix(text_input): always clip input text with the text bounds this issue seems unique to tiny-skia --- examples/application/Cargo.toml | 1 - src/widget/text_input/input.rs | 14 ++++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index bc037ec0..c494238f 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -18,7 +18,6 @@ features = [ "tokio", "xdg-portal", "a11y", - "wgpu", "single-instance", "surface-message", "multi-window", diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 806ceda0..4336c757 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -2740,14 +2740,14 @@ pub fn draw<'a, Message>( effective_alignment(state.value.raw()), ); - if !cursors.is_empty() { + if cursors.is_empty() { + renderer.with_translation(Vector::ZERO, |_| {}); + } else { renderer.with_translation(Vector::new(alignment_offset - offset, 0.0), |renderer| { for (quad, color) in &cursors { renderer.fill_quad(*quad, *color); } }); - } else { - renderer.with_translation(Vector::ZERO, |_| {}); } let bounds = Rectangle { @@ -2785,11 +2785,9 @@ pub fn draw<'a, Message>( ); }; - if is_selecting { - renderer.with_layer(bounds, render); - } else { - render(renderer); - } + // FIXME: we always must clip with a layer because of what appears to be a tiny-skia text clipping issue. + // Otherwise overflowing text escapes the bounds of the input. + renderer.with_layer(text_bounds, render); let trailing_icon_tree = children.get(child_index); From 6caccaba337ed9bab21c5fe3c2aa7392e322e89c Mon Sep 17 00:00:00 2001 From: Hojjat Date: Wed, 8 Apr 2026 16:13:31 -0600 Subject: [PATCH 542/556] fix: icon color when window is maximized --- src/theme/mod.rs | 2 +- src/theme/style/iced.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/theme/mod.rs b/src/theme/mod.rs index b7e85237..093bac05 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -307,7 +307,7 @@ impl DefaultStyle for Theme { fn default_style(&self) -> Appearance { let cosmic = self.cosmic(); Appearance { - icon_color: cosmic.bg_color().into(), + icon_color: cosmic.on_bg_color().into(), background_color: cosmic.bg_color().into(), text_color: cosmic.on_bg_color().into(), } diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 4633477d..aa6f4b33 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -43,7 +43,7 @@ pub mod application { iced::theme::Style { background_color: cosmic.bg_color().into(), text_color: cosmic.on_bg_color().into(), - icon_color: cosmic.bg_color().into(), + icon_color: cosmic.on_bg_color().into(), } } } From e287a789c1f33459d4a7ac737c2e7d4004e7e0e4 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Fri, 10 Apr 2026 20:53:43 -0600 Subject: [PATCH 543/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 7fd263d9..fc6b4634 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 7fd263d99e6ae1b07e51f25bda3367f7463806b1 +Subproject commit fc6b46342b365ca4f120a830b66204c2517945c8 From 0e72508dcca7161376e86167242b24e0469e53ee Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sun, 12 Apr 2026 18:50:19 +0200 Subject: [PATCH 544/556] i18n: translation updates from weblate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Amadɣas Co-authored-by: Asier Saratsua Garmendia Co-authored-by: ButterflyOfFire Co-authored-by: Ettore Atalan Co-authored-by: Geeson Wan Co-authored-by: Hosted Weblate Co-authored-by: 麋麓 BigELK176 Co-authored-by: 김유빈 Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/de/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/kab/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ko/ Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/zh_Hant/ Translation: Pop OS/libcosmic --- i18n/de/libcosmic.ftl | 2 +- i18n/eu/libcosmic.ftl | 0 i18n/kab/libcosmic.ftl | 33 +++++++++++++++++++++++++++++++++ i18n/ko/libcosmic.ftl | 21 ++++++++++++++------- i18n/zh-Hant/libcosmic.ftl | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 i18n/eu/libcosmic.ftl diff --git a/i18n/de/libcosmic.ftl b/i18n/de/libcosmic.ftl index 238000f5..2d3704a6 100644 --- a/i18n/de/libcosmic.ftl +++ b/i18n/de/libcosmic.ftl @@ -6,7 +6,7 @@ links = Links developers = Entwickler(innen) designers = Designer(innen) artists = Künstler(innen) -translators = Übersetzer*innen +translators = Übersetzer(innen) documenters = Dokumentierer(innen) # Calendar january = Januar { $year } diff --git a/i18n/eu/libcosmic.ftl b/i18n/eu/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/kab/libcosmic.ftl b/i18n/kab/libcosmic.ftl index e69de29b..6eac2bc7 100644 --- a/i18n/kab/libcosmic.ftl +++ b/i18n/kab/libcosmic.ftl @@ -0,0 +1,33 @@ +close = Mdel +license = Turagt +links = Iseɣwan +developers = Ineflayen +artists = Inaẓuren +translators = Imsuqlen +january = Yennayer { $year } +february = Fuṛar { $year } +march = Meɣres { $year } +april = Yebrir { $year } +may = Mayyu { $year } +june = Yunyu { $year } +july = Yulyu { $year } +august = Ɣuct { $year } +september = Ctembeṛ { $year } +october = Tubeṛ { $year } +november = Wambeṛ { $year } +december = Dujembeṛ { $year } +documenters = Imeskaren +monday = Arim +mon = Ari +tuesday = Aram +tue = Ara +wednesday = Ahad +wed = Aha +thursday = Amhad +thu = Amh +friday = Sem +fri = Sm +saturday = Sed +sat = Sd +sunday = Acer +sun = Ace diff --git a/i18n/ko/libcosmic.ftl b/i18n/ko/libcosmic.ftl index 8d499756..6cc0adbc 100644 --- a/i18n/ko/libcosmic.ftl +++ b/i18n/ko/libcosmic.ftl @@ -2,26 +2,33 @@ february = { $year }년 2월 close = 닫기 documenters = 문서 작성자 november = { $year }년 11월 -friday = 금 -tuesday = 화 +friday = 금요일 +tuesday = 화요일 may = { $year }년 5월 -wednesday = 수 +wednesday = 수요일 april = { $year }년 4월 -monday = 월 +monday = 월요일 translators = 번역가 artists = 아티스트 license = 라이선스 december = { $year }년 12월 -sunday = 일 +sunday = 일요일 links = 링크 march = { $year }년 3월 june = { $year }년 6월 -saturday = 토 +saturday = 토요일 august = { $year }년 8월 developers = 개발자 july = { $year }년 7월 -thursday = 목 +thursday = 목요일 september = { $year }년 9월 designers = 디자이너 october = { $year }년 10월 january = { $year }년 1월 +mon = 월 +tue = 화 +wed = 수 +thu = 목 +fri = 금 +sat = 토 +sun = 일 diff --git a/i18n/zh-Hant/libcosmic.ftl b/i18n/zh-Hant/libcosmic.ftl index e69de29b..8c9b201c 100644 --- a/i18n/zh-Hant/libcosmic.ftl +++ b/i18n/zh-Hant/libcosmic.ftl @@ -0,0 +1,34 @@ +close = 關閉 +developers = 開發人員 +designers = 設計人員 +artists = 美編設計 +translators = 翻譯人員 +documenters = 文件編輯人員 +january = { $year } 年 1 月 +monday = 星期一 +tuesday = 星期二 +wednesday = 星期三 +thursday = 星期四 +friday = 星期五 +saturday = 星期六 +sunday = 星期日 +mon = 週一 +tue = 週二 +wed = 週三 +thu = 週四 +fri = 週五 +sat = 週六 +sun = 週日 +license = 授權 +links = 連結 +february = { $year } 年 2 月 +march = { $year } 年 3 月 +april = { $year } 年 4 月 +may = { $year } 年 5 月 +june = { $year } 年 6 月 +july = { $year } 年 7 月 +august = { $year } 年 8 月 +september = { $year } 年 9 月 +october = { $year } 年 10 月 +november = { $year } 年 11 月 +december = { $year } 年 12 月 From 52116d2f36972c422a0953a4699e32d5eb30cdac Mon Sep 17 00:00:00 2001 From: Hojjat Date: Mon, 13 Apr 2026 14:07:31 -0600 Subject: [PATCH 545/556] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index fc6b4634..78caabba 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit fc6b46342b365ca4f120a830b66204c2517945c8 +Subproject commit 78caabba7ef91cd1030da6f70b41d266704ffece From 46d9f0c3442189b446ffeff452c314fa6592da7e Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 14 Apr 2026 11:53:33 -0700 Subject: [PATCH 546/556] widget/icon: Bundle icons on macOS, not just Windows --- Cargo.toml | 4 ++-- build.rs | 4 +++- src/widget/icon/bundle.rs | 6 +++--- src/widget/icon/named.rs | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 83fe90f0..e090ad21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -170,12 +170,12 @@ cosmic-config = { path = "cosmic-config", features = ["dbus"] } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" } zbus = { version = "5.14.0", default-features = false } -[target.'cfg(unix)'.dependencies] +[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies] freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } freedesktop-desktop-entry = { version = "0.8.1", optional = true } shlex = { version = "1.3.0", optional = true } -[target.'cfg(not(unix))'.dependencies] +[target.'cfg(any(not(unix), target_os = "macos"))'.dependencies] # Used to embed bundled icons for non-unix platforms. phf = { version = "0.13.1", features = ["macros"] } diff --git a/build.rs b/build.rs index c69feaf5..4ce0aa9e 100644 --- a/build.rs +++ b/build.rs @@ -3,7 +3,9 @@ use std::env; fn main() { println!("cargo::rerun-if-changed=build.rs"); - if env::var_os("CARGO_CFG_UNIX").is_none() { + if env::var_os("CARGO_CFG_UNIX").is_none() + || env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") + { generate_bundled_icons(); } } diff --git a/src/widget/icon/bundle.rs b/src/widget/icon/bundle.rs index 9d0877d0..bb6ce244 100644 --- a/src/widget/icon/bundle.rs +++ b/src/widget/icon/bundle.rs @@ -4,12 +4,12 @@ //! Embedded icons for platforms which do not support icon themes yet. /// Icon bundling is not enabled on unix platforms. -#[cfg(unix)] +#[cfg(all(unix, not(target_os = "macos")))] pub fn get(icon_name: &str) -> Option { None } -#[cfg(not(unix))] +#[cfg(any(not(unix), target_os = "macos"))] /// Get a bundled icon on non-unix platforms. pub fn get(icon_name: &str) -> Option { ICONS @@ -17,5 +17,5 @@ pub fn get(icon_name: &str) -> Option { .map(|bytes| super::Data::Svg(crate::iced::widget::svg::Handle::from_memory(*bytes))) } -#[cfg(not(unix))] +#[cfg(any(not(unix), target_os = "macos"))] include!(concat!(env!("OUT_DIR"), "/bundled_icons.rs")); diff --git a/src/widget/icon/named.rs b/src/widget/icon/named.rs index 8405e080..dfd66cf5 100644 --- a/src/widget/icon/named.rs +++ b/src/widget/icon/named.rs @@ -52,7 +52,7 @@ impl Named { } } - #[cfg(not(windows))] + #[cfg(all(unix, not(target_os = "macos")))] #[must_use] pub fn path(self) -> Option { let name = &*self.name; @@ -107,7 +107,7 @@ impl Named { result } - #[cfg(windows)] + #[cfg(any(not(unix), target_os = "macos"))] #[must_use] pub fn path(self) -> Option { //TODO: implement icon lookup for Windows From 3d8d8915be516229bd215403e0a800ea80f618ae Mon Sep 17 00:00:00 2001 From: Hojjat Date: Tue, 14 Apr 2026 23:14:41 -0600 Subject: [PATCH 547/556] chore: enable ico and xpm image support for desktop feature --- Cargo.toml | 6 ++++++ src/app/mod.rs | 3 +++ 2 files changed, 9 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index e090ad21..d73da2dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ desktop = [ "process", "dep:cosmic-settings-config", "dep:freedesktop-desktop-entry", + "dep:image-extras", "dep:mime", "dep:shlex", "tokio?/io-util", @@ -141,9 +142,14 @@ css-color = "0.2.8" derive_setters = "0.1.9" futures = "0.3" image = { version = "0.25.10", default-features = false, features = [ + "ico", "jpeg", "png", ] } +image-extras = { version = "0.1.0", default-features = false, features = [ + "xpm", + "xbm", +], optional = true } libc = { version = "0.2.183", optional = true } log = "0.4" mime = { version = "0.3.17", optional = true } diff --git a/src/app/mod.rs b/src/app/mod.rs index 5c0e95e4..42fa4b1b 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -128,6 +128,9 @@ impl BootFn, crate::Action(settings: Settings, flags: App::Flags) -> iced::Result { + #[cfg(feature = "desktop")] + image_extras::register(); + #[cfg(all(target_env = "gnu", not(target_os = "windows")))] if let Some(threshold) = settings.default_mmap_threshold { crate::malloc::limit_mmap_threshold(threshold); From 0fc4638af38d8edecf4b0bdc4e17e8e2bd2a2c22 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Wed, 15 Apr 2026 14:45:20 -0600 Subject: [PATCH 548/556] fix: register image_extras in run_single_instance too --- src/app/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/mod.rs b/src/app/mod.rs index 42fa4b1b..f78beac7 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -197,6 +197,9 @@ where App::Flags: CosmicFlags, App::Message: Clone + std::fmt::Debug + Send + 'static, { + #[cfg(feature = "desktop")] + image_extras::register(); + use std::collections::HashMap; let activation_token = std::env::var("XDG_ACTIVATION_TOKEN").ok(); From 9cac422c245777e492094177b21b8a8be4ab7bb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Sun, 5 Apr 2026 17:03:47 +0200 Subject: [PATCH 549/556] fix(toggler): animate external changes --- src/widget/toggler.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index 05371a17..b95b596e 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -161,7 +161,10 @@ impl<'a, Message> Widget for Toggler<'a, } fn state(&self) -> tree::State { - tree::State::new(State::default()) + tree::State::new(State { + prev_toggled: self.is_toggled, + ..State::default() + }) } fn id(&self) -> Option { @@ -238,6 +241,14 @@ impl<'a, Message> Widget for Toggler<'a, return; }; let state = tree.state.downcast_mut::(); + + // animate external changes + if state.prev_toggled != self.is_toggled { + state.anim.changed(self.duration); + shell.request_redraw(); + state.prev_toggled = self.is_toggled; + } + match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { @@ -246,6 +257,7 @@ impl<'a, Message> Widget for Toggler<'a, if mouse_over { shell.publish((on_toggle)(!self.is_toggled)); state.anim.changed(self.duration); + state.prev_toggled = !self.is_toggled; shell.capture_event(); } } @@ -430,4 +442,5 @@ pub fn next_to_each_other( pub struct State { text: widget::text::State<::Paragraph>, anim: anim::State, + prev_toggled: bool, } From 9b465a8b5c4d3bb75389bba49d6ee1cec8c26d9e 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: Sat, 4 Apr 2026 16:34:25 +0200 Subject: [PATCH 550/556] feat(list_column): button list items --- src/theme/style/button.rs | 30 ++++-- src/widget/list/column.rs | 128 ---------------------- src/widget/list/list_column.rs | 188 +++++++++++++++++++++++++++++++++ src/widget/list/mod.rs | 4 +- src/widget/settings/item.rs | 94 +++++++++++++---- src/widget/settings/section.rs | 17 +-- 6 files changed, 298 insertions(+), 163 deletions(-) delete mode 100644 src/widget/list/column.rs create mode 100644 src/widget/list/list_column.rs diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index 0575ce67..bb52d9a6 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -27,7 +27,7 @@ pub enum Button { IconVertical, Image, Link, - ListItem, + ListItem([f32; 4]), MenuFolder, MenuItem, MenuRoot, @@ -148,8 +148,8 @@ pub fn appearance( appearance.text_color = Some(component.on.into()); corner_radii = &cosmic.corner_radii.radius_s; } - Button::ListItem => { - corner_radii = &[0.0; 4]; + Button::ListItem(radii) => { + corner_radii = radii; let (background, text, icon) = color(&cosmic.background.component); if selected { @@ -197,7 +197,7 @@ impl Catalog for crate::Theme { return active(focused, self); } - appearance(self, focused, selected, false, style, move |component| { + let mut s = appearance(self, focused, selected, false, style, move |component| { let text_color = if matches!( style, Button::Icon | Button::IconVertical | Button::HeaderBar @@ -209,7 +209,15 @@ impl Catalog for crate::Theme { }; (component.base.into(), text_color, text_color) - }) + }); + + if let Button::ListItem(_) = style { + if !selected { + s.background = None; + } + } + + s } fn disabled(&self, style: &Self::Class) -> Style { @@ -237,7 +245,7 @@ impl Catalog for crate::Theme { return hovered(focused, self); } - appearance( + let mut s = appearance( self, focused || matches!(style, Button::Image), selected, @@ -256,7 +264,15 @@ impl Catalog for crate::Theme { (component.hover.into(), text_color, text_color) }, - ) + ); + + if let Button::ListItem(_) = style { + if !selected { + s.background = None; + } + } + + s } fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style { diff --git a/src/widget/list/column.rs b/src/widget/list/column.rs deleted file mode 100644 index 945b9140..00000000 --- a/src/widget/list/column.rs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2022 System76 -// SPDX-License-Identifier: MPL-2.0 - -use iced_core::Padding; -use iced_widget::container::Catalog; - -use crate::{ - Apply, Element, theme, - widget::{container, divider, space::vertical}, -}; - -#[inline] -pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> { - ListColumn::default() -} - -#[must_use] -pub struct ListColumn<'a, Message> { - spacing: u16, - padding: Padding, - list_item_padding: Padding, - divider_padding: u16, - style: theme::Container<'a>, - children: Vec>, -} - -impl Default for ListColumn<'_, Message> { - fn default() -> Self { - let cosmic_theme::Spacing { - space_xxs, space_m, .. - } = theme::spacing(); - - Self { - spacing: 0, - padding: Padding::from(0), - divider_padding: 16, - list_item_padding: [space_xxs, space_m].into(), - style: theme::Container::List, - children: Vec::with_capacity(4), - } - } -} - -impl<'a, Message: 'static> ListColumn<'a, Message> { - #[inline] - pub fn new() -> Self { - Self::default() - } - - #[allow(clippy::should_implement_trait)] - pub fn add(self, item: impl Into>) -> Self { - #[inline(never)] - fn inner<'a, Message: 'static>( - mut this: ListColumn<'a, Message>, - item: Element<'a, Message>, - ) -> ListColumn<'a, Message> { - if !this.children.is_empty() { - this.children.push( - container(divider::horizontal::default()) - .padding([0, this.divider_padding]) - .into(), - ); - } - - // Ensure a minimum height of 32. - let list_item = crate::widget::row![ - container(item).align_y(iced::Alignment::Center), - vertical().height(iced::Length::Fixed(32.)) - ] - .padding(this.list_item_padding) - .align_y(iced::Alignment::Center); - - this.children.push(list_item.into()); - this - } - - inner(self, item.into()) - } - - #[inline] - pub fn spacing(mut self, spacing: u16) -> Self { - self.spacing = spacing; - self - } - - /// Sets the style variant of this [`Circular`]. - #[inline] - pub fn style(mut self, style: ::Class<'a>) -> Self { - self.style = style; - self - } - - #[inline] - pub fn padding(mut self, padding: impl Into) -> Self { - self.padding = padding.into(); - self - } - - #[inline] - pub fn divider_padding(mut self, padding: u16) -> Self { - self.divider_padding = padding; - self - } - - pub fn list_item_padding(mut self, padding: impl Into) -> Self { - self.list_item_padding = padding.into(); - self - } - - #[must_use] - pub fn into_element(self) -> Element<'a, Message> { - crate::widget::column::with_children(self.children) - .spacing(self.spacing) - .padding(self.padding) - .width(iced::Length::Fill) - .apply(container) - .padding([self.spacing, 0]) - .class(self.style) - .width(iced::Length::Fill) - .into() - } -} - -impl<'a, Message: 'static> From> for Element<'a, Message> { - fn from(column: ListColumn<'a, Message>) -> Self { - column.into_element() - } -} diff --git a/src/widget/list/list_column.rs b/src/widget/list/list_column.rs new file mode 100644 index 00000000..89a87063 --- /dev/null +++ b/src/widget/list/list_column.rs @@ -0,0 +1,188 @@ +// Copyright 2022 System76 +// SPDX-License-Identifier: MPL-2.0 + +use crate::widget::container::Catalog; +use crate::widget::{button, column, container, divider, row, space::vertical}; +use crate::{Apply, Element, theme}; +use iced::{Length, Padding}; + +/// A button list item for use in a [`ListColumn`]. +pub struct ListButton<'a, Message> { + content: Element<'a, Message>, + on_press: Option, + selected: bool, +} + +/// Creates a [`ListButton`] with the given content. +pub fn button<'a, Message>(content: impl Into>) -> ListButton<'a, Message> { + ListButton { + content: content.into(), + on_press: None, + selected: false, + } +} + +impl<'a, Message: 'static> ListButton<'a, Message> { + pub fn on_press(mut self, on_press: Message) -> Self { + self.on_press = Some(on_press); + self + } + + pub fn on_press_maybe(mut self, on_press: Option) -> Self { + self.on_press = on_press; + self + } + + pub fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } +} + +pub enum ListItem<'a, Message> { + Element(Element<'a, Message>), + Button(ListButton<'a, Message>), +} + +/// A trait for types that can be added to a [`ListColumn`]. +pub trait IntoListItem<'a, Message> { + fn into_list_item(self) -> ListItem<'a, Message>; +} + +impl<'a, Message, T> IntoListItem<'a, Message> for T +where + T: Into>, +{ + fn into_list_item(self) -> ListItem<'a, Message> { + ListItem::Element(self.into()) + } +} + +impl<'a, Message> IntoListItem<'a, Message> for ListButton<'a, Message> { + fn into_list_item(self) -> ListItem<'a, Message> { + ListItem::Button(self) + } +} + +#[must_use] +pub struct ListColumn<'a, Message> { + list_item_padding: Padding, + style: theme::Container<'a>, + children: Vec>, +} + +#[inline] +pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> { + ListColumn::default() +} + +pub fn with_capacity<'a, Message: 'static>(capacity: usize) -> ListColumn<'a, Message> { + let cosmic_theme::Spacing { + space_xxs, space_m, .. + } = theme::spacing(); + + ListColumn { + list_item_padding: [space_xxs, space_m].into(), + style: theme::Container::List, + children: Vec::with_capacity(capacity), + } +} + +impl Default for ListColumn<'_, Message> { + fn default() -> Self { + with_capacity(4) + } +} + +impl<'a, Message: Clone + 'static> ListColumn<'a, Message> { + #[inline] + pub fn new() -> Self { + Self::default() + } + + /// Adds an element to the list column. + #[allow(clippy::should_implement_trait)] + pub fn add(mut self, item: impl IntoListItem<'a, Message>) -> Self { + self.children.push(item.into_list_item()); + self + } + + /// Sets the style variant of this [`ListColumn`]. + #[inline] + pub fn style(mut self, style: ::Class<'a>) -> Self { + self.style = style; + self + } + + pub fn list_item_padding(mut self, padding: impl Into) -> Self { + self.list_item_padding = padding.into(); + self + } + + #[must_use] + pub fn into_element(self) -> Element<'a, Message> { + let padding = self.list_item_padding; + let count = self.children.len(); + let last_index = count.saturating_sub(1); + let radius_s = theme::active().cosmic().radius_s(); + + // Ensure minimum height of 32 + let content_row = |content| { + row![container(content), vertical().height(32)].align_y(iced::Alignment::Center) + }; + + self.children + .into_iter() + .enumerate() + .fold( + column::with_capacity((2 * count).saturating_sub(1)), + |mut col, (i, item)| { + if i > 0 { + col = col.push(divider::horizontal::default()); + } + + match item { + ListItem::Element(content) => { + col.push(content_row(content).padding(padding).width(Length::Fill)) + } + ListItem::Button(ListButton { + content, + on_press, + selected, + }) => col.push( + content_row(content) + .apply(button::custom) + .padding(padding) + .width(Length::Fill) + .on_press_maybe(on_press) + .selected(selected) + .class(theme::Button::ListItem(get_radius( + radius_s, + i == 0, + i == last_index, + ))), + ), + } + }, + ) + .width(Length::Fill) + .apply(container) + .class(self.style) + .into() + } +} + +impl<'a, Message: Clone + 'static> From> for Element<'a, Message> { + fn from(column: ListColumn<'a, Message>) -> Self { + column.into_element() + } +} + +fn get_radius(radius: [f32; 4], first: bool, last: bool) -> [f32; 4] { + match (first, last) { + (true, true) => radius, + (true, false) => [radius[0], radius[1], 0.0, 0.0], + (false, true) => [0.0, 0.0, radius[2], radius[3]], + (false, false) => [0.0, 0.0, 0.0, 0.0], + } +} diff --git a/src/widget/list/mod.rs b/src/widget/list/mod.rs index c6e2051c..71eda086 100644 --- a/src/widget/list/mod.rs +++ b/src/widget/list/mod.rs @@ -1,6 +1,6 @@ // Copyright 2022 System76 // SPDX-License-Identifier: MPL-2.0 -pub mod column; +pub mod list_column; -pub use self::column::{ListColumn, list_column}; +pub use self::list_column::{ListButton, ListColumn, button, list_column}; diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index 349d93d8..a4092093 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -5,7 +5,7 @@ use std::borrow::Cow; use crate::{ Element, Theme, theme, - widget::{FlexRow, Row, column, container, flex_row, row, text}, + widget::{FlexRow, Row, column, container, flex_row, list, row, text}, }; use derive_setters::Setters; use iced_core::{Length, text::Wrapping}; @@ -114,39 +114,95 @@ impl<'a, Message: 'static> Item<'a, Message> { flex_item_row(self.control_(widget.into())) } - #[inline(never)] - fn control_(self, widget: Element<'a, Message>) -> Vec> { - let mut contents = Vec::with_capacity(4); - - if let Some(icon) = self.icon { - contents.push(icon); - } - + fn label(self) -> Element<'a, Message> { if let Some(description) = self.description { - let column = column::with_capacity(2) + column::with_capacity(2) .spacing(2) .push(text::body(self.title).wrapping(Wrapping::Word)) .push(text::caption(description).wrapping(Wrapping::Word)) - .width(Length::Fill); - - contents.push(column.into()); + .width(Length::Fill) + .into() } else { - contents.push(text(self.title).width(Length::Fill).into()); + text(self.title).width(Length::Fill).into() } + } + #[inline(never)] + fn control_(mut self, widget: Element<'a, Message>) -> Vec> { + let mut contents = Vec::with_capacity(3); + if let Some(icon) = self.icon.take() { + contents.push(icon); + } + contents.push(self.label()); contents.push(widget); contents } + fn control_start(self, widget: impl Into>) -> Row<'a, Message, Theme> { + item_row(vec![widget.into(), self.label()]) + } + pub fn toggler( self, is_checked: bool, message: impl Fn(bool) -> Message + 'static, - ) -> Row<'a, Message, Theme> { - self.control( - crate::widget::toggler(is_checked) - .width(Length::Shrink) - .on_toggle(message), + ) -> list::ListButton<'a, Message> { + let on_press = message(!is_checked); + list::button( + self.control( + crate::widget::toggler(is_checked) + .width(Length::Shrink) + .on_toggle(message), + ), ) + .on_press(on_press) + } + + pub fn toggler_maybe( + self, + is_checked: bool, + message: Option Message + 'static>, + ) -> list::ListButton<'a, Message> { + let on_press = message.as_ref().map(|f| f(!is_checked)); + list::button( + self.control( + crate::widget::toggler(is_checked) + .width(Length::Shrink) + .on_toggle_maybe(message), + ), + ) + .on_press_maybe(on_press) + } + + pub fn checkbox( + self, + is_checked: bool, + message: impl Fn(bool) -> Message + 'static, + ) -> list::ListButton<'a, Message> { + let on_press = message(!is_checked); + list::button( + self.control_start( + crate::widget::checkbox(is_checked) + .width(Length::Shrink) + .on_toggle(message), + ), + ) + .on_press(on_press) + } + + pub fn checkbox_maybe( + self, + is_checked: bool, + message: Option Message + 'static>, + ) -> list::ListButton<'a, Message> { + let on_press = message.as_ref().map(|f| f(!is_checked)); + list::button( + self.control_start( + crate::widget::checkbox(is_checked) + .width(Length::Shrink) + .on_toggle_maybe(message), + ), + ) + .on_press_maybe(on_press) } } diff --git a/src/widget/settings/section.rs b/src/widget/settings/section.rs index ab95b5ad..ee07c76d 100644 --- a/src/widget/settings/section.rs +++ b/src/widget/settings/section.rs @@ -2,16 +2,19 @@ // SPDX-License-Identifier: MPL-2.0 use crate::Element; +use crate::widget::list_column::IntoListItem; use crate::widget::{ListColumn, column, text}; use std::borrow::Cow; /// A section within a settings view column. -pub fn section<'a, Message: 'static>() -> Section<'a, Message> { +pub fn section<'a, Message: Clone + 'static>() -> Section<'a, Message> { with_column(ListColumn::default()) } /// A section with a pre-defined list column. -pub fn with_column(children: ListColumn<'_, Message>) -> Section<'_, Message> { +pub fn with_column( + children: ListColumn<'_, Message>, +) -> Section<'_, Message> { Section { header: None, children, @@ -24,9 +27,9 @@ pub struct Section<'a, Message> { children: ListColumn<'a, Message>, } -impl<'a, Message: 'static> Section<'a, Message> { +impl<'a, Message: Clone + 'static> Section<'a, Message> { /// Define an optional title for the section. - pub fn title(mut self, title: impl Into>) -> Self { + pub fn title(self, title: impl Into>) -> Self { self.header(text::heading(title.into())) } @@ -38,8 +41,8 @@ impl<'a, Message: 'static> Section<'a, Message> { /// Add a child element to the section's list column. #[allow(clippy::should_implement_trait)] - pub fn add(mut self, item: impl Into>) -> Self { - self.children = self.children.add(item.into()); + pub fn add(mut self, item: impl IntoListItem<'a, Message>) -> Self { + self.children = self.children.add(item); self } @@ -61,7 +64,7 @@ impl<'a, Message: 'static> Section<'a, Message> { } } -impl<'a, Message: 'static> From> for Element<'a, Message> { +impl<'a, Message: Clone + 'static> From> for Element<'a, Message> { fn from(data: Section<'a, Message>) -> Self { column::with_capacity(2) .spacing(8) From 917af9fda204d027ad55380521041b6691f17895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:59:37 +0200 Subject: [PATCH 551/556] feat(radio): internal method for radio without label Also adds the related settings item builder. --- src/widget/radio.rs | 165 ++++++++++++++++++++++-------------- src/widget/settings/item.rs | 16 +++- 2 files changed, 116 insertions(+), 65 deletions(-) diff --git a/src/widget/radio.rs b/src/widget/radio.rs index 338c0a4e..c3f115c0 100644 --- a/src/widget/radio.rs +++ b/src/widget/radio.rs @@ -1,5 +1,5 @@ //! Create choices using radio buttons. -use crate::Theme; +use crate::{Theme, theme}; use iced::border; use iced_core::event::{self, Event}; use iced_core::layout; @@ -92,7 +92,7 @@ where { is_selected: bool, on_click: Message, - label: Element<'a, Message, Theme, Renderer>, + label: Option>, width: Length, size: f32, spacing: f32, @@ -106,9 +106,6 @@ where /// The default size of a [`Radio`] button. pub const DEFAULT_SIZE: f32 = 16.0; - /// The default spacing of a [`Radio`] button. - pub const DEFAULT_SPACING: f32 = 8.0; - /// Creates a new [`Radio`] button. /// /// It expects: @@ -126,10 +123,29 @@ where Radio { is_selected: Some(value) == selected, on_click: f(value), - label: label.into(), + label: Some(label.into()), width: Length::Shrink, size: Self::DEFAULT_SIZE, - spacing: Self::DEFAULT_SPACING, + spacing: theme::spacing().space_xs as f32, + } + } + + /// Creates a new [`Radio`] button without a label. + /// + /// This is intended for internal use with the settings item builder, + /// where the label comes from the settings item title instead. + pub(crate) fn new_no_label(value: V, selected: Option, f: F) -> Self + where + V: Eq + Copy, + F: FnOnce(V) -> Message, + { + Radio { + is_selected: Some(value) == selected, + on_click: f(value), + label: None, + width: Length::Shrink, + size: Self::DEFAULT_SIZE, + spacing: theme::spacing().space_xs as f32, } } @@ -161,11 +177,17 @@ where Renderer: iced_core::Renderer, { fn children(&self) -> Vec { - vec![Tree::new(&self.label)] + if let Some(label) = &self.label { + vec![Tree::new(label)] + } else { + vec![] + } } fn diff(&mut self, tree: &mut Tree) { - tree.diff_children(std::slice::from_mut(&mut self.label)); + if let Some(label) = &mut self.label { + tree.diff_children(std::slice::from_mut(label)); + } } fn size(&self) -> Size { Size { @@ -180,16 +202,20 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - layout::next_to_each_other( - &limits.width(self.width), - self.spacing, - |_| layout::Node::new(Size::new(self.size, self.size)), - |limits| { - self.label - .as_widget_mut() - .layout(&mut tree.children[0], renderer, limits) - }, - ) + if let Some(label) = &mut self.label { + layout::next_to_each_other( + &limits.width(self.width), + self.spacing, + |_| layout::Node::new(Size::new(self.size, self.size)), + |limits| { + label + .as_widget_mut() + .layout(&mut tree.children[0], renderer, limits) + }, + ) + } else { + layout::Node::new(Size::new(self.size, self.size)) + } } fn operate( @@ -199,12 +225,14 @@ where renderer: &Renderer, operation: &mut dyn iced_core::widget::Operation<()>, ) { - self.label.as_widget_mut().operate( - &mut tree.children[0], - layout.children().nth(1).unwrap(), - renderer, - operation, - ); + if let Some(label) = &mut self.label { + label.as_widget_mut().operate( + &mut tree.children[0], + layout.children().nth(1).unwrap(), + renderer, + operation, + ); + } } fn update( @@ -218,24 +246,25 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - self.label.as_widget_mut().update( - &mut tree.children[0], - event, - layout.children().nth(1).unwrap(), - cursor, - renderer, - clipboard, - shell, - viewport, - ); + if let Some(label) = &mut self.label { + label.as_widget_mut().update( + &mut tree.children[0], + event, + layout.children().nth(1).unwrap(), + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } if !shell.is_event_captured() { match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) => { if cursor.is_over(layout.bounds()) { shell.publish(self.on_click.clone()); - shell.capture_event(); return; } @@ -253,13 +282,17 @@ where viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { - let interaction = self.label.as_widget().mouse_interaction( - &tree.children[0], - layout.children().nth(1).unwrap(), - cursor, - viewport, - renderer, - ); + let interaction = if let Some(label) = &self.label { + label.as_widget().mouse_interaction( + &tree.children[0], + layout.children().nth(1).unwrap(), + cursor, + viewport, + renderer, + ) + } else { + mouse::Interaction::default() + }; if interaction == mouse::Interaction::default() { if cursor.is_over(layout.bounds()) { @@ -284,8 +317,6 @@ where ) { let is_mouse_over = cursor.is_over(layout.bounds()); - let mut children = layout.children(); - let custom_style = if is_mouse_over { theme.style( &(), @@ -302,16 +333,21 @@ where ) }; - { - let layout = children.next().unwrap(); - let bounds = layout.bounds(); + let (dot_bounds, label_layout) = if self.label.is_some() { + let mut children = layout.children(); + let dot_bounds = children.next().unwrap().bounds(); + (dot_bounds, children.next()) + } else { + (layout.bounds(), None) + }; - let size = bounds.width; + { + let size = dot_bounds.width; let dot_size = 6.0; renderer.fill_quad( renderer::Quad { - bounds, + bounds: dot_bounds, border: Border { radius: (size / 2.0).into(), width: custom_style.border_width, @@ -326,8 +362,8 @@ where renderer.fill_quad( renderer::Quad { bounds: Rectangle { - x: bounds.x + (size - dot_size) / 2.0, - y: bounds.y + (size - dot_size) / 2.0, + x: dot_bounds.x + (size - dot_size) / 2.0, + y: dot_bounds.y + (size - dot_size) / 2.0, width: dot_size, height: dot_size, }, @@ -339,9 +375,8 @@ where } } - { - let label_layout = children.next().unwrap(); - self.label.as_widget().draw( + if let (Some(label), Some(label_layout)) = (&self.label, label_layout) { + label.as_widget().draw( &tree.children[0], renderer, theme, @@ -361,7 +396,7 @@ where viewport: &Rectangle, translation: Vector, ) -> Option> { - self.label.as_widget_mut().overlay( + self.label.as_mut()?.as_widget_mut().overlay( &mut tree.children[0], layout.children().nth(1).unwrap(), renderer, @@ -377,12 +412,14 @@ where renderer: &Renderer, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { - self.label.as_widget().drag_destinations( - &state.children[0], - layout.children().nth(1).unwrap(), - renderer, - dnd_rectangles, - ); + if let Some(label) = &self.label { + label.as_widget().drag_destinations( + &state.children[0], + layout.children().nth(1).unwrap(), + renderer, + dnd_rectangles, + ); + } } } diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index a4092093..11821335 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -103,7 +103,7 @@ pub struct Item<'a, Message> { icon: Option>, } -impl<'a, Message: 'static> Item<'a, Message> { +impl<'a, Message: Clone + 'static> Item<'a, Message> { /// Assigns a control to the item. pub fn control(self, widget: impl Into>) -> Row<'a, Message, Theme> { item_row(self.control_(widget.into())) @@ -205,4 +205,18 @@ impl<'a, Message: 'static> Item<'a, Message> { ) .on_press_maybe(on_press) } + + pub fn radio(self, value: V, selected: Option, f: F) -> list::ListButton<'a, Message> + where + V: Eq + Copy + 'static, + F: Fn(V) -> Message + 'static, + { + let on_press = f(value); + list::button( + self.control_start(crate::widget::radio::Radio::new_no_label( + value, selected, f, + )), + ) + .on_press(on_press) + } } From 3f9e93067b31d9ba81a4e3a28653b3380c61c352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:08:42 +0200 Subject: [PATCH 552/556] fix(item builder): remove unnecessary lifetime bound for radio --- src/widget/settings/item.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index 11821335..5abb464c 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -208,8 +208,8 @@ impl<'a, Message: Clone + 'static> Item<'a, Message> { pub fn radio(self, value: V, selected: Option, f: F) -> list::ListButton<'a, Message> where - V: Eq + Copy + 'static, - F: Fn(V) -> Message + 'static, + V: Eq + Copy, + F: Fn(V) -> Message, { let on_press = f(value); list::button( From c162a1f24a2b7fdf29286dfa807c4a1b4813ab7c Mon Sep 17 00:00:00 2001 From: Hojjat Date: Thu, 9 Apr 2026 18:32:14 -0600 Subject: [PATCH 553/556] fix(animated-image): update frames and fix compilation errors --- src/widget/frames.rs | 63 +++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 39 deletions(-) diff --git a/src/widget/frames.rs b/src/widget/frames.rs index 056a55ba..a542cec6 100644 --- a/src/widget/frames.rs +++ b/src/widget/frames.rs @@ -14,10 +14,10 @@ use iced_core::image::Renderer as ImageRenderer; use iced_core::mouse::Cursor; use iced_core::widget::{Tree, tree}; use iced_core::{ - Clipboard, ContentFit, Element, Event, Layout, Length, Rectangle, Shell, Size, Vector, Widget, - event, layout, renderer, window, + Clipboard, ContentFit, Element, Event, Layout, Length, Rectangle, Rotation, Shell, Size, + Widget, event, layout, renderer, window, }; -use iced_widget::image::{self, Handle}; +use iced_widget::image::{self, FilterMethod, Handle}; use image_rs::AnimationDecoder; use image_rs::codecs::gif::GifDecoder; use image_rs::codecs::png::PngDecoder; @@ -146,7 +146,7 @@ impl Frames { match image_type { ImageType::Gif => Self::from_decoder(GifDecoder::new(io::Cursor::new(bytes))?), - ImageType::Apng => Self::from_decoder(PngDecoder::new(io::Cursor::new(bytes))?.apng()), + ImageType::Apng => Self::from_decoder(PngDecoder::new(io::Cursor::new(bytes))?.apng()?), ImageType::WebP => Self::from_decoder(WebPDecoder::new(io::Cursor::new(bytes))?), } } @@ -168,10 +168,10 @@ impl Frames { let first = frames.first().cloned().unwrap(); let total_bytes = frames .iter() - .map(|f| match f.handle.data() { - iced_core::image::Handle::Path(..) => 0, - iced_core::image::Handle::Bytes(_, b) => b.len(), - iced_core::image::Handle::Rgba { pixels, .. } => pixels.len(), + .map(|f| match &f.handle { + Handle::Path(..) => 0, + Handle::Bytes(_, b) => b.len(), + Handle::Rgba { pixels, .. } => pixels.len(), }) .sum::() .try_into() @@ -324,7 +324,11 @@ where &self.frames.first.handle, self.width, self.height, + None, self.content_fit, + Rotation::default(), + false, + [0.0; 4], ) } @@ -371,37 +375,18 @@ where ) { let state = tree.state.downcast_ref::(); - // Pulled from iced_native::widget::::draw - // - // TODO: export iced_native::widget::image::draw as standalone function - { - let Size { width, height } = renderer.dimensions(&state.current.frame.handle); - let image_size = Size::new(width as f32, height as f32); - - let bounds = layout.bounds(); - let adjusted_fit = self.content_fit.fit(image_size, bounds.size()); - - let render = |renderer: &mut Renderer| { - let offset = Vector::new( - (bounds.width - adjusted_fit.width).max(0.0) / 2.0, - (bounds.height - adjusted_fit.height).max(0.0) / 2.0, - ); - - let drawing_bounds = Rectangle { - width: adjusted_fit.width, - height: adjusted_fit.height, - ..bounds - }; - - renderer.draw(state.current.frame.handle.clone(), drawing_bounds + offset); - }; - - if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height { - renderer.with_layer(bounds, render); - } else { - render(renderer); - } - } + iced_widget::image::draw( + renderer, + layout, + &state.current.frame.handle, + None, + iced_core::border::Radius::default(), + self.content_fit, + FilterMethod::default(), + Rotation::default(), + 1.0, + 1.0, + ); } } From 8d7bcab258ba61dc8184d85b63a0e689aefd085c 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, 17 Apr 2026 00:45:33 +0200 Subject: [PATCH 554/556] fix(list_column): add back `divider_padding` Also matches previous behavior of both paddings being applied to subsequent items, rather than globally. --- src/widget/list/list_column.rs | 101 ++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 38 deletions(-) diff --git a/src/widget/list/list_column.rs b/src/widget/list/list_column.rs index 89a87063..4ef3fc01 100644 --- a/src/widget/list/list_column.rs +++ b/src/widget/list/list_column.rs @@ -64,11 +64,19 @@ impl<'a, Message> IntoListItem<'a, Message> for ListButton<'a, Message> { } } +// Snapshots the padding values at the moment an item is added +struct ListEntry<'a, Message> { + item: ListItem<'a, Message>, + item_padding: Padding, + divider_padding: u16, +} + #[must_use] pub struct ListColumn<'a, Message> { list_item_padding: Padding, + divider_padding: u16, style: theme::Container<'a>, - children: Vec>, + children: Vec>, } #[inline] @@ -83,6 +91,7 @@ pub fn with_capacity<'a, Message: 'static>(capacity: usize) -> ListColumn<'a, Me ListColumn { list_item_padding: [space_xxs, space_m].into(), + divider_padding: 0, style: theme::Container::List, children: Vec::with_capacity(capacity), } @@ -100,10 +109,14 @@ impl<'a, Message: Clone + 'static> ListColumn<'a, Message> { Self::default() } - /// Adds an element to the list column. + /// Adds a [`ListItem`] to the [`ListColumn`]. #[allow(clippy::should_implement_trait)] pub fn add(mut self, item: impl IntoListItem<'a, Message>) -> Self { - self.children.push(item.into_list_item()); + self.children.push(ListEntry { + item: item.into_list_item(), + item_padding: self.list_item_padding, + divider_padding: self.divider_padding, + }); self } @@ -119,53 +132,65 @@ impl<'a, Message: Clone + 'static> ListColumn<'a, Message> { self } + #[inline] + pub fn divider_padding(mut self, padding: u16) -> Self { + self.divider_padding = padding; + self + } + #[must_use] pub fn into_element(self) -> Element<'a, Message> { - let padding = self.list_item_padding; let count = self.children.len(); let last_index = count.saturating_sub(1); let radius_s = theme::active().cosmic().radius_s(); + let mut col = column::with_capacity((2 * count).saturating_sub(1)); // Ensure minimum height of 32 let content_row = |content| { row![container(content), vertical().height(32)].align_y(iced::Alignment::Center) }; - self.children - .into_iter() - .enumerate() - .fold( - column::with_capacity((2 * count).saturating_sub(1)), - |mut col, (i, item)| { - if i > 0 { - col = col.push(divider::horizontal::default()); - } + for ( + i, + ListEntry { + item, + item_padding, + divider_padding, + }, + ) in self.children.into_iter().enumerate() + { + if i > 0 { + col = col + .push(container(divider::horizontal::default()).padding([0, divider_padding])); + } - match item { - ListItem::Element(content) => { - col.push(content_row(content).padding(padding).width(Length::Fill)) - } - ListItem::Button(ListButton { - content, - on_press, - selected, - }) => col.push( - content_row(content) - .apply(button::custom) - .padding(padding) - .width(Length::Fill) - .on_press_maybe(on_press) - .selected(selected) - .class(theme::Button::ListItem(get_radius( - radius_s, - i == 0, - i == last_index, - ))), - ), - } - }, - ) - .width(Length::Fill) + col = match item { + ListItem::Element(content) => col.push( + content_row(content) + .padding(item_padding) + .width(Length::Fill), + ), + ListItem::Button(ListButton { + content, + on_press, + selected, + }) => col.push( + content_row(content) + .apply(button::custom) + .padding(item_padding) + .width(Length::Fill) + .on_press_maybe(on_press) + .selected(selected) + .class(theme::Button::ListItem(get_radius( + radius_s, + i == 0, + i == last_index, + ))), + ), + }; + } + + col.width(Length::Fill) .apply(container) .class(self.style) .into() From c423ad1bfc25057922406c687f2ddc75ead5ab67 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, 17 Apr 2026 13:19:23 +0200 Subject: [PATCH 555/556] improv(about): use `ListButton` --- src/widget/about.rs | 25 +++++++++++++++---------- src/widget/settings/section.rs | 11 ++++++++--- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/widget/about.rs b/src/widget/about.rs index 148af02a..9b21e93a 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -1,8 +1,9 @@ use crate::{ Apply, Element, fl, iced::{Alignment, Length}, - widget::{self, space}, + widget::{self, list}, }; +use std::rc::Rc; #[derive(Debug, Default, Clone, derive_setters::Setters)] #[setters(into, strip_option)] @@ -104,19 +105,23 @@ 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::with_capacity(3) - .push(widget::text(name)) - .push(space::horizontal()) + let svg_accent = Rc::new(|theme: &crate::Theme| widget::svg::Style { + color: Some(theme.cosmic().accent_text_color().into()), + }); + + let section_button = |name: &'a str, url: &'a str| -> list::ListButton<'a, Message> { + widget::row::with_capacity(2) + .push(widget::text::body(name).width(Length::Fill)) .push_maybe( - (!url.is_empty()).then_some(crate::widget::icon::from_name("link-symbolic").icon()), + (!url.is_empty()).then_some( + widget::icon::from_name("link-symbolic") + .icon() + .class(crate::theme::Svg::Custom(svg_accent.clone())), + ), ) .align_y(Alignment::Center) - .apply(widget::button::custom) - .class(crate::theme::Button::Link) + .apply(list::button) .on_press(on_url_press(url)) - .width(Length::Fill) - .into() }; let section = |list: &'a Vec<(String, String)>, title: String| { diff --git a/src/widget/settings/section.rs b/src/widget/settings/section.rs index ee07c76d..3dddb1a1 100644 --- a/src/widget/settings/section.rs +++ b/src/widget/settings/section.rs @@ -3,7 +3,7 @@ use crate::Element; use crate::widget::list_column::IntoListItem; -use crate::widget::{ListColumn, column, text}; +use crate::widget::{ListColumn, column, list_column, text}; use std::borrow::Cow; /// A section within a settings view column. @@ -11,6 +11,11 @@ pub fn section<'a, Message: Clone + 'static>() -> Section<'a, Message> { with_column(ListColumn::default()) } +/// A section with a pre-defined list column of a given capacity. +pub fn with_capacity<'a, Message: Clone + 'static>(capacity: usize) -> Section<'a, Message> { + with_column(list_column::with_capacity(capacity)) +} + /// A section with a pre-defined list column. pub fn with_column( children: ListColumn<'_, Message>, @@ -47,7 +52,7 @@ impl<'a, Message: Clone + 'static> Section<'a, Message> { } /// Add a child element to the section's list column, if `Some`. - pub fn add_maybe(self, item: Option>>) -> Self { + pub fn add_maybe(self, item: Option>) -> Self { if let Some(item) = item { self.add(item) } else { @@ -58,7 +63,7 @@ impl<'a, Message: Clone + 'static> Section<'a, Message> { /// Extends the [`Section`] with the given children. pub fn extend( self, - children: impl IntoIterator>>, + children: impl IntoIterator>, ) -> Self { children.into_iter().fold(self, Self::add) } From 95756b1a576cf6dc9f6135cf1c66e1283bfc487f 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: Sat, 18 Apr 2026 16:33:57 +0200 Subject: [PATCH 556/556] improv(circular): prevent caps from touching --- examples/application/Cargo.toml | 1 + examples/application/src/main.rs | 3 +- src/widget/progress_bar/circular.rs | 57 +++++++++++++++++------------ 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index c494238f..7a6083e0 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -21,4 +21,5 @@ features = [ "single-instance", "surface-message", "multi-window", + "wgpu", ] diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index bceece6e..f6e571e0 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -200,7 +200,7 @@ impl cosmic::Application for App { .map_or("No page selected", String::as_str); let centered = widget::container( - widget::column::with_capacity(5) + widget::column::with_capacity(14) .push(widget::text::body(page_content)) .push( widget::text_input::text_input("", &self.input_1) @@ -223,6 +223,7 @@ impl cosmic::Application for App { .on_clear(Message::Ignore), ) .push(widget::progress_bar::circular::Circular::new().size(50.0)) + .push(widget::progress_bar::circular::Circular::new().size(20.0)) .push( widget::progress_bar::linear::Linear::new() .girth(10.0) diff --git a/src/widget/progress_bar/circular.rs b/src/widget/progress_bar/circular.rs index 7e8177d6..fa8c38fe 100644 --- a/src/widget/progress_bar/circular.rs +++ b/src/widget/progress_bar/circular.rs @@ -15,8 +15,6 @@ use std::f32::consts::PI; use std::time::Duration; const MIN_ANGLE: Radians = Radians(PI / 8.0); -const WRAP_ANGLE: Radians = Radians(2.0 * PI - PI / 4.0); -const BASE_ROTATION_SPEED: u32 = u32::MAX / 80; #[must_use] pub struct Circular @@ -83,6 +81,12 @@ where self.progress = Some(progress.clamp(0.0, 1.0)); self } + + fn min_wrap_angle(&self, track_radius: f32) -> (f32, f32) { + let cap_angle = self.bar_height / track_radius; + let gap = MIN_ANGLE.0.max(cap_angle); + (gap - cap_angle, 2.0 * PI - gap * 2.0) + } } impl Default for Circular @@ -122,7 +126,7 @@ impl Default for Animation { } impl Animation { - fn next(&self, additional_rotation: u32, now: Instant) -> Self { + fn next(&self, additional_rotation: u32, wrap_angle: f32, now: Instant) -> Self { match self { Self::Expanding { rotation, .. } => Self::Contracting { start: now, @@ -133,9 +137,9 @@ impl Animation { Self::Contracting { rotation, .. } => Self::Expanding { start: now, progress: 0.0, - rotation: rotation.wrapping_add(BASE_ROTATION_SPEED.wrapping_add( - (f64::from(WRAP_ANGLE / (2.0 * Radians::PI)) * f64::from(u32::MAX)) as u32, - )), + rotation: rotation.wrapping_add( + (f64::from((wrap_angle) / (2.0 * PI)) * f64::from(u32::MAX)) as u32, + ), last: now, }, } @@ -157,6 +161,7 @@ impl Animation { &self, cycle_duration: Duration, rotation_duration: Duration, + wrap_angle: f32, now: Instant, ) -> Self { let elapsed = now.duration_since(self.start()); @@ -165,7 +170,7 @@ impl Animation { * (u32::MAX) as f32) as u32; match elapsed { - elapsed if elapsed > cycle_duration => self.next(additional_rotation, now), + elapsed if elapsed > cycle_duration => self.next(additional_rotation, wrap_angle, now), _ => self.with_elapsed(cycle_duration, additional_rotation, elapsed, now), } } @@ -267,10 +272,13 @@ where return; } if let Event::Window(window::Event::RedrawRequested(now)) = event { - state.animation = - state - .animation - .timed_transition(self.cycle_duration, self.rotation_duration, *now); + let (_, wrap_angle) = self.min_wrap_angle(self.size / 2.0 - self.bar_height); + state.animation = state.animation.timed_transition( + self.cycle_duration, + self.rotation_duration, + wrap_angle, + *now, + ); state.cache.clear(); shell.request_redraw(); @@ -380,22 +388,23 @@ where } else { let mut builder = canvas::path::Builder::new(); - let start = Radians(state.animation.rotation() * 2.0 * PI); + let start = state.animation.rotation() * 2.0 * PI; + let (min_angle, wrap_angle) = self.min_wrap_angle(track_radius); let (start_angle, end_angle) = match state.animation { Animation::Expanding { progress, .. } => ( start, - start + MIN_ANGLE + WRAP_ANGLE * (smootherstep(progress)), + start + min_angle + wrap_angle * smootherstep(progress), ), Animation::Contracting { progress, .. } => ( - start + WRAP_ANGLE * (smootherstep(progress)), - start + MIN_ANGLE + WRAP_ANGLE, + start + wrap_angle * smootherstep(progress), + start + min_angle + wrap_angle, ), }; builder.arc(canvas::path::Arc { center: frame.center(), radius: track_radius, - start_angle, - end_angle, + start_angle: Radians(start_angle), + end_angle: Radians(end_angle), }); let bar_path = builder.build(); @@ -410,23 +419,23 @@ where let mut builder = canvas::path::Builder::new(); // get center of end of arc for rounded cap - let end_center = frame.center() - + Vector::new(end_angle.0.cos(), end_angle.0.sin()) * track_radius; + let end_center = + frame.center() + Vector::new(end_angle.cos(), end_angle.sin()) * track_radius; builder.arc(canvas::path::Arc { center: end_center, radius: self.bar_height / 2.0, - start_angle: Radians(end_angle.0), - end_angle: Radians(end_angle.0 + PI), + start_angle: Radians(end_angle), + end_angle: Radians(end_angle + PI), }); // get center of start of arc for rounded cap let start_center = frame.center() - + Vector::new(start_angle.0.cos(), start_angle.0.sin()) * track_radius; + + Vector::new(start_angle.cos(), start_angle.sin()) * track_radius; builder.arc(canvas::path::Arc { center: start_center, radius: self.bar_height / 2.0, - start_angle: Radians(start_angle.0 - PI), - end_angle: Radians(start_angle.0), + start_angle: Radians(start_angle - PI), + end_angle: Radians(start_angle), }); let cap_path = builder.build();