diff --git a/Cargo.toml b/Cargo.toml index 64172899..d73da2dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,6 @@ [package] -# Yoda fork: hard-renamed. Every consumer (leyoda/cosmic-files fork + each -# leyoda/cosmic-* app) depends directly on `libcosmic-yoda` by path, bypassing -# pop-os/libcosmic entirely. No [patch] shenanigans needed — transitive deps -# that used to ask for `libcosmic` are replaced by deps on our forks that ask -# for `libcosmic-yoda`. -name = "libcosmic-yoda" -version = "0.1.0-yoda.2" +name = "libcosmic" +version = "1.0.0" edition = "2024" rust-version = "1.90" @@ -14,10 +9,12 @@ name = "cosmic" [features] default = [ + "winit", "tokio", "a11y", "dbus-config", - "wayland", + "x11", + "iced-wayland", "multi-window", ] advanced-shaping = ["iced/advanced-shaping"] @@ -38,6 +35,7 @@ animated-image = [ autosize = [] applet = [ "autosize", + "winit", "wayland", "tokio", "cosmic-panel-config", @@ -83,34 +81,32 @@ tokio = [ "cosmic-config/tokio", ] # Tokio async runtime -# Wayland window support (yoda fork is Wayland-only; always active in default). -# We still need iced/winit because pop-os/iced hosts the runtime dispatcher -# (`iced_winit as shell`) there — the name is a misnomer, it's the same crate -# that provides both the winit path AND the sctk/cctk wayland path. -wayland = [ +# Wayland window support +iced-wayland = [ "ashpd?/wayland", "autosize", - "iced/winit", "iced/wayland", "iced_winit/wayland", + "surface-message", +] +wayland = [ + "iced-wayland", "iced_runtime/cctk", "iced_winit/cctk", "iced_wgpu/cctk", "iced/cctk", - "dep:iced_winit", "dep:cctk", - "surface-message", ] surface-message = [] # multi-window support multi-window = [] # Render with wgpu wgpu = ["iced/wgpu", "iced_wgpu"] -# Compat stubs — kept empty so upstream deps (cosmic-files, cosmic-text, …) -# that still ask for `winit` / `x11` features resolve cleanly against the -# yoda fork. Activating them has no effect: no code is gated on these. -winit = [] -x11 = [] +# X11 window support via winit +winit = ["iced/winit", "iced_winit"] +winit_debug = ["winit", "debug"] +winit_tokio = ["winit", "tokio"] +winit_wgpu = ["winit", "wgpu"] # Enables XDG portal integrations xdg-portal = ["ashpd"] qr_code = ["iced/qr_code"] @@ -123,6 +119,7 @@ async-std = [ "zbus?/async-io", "iced/async-std", ] +x11 = ["iced/x11", "iced_winit/x11"] [dependencies] apply = "0.3.0" @@ -176,7 +173,7 @@ float-cmp = "0.10.0" # Enable DBus feature on Linux targets [target.'cfg(target_os = "linux")'.dependencies] cosmic-config = { path = "cosmic-config", features = ["dbus"] } -cosmic-settings-daemon = { path = "../dbus-settings-bindings/cosmic-settings-daemon" } +cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" } zbus = { version = "5.14.0", default-features = false } [target.'cfg(all(unix, not(target_os = "macos")))'.dependencies] @@ -226,17 +223,10 @@ optional = true [dependencies.iced_tiny_skia] path = "./iced/tiny_skia" -# Yoda: drop the x11 default → softbuffer no longer pulls tiny-xlib/x11-dl/etc. -default-features = false -features = ["wayland"] [dependencies.iced_winit] path = "./iced/winit" optional = true -# Yoda: drop the x11 default → winit won't pull winit-x11/tiny-xlib/x11-dl. -# Keep wayland + wayland-dlopen (default behaviour minus x11). -default-features = false -features = ["wayland", "wayland-dlopen"] [dependencies.iced_wgpu] path = "./iced/wgpu" @@ -256,21 +246,12 @@ members = [ "cosmic-config", "cosmic-config-derive", "cosmic-theme", + "examples/*", ] -# examples/* excluded — many depend on the removed winit/x11 features. -# They will be revisited and adapted in a later phase. -exclude = ["iced", "examples"] +exclude = ["iced"] [workspace.dependencies] dirs = "6.0.0" -# Speed up snapshot diffing in cosmic-theme tests. Cargo silently ignores -# [profile.*] blocks in non-root manifests, so this lives at the -# workspace root. -[profile.dev.package.insta] -opt-level = 3 -[profile.dev.package.similar] -opt-level = 3 - [dev-dependencies] tempfile = "3.27.0" diff --git a/cosmic-config/Cargo.toml b/cosmic-config/Cargo.toml index 2a626f6b..0a7653e0 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -10,7 +10,7 @@ macro = ["cosmic-config-derive"] subscription = ["iced_futures"] [dependencies] -cosmic-settings-daemon = { path = "../../dbus-settings-bindings/cosmic-settings-daemon", optional = true } +cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } zbus = { version = "5.14.0", default-features = false, optional = true } atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" } calloop = { version = "0.14.4", optional = true } diff --git a/cosmic-config/src/subscription.rs b/cosmic-config/src/subscription.rs index 8246a12d..d16b9b65 100644 --- a/cosmic-config/src/subscription.rs +++ b/cosmic-config/src/subscription.rs @@ -1,4 +1,4 @@ -use iced_futures::futures::SinkExt; +use iced_futures::futures::{SinkExt, Stream}; use iced_futures::{futures::channel::mpsc, stream}; use notify::RecommendedWatcher; use std::{borrow::Cow, hash::Hash}; diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index 9ee641e8..7e408d8d 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -34,3 +34,6 @@ thiserror = "2.0.18" [dev-dependencies] insta = "1.47.2" +[profile.dev.package] +insta.opt-level = 3 +similar.opt-level = 3 diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 36480f9f..5db0f32c 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -953,12 +953,6 @@ impl ThemeBuilder { } #[allow(clippy::too_many_lines)] - // The component_hovered/pressed_overlay vars are seeded once near the - // top of this fn and then reassigned inside each container block - // (primary, secondary, …) before being read again. The initial seed - // is therefore overwritten before any read, which is what the - // unused_assignments lint flags below. - #[allow(unused_assignments)] /// build the theme pub fn build(self) -> Theme { let Self { diff --git a/cosmic-theme/src/output/gtk4_output.rs b/cosmic-theme/src/output/gtk4_output.rs index bbb4f246..40eba5b4 100644 --- a/cosmic-theme/src/output/gtk4_output.rs +++ b/cosmic-theme/src/output/gtk4_output.rs @@ -1,12 +1,10 @@ use crate::{Component, Theme, composite::over, steps::steps}; -use configparser::ini::Ini; use palette::{Darken, IntoColor, Lighten, Srgba, WithAlpha, rgb::Rgba}; use std::{ fs::{self, File}, io::{self, Write}, num::NonZeroUsize, path::Path, - process::Command, }; use super::{OutputError, to_rgba}; @@ -219,50 +217,6 @@ impl Theme { Ok(()) } - /// Apply the preferred GTK client-side decoration button layout. - /// - /// This writes the GTK 3/4 `settings.ini` value used by GTK header bars and - /// also best-effort updates GNOME's `button-layout` GSettings key for apps - /// that still consult it. - /// - /// # Errors - /// - /// Returns an `OutputError` if the GTK settings files cannot be written. - #[cold] - pub fn apply_gtk_decoration_layout(buttons_at_start: bool) -> Result<(), OutputError> { - let Some(config_dir) = dirs::config_dir() else { - return Err(OutputError::MissingConfigDir); - }; - - let layout = if buttons_at_start { - "close,minimize,maximize:" - } else { - ":minimize,maximize,close" - }; - - for gtk_version in ["gtk-3.0", "gtk-4.0"] { - let gtk_dir = config_dir.join(gtk_version); - fs::create_dir_all(>k_dir).map_err(OutputError::Io)?; - Self::write_gtk_settings_key( - >k_dir.join("settings.ini"), - "gtk-decoration-layout", - layout, - )?; - } - - // best-effort: gsettings is absent on non-GNOME systems - let _ = Command::new("gsettings") - .args([ - "set", - "org.gnome.desktop.wm.preferences", - "button-layout", - layout, - ]) - .status(); - - Ok(()) - } - /// Reset the applied gtk css /// /// # Errors @@ -302,20 +256,6 @@ impl Theme { Ok(()) } - #[cold] - fn write_gtk_settings_key(path: &Path, key: &str, value: &str) -> Result<(), OutputError> { - let mut ini = Ini::new_cs(); - - if path.exists() { - let file_content = fs::read_to_string(path).map_err(OutputError::Io)?; - ini.read(file_content).map_err(OutputError::Ini)?; - } - - ini.setstr("Settings", key, Some(value)); - ini.pretty_write(path, &super::qt_settings_ini_style()) - .map_err(OutputError::Io) - } - fn is_cosmic_css(path: &Path, cosmic_css: &Path) -> io::Result> { if !path.exists() { return Ok(None); diff --git a/cosmic-theme/src/output/vs_code.rs b/cosmic-theme/src/output/vs_code.rs index f49c888f..43c36bb6 100644 --- a/cosmic-theme/src/output/vs_code.rs +++ b/cosmic-theme/src/output/vs_code.rs @@ -266,14 +266,6 @@ impl From for VsTheme { } impl Theme { - /// Write this theme to VS Code's `settings.json` as a - /// `workbench.colorCustomizations` entry, and enable - /// `window.autoDetectColorScheme` so VS Code follows the system theme. - /// - /// # Errors - /// - /// Returns an `OutputError` if the user config dir is missing, the - /// settings file cannot be read/written, or its JSON is invalid. #[cold] pub fn apply_vs_code(self) -> Result<(), OutputError> { let vs_theme = VsTheme::from(self); @@ -299,13 +291,6 @@ impl Theme { Ok(()) } - /// Remove the `workbench.colorCustomizations` entry previously written - /// by [`Theme::apply_vs_code`] from VS Code's `settings.json`. - /// - /// # Errors - /// - /// Returns an `OutputError` if the user config dir is missing, the - /// settings file cannot be read/written, or its JSON is invalid. #[cold] pub fn reset_vs_code() -> Result<(), OutputError> { let mut config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?; diff --git a/examples/about/Cargo.toml b/examples/about/Cargo.toml index b27b5134..f980811c 100644 --- a/examples/about/Cargo.toml +++ b/examples/about/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] open = "5.3.3" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = [ "debug", diff --git a/examples/applet/Cargo.toml b/examples/applet/Cargo.toml index 265fbe7f..13eff684 100644 --- a/examples/applet/Cargo.toml +++ b/examples/applet/Cargo.toml @@ -12,7 +12,7 @@ tracing = "0.1" env_logger = "0.10.2" log = "0.4.29" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" default-features = false features = ["applet-token"] diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index d4c75171..7a6083e0 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -5,12 +5,12 @@ edition = "2021" [features] default = ["wayland"] -wayland = ["libcosmic-yoda/wayland"] +wayland = ["libcosmic/wayland"] [dependencies] env_logger = "0.11" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = [ "debug", diff --git a/examples/calendar/Cargo.toml b/examples/calendar/Cargo.toml index 203f7c17..b7286825 100644 --- a/examples/calendar/Cargo.toml +++ b/examples/calendar/Cargo.toml @@ -8,6 +8,6 @@ edition = "2024" [dependencies] jiff = "0.2" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/context-menu/Cargo.toml b/examples/context-menu/Cargo.toml index 4c1eed60..39c550f4 100644 --- a/examples/context-menu/Cargo.toml +++ b/examples/context-menu/Cargo.toml @@ -8,7 +8,7 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = [ "debug", diff --git a/examples/cosmic/Cargo.toml b/examples/cosmic/Cargo.toml index eebf6c31..8c2a3126 100644 --- a/examples/cosmic/Cargo.toml +++ b/examples/cosmic/Cargo.toml @@ -8,7 +8,7 @@ publish = false [dependencies] apply = "0.3.0" fraction = "0.15.3" -libcosmic-yoda = { path = "../..", features = [ +libcosmic = { path = "../..", features = [ "debug", "winit", "tokio", diff --git a/examples/image-button/Cargo.toml b/examples/image-button/Cargo.toml index 8bc521f7..c219a53b 100644 --- a/examples/image-button/Cargo.toml +++ b/examples/image-button/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" tracing = "0.1.44" tracing-subscriber = "0.3.22" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = ["debug", "winit", "wgpu", "tokio"] diff --git a/examples/menu/Cargo.toml b/examples/menu/Cargo.toml index 047055e5..430b26ea 100644 --- a/examples/menu/Cargo.toml +++ b/examples/menu/Cargo.toml @@ -8,6 +8,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/multi-window/Cargo.toml b/examples/multi-window/Cargo.toml index c38595fa..0b5440f8 100644 --- a/examples/multi-window/Cargo.toml +++ b/examples/multi-window/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -libcosmic-yoda = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "wgpu", "wayland"] } +libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "wgpu", "wayland"] } diff --git a/examples/nav-context/Cargo.toml b/examples/nav-context/Cargo.toml index ea2bc2b8..d829df0f 100644 --- a/examples/nav-context/Cargo.toml +++ b/examples/nav-context/Cargo.toml @@ -8,6 +8,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] diff --git a/examples/open-dialog/Cargo.toml b/examples/open-dialog/Cargo.toml index b09b98ca..94049270 100644 --- a/examples/open-dialog/Cargo.toml +++ b/examples/open-dialog/Cargo.toml @@ -5,8 +5,8 @@ edition = "2021" [features] default = ["xdg-portal"] -rfd = ["libcosmic-yoda/rfd"] -xdg-portal = ["libcosmic-yoda/xdg-portal"] +rfd = ["libcosmic/rfd"] +xdg-portal = ["libcosmic/xdg-portal"] [dependencies] apply = "0.3.0" @@ -15,6 +15,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" url = "2.5.8" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] features = ["debug", "winit", "wgpu", "wayland", "tokio"] path = "../../" diff --git a/examples/spin-button/Cargo.toml b/examples/spin-button/Cargo.toml index 082c0fd5..a522050b 100644 --- a/examples/spin-button/Cargo.toml +++ b/examples/spin-button/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] fraction = "0.15.3" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] features = ["debug", "wgpu", "winit", "desktop", "tokio"] path = "../.." default-features = false diff --git a/examples/subscriptions/Cargo.toml b/examples/subscriptions/Cargo.toml index ae31a395..8eb69ff3 100644 --- a/examples/subscriptions/Cargo.toml +++ b/examples/subscriptions/Cargo.toml @@ -5,6 +5,6 @@ edition = "2024" [dependencies] -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"] diff --git a/examples/table-view/Cargo.toml b/examples/table-view/Cargo.toml index 8f71e5b4..8ed45928 100644 --- a/examples/table-view/Cargo.toml +++ b/examples/table-view/Cargo.toml @@ -9,6 +9,6 @@ tracing-subscriber = "0.3.22" tracing-log = "0.2.0" chrono = "*" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] features = ["debug", "wgpu", "winit", "desktop", "tokio"] path = "../.." diff --git a/examples/text-input/Cargo.toml b/examples/text-input/Cargo.toml index 69bd2a19..fe6105c2 100644 --- a/examples/text-input/Cargo.toml +++ b/examples/text-input/Cargo.toml @@ -8,6 +8,6 @@ tracing = "0.1.44" tracing-subscriber = "0.3.22" tracing-log = "0.2.0" -[dependencies.libcosmic-yoda] +[dependencies.libcosmic] path = "../../" features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"] diff --git a/i18n/af/libcosmic_yoda.ftl b/i18n/af/libcosmic.ftl similarity index 100% rename from i18n/af/libcosmic_yoda.ftl rename to i18n/af/libcosmic.ftl diff --git a/i18n/ar/libcosmic_yoda.ftl b/i18n/ar/libcosmic.ftl similarity index 100% rename from i18n/ar/libcosmic_yoda.ftl rename to i18n/ar/libcosmic.ftl diff --git a/i18n/be/libcosmic_yoda.ftl b/i18n/be/libcosmic.ftl similarity index 100% rename from i18n/be/libcosmic_yoda.ftl rename to i18n/be/libcosmic.ftl diff --git a/i18n/bg/libcosmic_yoda.ftl b/i18n/bg/libcosmic.ftl similarity index 100% rename from i18n/bg/libcosmic_yoda.ftl rename to i18n/bg/libcosmic.ftl diff --git a/i18n/bn/libcosmic_yoda.ftl b/i18n/bn/libcosmic.ftl similarity index 100% rename from i18n/bn/libcosmic_yoda.ftl rename to i18n/bn/libcosmic.ftl diff --git a/i18n/ca/libcosmic_yoda.ftl b/i18n/ca/libcosmic.ftl similarity index 100% rename from i18n/ca/libcosmic_yoda.ftl rename to i18n/ca/libcosmic.ftl diff --git a/i18n/cs/libcosmic_yoda.ftl b/i18n/cs/libcosmic.ftl similarity index 100% rename from i18n/cs/libcosmic_yoda.ftl rename to i18n/cs/libcosmic.ftl diff --git a/i18n/da/libcosmic_yoda.ftl b/i18n/da/libcosmic.ftl similarity index 100% rename from i18n/da/libcosmic_yoda.ftl rename to i18n/da/libcosmic.ftl diff --git a/i18n/de/libcosmic_yoda.ftl b/i18n/de/libcosmic.ftl similarity index 100% rename from i18n/de/libcosmic_yoda.ftl rename to i18n/de/libcosmic.ftl diff --git a/i18n/el/libcosmic_yoda.ftl b/i18n/el/libcosmic.ftl similarity index 100% rename from i18n/el/libcosmic_yoda.ftl rename to i18n/el/libcosmic.ftl diff --git a/i18n/en-GB/libcosmic_yoda.ftl b/i18n/en-GB/libcosmic.ftl similarity index 100% rename from i18n/en-GB/libcosmic_yoda.ftl rename to i18n/en-GB/libcosmic.ftl diff --git a/i18n/en/libcosmic_yoda.ftl b/i18n/en/libcosmic.ftl similarity index 100% rename from i18n/en/libcosmic_yoda.ftl rename to i18n/en/libcosmic.ftl diff --git a/i18n/eo/libcosmic_yoda.ftl b/i18n/eo/libcosmic.ftl similarity index 100% rename from i18n/eo/libcosmic_yoda.ftl rename to i18n/eo/libcosmic.ftl diff --git a/i18n/es-419/libcosmic_yoda.ftl b/i18n/es-419/libcosmic.ftl similarity index 100% rename from i18n/es-419/libcosmic_yoda.ftl rename to i18n/es-419/libcosmic.ftl diff --git a/i18n/es-MX/libcosmic_yoda.ftl b/i18n/es-MX/libcosmic.ftl similarity index 100% rename from i18n/es-MX/libcosmic_yoda.ftl rename to i18n/es-MX/libcosmic.ftl diff --git a/i18n/es/libcosmic_yoda.ftl b/i18n/es/libcosmic.ftl similarity index 100% rename from i18n/es/libcosmic_yoda.ftl rename to i18n/es/libcosmic.ftl diff --git a/i18n/et/libcosmic_yoda.ftl b/i18n/et/libcosmic.ftl similarity index 100% rename from i18n/et/libcosmic_yoda.ftl rename to i18n/et/libcosmic.ftl diff --git a/i18n/eu/libcosmic_yoda.ftl b/i18n/eu/libcosmic.ftl similarity index 100% rename from i18n/eu/libcosmic_yoda.ftl rename to i18n/eu/libcosmic.ftl diff --git a/i18n/fa/libcosmic_yoda.ftl b/i18n/fa/libcosmic.ftl similarity index 100% rename from i18n/fa/libcosmic_yoda.ftl rename to i18n/fa/libcosmic.ftl diff --git a/i18n/fi/libcosmic_yoda.ftl b/i18n/fi/libcosmic.ftl similarity index 100% rename from i18n/fi/libcosmic_yoda.ftl rename to i18n/fi/libcosmic.ftl diff --git a/i18n/fr/libcosmic_yoda.ftl b/i18n/fr/libcosmic.ftl similarity index 100% rename from i18n/fr/libcosmic_yoda.ftl rename to i18n/fr/libcosmic.ftl diff --git a/i18n/fy/libcosmic_yoda.ftl b/i18n/fy/libcosmic.ftl similarity index 100% rename from i18n/fy/libcosmic_yoda.ftl rename to i18n/fy/libcosmic.ftl diff --git a/i18n/ga/libcosmic_yoda.ftl b/i18n/ga/libcosmic.ftl similarity index 100% rename from i18n/ga/libcosmic_yoda.ftl rename to i18n/ga/libcosmic.ftl diff --git a/i18n/gd/libcosmic_yoda.ftl b/i18n/gd/libcosmic.ftl similarity index 100% rename from i18n/gd/libcosmic_yoda.ftl rename to i18n/gd/libcosmic.ftl diff --git a/i18n/gu/libcosmic_yoda.ftl b/i18n/gu/libcosmic.ftl similarity index 100% rename from i18n/gu/libcosmic_yoda.ftl rename to i18n/gu/libcosmic.ftl diff --git a/i18n/he/libcosmic_yoda.ftl b/i18n/he/libcosmic.ftl similarity index 100% rename from i18n/he/libcosmic_yoda.ftl rename to i18n/he/libcosmic.ftl diff --git a/i18n/hi/libcosmic_yoda.ftl b/i18n/hi/libcosmic.ftl similarity index 100% rename from i18n/hi/libcosmic_yoda.ftl rename to i18n/hi/libcosmic.ftl diff --git a/i18n/hr/libcosmic_yoda.ftl b/i18n/hr/libcosmic.ftl similarity index 100% rename from i18n/hr/libcosmic_yoda.ftl rename to i18n/hr/libcosmic.ftl diff --git a/i18n/hu/libcosmic_yoda.ftl b/i18n/hu/libcosmic.ftl similarity index 100% rename from i18n/hu/libcosmic_yoda.ftl rename to i18n/hu/libcosmic.ftl diff --git a/i18n/id/libcosmic_yoda.ftl b/i18n/id/libcosmic.ftl similarity index 100% rename from i18n/id/libcosmic_yoda.ftl rename to i18n/id/libcosmic.ftl diff --git a/i18n/ie/libcosmic_yoda.ftl b/i18n/ie/libcosmic.ftl similarity index 100% rename from i18n/ie/libcosmic_yoda.ftl rename to i18n/ie/libcosmic.ftl diff --git a/i18n/is/libcosmic_yoda.ftl b/i18n/is/libcosmic.ftl similarity index 100% rename from i18n/is/libcosmic_yoda.ftl rename to i18n/is/libcosmic.ftl diff --git a/i18n/it/libcosmic_yoda.ftl b/i18n/it/libcosmic.ftl similarity index 100% rename from i18n/it/libcosmic_yoda.ftl rename to i18n/it/libcosmic.ftl diff --git a/i18n/ja/libcosmic_yoda.ftl b/i18n/ja/libcosmic.ftl similarity index 100% rename from i18n/ja/libcosmic_yoda.ftl rename to i18n/ja/libcosmic.ftl diff --git a/i18n/jv/libcosmic_yoda.ftl b/i18n/jv/libcosmic.ftl similarity index 100% rename from i18n/jv/libcosmic_yoda.ftl rename to i18n/jv/libcosmic.ftl diff --git a/i18n/ka/libcosmic_yoda.ftl b/i18n/ka/libcosmic.ftl similarity index 100% rename from i18n/ka/libcosmic_yoda.ftl rename to i18n/ka/libcosmic.ftl diff --git a/i18n/kab/libcosmic_yoda.ftl b/i18n/kab/libcosmic.ftl similarity index 100% rename from i18n/kab/libcosmic_yoda.ftl rename to i18n/kab/libcosmic.ftl diff --git a/i18n/kk/libcosmic_yoda.ftl b/i18n/kk/libcosmic.ftl similarity index 100% rename from i18n/kk/libcosmic_yoda.ftl rename to i18n/kk/libcosmic.ftl diff --git a/i18n/kmr/libcosmic_yoda.ftl b/i18n/kmr/libcosmic.ftl similarity index 100% rename from i18n/kmr/libcosmic_yoda.ftl rename to i18n/kmr/libcosmic.ftl diff --git a/i18n/kn/libcosmic_yoda.ftl b/i18n/kn/libcosmic.ftl similarity index 100% rename from i18n/kn/libcosmic_yoda.ftl rename to i18n/kn/libcosmic.ftl diff --git a/i18n/ko/libcosmic_yoda.ftl b/i18n/ko/libcosmic.ftl similarity index 100% rename from i18n/ko/libcosmic_yoda.ftl rename to i18n/ko/libcosmic.ftl diff --git a/i18n/li/libcosmic_yoda.ftl b/i18n/li/libcosmic.ftl similarity index 100% rename from i18n/li/libcosmic_yoda.ftl rename to i18n/li/libcosmic.ftl diff --git a/i18n/lt/libcosmic_yoda.ftl b/i18n/lt/libcosmic.ftl similarity index 100% rename from i18n/lt/libcosmic_yoda.ftl rename to i18n/lt/libcosmic.ftl diff --git a/i18n/ml/libcosmic_yoda.ftl b/i18n/ml/libcosmic.ftl similarity index 100% rename from i18n/ml/libcosmic_yoda.ftl rename to i18n/ml/libcosmic.ftl diff --git a/i18n/ms/libcosmic_yoda.ftl b/i18n/ms/libcosmic.ftl similarity index 100% rename from i18n/ms/libcosmic_yoda.ftl rename to i18n/ms/libcosmic.ftl diff --git a/i18n/nb-NO/libcosmic_yoda.ftl b/i18n/nb-NO/libcosmic.ftl similarity index 100% rename from i18n/nb-NO/libcosmic_yoda.ftl rename to i18n/nb-NO/libcosmic.ftl diff --git a/i18n/nl/libcosmic_yoda.ftl b/i18n/nl/libcosmic.ftl similarity index 100% rename from i18n/nl/libcosmic_yoda.ftl rename to i18n/nl/libcosmic.ftl diff --git a/i18n/nn/libcosmic_yoda.ftl b/i18n/nn/libcosmic.ftl similarity index 100% rename from i18n/nn/libcosmic_yoda.ftl rename to i18n/nn/libcosmic.ftl diff --git a/i18n/oc/libcosmic_yoda.ftl b/i18n/oc/libcosmic.ftl similarity index 100% rename from i18n/oc/libcosmic_yoda.ftl rename to i18n/oc/libcosmic.ftl diff --git a/i18n/pa/libcosmic_yoda.ftl b/i18n/pa/libcosmic.ftl similarity index 100% rename from i18n/pa/libcosmic_yoda.ftl rename to i18n/pa/libcosmic.ftl diff --git a/i18n/pl/libcosmic_yoda.ftl b/i18n/pl/libcosmic.ftl similarity index 100% rename from i18n/pl/libcosmic_yoda.ftl rename to i18n/pl/libcosmic.ftl diff --git a/i18n/pt-BR/libcosmic_yoda.ftl b/i18n/pt-BR/libcosmic.ftl similarity index 100% rename from i18n/pt-BR/libcosmic_yoda.ftl rename to i18n/pt-BR/libcosmic.ftl diff --git a/i18n/pt/libcosmic_yoda.ftl b/i18n/pt/libcosmic.ftl similarity index 100% rename from i18n/pt/libcosmic_yoda.ftl rename to i18n/pt/libcosmic.ftl diff --git a/i18n/ro/libcosmic_yoda.ftl b/i18n/ro/libcosmic.ftl similarity index 100% rename from i18n/ro/libcosmic_yoda.ftl rename to i18n/ro/libcosmic.ftl diff --git a/i18n/ru/libcosmic_yoda.ftl b/i18n/ru/libcosmic.ftl similarity index 100% rename from i18n/ru/libcosmic_yoda.ftl rename to i18n/ru/libcosmic.ftl diff --git a/i18n/sk/libcosmic_yoda.ftl b/i18n/sk/libcosmic.ftl similarity index 100% rename from i18n/sk/libcosmic_yoda.ftl rename to i18n/sk/libcosmic.ftl diff --git a/i18n/sl/libcosmic_yoda.ftl b/i18n/sl/libcosmic.ftl similarity index 100% rename from i18n/sl/libcosmic_yoda.ftl rename to i18n/sl/libcosmic.ftl diff --git a/i18n/sr-Cyrl/libcosmic_yoda.ftl b/i18n/sr-Cyrl/libcosmic.ftl similarity index 100% rename from i18n/sr-Cyrl/libcosmic_yoda.ftl rename to i18n/sr-Cyrl/libcosmic.ftl diff --git a/i18n/sr-Latn/libcosmic_yoda.ftl b/i18n/sr-Latn/libcosmic.ftl similarity index 100% rename from i18n/sr-Latn/libcosmic_yoda.ftl rename to i18n/sr-Latn/libcosmic.ftl diff --git a/i18n/sr/libcosmic_yoda.ftl b/i18n/sr/libcosmic.ftl similarity index 100% rename from i18n/sr/libcosmic_yoda.ftl rename to i18n/sr/libcosmic.ftl diff --git a/i18n/sv/libcosmic_yoda.ftl b/i18n/sv/libcosmic.ftl similarity index 100% rename from i18n/sv/libcosmic_yoda.ftl rename to i18n/sv/libcosmic.ftl diff --git a/i18n/ta/libcosmic_yoda.ftl b/i18n/ta/libcosmic.ftl similarity index 100% rename from i18n/ta/libcosmic_yoda.ftl rename to i18n/ta/libcosmic.ftl diff --git a/i18n/th/libcosmic_yoda.ftl b/i18n/th/libcosmic.ftl similarity index 100% rename from i18n/th/libcosmic_yoda.ftl rename to i18n/th/libcosmic.ftl diff --git a/i18n/ti/libcosmic_yoda.ftl b/i18n/ti/libcosmic.ftl similarity index 100% rename from i18n/ti/libcosmic_yoda.ftl rename to i18n/ti/libcosmic.ftl diff --git a/i18n/tr/libcosmic_yoda.ftl b/i18n/tr/libcosmic.ftl similarity index 100% rename from i18n/tr/libcosmic_yoda.ftl rename to i18n/tr/libcosmic.ftl diff --git a/i18n/uk/libcosmic_yoda.ftl b/i18n/uk/libcosmic.ftl similarity index 100% rename from i18n/uk/libcosmic_yoda.ftl rename to i18n/uk/libcosmic.ftl diff --git a/i18n/uz/libcosmic_yoda.ftl b/i18n/uz/libcosmic.ftl similarity index 100% rename from i18n/uz/libcosmic_yoda.ftl rename to i18n/uz/libcosmic.ftl diff --git a/i18n/vi/libcosmic_yoda.ftl b/i18n/vi/libcosmic.ftl similarity index 100% rename from i18n/vi/libcosmic_yoda.ftl rename to i18n/vi/libcosmic.ftl diff --git a/i18n/yue-Hant/libcosmic_yoda.ftl b/i18n/yue-Hant/libcosmic.ftl similarity index 100% rename from i18n/yue-Hant/libcosmic_yoda.ftl rename to i18n/yue-Hant/libcosmic.ftl diff --git a/i18n/zh-Hans/libcosmic_yoda.ftl b/i18n/zh-Hans/libcosmic.ftl similarity index 100% rename from i18n/zh-Hans/libcosmic_yoda.ftl rename to i18n/zh-Hans/libcosmic.ftl diff --git a/i18n/zh-Hant/libcosmic_yoda.ftl b/i18n/zh-Hant/libcosmic.ftl similarity index 100% rename from i18n/zh-Hant/libcosmic_yoda.ftl rename to i18n/zh-Hant/libcosmic.ftl diff --git a/iced b/iced index 81639935..78caabba 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 81639935398a856f3164dc406fbac78922c258fc +Subproject commit 78caabba7ef91cd1030da6f70b41d266704ffece diff --git a/src/action.rs b/src/action.rs index 19e228b8..b7162896 100644 --- a/src/action.rs +++ b/src/action.rs @@ -1,6 +1,7 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 +#[cfg(feature = "winit")] use crate::app; #[cfg(feature = "single-instance")] use crate::dbus_activation; @@ -8,6 +9,7 @@ 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) } @@ -21,6 +23,7 @@ pub const fn none() -> Action { pub enum Action { /// Messages from the application, for the application. App(M), + #[cfg(feature = "winit")] /// Internal messages to be handled by libcosmic. Cosmic(app::Action), #[cfg(feature = "single-instance")] diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 86af0992..030ed041 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -100,10 +100,9 @@ impl Cosmic where T::Message: Send + 'static, { - pub fn init((core, flags): (Core, T::Flags)) -> (Self, iced::Task>) { - #[cfg(all(feature = "dbus-config", target_os = "linux"))] - let mut core = core; - + pub fn init( + (mut core, flags): (Core, T::Flags), + ) -> (Self, iced::Task>) { #[cfg(all(feature = "dbus-config", target_os = "linux"))] { use iced_futures::futures::executor::block_on; @@ -365,6 +364,7 @@ where crate::surface::Action::Task(f) => { f().map(|sm| crate::Action::Cosmic(Action::Surface(sm))) } + _ => iced::Task::none(), } #[cfg(not(feature = "surface-message"))] @@ -408,7 +408,7 @@ where f64::from(self.app.core().scale_factor()) } - pub fn style(&self, _theme: &Theme) -> theme::Style { + pub fn style(&self, theme: &Theme) -> theme::Style { if let Some(style) = self.app.style() { style } else if self.app.core().window.is_maximized { @@ -480,11 +480,12 @@ where .into_iter() .filter(cosmic_config::Error::is_err) { - if let cosmic_config::Error::GetKey(_, err) = &why - && err.kind() == std::io::ErrorKind::NotFound { + 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"); } @@ -620,15 +621,15 @@ impl Cosmic { #[allow(clippy::too_many_lines)] fn cosmic_update(&mut self, message: Action) -> iced::Task> { match message { - Action::WindowMaximized(_id, _maximized) => { + Action::WindowMaximized(id, maximized) => { #[cfg(not(all(feature = "wayland", target_os = "linux")))] if self .app .core() .main_window_id() - .is_some_and(|main_id| main_id == _id) + .is_some_and(|main_id| main_id == id) { - self.app.core_mut().window.sharp_corners = _maximized; + self.app.core_mut().window.sharp_corners = maximized; } } diff --git a/src/app/mod.rs b/src/app/mod.rs index f53ee519..f78beac7 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -136,7 +136,7 @@ pub fn run(settings: Settings, flags: App::Flags) -> iced::Res crate::malloc::limit_mmap_threshold(threshold); } - let _default_font = settings.default_font; + let default_font = settings.default_font; let (settings, (mut core, flags), window_settings) = iced_settings::(settings, flags); #[cfg(not(feature = "multi-window"))] { @@ -284,7 +284,7 @@ where // app = app.window(window_settings); core.main_window = Some(iced_core::window::Id::RESERVED); } - let app = iced::daemon( + let mut app = iced::daemon( BootData(Rc::new(RefCell::new(Some(BootDataInner:: { flags, core, diff --git a/src/applet/column.rs b/src/applet/column.rs index bb16f085..9657b566 100644 --- a/src/applet/column.rs +++ b/src/applet/column.rs @@ -1,7 +1,7 @@ //! Distribute content vertically. use crate::iced; use iced::core::alignment::{self, Alignment}; -use iced::core::event::Event; +use iced::core::event::{self, Event}; use iced::core::layout; use iced::core::mouse; use iced::core::overlay; diff --git a/src/applet/mod.rs b/src/applet/mod.rs index d1ae6e87..48721e1c 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -10,7 +10,11 @@ use crate::{ widget::{ self, autosize::{self, Autosize, autosize}, + column::Column, layer_container, + row::Row, + space::horizontal, + space::vertical, }, }; @@ -213,6 +217,12 @@ impl Context { icon: widget::icon::Handle, ) -> crate::widget::Button<'a, Message> { let suggested = self.suggested_size(icon.symbolic); + let (applet_padding_major_axis, applet_padding_minor_axis) = self.suggested_padding(true); + let (horizontal_padding, vertical_padding) = if self.is_horizontal() { + (applet_padding_major_axis, applet_padding_minor_axis) + } else { + (applet_padding_minor_axis, applet_padding_major_axis) + }; let symbolic = icon.symbolic; let icon = widget::icon(icon) .class(if symbolic { @@ -455,8 +465,10 @@ impl Context { &self, content: impl Into>, ) -> Autosize<'a, Message, crate::Theme, crate::Renderer> { + let force_configured = matches!(&self.panel_type, PanelType::Other(n) if n.is_empty()); let w = autosize(content, AUTOSIZE_MAIN_ID.clone()); let mut limits = Limits::NONE; + let suggested_window_size = self.suggested_window_size(); if let Some(width) = self .suggested_bounds @@ -567,7 +579,7 @@ pub fn run(flags: App::Flags) -> iced::Result { // window_settings = window_settings.clone(); core.main_window = Some(iced_core::window::Id::RESERVED); } - let app = iced::daemon( + let mut app = iced::daemon( BootData(Rc::new(RefCell::new(Some(BootDataInner:: { flags, core, diff --git a/src/applet/row.rs b/src/applet/row.rs index 3ac9f7e0..a6745d1c 100644 --- a/src/applet/row.rs +++ b/src/applet/row.rs @@ -1,7 +1,7 @@ //! Distribute content horizontally. use crate::iced; use iced::core::alignment::{self, Alignment}; -use iced::core::event::Event; +use iced::core::event::{self, Event}; use iced::core::layout::{self, Layout}; use iced::core::mouse; use iced::core::overlay; @@ -10,6 +10,7 @@ use iced::core::widget::{Operation, Tree}; use iced::core::{ Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, widget, }; +use iced::touch; /// A container that distributes its contents horizontally. /// diff --git a/src/command.rs b/src/command.rs index c5c1c62b..1d6f635c 100644 --- a/src/command.rs +++ b/src/command.rs @@ -27,10 +27,12 @@ pub fn set_title(id: window::Id, title: String) -> iced::Task(factor: f32) -> iced::Task> { iced::Task::done(crate::app::Action::ScaleFactor(factor)).map(crate::Action::Cosmic) } +#[cfg(feature = "winit")] pub fn set_theme(theme: crate::Theme) -> iced::Task> { iced::Task::done(crate::app::Action::AppThemeChange(theme)).map(crate::Action::Cosmic) } diff --git a/src/config/mod.rs b/src/config/mod.rs index 08a4c0ca..9807961c 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -4,7 +4,6 @@ //! Configurations available to libcosmic applications. use crate::cosmic_theme::Density; -use crate::widget::WindowControlsPosition; use cosmic_config::cosmic_config_derive::CosmicConfigEntry; use cosmic_config::{Config, CosmicConfigEntry}; use serde::{Deserialize, Serialize}; @@ -23,11 +22,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 - && err.kind() == std::io::ErrorKind::NotFound { + 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 @@ -67,12 +67,6 @@ pub fn header_size() -> Density { COSMIC_TK.read().unwrap().header_size } -/// Position of the window control buttons (close / minimize / maximize). -#[allow(clippy::missing_panics_doc)] -pub fn window_controls_position() -> WindowControlsPosition { - COSMIC_TK.read().unwrap().window_controls_position -} - /// Interface density. #[allow(clippy::missing_panics_doc)] pub fn interface_density() -> Density { @@ -115,10 +109,6 @@ pub struct CosmicTk { /// Mono font family pub monospace_font: FontConfig, - - /// Side on which window control buttons (close / minimize / maximize) - /// are placed. `End` = right (Linux / GNOME), `Start` = left (macOS). - pub window_controls_position: WindowControlsPosition, } impl Default for CosmicTk { @@ -142,7 +132,6 @@ impl Default for CosmicTk { stretch: iced::font::Stretch::Normal, style: iced::font::Style::Normal, }, - window_controls_position: WindowControlsPosition::default(), } } } diff --git a/src/core.rs b/src/core.rs index f80954f9..970a5351 100644 --- a/src/core.rs +++ b/src/core.rs @@ -78,6 +78,8 @@ pub struct Core { pub(super) portal_accent: Option, + pub(super) portal_is_high_contrast: Option, + pub(super) title: HashMap, pub window: Window, @@ -153,6 +155,7 @@ impl Default for Core { settings_daemon: None, portal_is_dark: None, portal_accent: None, + portal_is_high_contrast: None, main_window: None, exit_on_main_window_closed: true, menu_bars: HashMap::new(), @@ -429,6 +432,7 @@ impl Core { id } + #[cfg(feature = "winit")] pub fn drag(&self, id: Option) -> crate::app::Task { let Some(id) = id.or(self.main_window) else { return iced::Task::none(); @@ -436,6 +440,7 @@ impl Core { crate::command::drag(id) } + #[cfg(feature = "winit")] pub fn maximize( &self, id: Option, @@ -447,6 +452,7 @@ impl Core { crate::command::maximize(id, maximized) } + #[cfg(feature = "winit")] pub fn minimize(&self, id: Option) -> crate::app::Task { let Some(id) = id.or(self.main_window) else { return iced::Task::none(); @@ -454,6 +460,7 @@ impl Core { crate::command::minimize(id) } + #[cfg(feature = "winit")] pub fn set_title( &self, id: Option, @@ -465,6 +472,7 @@ impl Core { crate::command::set_title(id, title) } + #[cfg(feature = "winit")] pub fn set_windowed(&self, id: Option) -> crate::app::Task { let Some(id) = id.or(self.main_window) else { return iced::Task::none(); @@ -472,6 +480,7 @@ impl Core { crate::command::set_windowed(id) } + #[cfg(feature = "winit")] pub fn toggle_maximize( &self, id: Option, diff --git a/src/dbus_activation.rs b/src/dbus_activation.rs index 10857fac..99e2f9f0 100644 --- a/src/dbus_activation.rs +++ b/src/dbus_activation.rs @@ -45,7 +45,7 @@ pub fn subscription() -> Subscription( exec: S, env_vars: I, - _app_id: Option<&str>, + app_id: Option<&str>, terminal: bool, ) where S: AsRef, @@ -816,17 +816,13 @@ pub async fn spawn_desktop_exec( // https://systemd.io/DESKTOP_ENVIRONMENTS // // Similar to what Gnome sets, for now. - if let Some(_pid) = crate::process::spawn(cmd).await { + if let Some(pid) = crate::process::spawn(cmd).await { #[cfg(feature = "desktop-systemd-scope")] if let Ok(session) = zbus::Connection::session().await { if let Ok(systemd_manager) = SystemdMangerProxy::new(&session).await { let _ = systemd_manager .start_transient_unit( - &format!( - "app-cosmic-{}-{}.scope", - _app_id.unwrap_or(&executable), - _pid - ), + &format!("app-cosmic-{}-{}.scope", app_id.unwrap_or(&executable), pid), "fail", &[ ( @@ -837,7 +833,7 @@ pub async fn spawn_desktop_exec( ), ( "PIDs".to_string(), - zbus::zvariant::Value::from(vec![_pid]) + zbus::zvariant::Value::from(vec![pid]) .try_to_owned() .unwrap(), ), diff --git a/src/ext.rs b/src/ext.rs index 65ca0145..8eb749e5 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 use iced::Color; +use iced_core::Widget; pub trait ElementExt { #[must_use] diff --git a/src/lib.rs b/src/lib.rs index b1f893af..02623799 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ /// Recommended default imports. pub mod prelude { + #[cfg(feature = "winit")] pub use crate::ApplicationExt; pub use crate::ext::*; pub use crate::{Also, Apply, Element, Renderer, Task, Theme}; @@ -20,7 +21,9 @@ pub use action::Action; pub mod anim; +#[cfg(feature = "winit")] pub mod app; +#[cfg(feature = "winit")] #[doc(inline)] pub use app::{Application, ApplicationExt}; @@ -63,17 +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_runtime; -#[doc(inline)] -pub use iced_widget; -#[doc(inline)] -#[cfg(feature = "wayland")] -pub use iced_winit; pub mod icon_theme; pub mod keyboard_nav; diff --git a/src/malloc.rs b/src/malloc.rs index bc5e8353..b99a66f4 100644 --- a/src/malloc.rs +++ b/src/malloc.rs @@ -1,49 +1,21 @@ // Copyright 2025 System76 // SPDX-License-Identifier: MPL-2.0 -use std::cell::Cell; use std::os::raw::c_int; -use std::time::{Duration, Instant}; const M_MMAP_THRESHOLD: c_int = -3; -/// Minimum interval between two actual `malloc_trim` calls. -/// -/// `trim` is called at the end of every `update()` and `view()`, which can -/// reach 60-200 Hz during typical scrolling, resize, or animation. Each -/// `malloc_trim` walks the glibc heap (10 µs to several ms depending on -/// fragmentation), so calling it at render frequency can consume a -/// substantial fraction of the frame budget. Throttling to 1 Hz keeps RSS -/// bounded while removing the per-frame syscall from the hot path. -const TRIM_MIN_INTERVAL: Duration = Duration::from_millis(1000); - -thread_local! { - static LAST_TRIM: Cell> = const { Cell::new(None) }; -} - unsafe extern "C" { fn malloc_trim(pad: usize); fn mallopt(param: c_int, value: c_int) -> c_int; } -/// Throttled wrapper over `malloc_trim`. Safe to call at render frequency: -/// consecutive calls within `TRIM_MIN_INTERVAL` (per-thread) skip the syscall. #[inline] pub fn trim(pad: usize) { - LAST_TRIM.with(|last| { - let now = Instant::now(); - let should_trim = match last.get() { - None => true, - Some(prev) => now.duration_since(prev) >= TRIM_MIN_INTERVAL, - }; - if should_trim { - last.set(Some(now)); - unsafe { - malloc_trim(pad); - } - } - }); + unsafe { + malloc_trim(pad); + } } /// Prevents glibc from hoarding memory via memory fragmentation. diff --git a/src/scroll.rs b/src/scroll.rs index 67947399..b6d42378 100644 --- a/src/scroll.rs +++ b/src/scroll.rs @@ -1,3 +1,4 @@ +use iced::Task; use iced::mouse::ScrollDelta; use std::time::{Duration, Instant}; @@ -94,7 +95,7 @@ impl Scroll { } else { // Return integer part of scroll, and keep remainder self.scroll = Some((scroll.fract(), Instant::now())); - let discrete = scroll.trunc() as isize; + let mut discrete = scroll.trunc() as isize; if discrete != 0 { self.last_discrete = Some(Instant::now()); } diff --git a/src/surface/action.rs b/src/surface/action.rs index 72dbad66..50e2b4a9 100644 --- a/src/surface/action.rs +++ b/src/surface/action.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 use super::Action; +#[cfg(feature = "winit")] use crate::Application; use iced::window; @@ -26,7 +27,7 @@ pub fn destroy_window(id: iced_core::window::Id) -> Action { Action::DestroyWindow(id) } -#[cfg(all(feature = "wayland", target_os = "linux"))] +#[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, @@ -59,7 +60,7 @@ pub fn app_window( } /// Used to create a window message from within a widget. -#[cfg(all(feature = "wayland", target_os = "linux"))] +#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[must_use] pub fn simple_window( settings: impl Fn() -> window::Settings + Send + Sync + 'static, @@ -91,7 +92,7 @@ pub fn simple_window( ) } -#[cfg(all(feature = "wayland", target_os = "linux"))] +#[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 @@ -125,7 +126,7 @@ pub fn app_popup( } /// Used to create a subsurface message from within a widget. -#[cfg(all(feature = "wayland", target_os = "linux"))] +#[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 @@ -154,7 +155,7 @@ pub fn simple_subsurface( } /// Used to create a popup message from within a widget. -#[cfg(all(feature = "wayland", target_os = "linux"))] +#[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 @@ -185,7 +186,7 @@ pub fn simple_popup( ) } -#[cfg(all(feature = "wayland", target_os = "linux"))] +#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[must_use] pub fn subsurface( settings: impl Fn( diff --git a/src/surface/mod.rs b/src/surface/mod.rs index 513cb91f..0dad6459 100644 --- a/src/surface/mod.rs +++ b/src/surface/mod.rs @@ -6,6 +6,7 @@ pub mod action; use iced::Limits; use iced::Size; use iced::Task; +use std::future::Future; use std::sync::Arc; /// Ignore this message in your application. It will be intercepted. diff --git a/src/theme/mod.rs b/src/theme/mod.rs index cf3beccb..093bac05 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -8,10 +8,12 @@ pub mod portal; pub mod style; use cosmic_config::CosmicConfigEntry; +use cosmic_config::config_subscription; use cosmic_theme::Component; use cosmic_theme::LayeredTheme; use cosmic_theme::Spacing; use cosmic_theme::ThemeMode; +use iced_futures::Subscription; use iced_runtime::{Appearance, DefaultStyle}; use std::sync::{Arc, LazyLock, Mutex}; pub use style::*; diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index c405455b..bb52d9a6 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -211,10 +211,11 @@ impl Catalog for crate::Theme { (component.base.into(), text_color, text_color) }); - if let Button::ListItem(_) = style - && !selected { + if let Button::ListItem(_) = style { + if !selected { s.background = None; } + } s } @@ -265,10 +266,11 @@ impl Catalog for crate::Theme { }, ); - if let Button::ListItem(_) = style - && !selected { + if let Button::ListItem(_) = style { + if !selected { s.background = None; } + } s } diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 746503e3..aa6f4b33 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -171,15 +171,18 @@ impl Button { * TODO: Checkbox */ #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[derive(Default)] pub enum Checkbox { - #[default] Primary, Secondary, Success, Danger, } +impl Default for Checkbox { + fn default() -> Self { + Self::Primary + } +} impl iced_checkbox::Catalog for Theme { type Class<'a> = Checkbox; @@ -789,7 +792,7 @@ impl menu::Catalog for Theme { fn default<'a>() -> ::Class<'a> {} - fn style(&self, _class: &::Class<'_>) -> menu::Style { + fn style(&self, class: &::Class<'_>) -> menu::Style { let cosmic = self.cosmic(); menu::Style { @@ -813,7 +816,7 @@ impl pick_list::Catalog for Theme { fn style( &self, - _class: &::Class<'_>, + class: &::Class<'_>, status: pick_list::Status, ) -> pick_list::Style { let cosmic = &self.cosmic(); @@ -854,7 +857,7 @@ impl radio::Catalog for Theme { fn default<'a>() -> Self::Class<'a> {} - fn style(&self, _class: &Self::Class<'_>, status: radio::Status) -> radio::Style { + fn style(&self, class: &Self::Class<'_>, status: radio::Status) -> radio::Style { let cur_container = self.current_container(); let theme = self.cosmic(); @@ -907,7 +910,7 @@ impl toggler::Catalog for Theme { fn default<'a>() -> Self::Class<'a> {} - fn style(&self, _class: &Self::Class<'_>, status: toggler::Status) -> toggler::Style { + fn style(&self, class: &Self::Class<'_>, status: toggler::Status) -> toggler::Style { let cosmic = self.cosmic(); const HANDLE_MARGIN: f32 = 2.0; let neutral_10 = cosmic.palette.neutral_10.with_alpha(0.1); @@ -935,8 +938,8 @@ impl toggler::Catalog for Theme { padding_ratio: 0.0, }; match status { - toggler::Status::Active { is_toggled: _ } => active, - toggler::Status::Hovered { is_toggled: _ } => { + toggler::Status::Active { is_toggled } => active, + toggler::Status::Hovered { is_toggled } => { let is_active = matches!(status, toggler::Status::Hovered { is_toggled: true }); toggler::Style { background: if is_active { @@ -955,7 +958,7 @@ impl toggler::Catalog for Theme { ..active } } - toggler::Status::Disabled { is_toggled: _ } => { + toggler::Status::Disabled { is_toggled } => { active.background = active.background.scale_alpha(0.5); active.foreground = active.foreground.scale_alpha(0.5); active @@ -972,7 +975,7 @@ impl pane_grid::Catalog for Theme { fn default<'a>() -> ::Class<'a> {} - fn style(&self, _class: &::Class<'_>) -> pane_grid::Style { + fn style(&self, class: &::Class<'_>) -> pane_grid::Style { let theme = self.cosmic(); pane_grid::Style { @@ -1140,8 +1143,8 @@ impl scrollable::Catalog for Theme { fn style(&self, class: &Self::Class<'_>, status: scrollable::Status) -> scrollable::Style { match status { scrollable::Status::Active { - is_horizontal_scrollbar_disabled: _, - is_vertical_scrollbar_disabled: _, + is_horizontal_scrollbar_disabled, + is_vertical_scrollbar_disabled, } => { let cosmic = self.cosmic(); let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7); @@ -1190,7 +1193,7 @@ impl scrollable::Catalog for Theme { background: Color::TRANSPARENT.into(), border: Border::default(), shadow: Shadow::default(), - icon: Color::TRANSPARENT, + icon: Color::TRANSPARENT.into(), }, }; let small_widget_container = self.current_container().small_widget.with_alpha(0.7); @@ -1258,7 +1261,7 @@ impl scrollable::Catalog for Theme { background: Color::TRANSPARENT.into(), border: Border::default(), shadow: Shadow::default(), - icon: Color::TRANSPARENT, + icon: Color::TRANSPARENT.into(), }, }; @@ -1300,7 +1303,7 @@ impl svg::Catalog for Theme { Svg::default() } - fn style(&self, class: &Self::Class<'_>, _status: svg::Status) -> svg::Style { + fn style(&self, class: &Self::Class<'_>, status: svg::Status) -> svg::Style { #[allow(clippy::match_same_arms)] match class { Svg::Default => svg::Style::default(), @@ -1430,7 +1433,7 @@ impl text_input::Catalog for Theme { }, } } - text_input::Status::Focused { is_hovered: _ } => { + text_input::Status::Focused { is_hovered } => { let bg = self.current_container().small_widget.with_alpha(0.25); match class { @@ -1507,7 +1510,7 @@ impl iced_widget::text_editor::Catalog for Theme { let selection = cosmic.accent.base.into(); let value = cosmic.palette.neutral_9.into(); let placeholder = cosmic.palette.neutral_9.with_alpha(0.7).into(); - let _icon: Color = cosmic.background.on.into(); + let icon: Color = cosmic.background.on.into(); // TODO do we need to add icon color back? match status { @@ -1524,7 +1527,7 @@ impl iced_widget::text_editor::Catalog for Theme { value, selection, }, - iced_widget::text_editor::Status::Focused { is_hovered: _ } => { + iced_widget::text_editor::Status::Focused { is_hovered } => { iced_widget::text_editor::Style { background: iced::Color::from(cosmic.bg_color()).into(), border: Border { @@ -1627,8 +1630,8 @@ impl Base for Theme { crate::theme::ThemeType::Light => "Cosmic Light Theme", crate::theme::ThemeType::HighContrastDark => "Cosmic High Contrast Dark Theme", crate::theme::ThemeType::HighContrastLight => "Cosmic High Contrast Light Theme", - crate::theme::ThemeType::Custom(_theme) => "Custom Cosmic Theme", - crate::theme::ThemeType::System { prefer_dark: _, theme } => &theme.name, + crate::theme::ThemeType::Custom(theme) => "Custom Cosmic Theme", + crate::theme::ThemeType::System { prefer_dark, theme } => &theme.name, } } } diff --git a/src/theme/style/mod.rs b/src/theme/style/mod.rs index cc489312..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", target_os = "linux"))] +#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] pub mod tooltip; -#[cfg(all(feature = "wayland", target_os = "linux"))] +#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] pub use tooltip::Tooltip; diff --git a/src/theme/style/segmented_button.rs b/src/theme/style/segmented_button.rs index de8b5782..b9863c88 100644 --- a/src/theme/style/segmented_button.rs +++ b/src/theme/style/segmented_button.rs @@ -181,7 +181,7 @@ mod horizontal { pub fn selection_active( cosmic: &cosmic_theme::Theme, - _component: &Component, + component: &Component, ) -> ItemStatusAppearance { let rad_xl = cosmic.corner_radii.radius_xl; let rad_0 = cosmic.corner_radii.radius_0; @@ -275,7 +275,7 @@ mod vertical { pub fn selection_active( cosmic: &cosmic_theme::Theme, - _component: &Component, + component: &Component, ) -> ItemStatusAppearance { let rad_0 = cosmic.corner_radii.radius_0; let rad_xl = cosmic.corner_radii.radius_xl; diff --git a/src/widget/button/icon.rs b/src/widget/button/icon.rs index a0e06323..04d2bdd5 100644 --- a/src/widget/button/icon.rs +++ b/src/widget/button/icon.rs @@ -152,7 +152,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes ); } - let button = if builder.variant.vertical { + let mut button = if builder.variant.vertical { crate::widget::column::with_children(content) .padding(builder.padding) .spacing(builder.spacing) @@ -169,11 +169,9 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes }; #[cfg(feature = "a11y")] - let button = { - let mut button = button; + { button = button.name(builder.name).description(builder.description); - button - }; + } let button = button .padding(0) diff --git a/src/widget/button/image.rs b/src/widget/button/image.rs index c5a40fd5..ab51e667 100644 --- a/src/widget/button/image.rs +++ b/src/widget/button/image.rs @@ -83,7 +83,7 @@ where .width(builder.width) .height(builder.height); - let button = 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) @@ -91,11 +91,9 @@ where .class(builder.class); #[cfg(feature = "a11y")] - let button = { - let mut button = button; + { button = button.name(builder.name).description(builder.description); - button - }; + } button.into() } diff --git a/src/widget/button/link.rs b/src/widget/button/link.rs index 9bcd7487..9ce81268 100644 --- a/src/widget/button/link.rs +++ b/src/widget/button/link.rs @@ -66,7 +66,7 @@ pub fn icon() -> Handle { impl<'a, Message: Clone + 'static> From> for Element<'a, Message> { fn from(mut builder: Button<'a, Message>) -> Element<'a, Message> { - let 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()) @@ -94,15 +94,13 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .class(builder.class); #[cfg(feature = "a11y")] - let button = { - let mut button = button; + { if !builder.label.is_empty() { button = button.name(builder.label); } button = button.description(builder.description); - button - }; + } if builder.tooltip.is_empty() { button.into() diff --git a/src/widget/button/text.rs b/src/widget/button/text.rs index 71cd1f53..bcdd02ba 100644 --- a/src/widget/button/text.rs +++ b/src/widget/button/text.rs @@ -119,7 +119,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .into() }); - let 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. @@ -138,15 +138,13 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .class(builder.class); #[cfg(feature = "a11y")] - let button = { - let mut button = button; + { if !builder.label.is_empty() { button = button.name(builder.label) } button = button.description(builder.description); - button - }; + } if builder.tooltip.is_empty() { button.into() diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index cdb2b5d6..4acf3f2d 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -9,7 +9,7 @@ use iced_runtime::core::widget::Id; use iced_runtime::{Action, Task, keyboard, task}; -use iced_core::event::Event; +use iced_core::event::{self, Event}; use iced_core::renderer::{self, Quad, Renderer}; use iced_core::touch; use iced_core::widget::Operation; @@ -381,12 +381,13 @@ impl<'a, Message: 'a + Clone> Widget match event { Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { - if let Some(position) = cursor.position() - && removal_bounds(layout.bounds(), 4.0).contains(position) { + if let Some(position) = cursor.position() { + if removal_bounds(layout.bounds(), 4.0).contains(position) { shell.publish(on_remove.clone()); shell.capture_event(); return; } + } } _ => (), @@ -559,9 +560,9 @@ impl<'a, Message: 'a + Clone> Widget } } - if on_remove.is_some() - && let Some(position) = cursor.position() - && bounds.contains(position) { + if on_remove.is_some() { + if let Some(position) = cursor.position() { + if bounds.contains(position) { let bounds = removal_bounds(layout.bounds(), 4.0); renderer.fill_quad( renderer::Quad { @@ -593,6 +594,8 @@ impl<'a, Message: 'a + Clone> Widget }, ); } + } + } }); } } @@ -664,7 +667,7 @@ impl<'a, Message: 'a + Clone> Widget height, } = layout.bounds(); let bounds = Rect::new(x as f64, y as f64, (x + width) as f64, (y + height) as f64); - let _is_hovered = state.state.downcast_ref::().is_hovered; + let is_hovered = state.state.downcast_ref::().is_hovered; let mut node = Node::new(Role::Button); node.add_action(Action::Focus); @@ -745,6 +748,12 @@ impl State { self.is_focused } + /// Returns whether the [`Button`] is currently hovered or not. + #[inline] + pub fn is_hovered(self) -> bool { + self.is_hovered + } + /// Focuses the [`Button`]. #[inline] pub fn focus(&mut self) { @@ -790,6 +799,7 @@ pub fn update<'a, Message: Clone>( } shell.capture_event(); + return; } } } @@ -809,6 +819,7 @@ pub fn update<'a, Message: Clone>( } shell.capture_event(); + return; } } else if on_press_down.is_some() { let state = state(); @@ -816,7 +827,7 @@ pub fn update<'a, Message: Clone>( } } #[cfg(feature = "a11y")] - Event::A11y(_event_id, iced_accessibility::accesskit::ActionRequest { action, .. }) => { + Event::A11y(event_id, iced_accessibility::accesskit::ActionRequest { action, .. }) => { let state = state(); if let Some(on_press) = matches!(action, iced_accessibility::accesskit::Action::Click) .then_some(on_press) @@ -828,6 +839,7 @@ pub fn update<'a, Message: Clone>( shell.publish(msg); } shell.capture_event(); + return; } Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { if let Some(on_press) = on_press { @@ -838,6 +850,7 @@ pub fn update<'a, Message: Clone>( shell.publish(msg); shell.capture_event(); + return; } } } @@ -857,7 +870,7 @@ pub fn draw( viewport_bounds: Rectangle, styling: &super::style::Style, draw_contents: impl FnOnce(&mut Renderer, &Style), - _is_image: bool, + is_image: bool, ) where Theme: super::style::Catalog, { diff --git a/src/widget/cards.rs b/src/widget/cards.rs index 72300cac..66267a73 100644 --- a/src/widget/cards.rs +++ b/src/widget/cards.rs @@ -98,7 +98,7 @@ where /// Get an expandable stack of cards #[allow(clippy::too_many_arguments)] pub fn new( - _id: widget::Id, + id: widget::Id, card_inner_elements: Vec>, on_clear_all: Message, on_show_more: Option, @@ -532,7 +532,7 @@ where let c_layout = layout.next().unwrap(); let state = clear_all_state.unwrap(); self.clear_all_button.as_widget_mut().update( - state, event, c_layout, cursor, renderer, clipboard, shell, viewport, + state, &event, c_layout, cursor, renderer, clipboard, shell, viewport, ); } @@ -542,7 +542,7 @@ where for ((inner, layout), c_state) in self.elements.iter_mut().zip(layout).zip(tree_children) { inner.as_widget_mut().update( - c_state, event, layout, cursor, renderer, clipboard, shell, viewport, + c_state, &event, layout, cursor, renderer, clipboard, shell, viewport, ); if shell.is_event_captured() || fully_unexpanded { break; diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 0de31fd6..318e943b 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -15,7 +15,7 @@ use crate::theme::{Button, THEME}; use crate::widget::{button::Catalog, container, segmented_button::Entity, slider}; use derive_setters::Setters; use iced::Task; -use iced_core::event::Event; +use iced_core::event::{self, Event}; use iced_core::gradient::{ColorStop, Linear}; use iced_core::renderer::Quad; use iced_core::widget::{Tree, tree}; @@ -619,10 +619,8 @@ where let bounds = canvas_layout.bounds(); // Draw the handle on the saturation value canvas - // Yoda: use the Theme passed into draw() instead of locking the global - // THEME Mutex and cloning the whole Theme. Fires on every color-picker - // redraw, so saving the Mutex lock + full Theme clone adds up. - let t = theme.cosmic(); + let t = THEME.lock().unwrap().clone(); + let t = t.cosmic(); let handle_radius = f32::from(t.space_xs()) / 2.0; let (x, y) = ( self.active_color @@ -745,7 +743,7 @@ where let column_tree = &mut tree.children[0]; self.inner.as_widget_mut().update( column_tree, - event, + &event, column_layout, cursor, renderer, @@ -758,19 +756,22 @@ where return; } - if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) = event { - let bounds = column_layout.children().nth(1).unwrap().bounds(); - if let Some(point) = cursor.position_over(bounds) { - let relative_pos = point - bounds.position(); - let (s, v) = ( - relative_pos.x / bounds.width, - 1.0 - relative_pos.y / bounds.height, - ); - state.dragging = true; - let hsv: palette::Hsv = palette::Hsv::new(self.active_color.hue, s, v); - shell.publish((self.on_update)(ColorPickerUpdate::ActiveColor(hsv))); - shell.capture_event(); + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { + let bounds = column_layout.children().nth(1).unwrap().bounds(); + if let Some(point) = cursor.position_over(bounds) { + let relative_pos = point - bounds.position(); + let (s, v) = ( + relative_pos.x / bounds.width, + 1.0 - relative_pos.y / bounds.height, + ); + state.dragging = true; + let hsv: palette::Hsv = palette::Hsv::new(self.active_color.hue, s, v); + shell.publish((self.on_update)(ColorPickerUpdate::ActiveColor(hsv))); + shell.capture_event(); + } } + _ => {} } } diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 37fce504..3f35f04a 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -6,6 +6,7 @@ #[cfg(all( feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; @@ -66,6 +67,7 @@ impl ContextMenu<'_, Message> { #[cfg(all( feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] #[allow(clippy::too_many_lines)] @@ -88,7 +90,7 @@ impl ContextMenu<'_, Message> { bounds.x = my_state.context_cursor.x; bounds.y = my_state.context_cursor.y; - let (id, _root_list) = my_state.menu_bar_state.inner.with_data_mut(|state| { + let (id, root_list) = my_state.menu_bar_state.inner.with_data_mut(|state| { if let Some(id) = state.popup_id.get(&self.window_id).copied() { // close existing popups state.menu_states.clear(); @@ -146,7 +148,7 @@ impl ContextMenu<'_, Message> { layout.bounds(), -bounds.height, ); - let (anchor_rect, _gravity) = my_state.menu_bar_state.inner.with_data_mut(|state| { + let (anchor_rect, gravity) = my_state.menu_bar_state.inner.with_data_mut(|state| { use iced::Rectangle; state.popup_id.insert(self.window_id, id); @@ -375,6 +377,7 @@ impl Widget #[cfg(all( feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) @@ -418,6 +421,7 @@ impl Widget #[cfg(all( feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { @@ -439,6 +443,7 @@ impl Widget #[cfg(all( feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) @@ -477,6 +482,7 @@ impl Widget #[cfg(all( feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index 683a766b..10bf7a8b 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -369,7 +369,7 @@ impl Widget x, y, mime_types, .. }, )) if *id == Some(my_id) => { - if !self.mime_matches(mime_types) { + if !self.mime_matches(&mime_types) { log::trace!( target: DND_DEST_LOG_TARGET, "offer enter id={my_id:?} ignored (mimes={mime_types:?} not in {:?})", @@ -408,6 +408,7 @@ impl Widget ); } shell.capture_event(); + return; } Event::Dnd(DndEvent::Offer(_, OfferEvent::Leave)) => { log::trace!( @@ -435,6 +436,7 @@ impl Widget viewport, ); } + return; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Motion { x, y })) if *id == Some(my_id) => { log::trace!( @@ -469,6 +471,7 @@ impl Widget ); } shell.capture_event(); + return; } Event::Dnd(DndEvent::Offer(_, OfferEvent::LeaveDestination)) => { log::trace!( @@ -481,6 +484,7 @@ impl Widget { shell.publish(msg); } + return; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Drop)) if *id == Some(my_id) => { log::trace!( @@ -493,6 +497,7 @@ impl Widget shell.publish(msg); } shell.capture_event(); + return; } Event::Dnd(DndEvent::Offer(id, OfferEvent::SelectedAction(action))) if *id == Some(my_id) => @@ -510,6 +515,7 @@ impl Widget shell.publish(msg); } shell.capture_event(); + return; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Data { data, mime_type })) if *id == Some(my_id) => @@ -549,6 +555,7 @@ impl Widget return; } shell.capture_event(); + return; } _ => {} } diff --git a/src/widget/dnd_source.rs b/src/widget/dnd_source.rs index 170d6940..980723e3 100644 --- a/src/widget/dnd_source.rs +++ b/src/widget/dnd_source.rs @@ -8,7 +8,8 @@ use crate::{ }; use iced::{ Event, Length, Point, Rectangle, Vector, - clipboard::dnd::{DndAction, DndEvent, SourceEvent}, mouse, overlay, + clipboard::dnd::{DndAction, DndEvent, SourceEvent}, + event, mouse, overlay, }; use iced_core::{ self, Clipboard, Shell, layout, renderer, diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index 99bee955..0c96c1c6 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -8,8 +8,9 @@ use std::sync::{Arc, Mutex}; pub use appearance::{Appearance, StyleSheet}; +use crate::surface; use crate::widget::{Container, RcWrapper, icon}; -use iced_core::event::Event; +use iced_core::event::{self, Event}; use iced_core::layout::{self, Layout}; use iced_core::text::{self, Text}; use iced_core::widget::Tree; @@ -390,7 +391,7 @@ impl<'a, Message: Clone + 'a> crate::widget::Widget { let hovered_guard = self.hovered_option.lock().unwrap(); - if cursor.is_over(layout.bounds()) - && let Some(index) = *hovered_guard { + 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.as_ref() { shell.publish(close_on_selected.clone()); } shell.capture_event(); + return; } + } } Event::Mouse(mouse::Event::CursorMoved { .. }) => { if let Some(cursor_position) = cursor.position_in(layout.bounds()) { @@ -496,10 +499,11 @@ where let new_hovered_option = (cursor_position.y / option_height) as usize; let mut hovered_guard = self.hovered_option.lock().unwrap(); - if let Some(on_option_hovered) = self.on_option_hovered - && *hovered_guard != Some(new_hovered_option) { + if let Some(on_option_hovered) = self.on_option_hovered { + if *hovered_guard != Some(new_hovered_option) { shell.publish(on_option_hovered(new_hovered_option)); } + } *hovered_guard = Some(new_hovered_option); } @@ -523,6 +527,7 @@ where shell.publish(close_on_selected.clone()); } shell.capture_event(); + return; } } } @@ -549,12 +554,12 @@ where 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(&()); diff --git a/src/widget/dropdown/mod.rs b/src/widget/dropdown/mod.rs index 0ea3a212..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 = "wayland", target_os = "linux"))] + #[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/multi/menu.rs b/src/widget/dropdown/multi/menu.rs index 883b8ada..0a761097 100644 --- a/src/widget/dropdown/multi/menu.rs +++ b/src/widget/dropdown/multi/menu.rs @@ -2,7 +2,7 @@ use super::Model; pub use crate::widget::dropdown::menu::{Appearance, StyleSheet}; use crate::widget::Container; -use iced_core::event::Event; +use iced_core::event::{self, Event}; use iced_core::layout::{self, Layout}; use iced_core::text::{self, Text}; use iced_core::widget::Tree; @@ -343,11 +343,13 @@ where match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { - if cursor.is_over(bounds) - && let Some(item) = self.hovered_option.as_ref() { + if cursor.is_over(bounds) { + if let Some(item) = self.hovered_option.as_ref() { shell.publish((self.on_selected)(item.clone())); shell.capture_event(); + return; } + } } Event::Mouse(mouse::Event::CursorMoved { .. }) => { if let Some(cursor_position) = cursor.position_in(bounds) { diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index dd877def..779c6d00 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -5,14 +5,15 @@ use super::menu::{self, Menu}; use crate::widget::icon; use derive_setters::Setters; -use iced_core::event::Event; -use iced_core::text::{self, Text}; +use iced_core::event::{self, Event}; +use iced_core::text::{self, Paragraph, Text}; use iced_core::widget::tree::{self, Tree}; use iced_core::{ Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, }; use iced_core::{Shadow, alignment, keyboard, layout, mouse, overlay, renderer, svg, touch}; use iced_widget::pick_list; +use std::ffi::OsStr; pub use iced_widget::pick_list::{Catalog, Style}; @@ -127,7 +128,7 @@ impl<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static> _viewport: &Rectangle, ) { update( - event, + &event, layout, cursor, shell, @@ -252,7 +253,7 @@ impl Default for State { /// Computes the layout of a [`Dropdown`]. #[allow(clippy::too_many_arguments)] pub fn layout( - _renderer: &crate::Renderer, + renderer: &crate::Renderer, limits: &layout::Limits, width: Length, gap: f32, @@ -375,7 +376,7 @@ pub fn mouse_interaction(layout: Layout<'_>, cursor: mouse::Cursor) -> mouse::In #[allow(clippy::too_many_arguments)] pub fn overlay<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static>( layout: Layout<'_>, - _renderer: &crate::Renderer, + renderer: &crate::Renderer, state: &'a mut State, gap: f32, padding: Padding, diff --git a/src/widget/dropdown/operation.rs b/src/widget/dropdown/operation.rs index 4cd266d4..1a4e1a9f 100644 --- a/src/widget/dropdown/operation.rs +++ b/src/widget/dropdown/operation.rs @@ -2,6 +2,9 @@ // SPDX-License-Identifier: MPL-2.0 AND MIT //! Operate on dropdown widgets. +use super::State; +use iced::Rectangle; +use iced_core::widget::{Id, Operation}; pub trait Dropdown { fn close(&mut self); diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 0ba04c15..2ff9c92f 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -8,7 +8,7 @@ use crate::widget::icon::{self, Handle}; use crate::{Element, surface}; use derive_setters::Setters; use iced::window; -use iced_core::event::Event; +use iced_core::event::{self, Event}; use iced_core::text::{self, Paragraph, Text}; use iced_core::widget::tree::{self, Tree}; use iced_core::{ @@ -17,6 +17,7 @@ use iced_core::{ use iced_core::{Shadow, alignment, keyboard, layout, mouse, overlay, renderer, svg, touch}; use iced_widget::pick_list::{self, Catalog}; use std::borrow::Cow; +use std::ffi::OsStr; use std::hash::{DefaultHasher, Hash, Hasher}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, LazyLock, Mutex}; @@ -59,7 +60,7 @@ where action_map: Option AppMessage + 'static + Send + Sync>>, #[setters(strip_option)] window_id: Option, - #[cfg(all(feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, } @@ -95,14 +96,14 @@ where text_line_height: text::LineHeight::Relative(1.2), font: None, window_id: None, - #[cfg(all(feature = "wayland", target_os = "linux"))] + #[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 = "wayland", target_os = "linux"))] + #[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( @@ -153,7 +154,7 @@ where self } - #[cfg(all(feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] pub fn with_positioner( mut self, positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, @@ -263,11 +264,11 @@ where _viewport: &Rectangle, ) { update::( - event, + &event, layout, cursor, shell, - #[cfg(all(feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] self.positioner.clone(), self.on_selected.clone(), self.selected, @@ -327,10 +328,10 @@ where fn operate( &mut self, - _tree: &mut Tree, + tree: &mut Tree, _layout: Layout<'_>, _renderer: &crate::Renderer, - _operation: &mut dyn iced_core::widget::Operation, + operation: &mut dyn iced_core::widget::Operation, ) { // TODO: double check operation handling // let state = tree.state.downcast_mut::(); @@ -342,10 +343,10 @@ where tree: &'b mut Tree, layout: Layout<'b>, renderer: &crate::Renderer, - _viewport: &Rectangle, + viewport: &Rectangle, translation: Vector, ) -> Option> { - #[cfg(all(feature = "wayland", target_os = "linux"))] + #[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] if self.window_id.is_some() || self.on_surface_action.is_some() { return None; } @@ -451,7 +452,7 @@ impl super::operation::Dropdown for State { /// Computes the layout of a [`Dropdown`]. #[allow(clippy::too_many_arguments)] pub fn layout( - _renderer: &crate::Renderer, + renderer: &crate::Renderer, limits: &layout::Limits, width: Length, gap: f32, @@ -544,8 +545,8 @@ pub fn update< layout: Layout<'_>, cursor: mouse::Cursor, shell: &mut Shell<'_, Message>, - #[cfg(all(feature = "wayland", target_os = "linux"))] - _positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, + #[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, selections: &[S], @@ -557,7 +558,7 @@ pub fn update< gap: f32, padding: Padding, text_size: Option, - _font: Option, + font: Option, selected_option: Option, ) { let state = state(); @@ -570,7 +571,7 @@ pub fn update< *hovered_guard = selected; let id = window::Id::unique(); state.popup_id = id; - #[cfg(all(feature = "wayland", target_os = "linux"))] + #[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) @@ -657,7 +658,7 @@ pub fn update< state.close_operation = false; state.is_open.store(false, Ordering::SeqCst); if is_open { - #[cfg(all(feature = "wayland", target_os = "linux"))] + #[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))); } @@ -680,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 = "wayland", target_os = "linux"))] + #[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))); } @@ -725,7 +726,7 @@ pub fn mouse_interaction(layout: Layout<'_>, cursor: mouse::Cursor) -> mouse::In } } -#[cfg(all(feature = "wayland", target_os = "linux"))] +#[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/flex_row/widget.rs b/src/widget/flex_row/widget.rs index e5c0b615..0b2e6e13 100644 --- a/src/widget/flex_row/widget.rs +++ b/src/widget/flex_row/widget.rs @@ -3,7 +3,7 @@ use crate::{Element, Renderer}; use derive_setters::Setters; -use iced_core::event::Event; +use iced_core::event::{self, Event}; use iced_core::widget::{Operation, Tree}; use iced_core::{ Clipboard, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, layout, mouse, overlay, diff --git a/src/widget/grid/widget.rs b/src/widget/grid/widget.rs index 55ce3c91..e59ba90d 100644 --- a/src/widget/grid/widget.rs +++ b/src/widget/grid/widget.rs @@ -3,7 +3,7 @@ use crate::{Element, Renderer}; use derive_setters::Setters; -use iced_core::event::Event; +use iced_core::event::{self, Event}; use iced_core::widget::{Operation, Tree}; use iced_core::{ Alignment, Clipboard, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, layout, mouse, diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 0fcbc6c6..a772f7d2 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -27,31 +27,9 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { is_ssd: false, on_double_click: None, transparent: false, - controls_position: None, } } -/// Position of the window control buttons (close/min/max) within the headerbar. -#[derive( - Clone, - Copy, - Debug, - Default, - PartialEq, - Eq, - serde::Serialize, - serde::Deserialize, -)] -pub enum WindowControlsPosition { - /// Controls packed at the start (left on LTR) — macOS style. - /// Internal icon order becomes close → minimize → maximize. - Start, - /// Controls packed at the end (right on LTR) — Linux / GNOME style. - /// Internal icon order is minimize → maximize → close. - #[default] - End, -} - #[derive(Setters)] pub struct HeaderBar<'a, Message> { /// Defines the title of the window @@ -113,14 +91,6 @@ pub struct HeaderBar<'a, Message> { /// Whether the headerbar should be transparent transparent: bool, - - /// Side on which the window control buttons (close / minimize / maximize) - /// are rendered. `None` falls back to the user's CosmicTk config - /// (`crate::config::window_controls_position()`). `Some` overrides it. - /// `End` matches Linux / GNOME conventions; `Start` provides macOS-style - /// left-side controls. - #[setters(strip_option)] - controls_position: Option, } impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { @@ -402,20 +372,12 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { } = theme::spacing(); // Take ownership of the regions to be packed. - let mut start = std::mem::take(&mut self.start); + let start = std::mem::take(&mut self.start); let center = std::mem::take(&mut self.center); let mut end = std::mem::take(&mut self.end); - // Pack window controls on the configured side (reads CosmicTk - // config when the builder did not set an explicit override). - let controls_position = self - .controls_position - .unwrap_or_else(crate::config::window_controls_position); - let controls = self.window_controls(space_xxs, controls_position); - match controls_position { - WindowControlsPosition::End => end.push(controls), - WindowControlsPosition::Start => start.insert(0, controls), - } + // Also packs the window controls at the very end. + end.push(self.window_controls(space_xxs)); let padding = if self.is_ssd { [2, 8, 2, 8] @@ -485,11 +447,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { } /// Creates the widget for window controls. - fn window_controls( - &mut self, - spacing: u16, - controls_position: WindowControlsPosition, - ) -> Element<'a, Message> { + fn window_controls(&mut self, spacing: u16) -> Element<'a, Message> { macro_rules! icon { ($name:expr, $size:expr, $on_press:expr) => {{ widget::icon::from_name($name) @@ -502,37 +460,25 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { }}; } - let minimize = self - .on_minimize - .take() - .map(|m| icon!("window-minimize-symbolic", 16, m)); - let maximize = self.on_maximize.take().map(|m| { - if self.maximized { - icon!("window-restore-symbolic", 16, m) - } else { - icon!("window-maximize-symbolic", 16, m) - } - }); - let close = self - .on_close - .take() - .map(|m| icon!("window-close-symbolic", 16, m)); - - // Icon order follows the OS convention for the chosen side: - // End → minimize, maximize, close (Linux / GNOME) - // Start → close, minimize, maximize (macOS) - let row = widget::row::with_capacity(3); - let row = match controls_position { - WindowControlsPosition::End => row - .push_maybe(minimize) - .push_maybe(maximize) - .push_maybe(close), - WindowControlsPosition::Start => row - .push_maybe(close) - .push_maybe(minimize) - .push_maybe(maximize), - }; - row.spacing(spacing) + widget::row::with_capacity(3) + .push_maybe( + self.on_minimize + .take() + .map(|m| icon!("window-minimize-symbolic", 16, m)), + ) + .push_maybe(self.on_maximize.take().map(|m| { + if self.maximized { + icon!("window-restore-symbolic", 16, m) + } else { + icon!("window-maximize-symbolic", 16, m) + } + })) + .push_maybe( + self.on_close + .take() + .map(|m| icon!("window-close-symbolic", 16, m)), + ) + .spacing(spacing) .align_y(iced::Alignment::Center) .into() } diff --git a/src/widget/icon/bundle.rs b/src/widget/icon/bundle.rs index 30a9938a..bb6ce244 100644 --- a/src/widget/icon/bundle.rs +++ b/src/widget/icon/bundle.rs @@ -5,7 +5,7 @@ /// Icon bundling is not enabled on unix platforms. #[cfg(all(unix, not(target_os = "macos")))] -pub fn get(_icon_name: &str) -> Option { +pub fn get(icon_name: &str) -> Option { None } diff --git a/src/widget/id_container.rs b/src/widget/id_container.rs index 8f280cab..716ee138 100644 --- a/src/widget/id_container.rs +++ b/src/widget/id_container.rs @@ -1,4 +1,4 @@ -use iced_core::event::Event; +use iced_core::event::{self, Event}; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; diff --git a/src/widget/layer_container.rs b/src/widget/layer_container.rs index 577261f6..110af518 100644 --- a/src/widget/layer_container.rs +++ b/src/widget/layer_container.rs @@ -1,7 +1,7 @@ use crate::Theme; use cosmic_theme::LayeredTheme; use iced::widget::Container; -use iced_core::event::Event; +use iced_core::event::{self, Event}; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; diff --git a/src/widget/menu/flex.rs b/src/widget/menu/flex.rs index 09dab466..4a58f13a 100644 --- a/src/widget/menu/flex.rs +++ b/src/widget/menu/flex.rs @@ -50,7 +50,6 @@ impl Axis { /// padding and alignment to the items as needed. /// /// It returns a new layout [`Node`]. -#[allow(dead_code)] // kept as public helper; not currently called by libcosmic pub fn resolve<'a, E, Message, Renderer>( axis: &Axis, renderer: &Renderer, @@ -249,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_mut().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, @@ -272,7 +271,7 @@ pub fn resolve_wrapper<'a, Message>( cross = fill_cross; } - for (i, (child, tree)) in items.iter_mut().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, @@ -315,7 +314,7 @@ pub fn resolve_wrapper<'a, Message>( let remaining = available.max(0.0); - for (i, (child, tree)) in items.iter_mut().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 981053b3..981446e8 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -13,6 +13,7 @@ use super::{ feature = "multi-window", feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; @@ -21,11 +22,12 @@ use crate::{ style::menu_bar::StyleSheet, widget::{ RcWrapper, + dropdown::menu::{self, State}, menu::menu_inner::init_root_menu, }, }; -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, @@ -54,6 +56,7 @@ pub(crate) struct MenuBarStateInner { pub(crate) tree: Tree, pub(crate) popup_id: HashMap, pub(crate) pressed: bool, + pub(crate) bar_pressed: bool, pub(crate) view_cursor: Cursor, pub(crate) open: bool, pub(crate) active_root: Vec, @@ -90,6 +93,7 @@ impl Default for MenuBarStateInner { vertical_direction: Direction::Positive, menu_states: Vec::new(), popup_id: HashMap::new(), + bar_pressed: false, } } } @@ -166,6 +170,14 @@ where } } +pub fn get_mut_or_default(vec: &mut Vec, index: usize) -> &mut T { + if index < vec.len() { + &mut vec[index] + } else { + vec.resize_with(index + 1, T::default); + &mut vec[index] + } +} /// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing. #[allow(missing_debug_implementations)] @@ -187,6 +199,7 @@ pub struct MenuBar { #[cfg(all( feature = "multi-window", feature = "wayland", + feature = "winit", target_os = "linux" ))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, @@ -226,6 +239,7 @@ where #[cfg(all( feature = "multi-window", feature = "wayland", + feature = "winit", target_os = "linux" ))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), @@ -324,6 +338,7 @@ where #[cfg(all( feature = "multi-window", feature = "wayland", + feature = "winit", target_os = "linux" ))] pub fn with_positioner( @@ -361,6 +376,7 @@ where feature = "multi-window", feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] #[allow(clippy::too_many_lines)] @@ -598,12 +614,14 @@ where .with_data(|d| !d.open && !d.active_root.is_empty()); let open = my_state.inner.with_data_mut(|state| { - if reset - && let Some(popup_id) = state.popup_id.get(&self.window_id).copied() - && let Some(handler) = self.on_surface_action.as_ref() { + 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 }); @@ -629,6 +647,7 @@ where #[cfg(all( feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] { @@ -652,6 +671,7 @@ where feature = "multi-window", feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { @@ -666,6 +686,7 @@ where feature = "multi-window", feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { @@ -741,13 +762,14 @@ where tree: &'b mut Tree, layout: Layout<'b>, _renderer: &Renderer, - _viewport: &Rectangle, + viewport: &Rectangle, translation: Vector, ) -> Option> { #[cfg(all( feature = "multi-window", feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index af0b3add..74afe60f 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -8,6 +8,7 @@ use super::{menu_bar::MenuBarState, menu_tree::MenuTree}; feature = "multi-window", feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; @@ -558,7 +559,10 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> Option<(usize, MenuState)> { - use event::Event::{Mouse, Touch}; + use event::{ + Event::{Mouse, Touch}, + Status::{Captured, Ignored}, + }; use mouse::{ Button::Left, Event::{ButtonPressed, ButtonReleased, CursorMoved, WheelScrolled}, @@ -578,7 +582,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { let viewport_size = viewport.size(); let overlay_offset = Point::ORIGIN - viewport.position(); let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset; - let _menu_roots = match &mut self.menu_roots { + let menu_roots = match &mut self.menu_roots { Cow::Borrowed(_) => panic!(), Cow::Owned(o) => o.as_mut_slice(), }; @@ -678,6 +682,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { feature = "multi-window", feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) @@ -948,7 +953,7 @@ impl Widget, cursor: mouse::Cursor, @@ -962,25 +967,26 @@ impl Widget Widget( feature = "multi-window", feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] pub(super) fn init_root_popup_menu( @@ -1520,14 +1527,15 @@ where .as_ref() .is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty()); - #[cfg(all(feature = "multi-window", feature = "wayland", target_os = "linux", feature = "surface-message"))] - if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove - && let Some(id) = state.popup_id.remove(&menu.window_id) { + #[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); 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); @@ -1599,7 +1607,7 @@ fn process_scroll_events( ) where Message: Clone, { - + use event::Status::{Captured, Ignored}; use mouse::ScrollDelta; menu.tree.inner.with_data_mut(|state| { diff --git a/src/widget/mod.rs b/src/widget/mod.rs index d83a74e2..f442b0da 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -119,6 +119,8 @@ pub mod calendar; pub use calendar::{Calendar, calendar}; pub mod card; +#[doc(inline)] +pub use card::*; pub mod color_picker; #[doc(inline)] @@ -213,17 +215,13 @@ pub mod flex_row; #[doc(inline)] pub use flex_row::{FlexRow, flex_row}; -pub mod reorderable_flex_row; -#[doc(inline)] -pub use reorderable_flex_row::{ReorderableFlexRow, reorderable_flex_row}; - pub mod grid; #[doc(inline)] pub use grid::{Grid, grid}; mod header_bar; #[doc(inline)] -pub use header_bar::{HeaderBar, WindowControlsPosition, header_bar}; +pub use header_bar::{HeaderBar, header_bar}; pub mod icon; #[doc(inline)] @@ -310,7 +308,7 @@ pub use toggler::{Toggler, toggler}; #[doc(inline)] pub use tooltip::{Tooltip, tooltip}; -#[cfg(all(feature = "wayland", target_os = "linux"))] +#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] pub mod wayland; pub mod tooltip { diff --git a/src/widget/popover.rs b/src/widget/popover.rs index 7ca9eeff..af5370a8 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -4,7 +4,7 @@ //! A container which displays an overlay when a popup widget is attached. use iced::widget; -use iced_core::event::Event; +use iced_core::event::{self, Event}; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; @@ -164,8 +164,8 @@ where shell.capture_event(); return; } - } else if let Some(on_close) = self.on_close.as_ref() - && matches!( + } else if let Some(on_close) = self.on_close.as_ref() { + if matches!( event, Event::Mouse(mouse::Event::ButtonPressed(_)) | Event::Touch(touch::Event::FingerPressed { .. }) @@ -173,6 +173,7 @@ where { shell.publish(on_close.clone()); } + } } // Hide cursor from background content when modal popup is active @@ -475,6 +476,12 @@ where } } +/// The local state of a [`Popover`]. +#[derive(Debug, Default)] +struct State { + is_open: bool, +} + /// The first child in [`Popover::children`] is always the wrapped content. fn content_tree(tree: &Tree) -> &Tree { &tree.children[0] diff --git a/src/widget/radio.rs b/src/widget/radio.rs index 51ed00a4..c3f115c0 100644 --- a/src/widget/radio.rs +++ b/src/widget/radio.rs @@ -1,7 +1,7 @@ //! Create choices using radio buttons. use crate::{Theme, theme}; use iced::border; -use iced_core::event::Event; +use iced_core::event::{self, Event}; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; @@ -266,6 +266,7 @@ where if cursor.is_over(layout.bounds()) { shell.publish(self.on_click.clone()); shell.capture_event(); + return; } } _ => {} diff --git a/src/widget/rectangle_tracker/mod.rs b/src/widget/rectangle_tracker/mod.rs index f5969589..b3066ecb 100644 --- a/src/widget/rectangle_tracker/mod.rs +++ b/src/widget/rectangle_tracker/mod.rs @@ -5,7 +5,7 @@ use iced::futures::channel::mpsc::UnboundedSender; use iced::widget::Container; pub use subscription::*; -use iced_core::event::Event; +use iced_core::event::{self, Event}; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; diff --git a/src/widget/reorderable_flex_row/mod.rs b/src/widget/reorderable_flex_row/mod.rs deleted file mode 100644 index d5da9f4a..00000000 --- a/src/widget/reorderable_flex_row/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2026 System76 -// SPDX-License-Identifier: MPL-2.0 - -//! A keyed wrapping flex row whose items can be dragged to reorder. - -mod widget; - -pub use widget::{ReorderableFlexRow, reorderable_flex_row}; diff --git a/src/widget/reorderable_flex_row/widget.rs b/src/widget/reorderable_flex_row/widget.rs deleted file mode 100644 index 03789877..00000000 --- a/src/widget/reorderable_flex_row/widget.rs +++ /dev/null @@ -1,1297 +0,0 @@ -// Copyright 2026 System76 -// SPDX-License-Identifier: MPL-2.0 - -use crate::{Element, Renderer}; -use iced::{Alignment, Pixels, alignment}; -use iced_core::event::Event; -use iced_core::layout::{self, Layout}; -use iced_core::mouse::{self, Cursor}; -use iced_core::widget::Operation; -use iced_core::widget::tree::{self, Tree}; -#[cfg(feature = "wgpu")] -use iced_core::{Background, Border, Shadow}; -use iced_core::{ - Clipboard, Length, Padding, Point, Rectangle, Renderer as _, Shell, Size, Vector, Widget, - overlay, renderer, window, -}; -use std::collections::{HashMap, HashSet}; -use std::hash::Hash; -use std::time::{Duration, Instant}; - -const DEFAULT_ANIMATION_DURATION: Duration = Duration::from_millis(180); -const DEFAULT_DRAG_LIFT: f32 = 10.0; -const DEFAULT_DRAG_THRESHOLD: f32 = 6.0; -#[cfg(feature = "wgpu")] -const SHADOW_BLUR_RADIUS: f32 = 20.0; -const POSITION_EPSILON: f32 = 0.5; - -#[derive(Debug, Clone)] -struct SlotAnimation { - from: Point, - to: Point, - started_at: Option, -} - -impl SlotAnimation { - fn new(position: Point) -> Self { - Self { - from: position, - to: position, - started_at: None, - } - } - - fn current_position(&self, duration: Duration) -> Point { - let Some(started_at) = self.started_at else { - return self.to; - }; - - let duration_secs = duration.as_secs_f32(); - if duration_secs <= f32::EPSILON { - return self.to; - } - - let progress = (started_at.elapsed().as_secs_f32() / duration_secs).clamp(0.0, 1.0); - let eased = 1.0 - (1.0 - progress).powi(3); - - Point::new( - self.from.x + (self.to.x - self.from.x) * eased, - self.from.y + (self.to.y - self.from.y) * eased, - ) - } - - fn retarget(&mut self, new_target: Point, duration: Duration) { - if approx_eq_point(self.to, new_target) { - if !self.is_animating(duration) { - self.from = new_target; - self.to = new_target; - self.started_at = None; - } - return; - } - - self.from = self.current_position(duration); - self.to = new_target; - self.started_at = Some(Instant::now()); - } - - fn is_animating(&self, duration: Duration) -> bool { - self.started_at - .is_some_and(|started_at| started_at.elapsed() < duration) - } - - fn finish_if_done(&mut self, duration: Duration) { - if self - .started_at - .is_some_and(|started_at| started_at.elapsed() >= duration) - { - self.from = self.to; - self.started_at = None; - } - } -} - -#[derive(Debug, Clone)] -struct PendingDragState -where - Key: Clone + Eq + Hash + 'static, -{ - key: Key, - item_index: usize, - original_index: usize, - press_local: Point, - pointer_offset: Vector, -} - -#[derive(Debug, Clone)] -struct DragState -where - Key: Clone + Eq + Hash + 'static, -{ - key: Key, - item_index: usize, - original_index: usize, - current_index: usize, - cursor_local: Point, - pointer_offset: Vector, -} - -#[derive(Debug, Clone)] -struct State -where - Key: Clone + Eq + Hash + 'static, -{ - keys: Vec, - slot_positions: HashMap, - pending_drag: Option>, - drag: Option>, - wrap_width: f32, - initialized: bool, -} - -impl Default for State -where - Key: Clone + Eq + Hash + 'static, -{ - fn default() -> Self { - Self { - keys: Vec::new(), - slot_positions: HashMap::new(), - pending_drag: None, - drag: None, - wrap_width: f32::INFINITY, - initialized: false, - } - } -} - -impl State -where - Key: Clone + Eq + Hash + 'static, -{ - fn retain_keys(&mut self, keys: &[Key]) { - let keep: HashSet<_> = keys.iter().cloned().collect(); - self.slot_positions.retain(|key, _| keep.contains(key)); - - if self - .pending_drag - .as_ref() - .is_some_and(|drag| !keep.contains(&drag.key)) - { - self.pending_drag = None; - } - - if self - .drag - .as_ref() - .is_some_and(|drag| !keep.contains(&drag.key)) - { - self.drag = None; - } - } - - fn finish_animations(&mut self, duration: Duration) { - self.slot_positions - .values_mut() - .for_each(|slot| slot.finish_if_done(duration)); - } - - fn is_animating(&self, duration: Duration) -> bool { - self.slot_positions - .values() - .any(|slot| slot.is_animating(duration)) - } - - fn current_slot_position(&self, key: &Key, fallback: Point, duration: Duration) -> Point { - self.slot_positions - .get(key) - .map(|slot| slot.current_position(duration)) - .unwrap_or(fallback) - } - - fn retarget_slot(&mut self, key: &Key, target: Point, duration: Duration) { - self.slot_positions - .entry(key.clone()) - .or_insert_with(|| SlotAnimation::new(target)) - .retarget(target, duration); - } - - fn snap_slot(&mut self, key: &Key, target: Point) { - self.slot_positions - .insert(key.clone(), SlotAnimation::new(target)); - } - - fn apply_layout_position(&mut self, key: &Key, target: Point, duration: Duration) { - if self.initialized { - self.retarget_slot(key, target, duration); - } else { - self.snap_slot(key, target); - } - } -} - -#[derive(Debug, Clone)] -struct LocalSlot -where - Key: Clone + Eq + Hash + 'static, -{ - key: Key, - locked: bool, - bounds: Rectangle, -} - -/// A horizontal flex row with drag-to-reorder behavior. -#[must_use] -pub struct ReorderableFlexRow<'a, Key, Message> -where - Key: Clone + Eq + Hash + 'static, -{ - spacing: f32, - padding: Padding, - width: Length, - height: Length, - align: Alignment, - clip: bool, - drag_lift: f32, - animation_duration: Duration, - on_reorder: Box) -> Message + 'a>, - keys: Vec, - locked: Vec, - children: Vec>, -} - -impl<'a, Key, Message> ReorderableFlexRow<'a, Key, Message> -where - Key: Clone + Eq + Hash + 'static, -{ - pub fn new(on_reorder: impl Fn(Vec) -> Message + 'a) -> Self { - Self { - spacing: 0.0, - padding: Padding::ZERO, - width: Length::Shrink, - height: Length::Shrink, - align: Alignment::Start, - clip: false, - drag_lift: DEFAULT_DRAG_LIFT, - animation_duration: DEFAULT_ANIMATION_DURATION, - on_reorder: Box::new(on_reorder), - keys: Vec::new(), - locked: Vec::new(), - children: Vec::new(), - } - } - - pub fn with_capacity(capacity: usize, on_reorder: impl Fn(Vec) -> Message + 'a) -> Self { - let mut row = Self::new(on_reorder); - row.keys = Vec::with_capacity(capacity); - row.locked = Vec::with_capacity(capacity); - row.children = Vec::with_capacity(capacity); - row - } - - pub fn spacing(mut self, amount: impl Into) -> Self { - self.spacing = amount.into().0; - self - } - - pub fn padding>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - pub fn width(mut self, width: impl Into) -> Self { - self.width = width.into(); - self - } - - pub fn height(mut self, height: impl Into) -> Self { - self.height = height.into(); - self - } - - pub fn align_y(mut self, align: impl Into) -> Self { - self.align = Alignment::from(align.into()); - self - } - - /// Leave disabled for dragged item to visibly lift above the row. - pub fn clip(mut self, clip: bool) -> Self { - self.clip = clip; - self - } - - pub fn drag_lift(mut self, lift: f32) -> Self { - self.drag_lift = lift.max(0.0); - self - } - - pub fn animation_duration(mut self, duration: Duration) -> Self { - self.animation_duration = duration; - self - } - - pub fn push(self, key: Key, child: impl Into>) -> Self { - self.push_with_lock(key, false, child) - } - - /// Item stays fixed, never participates in reordering. - pub fn push_locked(self, key: Key, child: impl Into>) -> Self { - self.push_with_lock(key, true, child) - } - - fn push_with_lock( - mut self, - key: Key, - locked: bool, - child: impl Into>, - ) -> Self { - let child = child.into(); - let child_size = child.as_widget().size_hint(); - - if !child_size.is_void() { - self.width = self.width.enclose(child_size.width); - self.height = self.height.enclose(child_size.height); - self.keys.push(key); - self.locked.push(locked); - self.children.push(child); - } - - self - } - - pub fn extend(self, items: impl IntoIterator)>) -> Self { - items - .into_iter() - .fold(self, |row, (key, child)| row.push(key, child)) - } - - pub fn extend_locked( - self, - items: impl IntoIterator)>, - ) -> Self { - items - .into_iter() - .fold(self, |row, (key, child)| row.push_locked(key, child)) - } - - fn item_local_slots_from_layout( - &self, - bounds: Rectangle, - child_layouts: &[Layout<'_>], - ) -> Vec> { - self.keys - .iter() - .zip(self.locked.iter()) - .zip(child_layouts.iter()) - .map(|((key, locked), child_layout)| { - let child_bounds = child_layout.bounds(); - LocalSlot { - key: key.clone(), - locked: *locked, - bounds: Rectangle { - x: child_bounds.x - bounds.x, - y: child_bounds.y - bounds.y, - width: child_bounds.width, - height: child_bounds.height, - }, - } - }) - .collect() - } - - fn sync_slot_positions(&self, state: &mut State, slots: &[LocalSlot]) { - let ordered_keys: Vec = slots.iter().map(|slot| slot.key.clone()).collect(); - state.retain_keys(&ordered_keys); - - let size_by_key: HashMap = slots - .iter() - .map(|slot| { - ( - slot.key.clone(), - Size::new(slot.bounds.width, slot.bounds.height), - ) - }) - .collect(); - let locked_by_key: HashMap = slots - .iter() - .map(|slot| (slot.key.clone(), slot.locked)) - .collect(); - - let Some(drag_snapshot) = state.drag.as_ref().map(|drag| { - ( - drag.key.clone(), - drag.cursor_local, - drag.pointer_offset, - drag.item_index, - ) - }) else { - for slot in slots { - state.apply_layout_position( - &slot.key, - Point::new(slot.bounds.x, slot.bounds.y), - self.animation_duration, - ); - } - return; - }; - - let (drag_key, cursor_local, pointer_offset, drag_item_index) = drag_snapshot; - - let Some(dragged_slot) = slots.iter().find(|slot| slot.key == drag_key) else { - state.drag = None; - for slot in slots { - state.apply_layout_position( - &slot.key, - Point::new(slot.bounds.x, slot.bounds.y), - self.animation_duration, - ); - } - return; - }; - - if dragged_slot.locked { - state.drag = None; - return; - } - - let dragged_center = Point::new( - cursor_local.x - pointer_offset.x + dragged_slot.bounds.width / 2.0, - cursor_local.y - pointer_offset.y + dragged_slot.bounds.height / 2.0, - ); - let target_index = target_index_for_drag(slots, &drag_key, dragged_center); - let prior_index = state.drag.as_ref().map(|drag| drag.current_index); - - if let Some(drag) = state.drag.as_mut() { - drag.current_index = target_index; - drag.item_index = drag_item_index; - } - - if prior_index == Some(target_index) { - return; - } - - let reordered_keys = - reordered_keys_for_drag(&ordered_keys, &locked_by_key, &drag_key, target_index); - let (target_slots, _) = compute_wrapped_slots( - &reordered_keys, - &locked_by_key, - &size_by_key, - state.wrap_width, - self.padding, - self.spacing, - self.align, - ); - let target_positions: HashMap = target_slots - .iter() - .map(|slot| (slot.key.clone(), Point::new(slot.bounds.x, slot.bounds.y))) - .collect(); - - for slot in slots { - let target = target_positions - .get(&slot.key) - .copied() - .unwrap_or(Point::new(slot.bounds.x, slot.bounds.y)); - state.retarget_slot(&slot.key, target, self.animation_duration); - } - } -} - -impl<'a, Key, Message> Widget - for ReorderableFlexRow<'a, Key, Message> -where - Key: Clone + Eq + Hash + 'static, - Message: 'a, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::>() - } - - fn state(&self) -> tree::State { - tree::State::new(State { - keys: self.keys.clone(), - ..State::default() - }) - } - - fn children(&self) -> Vec { - self.children.iter().map(Tree::new).collect() - } - - fn diff(&mut self, tree: &mut Tree) { - let Tree { - state, children, .. - } = tree; - let state = state.downcast_mut::>(); - let previous_keys = state.keys.clone(); - let previous_children = std::mem::take(children); - let mut previous_by_key = HashMap::with_capacity(previous_keys.len()); - - for (key, child_tree) in previous_keys.into_iter().zip(previous_children) { - previous_by_key.insert(key, child_tree); - } - - children.reserve(self.children.len()); - - for (key, child) in self.keys.iter().cloned().zip(self.children.iter_mut()) { - let mut child_tree = previous_by_key - .remove(&key) - .unwrap_or_else(|| Tree::new(child.as_widget())); - child.as_widget_mut().diff(&mut child_tree); - children.push(child_tree); - } - - if state.keys != self.keys { - state.keys.clone_from(&self.keys); - } - state.retain_keys(&self.keys); - } - - fn size(&self) -> Size { - Size { - width: self.width, - height: self.height, - } - } - - fn layout( - &mut self, - tree: &mut Tree, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let limits = limits - .width(self.width) - .height(self.height) - .shrink(self.padding); - let child_limits = limits.loose(); - let wrap_width = limits.max().width; - - let mut nodes = Vec::with_capacity(self.children.len()); - let mut size_by_key = HashMap::with_capacity(self.children.len()); - let locked_by_key: HashMap = self - .keys - .iter() - .cloned() - .zip(self.locked.iter().copied()) - .collect(); - - for ((key, child), child_tree) in self - .keys - .iter() - .zip(self.children.iter_mut()) - .zip(tree.children.iter_mut()) - { - let node = child - .as_widget_mut() - .layout(child_tree, renderer, &child_limits); - size_by_key.insert(key.clone(), node.size()); - nodes.push(node); - } - - let (slots, intrinsic_size) = compute_wrapped_slots( - &self.keys, - &locked_by_key, - &size_by_key, - wrap_width, - self.padding, - self.spacing, - self.align, - ); - - for (node, slot) in nodes.iter_mut().zip(&slots) { - node.move_to_mut(Point::new(slot.bounds.x, slot.bounds.y)); - } - - let size = limits.resolve(self.width, self.height, intrinsic_size); - let node = layout::Node::with_children(size.expand(self.padding), nodes); - let state = tree.state.downcast_mut::>(); - state.wrap_width = wrap_width; - self.sync_slot_positions(state, &slots); - - node - } - - fn operate( - &mut self, - tree: &mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn Operation, - ) { - operation.container(None, layout.bounds()); - operation.traverse(&mut |operation| { - self.children - .iter_mut() - .zip(&mut tree.children) - .zip(layout.children()) - .for_each(|((child, state), child_layout)| { - child.as_widget_mut().operate( - state, - child_layout.with_virtual_offset(layout.virtual_offset()), - renderer, - operation, - ); - }); - }); - } - - fn update( - &mut self, - tree: &mut Tree, - event: &Event, - layout: Layout<'_>, - cursor: Cursor, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - viewport: &Rectangle, - ) { - let bounds = layout.bounds(); - let state = tree.state.downcast_mut::>(); - let child_layouts: Vec<_> = layout.children().collect(); - - if let Event::Window(window::Event::RedrawRequested(_)) = event { - state.initialized = true; - state.finish_animations(self.animation_duration); - if state.drag.is_some() || state.is_animating(self.animation_duration) { - shell.request_redraw(); - } - } - - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { - if state.drag.is_none() - && state.pending_drag.is_none() - && let Some(cursor_pos) = cursor.position() - && let Some((index, child_layout)) = child_layouts - .iter() - .enumerate() - .find(|(_, child_layout)| child_layout.bounds().contains(cursor_pos)) - && !self.locked.get(index).copied().unwrap_or(false) - && let Some(reorder_index) = reorderable_index_for_child(&self.locked, index) - { - let child_bounds = child_layout.bounds(); - state.pending_drag = Some(PendingDragState { - key: self.keys[index].clone(), - item_index: index, - original_index: reorder_index, - press_local: Point::new(cursor_pos.x - bounds.x, cursor_pos.y - bounds.y), - pointer_offset: Vector::new( - cursor_pos.x - child_bounds.x, - cursor_pos.y - child_bounds.y, - ), - }); - } - } - Event::Mouse(mouse::Event::CursorMoved { .. }) => { - if let Some(pending) = state.pending_drag.clone() - && let Some(cursor_pos) = cursor.position() - { - let cursor_local = Point::new(cursor_pos.x - bounds.x, cursor_pos.y - bounds.y); - let delta = Vector::new( - cursor_local.x - pending.press_local.x, - cursor_local.y - pending.press_local.y, - ); - let distance = (delta.x.powi(2) + delta.y.powi(2)).sqrt(); - - if distance >= DEFAULT_DRAG_THRESHOLD { - if let (Some(child), Some(child_tree), Some(child_layout)) = ( - self.children.get_mut(pending.item_index), - tree.children.get_mut(pending.item_index), - child_layouts.get(pending.item_index), - ) { - let cursor_left = Event::Mouse(mouse::Event::CursorLeft); - child.as_widget_mut().update( - child_tree, - &cursor_left, - child_layout.with_virtual_offset(layout.virtual_offset()), - cursor, - renderer, - clipboard, - shell, - viewport, - ); - } - - state.pending_drag = None; - state.drag = Some(DragState { - key: pending.key, - item_index: pending.item_index, - original_index: pending.original_index, - current_index: pending.original_index, - cursor_local, - pointer_offset: pending.pointer_offset, - }); - let slots = self.item_local_slots_from_layout(bounds, &child_layouts); - self.sync_slot_positions(state, &slots); - shell.capture_event(); - shell.request_redraw(); - return; - } - } - - if let Some(drag) = state.drag.as_mut() - && let Some(cursor_pos) = cursor.position() - { - drag.cursor_local = - Point::new(cursor_pos.x - bounds.x, cursor_pos.y - bounds.y); - let slots = self.item_local_slots_from_layout(bounds, &child_layouts); - self.sync_slot_positions(state, &slots); - shell.capture_event(); - shell.request_redraw(); - return; - } - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { - state.pending_drag = None; - - if state.drag.is_some() { - let slots = self.item_local_slots_from_layout(bounds, &child_layouts); - self.sync_slot_positions(state, &slots); - } - if let Some(drag) = state.drag.take() { - if drag.current_index != drag.original_index { - let locked_by_key: HashMap = self - .keys - .iter() - .cloned() - .zip(self.locked.iter().copied()) - .collect(); - let new_order = reordered_keys_for_drag( - &self.keys, - &locked_by_key, - &drag.key, - drag.current_index, - ); - shell.publish((self.on_reorder)(new_order)); - } - shell.capture_event(); - shell.request_redraw(); - return; - } - } - _ => {} - } - - if state.drag.is_some() { - return; - } - - for ((item, tree), child_layout) in self - .children - .iter_mut() - .zip(&mut tree.children) - .zip(child_layouts.into_iter()) - { - item.as_widget_mut().update( - tree, - event, - child_layout.with_virtual_offset(layout.virtual_offset()), - cursor, - renderer, - clipboard, - shell, - viewport, - ); - } - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor: Cursor, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - let state = tree.state.downcast_ref::>(); - - if state.drag.is_some() { - return mouse::Interaction::Grabbing; - } - - if let Some(cursor_pos) = cursor.position() - && self - .locked - .iter() - .zip(layout.children()) - .any(|(locked, child_layout)| { - !*locked && child_layout.bounds().contains(cursor_pos) - }) - { - return mouse::Interaction::Grab; - } - - self.children - .iter() - .zip(&tree.children) - .zip(layout.children()) - .map(|((child, tree), child_layout)| { - child.as_widget().mouse_interaction( - tree, - child_layout.with_virtual_offset(layout.virtual_offset()), - cursor, - viewport, - renderer, - ) - }) - .max() - .unwrap_or_default() - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &crate::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor: Cursor, - viewport: &Rectangle, - ) { - let state = tree.state.downcast_ref::>(); - let bounds = layout.bounds(); - - let Some(clipped_viewport) = bounds.intersection(viewport) else { - return; - }; - - let viewport = if self.clip { - &clipped_viewport - } else { - viewport - }; - let drag_key = state.drag.as_ref().map(|drag| &drag.key); - let drag_item = state.drag.as_ref().and_then(|drag| { - self.keys - .iter() - .zip(&self.children) - .zip(&tree.children) - .zip(layout.children()) - .find_map(|(((key, child), state), child_layout)| { - (key == &drag.key).then_some((key, child, state, child_layout, drag)) - }) - }); - - for (((key, child), child_tree), child_layout) in self - .keys - .iter() - .zip(&self.children) - .zip(&tree.children) - .zip(layout.children()) - { - if drag_key.is_some_and(|drag_key| drag_key == key) { - continue; - } - - let child_layout = child_layout.with_virtual_offset(layout.virtual_offset()); - let child_bounds = child_layout.bounds(); - let base_local = Point::new(child_bounds.x - bounds.x, child_bounds.y - bounds.y); - let target_local = - state.current_slot_position(key, base_local, self.animation_duration); - let translation = - Vector::new(target_local.x - base_local.x, target_local.y - base_local.y); - let translated_bounds = translate_rect(child_bounds, translation); - - if translated_bounds.intersects(viewport) { - renderer.with_translation(translation, |renderer| { - child.as_widget().draw( - child_tree, - renderer, - theme, - style, - child_layout, - cursor, - viewport, - ); - }); - } - } - - if let Some((_key, child, child_tree, child_layout, drag)) = drag_item { - let child_layout = child_layout.with_virtual_offset(layout.virtual_offset()); - let child_bounds = child_layout.bounds(); - let base_local = Point::new(child_bounds.x - bounds.x, child_bounds.y - bounds.y); - let drag_local = Point::new( - drag.cursor_local.x - drag.pointer_offset.x, - drag.cursor_local.y - drag.pointer_offset.y - self.drag_lift, - ); - let translation = Vector::new(drag_local.x - base_local.x, drag_local.y - base_local.y); - - #[cfg(feature = "wgpu")] - { - let translated_bounds = translate_rect(child_bounds, translation); - draw_drag_backdrop(renderer, theme, translated_bounds); - } - - renderer.with_translation(translation, |renderer| { - child.as_widget().draw( - child_tree, - renderer, - theme, - style, - child_layout, - cursor, - viewport, - ); - }); - } - } - - fn overlay<'b>( - &'b mut self, - tree: &'b mut Tree, - layout: Layout<'b>, - renderer: &Renderer, - viewport: &Rectangle, - translation: Vector, - ) -> Option> { - overlay::from_children( - &mut self.children, - tree, - layout, - renderer, - viewport, - translation, - ) - } - - fn drag_destinations( - &self, - state: &Tree, - layout: Layout<'_>, - renderer: &Renderer, - dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, - ) { - for ((item, child_layout), child_state) in self - .children - .iter() - .zip(layout.children()) - .zip(state.children.iter()) - { - item.as_widget().drag_destinations( - child_state, - child_layout.with_virtual_offset(layout.virtual_offset()), - renderer, - dnd_rectangles, - ); - } - } -} - -impl<'a, Key, Message> From> for Element<'a, Message> -where - Key: Clone + Eq + Hash + 'static, - Message: 'a, -{ - fn from(row: ReorderableFlexRow<'a, Key, Message>) -> Self { - Element::new(row) - } -} - -/// Create a horizontal flex row with drag-to-reorder behavior. -pub fn reorderable_flex_row<'a, Key, Message>( - on_reorder: impl Fn(Vec) -> Message + 'a, -) -> ReorderableFlexRow<'a, Key, Message> -where - Key: Clone + Eq + Hash + 'static, -{ - ReorderableFlexRow::new(on_reorder) -} - -fn compute_wrapped_slots( - ordered_keys: &[Key], - locked_by_key: &HashMap, - size_by_key: &HashMap, - wrap_width: f32, - padding: Padding, - spacing: f32, - align: Alignment, -) -> (Vec>, Size) -where - Key: Clone + Eq + Hash + 'static, -{ - let wrap_width = if wrap_width.is_finite() { - wrap_width.max(0.0) - } else { - f32::INFINITY - }; - - let mut slots = Vec::with_capacity(ordered_keys.len()); - let mut intrinsic_size = Size::ZERO; - let mut row_start = 0; - let mut row_height = 0.0; - let mut x = 0.0; - let mut y = 0.0; - - let align_factor = match align { - Alignment::Start => 0.0, - Alignment::Center => 2.0, - Alignment::End => 1.0, - }; - - let align_row = - |range: std::ops::Range, row_height: f32, slots: &mut [LocalSlot]| { - if align_factor == 0.0 { - return; - } - - for slot in &mut slots[range] { - slot.bounds.y += (row_height - slot.bounds.height) / align_factor; - } - }; - - for (index, key) in ordered_keys.iter().enumerate() { - let size = size_by_key.get(key).copied().unwrap_or(Size::ZERO); - - if x != 0.0 && x + size.width > wrap_width { - intrinsic_size.width = intrinsic_size.width.max(x - spacing); - align_row(row_start..index, row_height, &mut slots); - y += row_height + spacing; - x = 0.0; - row_start = index; - row_height = 0.0; - } - - row_height = row_height.max(size.height); - - slots.push(LocalSlot { - key: key.clone(), - locked: locked_by_key.get(key).copied().unwrap_or(false), - bounds: Rectangle { - x: x + padding.left, - y: y + padding.top, - width: size.width, - height: size.height, - }, - }); - - x += size.width + spacing; - } - - if x != 0.0 { - intrinsic_size.width = intrinsic_size.width.max(x - spacing); - } - - intrinsic_size.height = y + row_height; - align_row(row_start..slots.len(), row_height, &mut slots); - - (slots, intrinsic_size) -} - -fn reordered_keys_for_drag( - ordered_keys: &[Key], - locked_by_key: &HashMap, - dragged_key: &Key, - target_index: usize, -) -> Vec -where - Key: Clone + Eq + Hash + 'static, -{ - let movable_keys: Vec = ordered_keys - .iter() - .filter(|key| !locked_by_key.get(*key).copied().unwrap_or(false)) - .cloned() - .collect(); - let mut remaining: Vec = movable_keys - .iter() - .filter(|key| *key != dragged_key) - .cloned() - .collect(); - - remaining.insert(target_index.min(remaining.len()), dragged_key.clone()); - merge_locked_and_reordered_keys(ordered_keys, locked_by_key, &remaining) -} - -/// Picks insertion index among movable items using row-aware midpoint rule. -/// -/// Walks laid-out slots in reading order, counting how many non-dragged movable -/// items the cursor has moved past. Skips locked slots. O(n) single pass, no -/// allocations. -fn target_index_for_drag( - slots: &[LocalSlot], - dragged_key: &Key, - dragged_center: Point, -) -> usize -where - Key: Clone + Eq + Hash + 'static, -{ - let mut target = 0; - let mut passed_movable: usize = 0; - let mut found_target = false; - - let mut i = 0; - while i < slots.len() { - let slot = &slots[i]; - - if slot.locked || &slot.key == dragged_key { - i += 1; - continue; - } - - let row_top = slot.bounds.y; - let row_bottom = row_top + slot.bounds.height; - - if !found_target && dragged_center.y < row_top { - target = passed_movable; - found_target = true; - break; - } - - if dragged_center.y > row_bottom { - passed_movable += 1; - i += 1; - continue; - } - - let center_x = slot.bounds.x + slot.bounds.width / 2.0; - if dragged_center.x < center_x { - target = passed_movable; - found_target = true; - break; - } - - passed_movable += 1; - i += 1; - } - - if !found_target { - target = passed_movable; - } - - target -} - -fn reorderable_index_for_child(locked: &[bool], item_index: usize) -> Option { - (!locked.get(item_index).copied().unwrap_or(false)).then(|| { - locked[..item_index] - .iter() - .filter(|is_locked| !**is_locked) - .count() - }) -} - -fn merge_locked_and_reordered_keys( - ordered_keys: &[Key], - locked_by_key: &HashMap, - reordered_movable_keys: &[Key], -) -> Vec -where - Key: Clone + Eq + Hash + 'static, -{ - let mut movable = reordered_movable_keys.iter(); - - ordered_keys - .iter() - .map(|key| { - if locked_by_key.get(key).copied().unwrap_or(false) { - key.clone() - } else { - movable.next().cloned().unwrap_or_else(|| key.clone()) - } - }) - .collect() -} - -fn approx_eq_point(a: Point, b: Point) -> bool { - (a.x - b.x).abs() <= POSITION_EPSILON && (a.y - b.y).abs() <= POSITION_EPSILON -} - -fn translate_rect(bounds: Rectangle, translation: Vector) -> Rectangle { - Rectangle { - x: bounds.x + translation.x, - y: bounds.y + translation.y, - width: bounds.width, - height: bounds.height, - } -} - -#[cfg(feature = "wgpu")] -fn draw_drag_backdrop(renderer: &mut Renderer, theme: &crate::Theme, bounds: Rectangle) { - let cosmic = theme.cosmic(); - let backdrop = iced::Color { - a: 0.08, - ..iced::Color::from(cosmic.bg_component_color()) - }; - - renderer.fill_quad( - renderer::Quad { - bounds, - border: Border { - radius: cosmic.corner_radii.radius_m.into(), - ..Border::default() - }, - shadow: Shadow { - color: cosmic.shade.into(), - offset: Vector::new(0.0, 8.0), - blur_radius: SHADOW_BLUR_RADIUS, - }, - snap: true, - }, - Background::Color(backdrop), - ); -} - -#[cfg(test)] -mod tests { - use super::{compute_wrapped_slots, reordered_keys_for_drag, target_index_for_drag}; - use iced::{Alignment, Padding, Point, Size}; - use std::collections::HashMap; - - fn size_map(keys: &[usize], width: f32, height: f32) -> HashMap { - keys.iter() - .copied() - .map(|key| (key, Size::new(width, height))) - .collect() - } - - fn locked_map(keys: &[usize], locked_keys: &[usize]) -> HashMap { - keys.iter() - .copied() - .map(|key| (key, locked_keys.contains(&key))) - .collect() - } - - #[test] - fn compute_wrapped_slots_creates_new_rows() { - let ordered_keys = vec![0, 1, 2]; - let locked_by_key = locked_map(&ordered_keys, &[]); - let size_by_key = size_map(&ordered_keys, 100.0, 40.0); - let (slots, intrinsic_size) = compute_wrapped_slots( - &ordered_keys, - &locked_by_key, - &size_by_key, - 220.0, - Padding::ZERO, - 10.0, - Alignment::Start, - ); - - assert_eq!(slots[0].bounds.x, 0.0); - assert_eq!(slots[0].bounds.y, 0.0); - assert_eq!(slots[1].bounds.x, 110.0); - assert_eq!(slots[1].bounds.y, 0.0); - assert_eq!(slots[2].bounds.x, 0.0); - assert_eq!(slots[2].bounds.y, 50.0); - assert_eq!(intrinsic_size.width, 210.0); - assert_eq!(intrinsic_size.height, 90.0); - } - - #[test] - fn reordered_keys_for_drag_inserts_key_at_target_index() { - let keys = [0, 1, 2, 3]; - let locked_by_key = locked_map(&keys, &[]); - let reordered = reordered_keys_for_drag(&keys, &locked_by_key, &0, 3); - assert_eq!(reordered, vec![1, 2, 3, 0]); - } - - #[test] - fn target_index_tracks_wrapped_drop_positions() { - let ordered_keys = vec![0, 1, 2, 3]; - let locked_by_key = locked_map(&ordered_keys, &[]); - let size_by_key = size_map(&ordered_keys, 100.0, 40.0); - - let (slots, _) = compute_wrapped_slots( - &ordered_keys, - &locked_by_key, - &size_by_key, - 220.0, - Padding::ZERO, - 10.0, - Alignment::Start, - ); - - let target_index = target_index_for_drag(&slots, &0, Point::new(160.0, 70.0)); - - assert_eq!(target_index, 3); - } - - #[test] - fn reordered_keys_for_drag_preserves_locked_positions() { - let keys = [10, 11, 12, 13]; - let locked_by_key = locked_map(&keys, &[10, 13]); - let reordered = reordered_keys_for_drag(&keys, &locked_by_key, &11, 1); - - assert_eq!(reordered, vec![10, 12, 11, 13]); - } -} diff --git a/src/widget/responsive_container.rs b/src/widget/responsive_container.rs index 14b7a030..b9b6a289 100644 --- a/src/widget/responsive_container.rs +++ b/src/widget/responsive_container.rs @@ -1,7 +1,7 @@ //! Responsive Container, which will notify of size changes. use iced::{Limits, Size}; -use iced_core::event::Event; +use iced_core::event::{self, Event}; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; diff --git a/src/widget/responsive_menu_bar.rs b/src/widget/responsive_menu_bar.rs index 86b7f0d2..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 = "wayland", target_os = "linux"))] + #[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 = "wayland", target_os = "linux")))] + #[cfg(not(all(feature = "winit", feature = "wayland", target_os = "linux")))] { ItemWidth::Static(84) } diff --git a/src/widget/segmented_button/model/mod.rs b/src/widget/segmented_button/model/mod.rs index fff1cf6b..e0dd8c54 100644 --- a/src/widget/segmented_button/model/mod.rs +++ b/src/widget/segmented_button/model/mod.rs @@ -132,10 +132,7 @@ where /// ``` #[inline] pub fn clear(&mut self) { - // `remove()` mutates `self.order`, so transfer ownership first: - // the inner `self.order.remove(index)` then no-ops because - // `position()` can't find the entity in the empty VecDeque. - for entity in std::mem::take(&mut self.order) { + for entity in self.order.clone() { self.remove(entity); } } diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 9edf3e3b..44ca8574 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -173,10 +173,6 @@ where pub(super) on_context: Option Message + 'static>>, #[setters(skip)] pub(super) on_middle_press: Option Message + 'static>>, - /// Emits the ID of the item that was double-clicked with the left button. - /// Fires in addition to `on_activate` (which keeps firing on each click). - #[setters(skip)] - pub(super) on_double_click: Option Message + 'static>>, #[setters(skip)] pub(super) on_dnd_drop: Option, String, DndAction) -> Message + 'static>>, @@ -236,7 +232,6 @@ where on_close: None, on_context: None, on_middle_press: None, - on_double_click: None, on_dnd_drop: None, on_dnd_enter: None, on_dnd_leave: None, @@ -359,16 +354,6 @@ where self } - /// Emitted when a tab is double-clicked with the left mouse button. - /// Fires in addition to `on_activate`, which keeps firing on each click. - pub fn on_double_click(mut self, on_double_click: T) -> Self - where - T: Fn(Entity) -> Message + 'static, - { - self.on_double_click = Some(Box::new(on_double_click)); - self - } - /// Enable drag-and-drop support for tabs using the provided payload builder. pub fn enable_tab_drag(mut self, mime: String) -> Self { self.tab_drag = Some(TabDragSource::new(mime)); @@ -406,12 +391,11 @@ where { return None; } - // Always use positional swap (Konsole/Firefox/Chrome semantics): - // dropping onto any part of a different tab swaps it with the dragged - // tab. drop_hint.side-based Before/After is counter-intuitive: dropping - // A (pos 0) on the left half of B (pos 1) resolves to "Before B" which, - // after removing A, lands at pos 0 — so the tab appears not to move. - let position = self.default_insert_position(dragged, target); + 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, @@ -692,10 +676,11 @@ where if let Some(icon) = self.model.icon(key) { non_text_width += f32::from(icon.size) + f32::from(self.button_spacing); - } else if self.model.is_active(key) - && let crate::theme::SegmentedButton::Control = self.style { + } 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 += @@ -927,7 +912,6 @@ where hovered: Default::default(), known_length: Default::default(), middle_clicked: Default::default(), - last_click: None, internal_layout: Default::default(), context_cursor: Point::default(), show_context: Default::default(), @@ -1082,10 +1066,11 @@ where { state.drop_hint = None; self.emit_drop_hint(shell, state.drop_hint); - if let Some(Some(entity)) = entity - && let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() { + if let Some(Some(entity)) = entity { + if let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() { shell.publish(on_dnd_leave(entity)); } + } log::trace!( target: TAB_REORDER_LOG_TARGET, "offer leave id={my_id:?} entity={entity:?}" @@ -1161,10 +1146,11 @@ where None:: Message>, None, ); - if let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() - && let Some(Some(entity)) = entity { + if let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() { + if let Some(Some(entity)) = entity { shell.publish(on_dnd_leave(entity)); } + } } } DndEvent::Offer(id, OfferEvent::Drop) if Some(my_id) == *id => { @@ -1199,14 +1185,7 @@ where .dnd_state .drag_offer .as_ref() - .is_some_and(|offer| offer.selected_action.contains(DndAction::Move)) - // Self-drop fallback: some compositors (cosmic-comp - // observed) do not emit OfferEvent::SelectedAction for - // internal drags, leaving selected_action empty. - // dragging_tab is only set by start_tab_drag on this - // same widget, so this covers the self-drop case - // safely; mime and on_reorder are checked below. - || state.dragging_tab.is_some(); + .is_some_and(|offer| offer.selected_action.contains(DndAction::Move)); let pending_reorder = if allow_reorder && self.on_reorder.is_some() && self.tab_drag.as_ref().is_some_and(|d| d.mime == *mime_type) @@ -1337,8 +1316,8 @@ where // Emit close message if the close button is pressed. if let Some(on_close) = self.on_close.as_ref() { if over_close_button - && (left_button_released(event) - || (touch_lifted(event) && fingers_pressed == 1)) + && (left_button_released(&event) + || (touch_lifted(&event) && fingers_pressed == 1)) { shell.publish(on_close(key)); shell.capture_event(); @@ -1392,45 +1371,18 @@ where } } - if is_lifted(event) { + if is_lifted(&event) { state.unfocus(); } if let Some(on_activate) = self.on_activate.as_ref() { if is_pressed(event) { state.pressed_item = Some(Item::Tab(key)); - } else if is_lifted(event) && self.button_is_pressed(state, key) { + } else if is_lifted(&event) && self.button_is_pressed(state, key) { shell.publish(on_activate(key)); state.set_focused(); state.focused_item = Item::Tab(key); state.pressed_item = None; - - // Double-click detection on the same entity - // within 400 ms — fires after on_activate so - // the tab is already focused when the handler - // runs. - if let Some(on_double_click) = - self.on_double_click.as_ref() - { - let now = Instant::now(); - let is_double = match state.last_click { - Some((prev, t)) => { - prev == key - && now.duration_since(t) - < Duration::from_millis(400) - } - None => false, - }; - state.last_click = if is_double { - None - } else { - Some((key, now)) - }; - if is_double { - shell.publish(on_double_click(key)); - } - } - shell.capture_event(); return; } @@ -1439,8 +1391,8 @@ where // Present a context menu on a right click event. if self.context_menu.is_some() && let Some(on_context) = self.on_context.as_ref() - && (right_button_released(event) - || (touch_lifted(event) && fingers_pressed == 2)) + && (right_button_released(&event) + || (touch_lifted(&event) && fingers_pressed == 2)) { state.show_context = Some(key); state.context_cursor = cursor_position.position().unwrap_or_default(); @@ -1536,12 +1488,12 @@ where } if state.is_focused() { // Unfocus on clicks outside of the boundaries of the segmented button. - if is_pressed(event) { + if is_pressed(&event) { state.unfocus(); state.pressed_item = None; return; } - } else if is_lifted(event) { + } else if is_lifted(&event) { state.pressed_item = None; } } @@ -2160,8 +2112,8 @@ where ); } - if show_drop_hint_marker - && matches!( + if show_drop_hint_marker { + if matches!( drop_hint_marker, Some(DropHint { entity, @@ -2176,6 +2128,7 @@ where appearance.active.text_color, ); } + } nth += 1; }); @@ -2360,7 +2313,7 @@ where } } -pub(super) struct TabDragSource { +struct TabDragSource { mime: String, threshold: f32, _marker: PhantomData, @@ -2411,6 +2364,7 @@ struct TabDragCandidate { #[derive(Debug, Clone, Copy)] struct Focus { updated_at: Instant, + now: Instant, } /// State that is maintained by each individual widget. @@ -2433,9 +2387,6 @@ pub struct LocalState { hovered: Item, /// The ID of the button that was middle-clicked, but not yet released. middle_clicked: Option, - /// Entity and timestamp of the most recent left-click activation, used - /// to detect double-clicks on the same tab. - last_click: Option<(Entity, Instant)>, /// Last known length of the model. pub(super) known_length: usize, /// Dimensions of internal buttons when shrinking @@ -2483,6 +2434,7 @@ impl LocalState { self.focused = Some(Focus { updated_at: now, + now, }); } } @@ -2580,7 +2532,6 @@ mod tests { hovered: Item::default(), known_length: 0, middle_clicked: None, - last_click: None, internal_layout: Vec::new(), context_cursor: Point::ORIGIN, show_context: None, diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index 3ff0e7bf..833e90b8 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -26,7 +26,7 @@ pub fn spin_button<'a, T, M>( where T: Copy + Sub + Add + PartialOrd, { - let button = SpinButton::new( + let mut button = SpinButton::new( label, value, step, @@ -37,11 +37,9 @@ where ); #[cfg(feature = "a11y")] - let button = { - let mut button = button; + { button = button.name(name.into()); - button - }; + } button } @@ -59,22 +57,20 @@ pub fn vertical<'a, T, M>( where T: Copy + Sub + Add + PartialOrd, { - let button = SpinButton::new( + let mut button = SpinButton::new( label, value, step, min, max, - Orientation::Vertical, + Orientation::Horizontal, on_press, ); #[cfg(feature = "a11y")] - let button = { - let mut button = button; + { button = button.name(name.into()); - button - }; + } button } diff --git a/src/widget/table/model/mod.rs b/src/widget/table/model/mod.rs index 749860f8..d6250eaf 100644 --- a/src/widget/table/model/mod.rs +++ b/src/widget/table/model/mod.rs @@ -100,10 +100,7 @@ where /// model.clear(); /// ``` pub fn clear(&mut self) { - // `remove()` mutates `self.order`, so transfer ownership first: - // the inner `self.order.remove(index)` then no-ops because - // `position()` can't find the entity in the empty VecDeque. - for entity in std::mem::take(&mut self.order) { + for entity in self.order.clone() { self.remove(entity); } } diff --git a/src/widget/table/widget/standard.rs b/src/widget/table/widget/standard.rs index c4cd648c..9ab76c9d 100644 --- a/src/widget/table/widget/standard.rs +++ b/src/widget/table/widget/standard.rs @@ -88,14 +88,15 @@ where let mut sort_state = 0; - if let Some(sort) = val.model.sort - && sort.0 == category { + 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::with_capacity(2) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 903c81d1..4336c757 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -21,12 +21,12 @@ use apply::Apply; use iced::Limits; use iced::clipboard::dnd::{DndAction, DndEvent, OfferEvent, SourceEvent}; use iced::clipboard::mime::AsMimeTypes; -use iced_core::event::Event; +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; @@ -670,15 +670,18 @@ where let old_value = Value::new(&old_value); if state.is_focused() && let cursor::State::Index(index) = state.cursor.state(&old_value) - && index == old_value.len() { + { + 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) - && f.updated_at != LAST_FOCUS_UPDATE.with(|f| f.get()) { + 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; } + } if self.is_editable_variant { if !state.is_focused() { @@ -826,7 +829,7 @@ where &mut self, tree: &mut Tree, layout: Layout<'_>, - _renderer: &crate::Renderer, + renderer: &crate::Renderer, operation: &mut dyn Operation, ) { operation.container(Some(&self.id), layout.bounds()); @@ -892,8 +895,8 @@ where let line_height = self.line_height; // Disables editing of the editable variant when clicking outside of, or for tab focus changes. - if self.is_editable_variant - && let Some(ref on_edit) = self.on_toggle_edit { + 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_some_and(|f| !f.focused) { state.is_read_only = true; @@ -905,6 +908,7 @@ where shell.publish((on_edit)(f.focused)); } } + } // Calculates the layout of the trailing icon button element. if !tree.children.is_empty() { @@ -915,9 +919,9 @@ where trailing_icon_layout = Some(text_layout.children().last().unwrap()); // Enable custom buttons defined on the trailing icon position to be handled. - if !self.is_editable_variant - && let Some(trailing_layout) = trailing_icon_layout { - trailing_icon.as_widget_mut().update( + if !self.is_editable_variant { + if let Some(trailing_layout) = trailing_icon_layout { + let res = trailing_icon.as_widget_mut().update( tree, event, trailing_layout, @@ -932,16 +936,18 @@ where return; } } + } } } let state = tree.state.downcast_mut::(); - if let Some(on_unfocus) = self.on_unfocus.as_ref() - && state.emit_unfocus { + if let Some(on_unfocus) = self.on_unfocus.as_ref() { + if state.emit_unfocus { state.emit_unfocus = false; shell.publish(on_unfocus.clone()); } + } let dnd_id = self.dnd_id(); let id = Widget::id(self); @@ -1650,10 +1656,11 @@ pub fn update<'a, Message: Clone + 'static>( if matches!(state.dragging_state, None | Some(DraggingState::Selection)) && (!state.is_focused() || (is_editable_variant && state.is_read_only)) { - if !state.is_focused() - && let Some(on_focus) = on_focus { + 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; @@ -1678,6 +1685,7 @@ pub fn update<'a, Message: Clone + 'static>( state.last_click = Some(click); shell.capture_event(); + return; } else { state.unfocus(); @@ -1713,6 +1721,7 @@ pub fn update<'a, Message: Clone + 'static>( if cursor.is_over(layout.bounds()) { shell.capture_event(); } + return; } Event::Mouse(mouse::Event::CursorMoved { position }) | Event::Touch(touch::Event::FingerMoved { position, .. }) => { @@ -1795,6 +1804,7 @@ pub fn update<'a, Message: Clone + 'static>( } shell.capture_event(); + return; } } Event::Keyboard(keyboard::Event::KeyPressed { @@ -1819,13 +1829,14 @@ pub fn update<'a, Message: Clone + 'static>( if state.keyboard_modifiers.command() { match key.to_latin(*physical_key) { Some('c') => { - if !is_secure - && let Some((start, end)) = state.cursor.selection(value) { + 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 @@ -2080,6 +2091,7 @@ pub fn update<'a, Message: Clone + 'static>( } shell.capture_event(); + return; } } Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => { @@ -2099,6 +2111,7 @@ pub fn update<'a, Message: Clone + 'static>( } shell.capture_event(); + return; } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { @@ -2114,6 +2127,7 @@ pub fn update<'a, Message: Clone + 'static>( state.preedit = matches!(event, input_method::Event::Opened) .then(input_method::Preedit::new); shell.capture_event(); + return; } input_method::Event::Preedit(content, selection) => { if state.is_focused() { @@ -2123,6 +2137,7 @@ pub fn update<'a, Message: Clone + 'static>( text_size: Some(size.into()), }); shell.capture_event(); + return; } } input_method::Event::Commit(text) => { @@ -2140,7 +2155,7 @@ pub fn update<'a, Message: Clone + 'static>( LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at)); let mut editor = Editor::new(unsecured_value, &mut state.cursor); - editor.paste(Value::new(text)); + editor.paste(Value::new(&text)); let contents = editor.contents(); let unsecured_value = Value::new(&contents); @@ -2160,6 +2175,7 @@ pub fn update<'a, Message: Clone + 'static>( update_cache(state, &value); shell.capture_event(); + return; } } } @@ -2189,6 +2205,7 @@ pub fn update<'a, Message: Clone + 'static>( // TODO: restore value in text input state.dragging_state = None; shell.capture_event(); + return; } } #[cfg(all(feature = "wayland", target_os = "linux"))] @@ -2198,12 +2215,12 @@ pub fn update<'a, Message: Clone + 'static>( x, y, mime_types, - surface: _surface, + surface, }, )) if *rectangle == Some(dnd_id) => { cold(); let state = state(); - let _is_clicked = text_layout.bounds().contains(Point { + let is_clicked = text_layout.bounds().contains(Point { x: *x as f32, y: *y as f32, }); @@ -2211,7 +2228,7 @@ pub fn update<'a, Message: Clone + 'static>( let mut accepted = false; for m in mime_types { if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) { - let _clone = m.clone(); + let clone = m.clone(); accepted = true; } } @@ -2238,10 +2255,11 @@ pub fn update<'a, Message: Clone + 'static>( state.cursor.set_affinity(affinity); state.cursor.move_to(position); shell.capture_event(); + return; } } #[cfg(all(feature = "wayland", target_os = "linux"))] - Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y: _ })) + Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y })) if *rectangle == Some(dnd_id) => { let state = state(); @@ -2266,13 +2284,14 @@ pub fn update<'a, Message: Clone + 'static>( state.cursor.set_affinity(affinity); state.cursor.move_to(position); shell.capture_event(); + return; } #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if *rectangle == Some(dnd_id) => { cold(); let state = state(); if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() { - let Some(_mime_type) = SUPPORTED_TEXT_MIME_TYPES + let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES .iter() .find(|&&m| mime_types.iter().any(|t| t == m)) else { @@ -2282,12 +2301,14 @@ pub fn update<'a, Message: Clone + 'static>( }; state.dnd_offer = DndOfferState::Dropped; } + + return; } #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if Some(dnd_id) != *id => {} #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer( - _rectangle, + rectangle, OfferEvent::Leave | OfferEvent::LeaveDestination, )) => { cold(); @@ -2301,6 +2322,7 @@ pub fn update<'a, Message: Clone + 'static>( } }; shell.capture_event(); + return; } #[cfg(all(feature = "wayland", target_os = "linux"))] Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type })) @@ -2337,7 +2359,9 @@ pub fn update<'a, Message: Clone + 'static>( }; update_cache(state, &value); shell.capture_event(); + return; } + return; } _ => {} } @@ -2587,7 +2611,7 @@ pub fn draw<'a, Message>( let handling_dnd_offer = !matches!(state.dnd_offer, DndOfferState::None); #[cfg(not(all(feature = "wayland", target_os = "linux")))] let handling_dnd_offer = false; - let (cursors, offset, _is_selecting) = if let Some(focus) = + let (cursors, offset, is_selecting) = if let Some(focus) = state.is_focused.filter(|f| f.focused).or_else(|| { let now = Instant::now(); handling_dnd_offer.then_some(Focus { @@ -2770,11 +2794,11 @@ pub fn draw<'a, Message>( // draw the end icon in the text input if let (Some(icon), Some(tree)) = (trailing_icon, trailing_icon_tree) { let mut children = text_layout.children(); - children.next().unwrap(); // skip text layout + let mut icon_layout = children.next().unwrap(); if has_start_icon { - children.next().unwrap(); // skip start-icon layout + icon_layout = children.next().unwrap(); } - let icon_layout = children.next().unwrap(); // trailing-icon layout + icon_layout = children.next().unwrap(); icon.as_widget().draw( tree, diff --git a/src/widget/text_input/value.rs b/src/widget/text_input/value.rs index 59e77752..3f7b8d73 100644 --- a/src/widget/text_input/value.rs +++ b/src/widget/text_input/value.rs @@ -45,7 +45,9 @@ impl Value { pub fn previous_start_of_word(&self, index: usize) -> usize { let previous_string = &self.graphemes[..index.min(self.graphemes.len())].concat(); - UnicodeSegmentation::split_word_bound_indices(previous_string as &str).rfind(|(_, word)| !word.trim_start().is_empty()) + UnicodeSegmentation::split_word_bound_indices(previous_string as &str) + .filter(|(_, word)| !word.trim_start().is_empty()) + .next_back() .map_or(0, |(i, previous_word)| { index - UnicodeSegmentation::graphemes(previous_word, true).count() diff --git a/src/widget/toaster/widget.rs b/src/widget/toaster/widget.rs index 9b6939e7..de47a9bd 100644 --- a/src/widget/toaster/widget.rs +++ b/src/widget/toaster/widget.rs @@ -6,7 +6,7 @@ use iced_core::layout::Node; use iced_core::Element; use iced_core::Overlay; -use iced_core::event::Event; +use iced_core::event::{self, Event}; use iced_core::layout; use iced_core::mouse; use iced_core::overlay; @@ -154,7 +154,7 @@ where translation, ) } else { - let _bounds = layout.bounds(); + let bounds = layout.bounds(); Some(overlay::Element::new(Box::new(ToasterOverlay::new( &mut state.children[1], diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index 5e5bc2ee..b95b596e 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -1,10 +1,11 @@ //! Show toggle controls using togglers. -use std::time::Duration; +use std::time::{Duration, Instant}; use crate::{Element, anim}; use iced_core::{ - Border, Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment, 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}, @@ -12,7 +13,7 @@ use iced_core::{ }; use iced_widget::{Id, toggler::Status}; -pub use iced_widget::toggler::Catalog; +pub use iced_widget::toggler::{Catalog, Style}; pub fn toggler<'a, Message>(is_checked: bool) -> Toggler<'a, Message> { Toggler::new(is_checked) @@ -182,8 +183,7 @@ impl<'a, Message> Widget for Toggler<'a, ) -> layout::Node { let limits = limits.width(self.width); - - next_to_each_other( + let res = next_to_each_other( &limits, self.spacing, |limits| { @@ -222,7 +222,8 @@ impl<'a, Message> Widget for Toggler<'a, } }, |_| layout::Node::new(Size::new(48., 24.)), - ) + ); + res } fn update( @@ -260,7 +261,7 @@ impl<'a, Message> Widget for Toggler<'a, shell.capture_event(); } } - Event::Window(window::Event::RedrawRequested(_now)) => { + Event::Window(window::Event::RedrawRequested(now)) => { state.anim.anim_done(self.duration); if state.anim.last_change.is_some() { shell.request_redraw(); @@ -370,7 +371,7 @@ impl<'a, Message> Widget for Toggler<'a, }, style.background, ); - let t = state.anim.t(self.duration, self.is_toggled); + let mut t = state.anim.t(self.duration, self.is_toggled); let toggler_foreground_bounds = Rectangle { x: bounds.x diff --git a/src/widget/wayland/tooltip/widget.rs b/src/widget/wayland/tooltip/widget.rs index 0526e8db..7bf0991a 100644 --- a/src/widget/wayland/tooltip/widget.rs +++ b/src/widget/wayland/tooltip/widget.rs @@ -13,7 +13,7 @@ use std::time::Duration; use iced::Task; use iced_runtime::core::widget::Id; -use iced_core::event::Event; +use iced_core::event::{self, Event}; use iced_core::renderer; use iced_core::touch; use iced_core::widget::Operation; @@ -23,10 +23,18 @@ use iced_core::{ }; use iced_core::{Border, mouse}; use iced_core::{Shadow, overlay}; -use iced_core::layout; +use iced_core::{layout, svg}; 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)] @@ -302,7 +310,7 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> } let content_layout = layout.children().next().unwrap(); - let _state = tree.state.downcast_ref::(); + let state = tree.state.downcast_ref::(); let styling = theme.style(&self.style); @@ -456,6 +464,7 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>( shell.publish(on_leave.clone()); shell.capture_event(); + return; } } @@ -483,8 +492,8 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>( } } else { *guard = cursor.is_over(bounds); - if *guard - && let Some(settings) = settings { + if *guard { + if let Some(settings) = settings { if let Some(delay) = delay { let s = settings.clone(); let view = view.clone(); @@ -568,6 +577,7 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>( shell.publish((on_surface_action)(sm)); } } + } } } _ => {} diff --git a/src/widget/wrapper.rs b/src/widget/wrapper.rs index b27bb9ff..133f9b87 100644 --- a/src/widget/wrapper.rs +++ b/src/widget/wrapper.rs @@ -6,7 +6,7 @@ use std::{ }; use crate::Element; -use iced::{Length, Rectangle, Size}; +use iced::{Length, Rectangle, Size, event}; use iced_core::{Widget, id::Id, widget, widget::tree}; #[derive(Debug)]