diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a5258f3..7897eb01 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,20 +6,26 @@ on: - master pull_request: + jobs: format: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout sources - uses: actions/checkout@v6 + uses: actions/checkout@v3 - name: Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: - toolchain: nightly components: rustfmt - rustflags: "" + - name: Cargo cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-cargo-rust_stable-${{ hashFiles('**/Cargo.toml') }} - name: Format - run: cargo +nightly fmt -- --check + run: cargo fmt -- --check tests: needs: @@ -28,28 +34,38 @@ jobs: fail-fast: false matrix: test_args: - - --no-default-features --features "" # for cosmic-comp, don't remove! - - --no-default-features --features "winit_debug" - - --no-default-features --features "winit_tokio" - - --no-default-features --features "winit" - - --no-default-features --features "winit_wgpu" - - --no-default-features --features "wayland" - - --no-default-features --features "applet" - - --no-default-features --features "desktop,smol" - - --no-default-features --features "desktop,tokio" - - -p cosmic-theme - runs-on: ubuntu-latest + - --no-default-features --features "" # for cosmic-comp, don't remove! + - --no-default-features --features "winit_debug" + - --no-default-features --features "winit_tokio" + - --no-default-features --features "winit" + - --no-default-features --features "winit_wgpu" + - --no-default-features --features "wayland" + - --no-default-features --features "applet" + - --no-default-features --features "desktop,smol" + - --no-default-features --features "desktop,tokio" + - -p cosmic-theme + runs-on: ubuntu-22.04 steps: - name: Checkout sources - uses: actions/checkout@v6 + uses: actions/checkout@v3 with: submodules: recursive + - name: Get date for registry cache + id: date + run: echo "::set-output name=date::$(date +'%Y-%m-%d')" + - name: Cargo registry cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git + key: ${{ runner.os }}-cargo-registry-${{ steps.date.outputs.date }} + restore-keys: ${{ runner.os }}-cargo-registry- - name: System dependencies run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev - name: Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - rustflags: "" + uses: dtolnay/rust-toolchain@stable - name: Test features run: cargo test ${{ matrix.test_args }} -- --test-threads=1 env: @@ -66,18 +82,28 @@ jobs: - "open-dialog" - "context-menu" - "nav-context" - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout sources - uses: actions/checkout@v6 + uses: actions/checkout@v3 with: submodules: recursive + - name: Get date for registry cache + id: date + run: echo "::set-output name=date::$(date +'%Y-%m-%d')" + - name: Cargo registry cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git + key: ${{ runner.os }}-cargo-registry-${{ steps.date.outputs.date }} + restore-keys: ${{ runner.os }}-cargo-registry- - name: System dependencies run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev - name: Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - rustflags: "" + uses: dtolnay/rust-toolchain@stable - name: Check example run: cargo check -p "${{ matrix.examples }}" env: diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index e51c552f..3e3a042e 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -11,26 +11,25 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@v6 + uses: actions/checkout@v3 with: submodules: recursive - name: Install Rust nightly - uses: actions-rust-lang/setup-rust-toolchain@v1 + uses: dtolnay/rust-toolchain@master with: - toolchain: nightly-2026-04-27 - rustflags: "" + toolchain: nightly-2025-07-31 - name: System dependencies run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev - name: Build documentation run: | RUSTDOCFLAGS="--cfg docsrs" \ - cargo +nightly-2026-04-27 doc --no-deps \ + cargo +nightly-2025-07-31 doc --no-deps \ -p cosmic-client-toolkit \ -p cosmic-protocols \ -p libcosmic \ --verbose --features tokio,winit,wayland,desktop,single-instance,applet,xdg-portal,multi-window - name: Deploy documentation - uses: peaceiris/actions-gh-pages@v4 + uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./target/doc diff --git a/.zed/settings.json b/.zed/settings.json deleted file mode 100644 index 2cc7b98d..00000000 --- a/.zed/settings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "format_on_save": "on", - "lsp": { - "rust-analyzer": { - "initialization_options": { - "check": { - "command": "clippy", - }, - "rustfmt": { - "extraArgs": ["+nightly"], - }, - }, - }, - }, -} diff --git a/Cargo.toml b/Cargo.toml index 9948711a..d73da2dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,23 +1,20 @@ [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.93" +rust-version = "1.90" [lib] 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,17 +119,18 @@ async-std = [ "zbus?/async-io", "iced/async-std", ] +x11 = ["iced/x11", "iced_winit/x11"] [dependencies] apply = "0.3.0" ashpd = { version = "0.12.3", default-features = false, optional = true } async-fs = { version = "2.2", optional = true } -async-std = { workspace = true, optional = true } +async-std = { version = "1.13", optional = true } auto_enums = "0.8.8" -cctk = { path = "../cosmic-protocols/client-toolkit", package = "cosmic-client-toolkit", optional = true } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true } jiff = "0.2" cosmic-config = { path = "cosmic-config" } -cosmic-settings-config = { path = "../cosmic-settings-daemon/config", optional = true } +cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } # Internationalization i18n-embed = { version = "0.16.0", features = [ "fluent-system", @@ -153,36 +150,34 @@ image-extras = { version = "0.1.0", default-features = false, features = [ "xpm", "xbm", ], optional = true } -libc = { version = "0.2.186", optional = true } +libc = { version = "0.2.183", optional = true } log = "0.4" mime = { version = "0.3.17", optional = true } -palette.workspace = true +palette = "0.7.6" rfd = { version = "0.16.0", default-features = false, features = [ "xdg-portal", ], optional = true } rustix = { version = "1.1", features = ["pipe", "process"], optional = true } -serde = { workspace = true, features = ["derive"] } +serde = { version = "1.0.228", features = ["derive"] } slotmap = "1.1.1" smol = { version = "2.0.2", optional = true } -thiserror.workspace = true +thiserror = "2.0.18" taffy = { version = "0.9.2", features = ["grid"] } -tokio = { workspace = true, optional = true } -tracing.workspace = true -unicode-segmentation = "1.13" +tokio = { version = "1.50.0", optional = true } +tracing = "0.1.44" +unicode-segmentation = "1.12" url = "2.5.8" -zbus = { workspace = true, optional = true } +zbus = { version = "5.14.0", default-features = false, optional = true } float-cmp = "0.10.0" -ron = { workspace = true, optional = true } -enumflags2 = "0.7.12" # 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] -freedesktop-icons = { package = "cosmic-freedesktop-icons", path = "../cosmic-freedesktop-icons" } +freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } freedesktop-desktop-entry = { version = "0.8.1", optional = true } shlex = { version = "1.3.0", optional = true } @@ -228,77 +223,35 @@ 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" optional = true [dependencies.cosmic-panel-config] -path = "../cosmic-panel/cosmic-panel-config" +git = "https://github.com/pop-os/cosmic-panel" +# path = "../cosmic-panel/cosmic-panel-config" optional = true -[patch.'https://github.com/pop-os/freedesktop-icons'] -cosmic-freedesktop-icons = { path = "../cosmic-freedesktop-icons" } - -[patch.'https://github.com/pop-os/softbuffer'] -softbuffer = { path = "../softbuffer" } - -[patch.'https://github.com/pop-os/smithay-clipboard'] -smithay-clipboard = { path = "../smithay-clipboard" } - -[patch.'https://github.com/pop-os/winit.git'] -dpi = { path = "../winit/dpi" } -winit = { path = "../winit/winit" } -winit-android = { path = "../winit/winit-android" } -winit-appkit = { path = "../winit/winit-appkit" } -winit-common = { path = "../winit/winit-common" } -winit-core = { path = "../winit/winit-core" } -winit-orbital = { path = "../winit/winit-orbital" } -winit-uikit = { path = "../winit/winit-uikit" } -winit-wayland = { path = "../winit/winit-wayland" } -winit-web = { path = "../winit/winit-web" } -winit-win32 = { path = "../winit/winit-win32" } -winit-x11 = { path = "../winit/winit-x11" } +[dependencies.ron] +version = "0.12" +optional = true [workspace] 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] -async-std = "1.13" -dirs = "6.0" -palette = "0.7" -ron = "0.12" -serde = "1.0" -thiserror = "2.0" -tracing = "0.1" -tokio = "1.52" -zbus = { version = "5.15", default-features = false } - -# 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 +dirs = "6.0.0" [dev-dependencies] tempfile = "3.27.0" diff --git a/README.md b/README.md index 698316db..23da97bc 100644 --- a/README.md +++ b/README.md @@ -20,24 +20,6 @@ While libcosmic is written entirely in Rust, some of its dependencies may requir sudo apt install cargo cmake just libexpat1-dev libfontconfig-dev libfreetype-dev libxkbcommon-dev pkgconf ``` -## Made-for-COSMIC Flatpak IDs - -To identify a project as a COSMIC Application, add `com.system76.CosmicApplication` to the provides section of the project's metainfo. - -```xml - - com.system76.CosmicApplication - -``` - -For COSMIC Applets, use `com.system76.CosmicApplet`. - -```xml - - com.system76.CosmicApplet - -``` - ## Examples Some examples are included in the [examples](./examples) directory to to kickstart your diff --git a/cosmic-config-derive/src/lib.rs b/cosmic-config-derive/src/lib.rs index b49cf447..cc19a91e 100644 --- a/cosmic-config-derive/src/lib.rs +++ b/cosmic-config-derive/src/lib.rs @@ -1,32 +1,17 @@ use proc_macro::TokenStream; use quote::quote; -use syn; +use syn::{self}; -#[proc_macro_derive(CosmicConfigEntry, attributes(version, id, cosmic_config_entry))] +#[proc_macro_derive(CosmicConfigEntry, attributes(version, id))] pub fn cosmic_config_entry_derive(input: TokenStream) -> TokenStream { + // Construct a representation of Rust code as a syntax tree + // that we can manipulate let ast = syn::parse(input).unwrap(); + + // Build the trait implementation impl_cosmic_config_entry_macro(&ast) } -fn get_cosmic_config_attrs(field: &syn::Field) -> Result, syn::Error> { - let mut with = None; - - for attr in &field.attrs { - if !attr.path().is_ident("cosmic_config_entry") { - continue; - } - attr.parse_nested_meta(|meta| { - if meta.path.is_ident("with") { - let value = meta.value()?; - with = Some(value.parse()?); - } - Ok(()) - })?; - } - - Ok(with) -} - fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream { let attributes = &ast.attrs; let version = attributes @@ -63,54 +48,19 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream { let write_each_config_field = fields.iter().map(|field| { let field_name = &field.ident; - let with = match get_cosmic_config_attrs(field) { - Ok(attrs) => attrs, - Err(e) => { - return e.to_compile_error(); - } - }; - - if let Some(with) = with { - quote! { - { - let conv = self.#field_name.clone().into(); - cosmic_config::ConfigSet::set::<#with>(&tx, stringify!(#field_name), conv)?; - } - } - } else { - quote! { - cosmic_config::ConfigSet::set(&tx, stringify!(#field_name), &self.#field_name)?; - } + quote! { + cosmic_config::ConfigSet::set(&tx, stringify!(#field_name), &self.#field_name)?; } }); let get_each_config_field = fields.iter().map(|field| { let field_name = &field.ident; let field_type = &field.ty; - let with = match get_cosmic_config_attrs(field) { - Ok(attrs) => attrs, - Err(e) => { - return e.to_compile_error(); - } - }; - - if let Some(with) = with { - quote! { - match cosmic_config::ConfigGet::get::<#with>(config, stringify!(#field_name)) { - Ok(value) => { - default.#field_name = value.into(); - } - Err(why) if matches!(why, cosmic_config::Error::NoConfigDirectory) => (), - Err(e) => errors.push(e), - } - } - } else { - quote! { - match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) { - Ok(#field_name) => default.#field_name = #field_name, - Err(why) if matches!(why, cosmic_config::Error::NoConfigDirectory) => (), - Err(e) => errors.push(e), - } + quote! { + match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) { + Ok(#field_name) => default.#field_name = #field_name, + Err(why) if matches!(why, cosmic_config::Error::NoConfigDirectory) => (), + Err(e) => errors.push(e), } } }); @@ -118,39 +68,17 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream { let update_each_config_field = fields.iter().map(|field| { let field_name = &field.ident; let field_type = &field.ty; - let with = match get_cosmic_config_attrs(field) { - Ok(attrs) => attrs, - Err(e) => { - return e.to_compile_error(); - } - }; - - if let Some(with) = with { - quote! { - stringify!(#field_name) => { - match cosmic_config::ConfigGet::get::<#with>(config, stringify!(#field_name)) { - Ok(value) => { - let value = value.into(); - if self.#field_name != value { - keys.push(stringify!(#field_name)); - } - self.#field_name = value; - }, - Err(e) => errors.push(e), - } - } - } - } else { - quote! { - stringify!(#field_name) => { - match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) { - Ok(value) => { - if self.#field_name != value { - keys.push(stringify!(#field_name)); - } - self.#field_name = value; - }, - Err(e) => errors.push(e), + quote! { + stringify!(#field_name) => { + match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) { + Ok(value) => { + if self.#field_name != value { + keys.push(stringify!(#field_name)); + } + self.#field_name = value; + }, + Err(e) => { + errors.push(e); } } } diff --git a/cosmic-config/Cargo.toml b/cosmic-config/Cargo.toml index a81d13fd..0a7653e0 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -10,21 +10,21 @@ 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 } notify = "8.2.0" -ron.workspace = true -serde.workspace = true +ron = "0.12.0" +serde = "1.0.228" cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true } iced = { path = "../iced/", default-features = false, optional = true } iced_futures = { path = "../iced/futures/", default-features = false, optional = true } futures-util = { version = "0.3", optional = true } dirs.workspace = true -tokio = { workspace = true, optional = true, features = ["time"] } -async-std = { workspace = true, optional = true } -tracing.workspace = true +tokio = { version = "1.50", optional = true, features = ["time"] } +async-std = { version = "1.13", optional = true } +tracing = "0.1" [target.'cfg(unix)'.dependencies] xdg = "3.0" diff --git a/cosmic-config/src/dbus.rs b/cosmic-config/src/dbus.rs index 9b6e8697..da7bcb68 100644 --- a/cosmic-config/src/dbus.rs +++ b/cosmic-config/src/dbus.rs @@ -1,12 +1,13 @@ -use std::any::TypeId; -use std::ops::Deref; +use std::{any::TypeId, ops::Deref}; use crate::{CosmicConfigEntry, Update}; use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy}; use futures_util::SinkExt; -use iced_futures::futures::future::pending; -use iced_futures::futures::{self, StreamExt}; -use iced_futures::{Subscription, stream}; +use iced_futures::{ + Subscription, + futures::{self, StreamExt, future::pending}, + stream, +}; pub async fn settings_daemon_proxy() -> zbus::Result> { let conn = zbus::Connection::session().await?; diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 1396b5cb..c8eda064 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -1,13 +1,16 @@ //! Integrations for cosmic-config — the cosmic configuration system. -use notify::event::{EventKind, ModifyKind, RenameMode}; -use notify::{RecommendedWatcher, Watcher}; -use serde::Serialize; -use serde::de::DeserializeOwned; -use std::io::Write; -use std::path::{Path, PathBuf}; -use std::sync::Mutex; -use std::{env, fmt, fs}; +use notify::{ + RecommendedWatcher, Watcher, + event::{EventKind, ModifyKind, RenameMode}, +}; +use serde::{Serialize, de::DeserializeOwned}; +use std::{ + env, fmt, fs, + io::Write, + path::{Path, PathBuf}, + sync::Mutex, +}; /// Get the config directory, with Flatpak sandbox support. /// In Flatpak, HOST_XDG_CONFIG_HOME points to the real user config directory, @@ -51,22 +54,6 @@ fn get_state_dir() -> Option { dirs::state_dir() } -/// Get the data directory, with Flatpak sandbox support. -fn get_data_dir() -> Option { - // Check if we're running in Flatpak - if env::var_os("FLATPAK_ID").is_some() { - // Try HOST_XDG_DATA_HOME first - if let Some(host_data) = env::var_os("HOST_XDG_DATA_HOME") { - return Some(PathBuf::from(host_data)); - } - // Fallback: try to construct from HOME - if let Some(home) = env::var_os("HOME") { - return Some(PathBuf::from(home).join(".local").join("share")); - } - } - dirs::data_dir() -} - #[cfg(feature = "subscription")] mod subscription; #[cfg(feature = "subscription")] @@ -175,7 +162,6 @@ pub trait ConfigSet { pub struct Config { system_path: Option, user_path: Option, - previous: Option>, } /// Check that the name is relative and doesn't contain . or .. @@ -194,13 +180,9 @@ fn sanitize_name(name: &str) -> Result<&Path, Error> { impl Config { /// Get a system config for the given name and config version pub fn system(name: &str, version: u64) -> Result { - Self::system_inner(name, version, true) - } - - fn system_inner(name: &str, version: u64, look_for_previous: bool) -> Result { let path = sanitize_name(name)?.join(format!("v{version}")); #[cfg(unix)] - let system_path = xdg::BaseDirectories::with_prefix("cosmic").find_data_file(&path); + let system_path = xdg::BaseDirectories::with_prefix("cosmic").find_data_file(path); #[cfg(windows)] let system_path = @@ -210,13 +192,6 @@ impl Config { Ok(Self { system_path, user_path: None, - previous: if version > 1 && look_for_previous { - Self::system_inner(name, version - 1, false) - .ok() - .map(Box::new) - } else { - None - }, }) } @@ -224,10 +199,6 @@ impl Config { // Use folder at XDG config/name for config storage, return Config if successful //TODO: fallbacks for flatpak (HOST_XDG_CONFIG_HOME, xdg-desktop settings proxy) pub fn new(name: &str, version: u64) -> Result { - Self::new_inner(name, version, true) - } - - fn new_inner(name: &str, version: u64, look_for_previous: bool) -> Result { // Look for [name]/v[version] let path = sanitize_name(name)?.join(format!("v{}", version)); @@ -252,29 +223,15 @@ impl Config { Ok(Self { system_path, user_path: Some(user_path), - previous: if version > 1 && look_for_previous { - Self::new_inner(name, version - 1, false).ok().map(Box::new) - } else { - None - }, }) } /// Get config for the given application name and config version and custom path. pub fn with_custom_path(name: &str, version: u64, custom_path: PathBuf) -> Result { - Self::with_custom_path_inner(name, version, custom_path, true) - } - - fn with_custom_path_inner( - name: &str, - version: u64, - custom_path: PathBuf, - look_for_previous: bool, - ) -> Result { // Look for [name]/v[version] let path = sanitize_name(name)?.join(format!("v{version}")); - let mut user_path = custom_path.clone(); + let mut user_path = custom_path; user_path.push("cosmic"); user_path.push(path); // Create new configuration directory if not found. @@ -284,13 +241,6 @@ impl Config { Ok(Self { system_path: None, user_path: Some(user_path), - previous: if version > 1 && look_for_previous { - Self::with_custom_path_inner(name, version - 1, custom_path.clone(), false) - .ok() - .map(Box::new) - } else { - None - }, }) } @@ -300,10 +250,6 @@ impl Config { // Use folder at XDG config/name for config storage, return Config if successful //TODO: fallbacks for flatpak (HOST_XDG_CONFIG_HOME, xdg-desktop settings proxy) pub fn new_state(name: &str, version: u64) -> Result { - Self::new_state_inner(name, version, true) - } - - fn new_state_inner(name: &str, version: u64, look_for_previous: bool) -> Result { // Look for [name]/v[version] let path = sanitize_name(name)?.join(format!("v{}", version)); @@ -317,47 +263,6 @@ impl Config { Ok(Self { system_path: None, user_path: Some(user_path), - previous: if version > 1 && look_for_previous { - Self::new_state_inner(name, version - 1, false) - .ok() - .map(Box::new) - } else { - None - }, - }) - } - - /// Get data for the given application name and config version. - /// pub fn new_state(name: &str, version: u64) -> Result { - pub fn new_data(name: &str, version: u64) -> Result { - Self::new_data_inner(name, version, true) - } - - pub fn new_data_inner( - name: &str, - version: u64, - look_for_previous: bool, - ) -> Result { - // Look for [name]/v[version] - let path = sanitize_name(name)?.join(format!("v{}", version)); - - // Get libcosmic user data directory - let mut user_path = get_data_dir().ok_or(Error::NoConfigDirectory)?; - user_path.push("cosmic"); - user_path.push(path); - // Create new data directory if not found. - fs::create_dir_all(&user_path)?; - - Ok(Self { - system_path: None, - user_path: Some(user_path), - previous: if version > 1 && look_for_previous { - Self::new_data_inner(name, version - 1, false) - .ok() - .map(Box::new) - } else { - None - }, }) } @@ -468,13 +373,7 @@ impl ConfigGet for Config { Ok(ron::from_str(&data)?) } - _ => { - if let Some(previous) = self.previous.as_ref() { - previous.get_local(key) - } else { - Err(Error::NotFound) - } - } + _ => Err(Error::NotFound), } } diff --git a/cosmic-config/src/subscription.rs b/cosmic-config/src/subscription.rs index f0387877..d16b9b65 100644 --- a/cosmic-config/src/subscription.rs +++ b/cosmic-config/src/subscription.rs @@ -1,9 +1,7 @@ -use iced_futures::futures::channel::mpsc; use iced_futures::futures::{SinkExt, Stream}; -use iced_futures::stream; +use iced_futures::{futures::channel::mpsc, stream}; use notify::RecommendedWatcher; -use std::borrow::Cow; -use std::hash::Hash; +use std::{borrow::Cow, hash::Hash}; use crate::{Config, CosmicConfigEntry}; @@ -79,8 +77,7 @@ async fn start_listening, output: &mut mpsc::Sender>, ) -> ConfigState { - use iced_futures::futures::StreamExt; - use iced_futures::futures::future::pending; + use iced_futures::futures::{StreamExt, future::pending}; match state { ConfigState::Init(config_id, version, is_state) => { diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index 2907441f..7e408d8d 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -15,14 +15,13 @@ export = ["serde_json"] no-default = [] [dependencies] -palette = { workspace = true, features = ["serializing"] } -hex_color = { version = "3", features = ["serde"] } +palette = { version = "0.7.6", features = ["serializing"] } almost = "0.2" -serde = { workspace = true, features = ["derive"] } +serde = { version = "1.0.228", features = ["derive"] } serde_json = { version = "1.0.149", optional = true, features = [ "preserve_order", ] } -ron.workspace = true +ron = "0.12.0" csscolorparser = { version = "0.8.3", features = ["serde"] } cosmic-config = { path = "../cosmic-config/", default-features = false, features = [ "subscription", @@ -30,8 +29,11 @@ cosmic-config = { path = "../cosmic-config/", default-features = false, features ] } configparser = "3.1.0" dirs.workspace = true -thiserror.workspace = true +thiserror = "2.0.18" [dev-dependencies] insta = "1.47.2" +[profile.dev.package] +insta.opt-level = 3 +similar.opt-level = 3 diff --git a/cosmic-theme/src/model/color.rs b/cosmic-theme/src/model/color.rs deleted file mode 100644 index fb4c2202..00000000 --- a/cosmic-theme/src/model/color.rs +++ /dev/null @@ -1,173 +0,0 @@ -//! Color representation and serde helpers for the Cosmic theme - -use hex_color::HexColor; -use palette::{Srgb, Srgba}; -use serde::{Deserialize, Serialize}; - -/// A color in the Cosmic theme for serialization and deserialization -#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)] -#[serde(untagged)] -pub enum ColorRepr { - /// A color represented as a hex string - #[serde(with = "hex_color::rgba")] - Hex(HexColor), - /// A color represented as an RGBA value - Rgba(Srgba), - /// A color represented as an RGB value - Rgb(Srgb), -} - -/// An optional color in the Cosmic theme for serialization and deserialization -#[repr(transparent)] -#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)] -#[serde(transparent)] -pub struct ColorReprOption(Option); - -impl From for ColorRepr { - fn from(color: Srgb) -> Self { - let rgb_u8: Srgb = color.into_format(); - ColorRepr::Hex(HexColor { - r: rgb_u8.red, - g: rgb_u8.green, - b: rgb_u8.blue, - a: 255, - }) - } -} - -impl From for ColorRepr { - fn from(color: Srgba) -> Self { - let rgba_u8: Srgba = color.into_format(); - ColorRepr::Hex(HexColor { - r: rgba_u8.red, - g: rgba_u8.green, - b: rgba_u8.blue, - a: rgba_u8.alpha, - }) - } -} - -impl From for Srgb { - fn from(value: ColorRepr) -> Self { - match value { - ColorRepr::Hex(hex) => Srgb::::new(hex.r, hex.g, hex.b).into_format(), - ColorRepr::Rgb(rgb) => rgb, - ColorRepr::Rgba(rgba) => Srgb::new(rgba.red, rgba.green, rgba.blue), - } - } -} - -impl From for Srgba { - fn from(value: ColorRepr) -> Self { - match value { - ColorRepr::Hex(hex) => Srgba::::new(hex.r, hex.g, hex.b, hex.a).into_format(), - ColorRepr::Rgb(rgb) => Srgba::new(rgb.red, rgb.green, rgb.blue, 1.0), - ColorRepr::Rgba(rgba) => rgba, - } - } -} - -impl From for Option { - fn from(value: ColorReprOption) -> Self { - value.0.map(std::convert::Into::into) - } -} - -impl From for Option { - fn from(value: ColorReprOption) -> Self { - value.0.map(std::convert::Into::into) - } -} - -impl From> for ColorReprOption { - fn from(value: Option) -> Self { - ColorReprOption(value.map(std::convert::Into::into)) - } -} - -impl From> for ColorReprOption { - fn from(value: Option) -> Self { - ColorReprOption(value.map(std::convert::Into::into)) - } -} - -/// A trait for converting between a color type and its representation for serialization and deserialization -pub trait ConvColorRepr: Sized { - /// Convert from a color representation to the color type - fn from_repr(repr: ColorRepr) -> Self; - /// Convert from the color type to its representation for serialization - fn to_repr(&self) -> ColorRepr; -} - -impl ConvColorRepr for Srgba { - fn from_repr(repr: ColorRepr) -> Self { - repr.into() - } - - fn to_repr(&self) -> ColorRepr { - (*self).into() - } -} - -impl ConvColorRepr for Srgb { - fn from_repr(repr: ColorRepr) -> Self { - repr.into() - } - - fn to_repr(&self) -> ColorRepr { - (*self).into() - } -} - -/// Serde helpers for serializing and deserializing colors in the Cosmic theme -pub mod color_serde { - use super::*; - use serde::{Deserialize, Deserializer, Serializer}; - - /// Serialize a color to a hex string - pub fn serialize(color: &T, serializer: S) -> Result - where - T: ConvColorRepr, - S: Serializer, - { - let repr = color.to_repr(); - repr.serialize(serializer) - } - - /// Deserialize a color from a hex string or RGB/RGBA - pub fn deserialize<'de, T, D>(deserializer: D) -> Result - where - T: ConvColorRepr, - D: Deserializer<'de>, - { - let repr = ColorRepr::deserialize(deserializer)?; - Ok(T::from_repr(repr)) - } - - /// Serde helpers for serializing and deserializing optional colors in the Cosmic theme - pub mod option { - use super::*; - - /// Serialize an optional color - pub fn serialize(value: &Option, serializer: S) -> Result - where - T: ConvColorRepr, - S: Serializer, - { - match value { - Some(v) => Some(v.to_repr()).serialize(serializer), - None => serializer.serialize_none(), - } - } - - /// Deserialize an optional color - pub fn deserialize<'de, T, D>(deserializer: D) -> Result, D::Error> - where - T: ConvColorRepr, - D: Deserializer<'de>, - { - let opt = Option::::deserialize(deserializer)?; - Ok(opt.map(T::from_repr)) - } - } -} diff --git a/cosmic-theme/src/model/corner.rs b/cosmic-theme/src/model/corner.rs index f2fa95ec..ecd18c0b 100644 --- a/cosmic-theme/src/model/corner.rs +++ b/cosmic-theme/src/model/corner.rs @@ -29,51 +29,3 @@ impl Default for CornerRadii { } } } - -/// Roundness options for the Cosmic theme -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] -pub enum Roundness { - /// Round style - #[default] - Round, - /// Slightly round style - SlightlyRound, - /// Square style - Square, -} - -impl From for CornerRadii { - fn from(value: Roundness) -> Self { - match value { - Roundness::Round => CornerRadii::default(), - Roundness::SlightlyRound => CornerRadii { - radius_0: [0.0; 4], - radius_xs: [2.0; 4], - radius_s: [8.0; 4], - radius_m: [8.0; 4], - radius_l: [8.0; 4], - radius_xl: [8.0; 4], - }, - Roundness::Square => CornerRadii { - radius_0: [0.0; 4], - radius_xs: [2.0; 4], - radius_s: [2.0; 4], - radius_m: [2.0; 4], - radius_l: [2.0; 4], - radius_xl: [2.0; 4], - }, - } - } -} - -impl From for Roundness { - fn from(value: CornerRadii) -> Self { - if (value.radius_m[0] - 16.0).abs() < 0.01 { - Self::Round - } else if (value.radius_m[0] - 8.0).abs() < 0.01 { - Self::SlightlyRound - } else { - Self::Square - } - } -} diff --git a/cosmic-theme/src/model/cosmic_palette.rs b/cosmic-theme/src/model/cosmic_palette.rs index 4360f8f4..3852742b 100644 --- a/cosmic-theme/src/model/cosmic_palette.rs +++ b/cosmic-theme/src/model/cosmic_palette.rs @@ -1,4 +1,3 @@ -use crate::color::color_serde; use palette::Srgba; use serde::{Deserialize, Serialize}; use std::sync::LazyLock; @@ -96,107 +95,75 @@ pub struct CosmicPaletteInner { /// Utility Colors /// Colors used for various points of emphasis in the UI. - #[serde(with = "color_serde")] pub bright_red: Srgba, /// Colors used for various points of emphasis in the UI. - #[serde(with = "color_serde")] pub bright_green: Srgba, /// Colors used for various points of emphasis in the UI. - #[serde(with = "color_serde")] pub bright_orange: Srgba, /// Surface Grays /// Colors used for three levels of surfaces in the UI. - #[serde(with = "color_serde")] pub gray_1: Srgba, /// Colors used for three levels of surfaces in the UI. - #[serde(with = "color_serde")] pub gray_2: Srgba, /// System Neutrals /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_0: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_1: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_2: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_3: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_4: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_5: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_6: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_7: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_8: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_9: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_10: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_blue: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_indigo: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_purple: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_pink: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_red: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_orange: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_yellow: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_green: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_warm_grey: Srgba, /// Extended Color Palette /// Colors used for themes, app icons, illustrations, and other brand purposes. - #[serde(with = "color_serde")] pub ext_warm_grey: Srgba, /// Colors used for themes, app icons, illustrations, and other brand purposes. - #[serde(with = "color_serde")] pub ext_orange: Srgba, /// Colors used for themes, app icons, illustrations, and other brand purposes. - #[serde(with = "color_serde")] pub ext_yellow: Srgba, /// Colors used for themes, app icons, illustrations, and other brand purposes. - #[serde(with = "color_serde")] pub ext_blue: Srgba, /// Colors used for themes, app icons, illustrations, and other brand purposes. - #[serde(with = "color_serde")] pub ext_purple: Srgba, /// Colors used for themes, app icons, illustrations, and other brand purposes. - #[serde(with = "color_serde")] pub ext_pink: Srgba, /// Colors used for themes, app icons, illustrations, and other brand purposes. - #[serde(with = "color_serde")] pub ext_indigo: Srgba, } diff --git a/cosmic-theme/src/model/dark.ron b/cosmic-theme/src/model/dark.ron index ffec0818..4453b8bf 100644 --- a/cosmic-theme/src/model/dark.ron +++ b/cosmic-theme/src/model/dark.ron @@ -1 +1 @@ -Dark((name: "cosmic-dark",bright_red: "#FFA09AFF",bright_green: "#5EDB8CFF",bright_orange: "#FFA37DFF",gray_1: "#1B1B1BFF",gray_2: "#262626FF",neutral_0: "#000000FF",neutral_1: "#030303FF",neutral_2: "#161616FF",neutral_3: "#2E2E2EFF",neutral_4: "#484848FF",neutral_5: "#636363FF",neutral_6: "#808080FF",neutral_7: "#9E9E9EFF",neutral_8: "#BEBEBEFF",neutral_9: "#DEDEDEFF",neutral_10: "#FFFFFFFF",accent_blue: "#63D0DFFF",accent_indigo: "#A1C0EBFF",accent_purple: "#E79CFEFF",accent_pink: "#FF9CB1FF",accent_red: "#FDA1A0FF",accent_orange: "#FFAD00FF",accent_yellow: "#F7E062FF",accent_green: "#92CF9CFF",accent_warm_grey: "#CABAB4FF",ext_warm_grey: "#9B8E8AFF",ext_orange: "#FFAD00FF",ext_yellow: "#FEDB40FF",ext_blue: "#48B9C7FF",ext_purple: "#CF7DFFFF",ext_pink: "#F93A83FF",ext_indigo: "#3E88FFFF",)) +Dark((name:"cosmic-dark",bright_red:(red:1.0,green:0.62745098,blue:0.60392157,alpha:1.0),bright_green:(red:0.36862745,green:0.85882352,blue:0.54901960,alpha:1.0),bright_orange:(red:1.0,green:0.63921569,blue:0.49019608,alpha:1.0),gray_1:(red:0.10588235,green:0.10588235,blue:0.10588235,alpha:1.0),gray_2:(red:0.14901961,green:0.14901961,blue:0.14901961,alpha:1.0),neutral_0:(red:0.0,green:0.0,blue:0.0,alpha:1.0),neutral_1:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_2:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_3:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_4:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_7:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_8:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_9:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_10:(red:1.0,green:1.0,blue:1.0,alpha:1.0),accent_blue:(red:0.3882353,green:0.81568627,blue:0.87450981,alpha:1.0),accent_indigo:(red:0.63137255,green:0.75294118,blue:0.92156863,alpha:1.0),accent_purple:(red:0.90588235,green:0.61176471,blue:0.99607843,alpha:1.0),accent_pink:(red:1.0,green:0.61176471,blue:0.69411765,alpha:1.0),accent_red:(red:0.99215686,green:0.63137255,blue:0.62745098,alpha:1.0),accent_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),accent_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),accent_green:(red:0.57254902,green:0.81176471,blue:0.61176471,alpha:1.0),accent_warm_grey:(red:0.79215686,green:0.72941176,blue:0.70588235,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),ext_yellow:(red:0.99607843,green:0.85882353,blue:0.25098039,alpha:1.0),ext_blue:(red:0.28235294,green:0.72549020,blue:0.78039216,alpha:1.0),ext_purple:(red:0.81176471,green:0.49019608,blue:1.0,alpha:1.0),ext_pink:(red:0.97647059,green:0.22745098,blue:0.51372549,alpha:1.0),ext_indigo:(red:0.24313725,green:0.53333333,blue:1.0,alpha:1.0))) diff --git a/cosmic-theme/src/model/density.rs b/cosmic-theme/src/model/density.rs new file mode 100644 index 00000000..7655361c --- /dev/null +++ b/cosmic-theme/src/model/density.rs @@ -0,0 +1,69 @@ +use crate::Spacing; +use serde::{Deserialize, Serialize}; + +/// Density options for the Cosmic theme +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +pub enum Density { + /// Lower padding/spacing of elements + Compact, + /// Higher padding/spacing of elements + Spacious, + /// Standard padding/spacing of elements + #[default] + Standard, +} + +impl From for Spacing { + fn from(value: Density) -> Self { + match value { + Density::Compact => Spacing { + space_none: 0, + space_xxxs: 4, + space_xxs: 4, + space_xs: 8, + space_s: 8, + space_m: 16, + space_l: 24, + space_xl: 32, + space_xxl: 48, + space_xxxl: 64, + }, + Density::Spacious => Spacing { + space_none: 4, + space_xxxs: 8, + space_xxs: 12, + space_xs: 16, + space_s: 24, + space_m: 32, + space_l: 48, + space_xl: 64, + space_xxl: 128, + space_xxxl: 160, + }, + Density::Standard => Spacing { + space_none: 0, + space_xxxs: 4, + space_xxs: 8, + space_xs: 12, + space_s: 16, + space_m: 24, + space_l: 32, + space_xl: 48, + space_xxl: 64, + space_xxxl: 128, + }, + } + } +} + +impl From for Density { + fn from(value: Spacing) -> Self { + if value.space_m.saturating_sub(16) == 0 { + Self::Compact + } else if value.space_m.saturating_sub(24) == 0 { + Self::Standard + } else { + Self::Spacious + } + } +} diff --git a/cosmic-theme/src/model/derivation.rs b/cosmic-theme/src/model/derivation.rs index 796ddab3..dce653e5 100644 --- a/cosmic-theme/src/model/derivation.rs +++ b/cosmic-theme/src/model/derivation.rs @@ -1,4 +1,3 @@ -use crate::color::color_serde; use palette::{Srgba, WithAlpha}; use serde::{Deserialize, Serialize}; @@ -9,18 +8,14 @@ use crate::composite::over; #[must_use] pub struct Container { /// the color of the container - #[serde(with = "color_serde")] pub base: Srgba, /// the color of components in the container pub component: Component, /// the color of dividers in the container - #[serde(with = "color_serde")] pub divider: Srgba, /// the color of text in the container - #[serde(with = "color_serde")] pub on: Srgba, /// the color of @small_widget_container - #[serde(with = "color_serde")] pub small_widget: Srgba, } @@ -50,42 +45,30 @@ impl Container { #[must_use] pub struct Component { /// The base color of the widget - #[serde(with = "color_serde")] pub base: Srgba, /// The color of the widget when it is hovered - #[serde(with = "color_serde")] pub hover: Srgba, /// the color of the widget when it is pressed - #[serde(with = "color_serde")] pub pressed: Srgba, /// the color of the widget when it is selected - #[serde(with = "color_serde")] pub selected: Srgba, /// the color of the widget when it is selected - #[serde(with = "color_serde")] pub selected_text: Srgba, /// the color of the widget when it is focused - #[serde(with = "color_serde")] pub focus: Srgba, /// the color of dividers for this widget - #[serde(with = "color_serde")] pub divider: Srgba, /// the color of text for this widget - #[serde(with = "color_serde")] pub on: Srgba, // the color of text with opacity 80 for this widget // pub text_opacity_80: Srgba, /// the color of the widget when it is disabled - #[serde(with = "color_serde")] pub disabled: Srgba, /// the color of text in the widget when it is disabled - #[serde(with = "color_serde")] pub on_disabled: Srgba, /// the color of the border for the widget - #[serde(with = "color_serde")] pub border: Srgba, /// the color of the border for the widget when it is disabled - #[serde(with = "color_serde")] pub disabled_border: Srgba, } diff --git a/cosmic-theme/src/model/light.ron b/cosmic-theme/src/model/light.ron index c9fcc6ce..29b3ad65 100644 --- a/cosmic-theme/src/model/light.ron +++ b/cosmic-theme/src/model/light.ron @@ -1 +1 @@ -Light((name: "cosmic-light",bright_red: "#890418FF",bright_green: "#00572CFF",bright_orange: "#792C00FF",gray_1: "#D7D7D7FF",gray_2: "#E4E4E4FF",neutral_0: "#FFFFFFFF",neutral_1: "#DEDEDEFF",neutral_2: "#BEBEBEFF",neutral_3: "#9E9E9EFF",neutral_4: "#808080FF",neutral_5: "#636363FF",neutral_6: "#484848FF",neutral_7: "#2E2E2EFF",neutral_8: "#161616FF",neutral_9: "#030303FF",neutral_10: "#000000FF",accent_blue: "#00525AFF",accent_indigo: "#2E496DFF",accent_purple: "#68217CFF",accent_pink: "#86043AFF",accent_red: "#78292EFF",accent_orange: "#624000FF",accent_yellow: "#534800FF",accent_green: "#185529FF",accent_warm_grey: "#554742FF",ext_warm_grey: "#9B8E8AFF",ext_orange: "#FBB86CFF",ext_yellow: "#F7E062FF",ext_blue: "#6ACAD8FF",ext_purple: "#D58CFFFF",ext_pink: "#FF9CDDFF",ext_indigo: "#95C4FCFF",)) +Light((name:"cosmic-light",bright_red:(red:0.53725490,green:0.01568627,blue:0.09411765,alpha:1.0),bright_green:(red:0.0,green:0.34117647,blue:0.17254901,alpha:1.0),bright_orange:(red:0.47450980,green:0.17254902,blue:0.0,alpha:1.0),gray_1:(red:0.84313725,green:0.84313725,blue:0.84313725,alpha:1.0),gray_2:(red:0.89411765,green:0.89411765,blue:0.89411765,alpha:1.0),neutral_0:(red:1.0,green:1.0,blue:1.0,alpha:1.0),neutral_1:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_2:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_3:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_4:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_7:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_8:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_9:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_10:(red:0.0,green:0.0,blue:0.0,alpha:1.0),accent_blue:(red:0.0,green:0.32156863,blue:0.35294118,alpha:1.0),accent_indigo:(red:0.18039216,green:0.28627451,blue:0.42745098,alpha:1.0),accent_purple:(red:0.40784314,green:0.12941176,blue:0.48627451,alpha:1.0),accent_pink:(red:0.52549020,green:0.01568627,blue:0.22745098,alpha:1.0),accent_red:(red:0.47058824,green:0.16078431,blue:0.18039216,alpha:1.0),accent_orange:(red:0.38431373,green:0.25098039,blue:0.0,alpha:1.0),accent_yellow:(red:0.32549020,green:0.28235294,blue:0.0,alpha:1.0),accent_green:(red:0.09411765,green:0.33333333,blue:0.16078431,alpha:1.0),accent_warm_grey:(red:0.33333333,green:0.27843137,blue:0.25882353,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:0.98431373,green:0.72156863,blue:0.42352941,alpha:1.0),ext_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),ext_blue:(red:0.41568627,green:0.79215686,blue:0.84705882,alpha:1.0),ext_purple:(red:0.83529412,green:0.54901961,blue:1.0,alpha:1.0),ext_pink:(red:1.0,green:0.61176471,blue:0.86666667,alpha:1.0),ext_indigo:(red:0.58431373,green:0.76862745,blue:0.98823529,alpha:1.0))) diff --git a/cosmic-theme/src/model/mod.rs b/cosmic-theme/src/model/mod.rs index a7ac613f..f48d1a8d 100644 --- a/cosmic-theme/src/model/mod.rs +++ b/cosmic-theme/src/model/mod.rs @@ -1,13 +1,14 @@ pub use corner::*; pub use cosmic_palette::*; +pub use density::*; pub use derivation::*; pub use mode::*; pub use spacing::*; pub use theme::*; -pub mod color; mod corner; mod cosmic_palette; +mod density; mod derivation; mod mode; mod spacing; diff --git a/cosmic-theme/src/model/spacing.rs b/cosmic-theme/src/model/spacing.rs index f02cf510..93b1bf43 100644 --- a/cosmic-theme/src/model/spacing.rs +++ b/cosmic-theme/src/model/spacing.rs @@ -41,59 +41,3 @@ impl Default for Spacing { } } } - -/// Density options for the Cosmic theme -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] -pub enum Density { - /// Lower padding/spacing of elements - Compact, - /// Standard padding/spacing of elements - #[default] - Standard, - /// Higher padding/spacing of elements - Spacious, -} - -impl From for Spacing { - fn from(value: Density) -> Self { - match value { - Density::Compact => Spacing { - space_none: 0, - space_xxxs: 4, - space_xxs: 4, - space_xs: 8, - space_s: 8, - space_m: 16, - space_l: 24, - space_xl: 32, - space_xxl: 48, - space_xxxl: 64, - }, - Density::Standard => Spacing::default(), - Density::Spacious => Spacing { - space_none: 4, - space_xxxs: 8, - space_xxs: 12, - space_xs: 16, - space_s: 24, - space_m: 32, - space_l: 48, - space_xl: 64, - space_xxl: 128, - space_xxxl: 160, - }, - } - } -} - -impl From for Density { - fn from(value: Spacing) -> Self { - if value.space_m.saturating_sub(16) == 0 { - Self::Compact - } else if value.space_m.saturating_sub(24) == 0 { - Self::Standard - } else { - Self::Spacious - } - } -} diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 64f81f8a..5db0f32c 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -1,17 +1,14 @@ -use crate::color::color_serde::option as color_serde_option; -use crate::color::{ColorRepr, ColorReprOption, color_serde}; -use crate::composite::over; -use crate::steps::{color_index, get_small_widget_color, get_surface_color, get_text, steps}; use crate::{ Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, DARK_PALETTE, LIGHT_PALETTE, NAME, Spacing, ThemeMode, + composite::over, + steps::{color_index, get_small_widget_color, get_surface_color, get_text, steps}, }; use cosmic_config::{Config, CosmicConfigEntry}; -use palette::color_difference::Wcag21RelativeContrast; -use palette::rgb::Rgb; -use palette::{IntoColor, Oklcha, Srgb, Srgba, WithAlpha}; +use palette::{ + IntoColor, Oklcha, Srgb, Srgba, WithAlpha, color_difference::Wcag21RelativeContrast, rgb::Rgb, +}; use serde::{Deserialize, Serialize}; -use std::default; use std::num::NonZeroUsize; /// ID for the current dark `ThemeBuilder` config @@ -48,24 +45,16 @@ pub enum Layer { PartialEq, cosmic_config::cosmic_config_derive::CosmicConfigEntry, )] -#[version = 2] +#[version = 1] pub struct Theme { /// name of the theme pub name: String, /// background element colors - pub(crate) background: Container, + pub background: Container, /// primary element colors - pub(crate) primary: Container, + pub primary: Container, /// secondary element colors - pub(crate) secondary: Container, - /// background element colors - pub(crate) transparent_background: Container, - /// primary element colors - pub(crate) transparent_primary: Container, - /// secondary element colors - pub(crate) transparent_secondary: Container, - /// button component styling - pub button: Component, + pub secondary: Container, /// accent element colors pub accent: Component, /// suggested element colors @@ -86,10 +75,10 @@ pub struct Theme { pub icon_button: Component, /// link button element colors pub link_button: Component, - /// list button element colors - pub list_button: Component, /// text button element colors pub text_button: Component, + /// button component styling + pub button: Component, /// palette pub palette: CosmicPaletteInner, /// spacing @@ -106,40 +95,16 @@ pub struct Theme { pub active_hint: u32, /// cosmic-comp custom window hint color pub window_hint: Option, - #[serde(default)] /// enables blurred transparency - pub frosted: BlurStrength, - #[serde(default)] - /// frosted windows - pub frosted_windows: bool, - #[serde(default)] - /// frosted system interface - pub frosted_system_interface: bool, - #[serde(default)] - /// frosted panel - pub frosted_panel: bool, - #[serde(default)] - /// frosted applet popups - pub frosted_applets: bool, - #[serde(default)] - /// alpha map - pub alpha_map: AlphaMap, + pub is_frosted: bool, /// shade color for dialogs - #[serde(with = "color_serde")] - #[cosmic_config_entry(with = ColorRepr)] pub shade: Srgba, /// accent text colors /// If None, accent base color is the accent text color. - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub accent_text: Option, /// control tint color - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub control_tint: Option, /// text tint color - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub text_tint: Option, } @@ -205,39 +170,6 @@ impl Theme { todo!(); } - #[allow(clippy::doc_markdown)] - #[inline] - /// get opaque or transparent background based on whether blur is active - pub fn background(&self, transparent: bool) -> &Container { - if transparent { - &self.transparent_background - } else { - &self.background - } - } - - #[allow(clippy::doc_markdown)] - #[inline] - /// get opaque or transparent primary based on whether blur is active - pub fn primary(&self, transparent: bool) -> &Container { - if transparent { - &self.transparent_primary - } else { - &self.primary - } - } - - #[allow(clippy::doc_markdown)] - #[inline] - /// get opaque or transparent secondary based on whether blur is active - pub fn secondary(&self, transparent: bool) -> &Container { - if transparent { - &self.transparent_secondary - } else { - &self.secondary - } - } - #[must_use] #[allow(clippy::doc_markdown)] #[inline] @@ -807,7 +739,7 @@ impl Theme { if color_scheme.trim().contains("default") || color_scheme.trim().contains("light") { return Self::light_default(); } - } + }; Self::dark_default() } @@ -816,10 +748,10 @@ impl Theme { pub fn preferred_theme() -> Self { let current_desktop = std::env::var("XDG_CURRENT_DESKTOP"); - if let Ok(desktop) = current_desktop - && desktop.trim().to_lowercase().contains("gnome") - { - return Self::gtk_prefer_colorscheme(); + if let Ok(desktop) = current_desktop { + if desktop.trim().to_lowercase().contains("gnome") { + return Self::gtk_prefer_colorscheme(); + } } Self::dark_default() @@ -842,7 +774,7 @@ impl From for Theme { cosmic_config::cosmic_config_derive::CosmicConfigEntry, PartialEq, )] -#[version = 2] +#[version = 1] pub struct ThemeBuilder { /// override the palette for the builder pub palette: CosmicPalette, @@ -851,67 +783,31 @@ pub struct ThemeBuilder { /// override corner radii for the builder pub corner_radii: CornerRadii, /// override neutral_tint for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub neutral_tint: Option, /// override bg_color for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub bg_color: Option, /// override the primary container bg color for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub primary_container_bg: Option, /// override the secontary container bg color for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub secondary_container_bg: Option, /// override the text tint for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub text_tint: Option, /// override the accent color for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub accent: Option, /// override the success color for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub success: Option, /// override the warning color for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub warning: Option, /// override the destructive color for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub destructive: Option, - #[serde(default)] /// enabled blurred transparency - pub frosted: BlurStrength, + pub is_frosted: bool, // TODO handle /// cosmic-comp window gaps size (outer, inner) pub gaps: (u32, u32), /// cosmic-comp active hint window outline width pub active_hint: u32, /// cosmic-comp custom window hint color - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub window_hint: Option, - #[serde(default)] - /// frosted windows - pub frosted_windows: bool, - #[serde(default)] - /// frosted system interface - pub frosted_system_interface: bool, - #[serde(default)] - /// frosted panel - pub frosted_panel: bool, - #[serde(default)] - /// frosted applet popups - pub frosted_applets: bool, - #[serde(default)] - /// alpha map - pub alpha_map: AlphaMap, } impl Default for ThemeBuilder { @@ -929,16 +825,11 @@ impl Default for ThemeBuilder { success: Default::default(), warning: Default::default(), destructive: Default::default(), - frosted: BlurStrength::default(), + is_frosted: false, // cosmic-comp theme settings gaps: (0, 8), active_hint: 3, window_hint: None, - frosted_windows: false, - frosted_system_interface: false, - frosted_panel: false, - frosted_applets: false, - alpha_map: AlphaMap::default(), } } } @@ -1062,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 { @@ -1086,22 +971,9 @@ impl ThemeBuilder { gaps, active_hint, window_hint, - frosted, - frosted_windows, - frosted_system_interface, - frosted_panel, - frosted_applets, - alpha_map, + is_frosted, } = self; - let container_alpha = alpha_map.blurred_alpha(frosted); - let actual_alpha = - if (frosted_windows || frosted_system_interface || frosted_panel || frosted_applets) { - container_alpha - } else { - 1.0 - }; - let is_dark = palette.is_dark(); let is_high_contrast = palette.is_high_contrast(); @@ -1156,10 +1028,6 @@ impl ThemeBuilder { let step_array = steps(bg, NonZeroUsize::new(100).unwrap()); let bg_index = color_index(bg, step_array.len()); - let mut transparent_bg = bg; - transparent_bg.alpha = container_alpha; - let transparent_bg_steps_array = steps(transparent_bg, NonZeroUsize::new(100).unwrap()); - let mut component_hovered_overlay = if bg_index < 91 { control_steps_array[10] } else { @@ -1184,26 +1052,10 @@ impl ThemeBuilder { &step_array, &control_steps_array[8], text_steps_array.as_deref(), - actual_alpha, - ); - - let transparent_bg_component = get_surface_color( - bg_index, - 8, - &transparent_bg_steps_array, - is_dark, - &p_ref.neutral_2, - ); - let on_transparent_bg_component = get_text( - color_index(transparent_bg_component, transparent_bg_steps_array.len()), - &transparent_bg_steps_array, - &control_steps_array[8], - text_steps_array.as_deref(), - actual_alpha, ); let primary = { - let mut container_bg = if let Some(primary_container_bg_color) = primary_container_bg { + let container_bg = if let Some(primary_container_bg_color) = primary_container_bg { primary_container_bg_color } else { get_surface_color(bg_index, 5, &step_array, is_dark, &control_steps_array[1]) @@ -1233,7 +1085,6 @@ impl ThemeBuilder { &step_array, &control_steps_array[8], text_steps_array.as_deref(), - container_alpha, ), component_hovered_overlay, component_pressed_overlay, @@ -1246,48 +1097,6 @@ impl ThemeBuilder { &step_array, &control_steps_array[8], text_steps_array.as_deref(), - container_alpha, - ), - get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]), - is_high_contrast, - ) - }; - let transparent_primary = { - let mut container_bg = if let Some(primary_container_bg_color) = primary_container_bg { - primary_container_bg_color - } else { - get_surface_color(bg_index, 5, &step_array, is_dark, &control_steps_array[1]) - }; - container_bg.alpha = (container_alpha + if is_dark { 0.3 } else { 0.25 }).min(1.0); - - let step_array = steps(container_bg, NonZeroUsize::new(100).unwrap()); - let base_index: usize = color_index(container_bg, step_array.len()); - let component_base = - get_surface_color(base_index, 6, &step_array, is_dark, &control_steps_array[3]); - - Container::new( - Component::component( - component_base, - accent, - get_text( - color_index(component_base, step_array.len()), - &step_array, - &control_steps_array[8], - text_steps_array.as_deref(), - actual_alpha, - ), - Srgba::new(0., 0., 0., 0.0), - Srgba::new(0., 0., 0., 0.0), - is_high_contrast, - control_steps_array[8], - ), - container_bg, - get_text( - base_index, - &step_array, - &control_steps_array[8], - text_steps_array.as_deref(), - actual_alpha, ), get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]), is_high_contrast, @@ -1361,7 +1170,6 @@ impl ThemeBuilder { &step_array, &control_steps_array[8], text_steps_array.as_deref(), - actual_alpha, ), get_small_widget_color(bg_index, 5, &neutral_steps, &control_steps_array[6]), is_high_contrast, @@ -1398,7 +1206,6 @@ impl ThemeBuilder { &step_array, &control_steps_array[8], text_steps_array.as_deref(), - actual_alpha, ), component_hovered_overlay, component_pressed_overlay, @@ -1411,7 +1218,6 @@ impl ThemeBuilder { &step_array, &control_steps_array[8], text_steps_array.as_deref(), - actual_alpha, ), get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]), is_high_contrast, @@ -1424,24 +1230,14 @@ impl ThemeBuilder { button_hovered_overlay, button_pressed_overlay, ), - accent_button: { - let step_array = steps(control_steps_array[5], NonZeroUsize::new(100).unwrap()); - - Component::colored_button( - accent, - control_steps_array[1], - get_text( - color_index(accent, step_array.len()), - &step_array, - &step_array[0], - text_steps_array.as_deref(), - 1., // accent is opaque anyway - ), - accent, - button_hovered_overlay, - button_pressed_overlay, - ) - }, + accent_button: Component::colored_button( + accent, + control_steps_array[1], + control_steps_array[0], + accent, + button_hovered_overlay, + button_pressed_overlay, + ), button: Component::component( button_bg, accent, @@ -1489,15 +1285,6 @@ impl ThemeBuilder { component.on_disabled = over(component.on.with_alpha(0.5), component.base); component }, - list_button: Component::component( - Srgba::new(0.0, 0.0, 0.0, 0.0), - accent, - on_bg_component, - Srgba::new(0.0, 0.0, 0.0, 0.0), - button_pressed_overlay, - is_high_contrast, - control_steps_array[8], - ), success: Component::colored_component( success, control_steps_array[0], @@ -1545,79 +1332,10 @@ impl ThemeBuilder { gaps, active_hint, window_hint, - frosted, + is_frosted, accent_text, control_tint: neutral_tint, text_tint, - frosted_windows, - frosted_system_interface, - frosted_panel, - frosted_applets, - alpha_map, - transparent_background: Container::new( - Component::component( - transparent_bg_component, - accent, - on_transparent_bg_component, - component_hovered_overlay, - component_pressed_overlay, - is_high_contrast, - control_steps_array[8], - ), - transparent_bg, - get_text( - bg_index, - &transparent_bg_steps_array, - &control_steps_array[8], - text_steps_array.as_deref(), - actual_alpha, - ), - get_small_widget_color(bg_index, 5, &neutral_steps, &control_steps_array[6]), - is_high_contrast, - ), - transparent_primary, - transparent_secondary: { - let mut container_bg = if let Some(secondary_container_bg) = secondary_container_bg - { - secondary_container_bg - } else { - get_surface_color(bg_index, 10, &step_array, is_dark, &control_steps_array[2]) - }; - container_bg.alpha = (container_alpha + if is_dark { 0.6 } else { 0.5 }).min(1.0); - - let step_array = steps(container_bg, NonZeroUsize::new(100).unwrap()); - let base_index = color_index(container_bg, step_array.len()); - let secondary_component = - get_surface_color(base_index, 3, &step_array, is_dark, &control_steps_array[4]); - - Container::new( - Component::component( - secondary_component, - accent, - get_text( - color_index(secondary_component, step_array.len()), - &step_array, - &control_steps_array[8], - text_steps_array.as_deref(), - actual_alpha, - ), - component_hovered_overlay, - component_pressed_overlay, - is_high_contrast, - control_steps_array[8], - ), - container_bg, - get_text( - base_index, - &step_array, - &control_steps_array[8], - text_steps_array.as_deref(), - actual_alpha, - ), - get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]), - is_high_contrast, - ) - }, }; theme.spacing = spacing; theme.corner_radii = corner_radii; @@ -1636,181 +1354,3 @@ impl ThemeBuilder { Config::new(LIGHT_THEME_BUILDER_ID, Self::VERSION) } } - -/// Actual blur radius is decided by cosmic-comp, -/// but this represents the strength of the blur effect. -#[allow(missing_docs)] -#[repr(u8)] -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] -pub enum BlurStrength { - ExtremelyLow, - ExtremelyLow2, - VeryLow, - VeryLow2, - Low, - Low2, - Medium, - Medium2, - High, - High2, - VeryHigh, - VeryHigh2, - ExtremelyHigh, - ExtremelyHigh2, -} - -impl Default for BlurStrength { - fn default() -> Self { - Self::Medium - } -} - -impl TryFrom for BlurStrength { - type Error = (); - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(BlurStrength::ExtremelyLow), - 1 => Ok(BlurStrength::ExtremelyLow2), - 2 => Ok(BlurStrength::VeryLow), - 3 => Ok(BlurStrength::VeryLow2), - 4 => Ok(BlurStrength::Low), - 5 => Ok(BlurStrength::Low2), - 6 => Ok(BlurStrength::Medium), - 7 => Ok(BlurStrength::Medium2), - 8 => Ok(BlurStrength::High), - 9 => Ok(BlurStrength::High2), - 10 => Ok(BlurStrength::VeryHigh), - 11 => Ok(BlurStrength::VeryHigh2), - 12 => Ok(BlurStrength::ExtremelyHigh), - 13 => Ok(BlurStrength::ExtremelyHigh2), - _ => Err(()), - } - } -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct AlphaMap { - pub extremely_low: f32, - pub extremely_low_2: f32, - pub very_low: f32, - pub very_low_2: f32, - pub low: f32, - pub low_2: f32, - pub medium: f32, - pub medium_2: f32, - pub high: f32, - pub high_2: f32, - pub very_high: f32, - pub very_high_2: f32, - pub extremely_high: f32, - pub extremely_high_2: f32, -} - -impl AlphaMap { - pub fn blurred_alpha(&self, blur: BlurStrength) -> f32 { - match blur { - BlurStrength::ExtremelyLow => self.extremely_low, - BlurStrength::ExtremelyLow2 => self.extremely_low_2, - BlurStrength::VeryLow => self.very_low, - BlurStrength::VeryLow2 => self.very_low_2, - BlurStrength::Low => self.low, - BlurStrength::Low2 => self.low_2, - BlurStrength::Medium => self.medium, - BlurStrength::Medium2 => self.medium_2, - BlurStrength::High => self.high, - BlurStrength::High2 => self.high_2, - BlurStrength::VeryHigh => self.very_high, - BlurStrength::VeryHigh2 => self.very_high_2, - BlurStrength::ExtremelyHigh => self.extremely_high, - BlurStrength::ExtremelyHigh2 => self.extremely_high_2, - } - } - - pub fn offset(self, offset: f32) -> Self { - let Self { - mut extremely_low, - mut extremely_low_2, - mut very_low, - mut very_low_2, - mut low, - mut low_2, - mut medium, - mut medium_2, - mut high, - mut high_2, - mut very_high, - mut very_high_2, - mut extremely_high, - mut extremely_high_2, - } = self; - extremely_low += offset; - extremely_low_2 += offset; - very_low += offset; - very_low_2 += offset; - low += offset; - low_2 += offset; - medium += offset; - medium_2 += offset; - high += offset; - high_2 += offset; - very_high += offset; - very_high_2 += offset; - extremely_high += offset; - extremely_high_2 += offset; - Self { - extremely_low, - extremely_low_2, - very_low, - very_low_2, - low, - low_2, - medium, - medium_2, - high, - high_2, - very_high, - very_high_2, - extremely_high, - extremely_high_2, - } - } - - pub fn guess_offset(&self) -> f32 { - let Self { - extremely_low, - extremely_high_2, - .. - } = self; - let default_low = Self::default().extremely_low; - let default_high = Self::default().extremely_high_2; - if *extremely_low > default_low { - 0.5 * (*extremely_low - default_low) / (1.0 - default_low) + 0.5 - } else if *extremely_high_2 < default_high { - 0.5 - (0.5 * (default_high - *extremely_high_2) / default_high) - } else { - 0.5 - } - } -} - -impl Default for AlphaMap { - fn default() -> Self { - Self { - extremely_low: 0.90, - extremely_low_2: 0.87692, - very_low: 0.85385, - very_low_2: 0.83076, - low: 0.80769, - low_2: 0.78461, - medium: 0.76154, - medium_2: 0.73846, - high: 0.71538, - high_2: 0.69231, - very_high: 0.66023, - very_high_2: 0.64615, - extremely_high: 0.62308, - extremely_high_2: 0.6, - } - } -} 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/mod.rs b/cosmic-theme/src/output/mod.rs index 04dce39f..19f7bc5b 100644 --- a/cosmic-theme/src/output/mod.rs +++ b/cosmic-theme/src/output/mod.rs @@ -1,6 +1,5 @@ use configparser::ini::WriteOptions; -use palette::Srgba; -use palette::rgb::Rgba; +use palette::{Srgba, rgb::Rgba}; use thiserror::Error; use crate::Theme; diff --git a/cosmic-theme/src/output/qt56ct_output.rs b/cosmic-theme/src/output/qt56ct_output.rs index 16df211f..43a45470 100644 --- a/cosmic-theme/src/output/qt56ct_output.rs +++ b/cosmic-theme/src/output/qt56ct_output.rs @@ -1,12 +1,12 @@ use crate::Theme; use configparser::ini::Ini; -use palette::blend::Compose; -use palette::rgb::Rgba; -use palette::{Mix, Srgba, WithAlpha}; -use std::fs::{self, File}; -use std::io::Write; -use std::path::PathBuf; -use std::vec; +use palette::{Mix, Srgba, WithAlpha, blend::Compose, rgb::Rgba}; +use std::{ + fs::{self, File}, + io::Write, + path::PathBuf, + vec, +}; use super::{OutputError, qt_settings_ini_style}; diff --git a/cosmic-theme/src/output/qt_output.rs b/cosmic-theme/src/output/qt_output.rs index 84614e7b..d42d553b 100644 --- a/cosmic-theme/src/output/qt_output.rs +++ b/cosmic-theme/src/output/qt_output.rs @@ -1,11 +1,12 @@ use crate::Theme; use configparser::ini::Ini; use cosmic_config::CosmicConfigEntry; -use palette::blend::Compose; -use palette::{Mix, Srgba}; -use std::fs::{self, File}; -use std::io::{self, Write}; -use std::path::{Path, PathBuf}; +use palette::{Mix, Srgba, blend::Compose}; +use std::{ + fs::{self, File}, + io::{self, Write}, + path::{Path, PathBuf}, +}; use super::{OutputError, qt_settings_ini_style}; diff --git a/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap b/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap index 40363f95..ae2bcb66 100644 --- a/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap +++ b/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap @@ -81,7 +81,7 @@ ForegroundPositive=0,87,44 ForegroundVisited=0,82,90 [Colors:Selection] -BackgroundAlternate=108,149,153 +BackgroundAlternate=108,149,152 BackgroundNormal=0,82,90 DecorationFocus=0,82,90 DecorationHover=0,82,90 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/cosmic-theme/src/steps.rs b/cosmic-theme/src/steps.rs index e595d8f8..6ebf1015 100644 --- a/cosmic-theme/src/steps.rs +++ b/cosmic-theme/src/steps.rs @@ -1,8 +1,7 @@ use std::num::NonZeroUsize; use almost::equal; -use palette::convert::FromColorUnclamped; -use palette::{ClampAssign, FromColor, Lch, Oklcha, Srgb, Srgba}; +use palette::{ClampAssign, FromColor, Lch, Oklcha, Srgb, Srgba, convert::FromColorUnclamped}; /// Get an array of 100 colors with a specific hue and chroma /// over the full range of lightness. @@ -81,7 +80,6 @@ pub fn get_text( step_array: &[Srgba], fallback: &Srgba, tint_array: Option<&[Srgba]>, - alpha: f32, ) -> Srgba { assert!(step_array.len() == 100); let step_array = if let Some(tint_array) = tint_array { @@ -91,28 +89,11 @@ pub fn get_text( step_array }; - let alpha_extra_steps = if alpha < 1.0 { - ((1. - alpha) * 100.0).round() as usize - } else { - 0 - }; let is_dark = base_index < 60; - let index = get_index( - base_index, - 70 + alpha_extra_steps, - step_array.len(), - is_dark, - ) - .or_else(|| { - get_index( - base_index, - 50 + alpha_extra_steps, - step_array.len(), - is_dark, - ) - }) - .unwrap_or(if is_dark { 99 } else { 0 }); + let index = get_index(base_index, 70, step_array.len(), is_dark) + .or_else(|| get_index(base_index, 50, step_array.len(), is_dark)) + .unwrap_or(if is_dark { 99 } else { 0 }); *step_array.get(index).unwrap_or(fallback) } 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/about/src/main.rs b/examples/about/src/main.rs index d1ea475e..c25a9b9a 100644 --- a/examples/about/src/main.rs +++ b/examples/about/src/main.rs @@ -8,8 +8,7 @@ use cosmic::app::{Core, Settings, Task}; use cosmic::executor; use cosmic::iced::{alignment, Length, Size}; use cosmic::prelude::*; -use cosmic::widget::about::About; -use cosmic::widget::{self, nav_bar}; +use cosmic::widget::{self, about::About, nav_bar}; /// Runs application with these settings #[rustfmt::skip] 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/applet/src/window.rs b/examples/applet/src/window.rs index f77bf512..22903eac 100644 --- a/examples/applet/src/window.rs +++ b/examples/applet/src/window.rs @@ -4,8 +4,7 @@ use cosmic::iced::core::window; use cosmic::iced::window::Id; use cosmic::iced::{Length, Rectangle}; use cosmic::surface::action::{app_popup, destroy_popup}; -use cosmic::widget::dropdown::popup_dropdown; -use cosmic::widget::{list_column, settings, toggler}; +use cosmic::widget::{dropdown::popup_dropdown, list_column, settings, toggler}; use cosmic::Element; const ID: &str = "com.system76.CosmicAppletExample"; @@ -101,7 +100,6 @@ impl cosmic::Application for Window { Message::Surface(destroy_popup(id)) } else { Message::Surface(app_popup::( - |_| Default::default(), move |state: &mut Window| { let new_id = Id::unique(); state.popup = Some(new_id); @@ -124,6 +122,8 @@ impl cosmic::Application for Window { }, Some(Box::new(move |state: &Window| { let content_list = list_column() + .padding(5) + .spacing(0) .add(settings::item( "Example row", cosmic::widget::container( diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index b5968461..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", @@ -22,5 +22,4 @@ features = [ "surface-message", "multi-window", "wgpu", - "wayland", ] diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index af4200c9..f6e571e0 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -5,10 +5,9 @@ use cosmic::app::Settings; use cosmic::iced::{Alignment, Length, Size}; -use cosmic::prelude::*; use cosmic::widget::menu::{self, KeyBind}; -use cosmic::widget::{nav_bar, RcElementWrapper}; -use cosmic::{executor, iced, widget, Core}; +use cosmic::widget::nav_bar; +use cosmic::{executor, iced, prelude::*, widget, Core}; use std::collections::HashMap; use std::sync::LazyLock; @@ -43,7 +42,7 @@ pub enum Action { impl widget::menu::Action for Action { type Message = Message; - fn message(&self) -> Self::Message { + fn message(&self) -> Message { match self { Action::Hi => Message::Hi, Action::Hi2 => Message::Hi2, @@ -52,25 +51,6 @@ impl widget::menu::Action for Action { } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Action2 { - Hi, - Hi2, - Hi3, -} - -impl widget::menu::Action for Action2 { - type Message = cosmic::Action; - - fn message(&self) -> Self::Message { - cosmic::Action::App(match self { - Action2::Hi => Message::Hi, - Action2::Hi2 => Message::Hi2, - Action2::Hi3 => Message::Hi3, - }) - } -} - /// Runs application with these settings #[rustfmt::skip] fn main() -> Result<(), Box> { @@ -103,7 +83,6 @@ pub enum Message { Hi2, Hi3, Tick, - ValueChanged(f32), } /// The [`App`] stores application-specific state. @@ -115,7 +94,6 @@ pub struct App { hidden: bool, keybinds: HashMap, progress: f32, - progress_slider: f32, } /// Implement [`cosmic::Application`] to integrate with COSMIC. @@ -158,7 +136,6 @@ impl cosmic::Application for App { hidden: true, keybinds: HashMap::new(), progress: 0.0, - progress_slider: 0.0, }; let command = app.update_title(); @@ -177,70 +154,6 @@ impl cosmic::Application for App { self.update_title() } - fn nav_context_menu(&self) -> Option>>> { - Some(menu::nav_context( - &HashMap::new(), - vec![ - vec![cosmic::widget::menu::Item::Button( - "Hi", - Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), - Action2::Hi, - )], - vec![ - cosmic::widget::menu::Item::Button( - "Hi 2", - Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), - Action2::Hi, - ), - cosmic::widget::menu::Item::Button( - "Hi 22", - Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), - Action2::Hi, - ), - ], - vec![ - cosmic::widget::menu::Item::Button( - "Hi 3", - Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), - Action2::Hi, - ), - cosmic::widget::menu::Item::Button( - "Hi 33", - Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), - Action2::Hi, - ), - cosmic::widget::menu::Item::Button( - "Hi 333", - Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), - Action2::Hi, - ), - ], - vec![ - cosmic::widget::menu::Item::Button( - "Hi 44", - Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), - Action2::Hi, - ), - cosmic::widget::menu::Item::Button( - "Hi 4444", - Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), - Action2::Hi, - ), - cosmic::widget::menu::Item::Button( - "Hi 444444", - Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), - Action2::Hi, - ), - cosmic::widget::menu::Item::Button( - "Hi 444444444444", - Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), - Action2::Hi, - ), - ], - ], - )) - } - /// Handle application events here. fn update(&mut self, message: Self::Message) -> cosmic::app::Task { match message { @@ -271,9 +184,6 @@ impl cosmic::Application for App { Message::Tick => { self.progress = (self.progress + 0.01) % 1.0; } - Message::ValueChanged(value) => { - self.progress_slider = value; - } } Task::none() } @@ -290,7 +200,7 @@ impl cosmic::Application for App { .map_or("No page selected", String::as_str); let centered = widget::container( - widget::column::with_capacity(16) + widget::column::with_capacity(14) .push(widget::text::body(page_content)) .push( widget::text_input::text_input("", &self.input_1) @@ -331,18 +241,6 @@ impl cosmic::Application for App { .progress(self.progress) .width(Length::Fill), ) - .push( - widget::slider(0.0..=1.0, self.progress_slider, Message::ValueChanged) - .step(0.001), - ) - .push( - widget::progress_bar::linear::Linear::new() - .girth(10.0) - .progress(self.progress_slider) - .width(Length::Fill) - .markers([0.25, 0.5, 0.75]) - .segment_spacing(2), - ) .push( widget::progress_bar::circular::Circular::new() .size(50.0) @@ -365,7 +263,6 @@ impl cosmic::Application for App { .progress(1.0) .width(Length::Fill), ) - .push(widget::button::suggested("asdf").on_press(Message::Ignore)) .spacing(cosmic::theme::spacing().space_s) .width(Length::Fill) .height(Length::Shrink) 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/config/src/main.rs b/examples/config/src/main.rs index dfcc5b99..f6fb5c0d 100644 --- a/examples/config/src/main.rs +++ b/examples/config/src/main.rs @@ -88,7 +88,4 @@ pub fn main() { println!("Testing state"); test_config(Config::new_state("com.system76.Example", 1).unwrap()); - - println!("Testing data"); - test_config(Config::new_data("com.system76.Example", 1).unwrap()); } 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/cosmic/src/window.rs b/examples/cosmic/src/window.rs index 4168718d..9fce8767 100644 --- a/examples/cosmic/src/window.rs +++ b/examples/cosmic/src/window.rs @@ -23,11 +23,15 @@ use cosmic::{ Element, }; use cosmic_time::{Instant, Timeline}; -use std::cell::RefCell; -use std::rc::Rc; -use std::sync::atomic::{AtomicU32, Ordering}; -use std::sync::Arc; -use std::vec; +use std::{ + cell::RefCell, + rc::Rc, + sync::{ + atomic::{AtomicU32, Ordering}, + Arc, + }, + vec, +}; // XXX The use of button is removed because it assigns the same ID to multiple buttons, causing a crash when a11y is enabled... // static BTN: Lazy = Lazy::new(|| id::Id::new("BTN")); diff --git a/examples/cosmic/src/window/bluetooth.rs b/examples/cosmic/src/window/bluetooth.rs index 2878b46a..1b5892f6 100644 --- a/examples/cosmic/src/window/bluetooth.rs +++ b/examples/cosmic/src/window/bluetooth.rs @@ -1,7 +1,9 @@ use super::{Page, Window}; -use cosmic::iced::widget::{column, text}; -use cosmic::widget::{list_column, settings, toggler}; -use cosmic::Element; +use cosmic::{ + iced::widget::{column, text}, + widget::{list_column, settings, toggler}, + Element, +}; #[derive(Clone, Copy, Debug)] pub enum Message { diff --git a/examples/cosmic/src/window/demo.rs b/examples/cosmic/src/window/demo.rs index 20a730b5..0d31fa93 100644 --- a/examples/cosmic/src/window/demo.rs +++ b/examples/cosmic/src/window/demo.rs @@ -1,17 +1,19 @@ -use std::cell::RefCell; -use std::rc::Rc; +use std::{cell::RefCell, rc::Rc}; use apply::Apply; -use cosmic::iced::widget::{checkbox, column, progress_bar, radio, slider, text}; -use cosmic::iced::{Alignment, Length}; -use cosmic::iced_core::id; -use cosmic::theme::ThemeType; -use cosmic::widget::color_picker::ColorPickerUpdate; -use cosmic::widget::{ - button, dropdown, icon, layer_container as container, segmented_button, segmented_control, - settings, spin_button, tab_bar, toggler, ColorPickerModel, +use cosmic::{ + cosmic_theme, + iced::widget::{checkbox, column, progress_bar, radio, slider, text}, + iced::{Alignment, Length}, + iced_core::id, + theme::ThemeType, + widget::{ + button, color_picker::ColorPickerUpdate, dropdown, icon, layer_container as container, + segmented_button, segmented_control, settings, spin_button, tab_bar, toggler, + ColorPickerModel, + }, + Element, }; -use cosmic::{cosmic_theme, Element}; use cosmic_time::{anim, chain, Timeline}; use fraction::{Decimal, ToPrimitive}; use once_cell::sync::Lazy; diff --git a/examples/cosmic/src/window/desktop.rs b/examples/cosmic/src/window/desktop.rs index a087de9e..46a4e5b8 100644 --- a/examples/cosmic/src/window/desktop.rs +++ b/examples/cosmic/src/window/desktop.rs @@ -1,7 +1,10 @@ -use cosmic::iced::widget::{column, container, horizontal_space, image, row, svg, text}; -use cosmic::iced::Length; -use cosmic::widget::{list_column, settings, toggler}; -use cosmic::{theme, Element}; +use cosmic::{ + iced::widget::{column, container, horizontal_space, image, row, svg, text}, + iced::Length, + theme, + widget::{list_column, settings, toggler}, + Element, +}; use super::{Page, SubPage, Window}; diff --git a/examples/cosmic/src/window/system_and_accounts.rs b/examples/cosmic/src/window/system_and_accounts.rs index 5f498857..ed1bd004 100644 --- a/examples/cosmic/src/window/system_and_accounts.rs +++ b/examples/cosmic/src/window/system_and_accounts.rs @@ -1,7 +1,9 @@ -use cosmic::iced::widget::{horizontal_space, row, text}; -use cosmic::iced::Length; -use cosmic::widget::{icon, list_column, settings}; -use cosmic::Element; +use cosmic::{ + iced::widget::{horizontal_space, row, text}, + iced::Length, + widget::{icon, list_column, settings}, + Element, +}; use super::{Message, Page, SubPage, Window}; 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/menu/src/main.rs b/examples/menu/src/main.rs index 900d0322..da0c3231 100644 --- a/examples/menu/src/main.rs +++ b/examples/menu/src/main.rs @@ -9,9 +9,11 @@ use std::{env, process}; use cosmic::app::{Core, Settings, Task}; use cosmic::iced::alignment::{Horizontal, Vertical}; use cosmic::iced::keyboard::Key; -use cosmic::iced::{window, Length, Size}; +use cosmic::iced::window; +use cosmic::iced::{Length, Size}; use cosmic::widget::menu::action::MenuAction; -use cosmic::widget::menu::key_bind::{KeyBind, Modifier}; +use cosmic::widget::menu::key_bind::KeyBind; +use cosmic::widget::menu::key_bind::Modifier; use cosmic::widget::menu::{self, ItemHeight, ItemWidth}; use cosmic::widget::RcElementWrapper; use cosmic::{executor, Element}; 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/multi-window/src/window.rs b/examples/multi-window/src/window.rs index a6d40d36..754a0d86 100644 --- a/examples/multi-window/src/window.rs +++ b/examples/multi-window/src/window.rs @@ -1,11 +1,13 @@ use std::collections::HashMap; -use cosmic::app::Core; -use cosmic::iced::core::{id, Alignment, Length, Point}; -use cosmic::iced::widget::{column, container, scrollable, text}; -use cosmic::iced::{self, event, window, Subscription}; -use cosmic::prelude::*; -use cosmic::widget::{button, header_bar}; +use cosmic::{ + app::Core, + iced::core::{id, Alignment, Length, Point}, + iced::widget::{column, container, scrollable, text}, + iced::{self, event, window, Subscription}, + prelude::*, + widget::{button, header_bar}, +}; #[derive(Debug, Clone, PartialEq)] pub enum Message { 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/nav-context/src/main.rs b/examples/nav-context/src/main.rs index 875b83ff..1992066f 100644 --- a/examples/nav-context/src/main.rs +++ b/examples/nav-context/src/main.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use cosmic::app::{Core, Settings, Task}; use cosmic::iced::Size; -use cosmic::widget::{menu, nav_bar, space}; +use cosmic::widget::{menu, nav_bar}; use cosmic::{executor, iced, ApplicationExt, Element}; #[derive(Clone, Copy)] @@ -127,20 +127,18 @@ impl cosmic::Application for App { Some(&self.nav_model) } - /// The context menu list. - fn nav_context_menu(&self) -> Option>>> { - Some(menu::nav_context( + /// The context menu to display for the given nav bar item ID. + fn nav_context_menu( + &self, + id: nav_bar::Id, + ) -> Option>>> { + Some(menu::items( &HashMap::new(), - self.nav_model - .iter() - .map(|e| { - vec![ - menu::Item::Button("Move Up", None, NavMenuAction::MoveUp(e)), - menu::Item::Button("Move Down", None, NavMenuAction::MoveDown(e)), - menu::Item::Button("Delete", None, NavMenuAction::Delete(e)), - ] - }) - .collect(), + vec![ + menu::Item::Button("Move Up", None, NavMenuAction::MoveUp(id)), + menu::Item::Button("Move Down", None, NavMenuAction::MoveDown(id)), + menu::Item::Button("Delete", None, NavMenuAction::Delete(id)), + ], )) } 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/spin-button/src/main.rs b/examples/spin-button/src/main.rs index 0228291c..47db4dce 100644 --- a/examples/spin-button/src/main.rs +++ b/examples/spin-button/src/main.rs @@ -1,8 +1,15 @@ -use cosmic::app::{Core, Task}; -use cosmic::iced::alignment::{Horizontal, Vertical}; -use cosmic::iced::{self, Alignment, Length, Size}; +use cosmic::iced::Length; use cosmic::widget::{column, container, spin_button}; -use cosmic::{Application, Apply, Element}; +use cosmic::Apply; +use cosmic::{ + app::{Core, Task}, + iced::{ + self, + alignment::{Horizontal, Vertical}, + Alignment, Size, + }, + Application, Element, +}; use fraction::Decimal; pub struct SpinButtonExamplApp { 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/subscriptions/src/main.rs b/examples/subscriptions/src/main.rs index 325af162..17e630aa 100644 --- a/examples/subscriptions/src/main.rs +++ b/examples/subscriptions/src/main.rs @@ -5,8 +5,7 @@ use cosmic::app::{Core, Settings, Task}; use cosmic::iced::Subscription; -use cosmic::prelude::*; -use cosmic::{executor, widget}; +use cosmic::{executor, prelude::*, widget}; /// Runs application with these settings fn main() -> Result<(), Box> { 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/table-view/src/main.rs b/examples/table-view/src/main.rs index e518b3cc..d2478429 100644 --- a/examples/table-view/src/main.rs +++ b/examples/table-view/src/main.rs @@ -9,7 +9,8 @@ use chrono::Datelike; use cosmic::app::{Core, Settings, Task}; use cosmic::iced::Size; use cosmic::prelude::*; -use cosmic::widget::{self, nav_bar, table}; +use cosmic::widget::table; +use cosmic::widget::{self, nav_bar}; use cosmic::{executor, iced}; #[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash)] 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/lo/libcosmic.ftl b/i18n/af/libcosmic.ftl similarity index 100% rename from i18n/lo/libcosmic.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/af/libcosmic_yoda.ftl b/i18n/bn/libcosmic.ftl similarity index 100% rename from i18n/af/libcosmic_yoda.ftl rename to i18n/bn/libcosmic.ftl diff --git a/i18n/bn/libcosmic_yoda.ftl b/i18n/ca/libcosmic.ftl similarity index 100% rename from i18n/bn/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/ca/libcosmic_yoda.ftl b/i18n/da/libcosmic.ftl similarity index 100% rename from i18n/ca/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/da/libcosmic_yoda.ftl b/i18n/el/libcosmic.ftl similarity index 100% rename from i18n/da/libcosmic_yoda.ftl rename to i18n/el/libcosmic.ftl diff --git a/i18n/el/libcosmic_yoda.ftl b/i18n/el/libcosmic_yoda.ftl deleted file mode 100644 index f4fe0b31..00000000 --- a/i18n/el/libcosmic_yoda.ftl +++ /dev/null @@ -1,34 +0,0 @@ -close = Κλείσιμο -february = Φεβρουάριος { $year } -documenters = Τεκμηριωτές -november = Νοέμβριος { $year } -may = Μάιος { $year } -april = Απρίλιος { $year } -translators = Μεταφραστές -artists = Καλλιτέχνες -license = Άδεια χρήσης -december = Δεκέμβριος { $year } -links = Σύνδεσμοι -march = Μάρτιος { $year } -june = Ιούνιος { $year } -august = Αύγουστος { $year } -developers = Προγραμματιστές -july = Ιούλιος { $year } -september = Σεπτέμβριος { $year } -designers = Σχεδιαστές -october = Οκτώβριος { $year } -january = Ιανουάριος { $year } -monday = Δευτέρα -mon = Δευ -tuesday = Τρίτη -tue = Τρί -wednesday = Τετάρτη -wed = Τετ -thursday = Πέμπτη -thu = Πέμ -friday = Παρασκευή -fri = Παρ -saturday = Σάββατο -sat = Σάβ -sunday = Κυριακή -sun = Κυρ 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/kn/libcosmic_yoda.ftl b/i18n/kmr/libcosmic.ftl similarity index 100% rename from i18n/kn/libcosmic_yoda.ftl rename to i18n/kmr/libcosmic.ftl diff --git a/i18n/kmr/libcosmic_yoda.ftl b/i18n/kmr/libcosmic_yoda.ftl deleted file mode 100644 index a771f86d..00000000 --- a/i18n/kmr/libcosmic_yoda.ftl +++ /dev/null @@ -1,34 +0,0 @@ -february = Reşemî { $year } -close = Bigire -documenters = Pêlbendvan -november = Mijdar { $year } -may = Gulan { $year } -april = Avrêl { $year } -translators = Wergêr -artists = Hunermend -license = Lîsans -december = Berfanbar { $year } -links = Girêdan -march = Adar { $year } -june = Pûşber { $year } -august = Tebax { $year } -developers = Pêşvebir -july = Tîrmeh { $year } -september = Îlon { $year } -designers = Nigarvan -october = Cotmeh { $year } -january = Çile { $year } -monday = Duşem -mon = Duş -tuesday = Sêşem -tue = Sêş -wednesday = Çarşem -wed = Çar -thursday = Pênçşem -thu = Pên -friday = Înî -fri = Înî -saturday = Şemî -sat = Şem -sunday = Yekşem -sun = Yek diff --git a/i18n/li/libcosmic_yoda.ftl b/i18n/kn/libcosmic.ftl similarity index 100% rename from i18n/li/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/ml/libcosmic_yoda.ftl b/i18n/li/libcosmic.ftl similarity index 100% rename from i18n/ml/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/ms/libcosmic_yoda.ftl b/i18n/ml/libcosmic.ftl similarity index 100% rename from i18n/ms/libcosmic_yoda.ftl rename to i18n/ml/libcosmic.ftl diff --git a/i18n/oc/libcosmic_yoda.ftl b/i18n/ms/libcosmic.ftl similarity index 100% rename from i18n/oc/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/sk/libcosmic_yoda.ftl b/i18n/oc/libcosmic.ftl similarity index 100% rename from i18n/sk/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/sl/libcosmic_yoda.ftl b/i18n/sk/libcosmic.ftl similarity index 100% rename from i18n/sl/libcosmic_yoda.ftl rename to i18n/sk/libcosmic.ftl diff --git a/i18n/ta/libcosmic_yoda.ftl b/i18n/sl/libcosmic.ftl similarity index 100% rename from i18n/ta/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/th/libcosmic_yoda.ftl b/i18n/sr/libcosmic.ftl similarity index 100% rename from i18n/th/libcosmic_yoda.ftl rename to i18n/sr/libcosmic.ftl diff --git a/i18n/sr/libcosmic_yoda.ftl b/i18n/sr/libcosmic_yoda.ftl deleted file mode 100644 index e8b88303..00000000 --- a/i18n/sr/libcosmic_yoda.ftl +++ /dev/null @@ -1,34 +0,0 @@ -close = Затвори -february = Фебруар { $year } -documenters = Документатори -november = Новембар { $year } -may = Мај { $year } -april = Април { $year } -translators = Преводиоци -artists = Уметници -license = Дозвола -december = Децембар { $year } -links = Везе -march = Март { $year } -june = Јун { $year } -august = Август { $year } -developers = Програмери -july = Јул { $year } -september = Септембар { $year } -designers = Дизајнери -october = Октобар { $year } -january = Јануар { $year } -monday = Понедељак -mon = Пон -tuesday = Уторак -tue = Уто -wednesday = Среда -wed = Сре -thursday = Четвртак -thu = Чет -friday = Петак -fri = Пет -saturday = Субота -sat = Суб -sunday = Недеља -sun = Нед 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/ti/libcosmic_yoda.ftl b/i18n/ta/libcosmic.ftl similarity index 100% rename from i18n/ti/libcosmic_yoda.ftl rename to i18n/ta/libcosmic.ftl diff --git a/i18n/uz/libcosmic_yoda.ftl b/i18n/th/libcosmic.ftl similarity index 100% rename from i18n/uz/libcosmic_yoda.ftl rename to i18n/th/libcosmic.ftl diff --git a/i18n/vi/libcosmic_yoda.ftl b/i18n/ti/libcosmic.ftl similarity index 100% rename from i18n/vi/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/yue-Hant/libcosmic_yoda.ftl b/i18n/uz/libcosmic.ftl similarity index 100% rename from i18n/yue-Hant/libcosmic_yoda.ftl rename to i18n/uz/libcosmic.ftl diff --git a/i18n/vi/libcosmic.ftl b/i18n/vi/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/yue-Hant/libcosmic.ftl b/i18n/yue-Hant/libcosmic.ftl new file mode 100644 index 00000000..e69de29b 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 1a57634a..78caabba 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 1a57634add6e820c299f1c59664cd98098a9c348 +Subproject commit 78caabba7ef91cd1030da6f70b41d266704ffece diff --git a/justfile b/justfile index 7d9aecee..4653434e 100644 --- a/justfile +++ b/justfile @@ -23,12 +23,6 @@ check-json: (check '--message-format=json') clean: cargo clean -# Generate documentation -doc: - env RUSTDOCFLAGS="--cfg docsrs" cargo +nightly-2026-04-27 doc --no-deps --verbose \ - -p cosmic-client-toolkit -p cosmic-protocols -p libcosmic \ - --features tokio,winit,wayland,desktop,single-instance,applet,xdg-portal,multi-window - # Also remove .cargo and vendored dependencies clean-dist: clean rm -rf .cargo vendor vendor.tar target diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index c1578aaf..00000000 --- a/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -imports_granularity = "Module" 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/action.rs b/src/app/action.rs index a3b9dd6a..fb982acb 100644 --- a/src/app/action.rs +++ b/src/app/action.rs @@ -1,10 +1,10 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -use crate::config::CosmicTk; +use crate::surface; use crate::theme::Theme; use crate::widget::nav_bar; -use crate::{keyboard_nav, surface}; +use crate::{config::CosmicTk, keyboard_nav}; #[cfg(all(feature = "wayland", target_os = "linux"))] use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use cosmic_theme::ThemeMode; @@ -64,8 +64,6 @@ pub enum Action { Unfocus(iced::window::Id), /// Windowing system initialized WindowingSystemInitialized, - /// Blur support enabled - BlurEnabled, /// Updates the window maximized state WindowMaximized(iced::window::Id, bool), /// Updates the tracked window geometry. diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index e29b9a99..030ed041 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -2,27 +2,22 @@ // SPDX-License-Identifier: MPL-2.0 use std::borrow::Borrow; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::sync::Arc; use super::{Action, Application, ApplicationExt, Subscription}; -#[cfg(all(feature = "wayland", target_os = "linux"))] -use crate::core::Auto; -#[cfg(all(feature = "wayland", target_os = "linux"))] -use crate::surface::action::LiveSettings; use crate::theme::{THEME, Theme, ThemeType}; use crate::{Core, Element, keyboard_nav}; #[cfg(all(feature = "wayland", target_os = "linux"))] use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use cosmic_theme::ThemeMode; -#[cfg(all(feature = "wayland", target_os = "linux"))] -use enumflags2::BitFlags; #[cfg(not(any(feature = "multi-window", feature = "wayland", target_os = "linux")))] use iced::Application as IcedApplication; #[cfg(all(feature = "wayland", target_os = "linux"))] use iced::event::wayland; use iced::{Task, theme, window}; use iced_futures::event::listen_with; +#[cfg(all(feature = "wayland", target_os = "linux"))] use iced_winit::SurfaceIdWrapper; use palette::color_difference::EuclideanDistance; @@ -88,24 +83,17 @@ fn init_windowing_system(handle: window::raw_window_handle::WindowHandle) -> #[derive(Default)] pub struct Cosmic { pub app: App, + #[cfg(all(feature = "wayland", target_os = "linux"))] pub surface_views: HashMap< window::Id, ( Option, SurfaceIdWrapper, - Box Fn(&'a App) -> crate::surface::action::LiveSettings>, - Option< - Box< - dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action> - + Send - + Sync - + 'static, - >, - >, + Box Fn(&'a App) -> Element<'a, crate::Action>>, ), >, + pub tracked_windows: HashSet, pub opened_surfaces: HashMap, - blur_enabled: bool, } impl Cosmic @@ -123,21 +111,14 @@ where let id = core.main_window_id().unwrap_or(window::Id::RESERVED); let (model, command) = T::init(core, flags); - let existing_theme = THEME.lock().unwrap(); - let blur = existing_theme.transparent; - drop(existing_theme); - let mut cmds = vec![ - iced_runtime::window::run_with_handle(id, init_windowing_system), - command, - ]; - - if blur { - cmds.push(crate::task::message(crate::action::cosmic( - Action::BlurEnabled, - ))); - } - (Self::new(model), Task::batch(cmds)) + ( + Self::new(model), + Task::batch([ + command, + iced_runtime::window::run_with_handle(id, init_windowing_system), + ]), + ) } #[cfg(not(feature = "multi-window"))] @@ -158,7 +139,7 @@ where #[cfg(feature = "surface-message")] match _surface_message { #[cfg(all(feature = "wayland", target_os = "linux"))] - crate::surface::Action::AppSubsurface(settings, _live_settings, view) => { + crate::surface::Action::AppSubsurface(settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else { @@ -166,9 +147,7 @@ where return Task::none(); }; - let settings = settings(&mut self.app); - - let view = view.and_then(|view| { + if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Fn(&'a T) -> Element<'a, crate::Action> + Send @@ -181,18 +160,22 @@ where None } } - }); - self.get_subsurface(settings, view.map(|v| *v)) + }) { + let settings = settings(&mut self.app); + + self.get_subsurface(settings, *view) + } else { + iced_winit::commands::subsurface::get_subsurface(settings(&mut self.app)) + } } #[cfg(all(feature = "wayland", target_os = "linux"))] - crate::surface::Action::Subsurface(settings, _live_settings, view) => { + crate::surface::Action::Subsurface(settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else { tracing::error!("Invalid settings for subsurface"); return Task::none(); }; - let settings = settings(); if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: { + crate::surface::Action::AppPopup(settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync>>().ok()) else { tracing::error!("Invalid settings for popup"); return Task::none(); }; - let Some(live_settings) = - std::sync::Arc::try_unwrap(live_settings) - .ok() - .and_then(|s| { - s.downcast:: LiveSettings + Send + Sync>>() - .ok() - }) - else { - tracing::error!("Invalid live settings for popup"); - return Task::none(); - }; - let view = view.and_then(|view| { + if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Fn(&'a T) -> Element<'a, crate::Action> + Send @@ -243,10 +217,13 @@ where None } } - }); - let settings = settings(&mut self.app); + }) { + let settings = settings(&mut self.app); - self.get_popup(settings, *live_settings, view.map(|v| *v)) + self.get_popup(settings, *view) + } else { + iced_winit::commands::popup::get_popup(settings(&mut self.app)) + } } #[cfg(all(feature = "wayland", target_os = "linux"))] crate::surface::Action::DestroyPopup(id) => { @@ -279,7 +256,7 @@ where iced::Task::none() } #[cfg(all(feature = "wayland", target_os = "linux"))] - crate::surface::Action::Popup(settings, live_settings, view) => { + crate::surface::Action::Popup(settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync>>().ok()) else { @@ -287,20 +264,6 @@ where return Task::none(); }; - let Some(live_settings) = - std::sync::Arc::try_unwrap(live_settings) - .ok() - .and_then(|s| { - s.downcast:: LiveSettings + Send + Sync>>() - .ok() - }) - else { - tracing::error!("Invalid live settings for popup"); - return Task::none(); - }; - let settings = settings(); - let live_settings = Box::new(move |_: &T| live_settings()); - if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Element<'static, crate::Action> + Send + Sync, @@ -312,13 +275,15 @@ where } } }) { - self.get_popup(settings, live_settings, Some(Box::new(move |_| view()))) + let settings = settings(); + + self.get_popup(settings, Box::new(move |_| view())) } else { - self.get_popup(settings, live_settings, None) + iced_winit::commands::popup::get_popup(settings()) } } #[cfg(all(feature = "wayland", target_os = "linux"))] - crate::surface::Action::AppWindow(id, settings, live_settings, view) => { + crate::surface::Action::AppWindow(id, settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| { s.downcast:: iced::window::Settings + Send + Sync>>() .ok() @@ -326,19 +291,8 @@ where tracing::error!("Invalid settings for AppWindow"); return Task::none(); }; - let Some(live_settings) = - std::sync::Arc::try_unwrap(live_settings) - .ok() - .and_then(|s| { - s.downcast:: LiveSettings + Send + Sync>>() - .ok() - }) - else { - tracing::error!("Invalid live settings for popup"); - return Task::none(); - }; - let view = view.and_then(|view| { + if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Fn(&'a T) -> Element<'a, crate::Action> + Send @@ -350,13 +304,25 @@ where None } } - }); - let settings = settings(&mut self.app); + }) { + let settings = settings(&mut self.app); + self.tracked_windows.insert(id); - self.get_window(id, settings, *live_settings, view.map(|v| *v)) + self.get_window(id, settings, *view) + } else { + let settings = settings(&mut self.app); + + self.tracked_windows.insert(id); + iced_runtime::task::oneshot(|channel| { + iced_runtime::Action::Window(iced_runtime::window::Action::Open( + id, settings, channel, + )) + }) + .discard() + } } #[cfg(all(feature = "wayland", target_os = "linux"))] - crate::surface::Action::Window(id, settings, live_settings, view) => { + crate::surface::Action::Window(id, settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| { s.downcast:: iced::window::Settings + Send + Sync>>() .ok() @@ -365,18 +331,6 @@ where return Task::none(); }; - let Some(live_settings) = - std::sync::Arc::try_unwrap(live_settings) - .ok() - .and_then(|s| { - s.downcast:: LiveSettings + Send + Sync>>() - .ok() - }) - else { - tracing::error!("Invalid live settings for popup"); - return Task::none(); - }; - if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Element<'static, crate::Action> + Send + Sync, @@ -389,16 +343,14 @@ where } }) { let settings = settings(); + self.tracked_windows.insert(id); - self.get_window( - id, - settings, - Box::new(move |_| live_settings()), - Some(Box::new(move |_| view())), - ) + self.get_window(id, settings, Box::new(move |_| view())) } else { let settings = settings(); + self.tracked_windows.insert(id); + iced_runtime::task::oneshot(|channel| { iced_runtime::Action::Window(iced_runtime::window::Action::Open( id, settings, channel, @@ -412,95 +364,6 @@ where crate::surface::Action::Task(f) => { f().map(|sm| crate::Action::Cosmic(Action::Surface(sm))) } - #[cfg(all(feature = "wayland", target_os = "linux"))] - crate::surface::Action::AppLayerShell(settings, live_settings, view) => { - let Some(settings) = std::sync::Arc::try_unwrap(settings) - .ok() - .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings + Send + Sync>>().ok()) else { - tracing::error!("Invalid settings for layer surface"); - return Task::none(); - }; - let Some(live_settings) = - std::sync::Arc::try_unwrap(live_settings) - .ok() - .and_then(|s| { - s.downcast:: LiveSettings + Send + Sync>>() - .ok() - }) - else { - tracing::error!("Invalid live settings for popup"); - return Task::none(); - }; - - let view = view.and_then(|view| { - match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Fn(&'a T) -> Element<'a, crate::Action> - + Send - + Sync, - >>() { - Ok(v) => Some(v), - Err(err) => { - tracing::error!("Invalid view for layer surface: {err:?}"); - None - } - } - }); - - let settings = settings(&mut self.app); - - self.get_layer_shell(settings, *live_settings, view.map(|v| *v)) - } - #[cfg(all(feature = "wayland", target_os = "linux"))] - crate::surface::Action::LayerShell(settings, live_settings, view) => { - let Some(settings) = std::sync::Arc::try_unwrap(settings) - .ok() - .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings + Send + Sync>>().ok()) else { - tracing::error!("Invalid settings for layer surface"); - return Task::none(); - }; - - let Some(live_settings) = - std::sync::Arc::try_unwrap(live_settings) - .ok() - .and_then(|s| { - s.downcast:: LiveSettings + Send + Sync>>() - .ok() - }) - else { - tracing::error!("Invalid live settings for popup"); - return Task::none(); - }; - let settings = settings(); - let live_settings = live_settings(); - let live_settings = Box::new(move |_app: &T| live_settings); - - if let Some(view) = view.and_then(|view| { - match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Element<'static, crate::Action> + Send + Sync, - >>() { - Ok(v) => Some(v), - Err(err) => { - tracing::error!("Invalid view for layer surface: {err:?}"); - None - } - } - }) { - self.get_layer_shell(settings, live_settings, Some(Box::new(move |_| view()))) - } else { - self.get_layer_shell(settings, live_settings, None) - } - } - #[cfg(all(feature = "wayland", target_os = "linux"))] - crate::surface::Action::DestroyLayerShell(id) => { - iced_winit::commands::layer_surface::destroy_layer_surface(id) - } - crate::surface::Action::SyncLiveSettings(id) => { - if let Some((_, id, live_settings, _)) = self.surface_views.get(&id) { - let live_settings = live_settings(&self.app); - return self.apply_live_settings(*id, &live_settings); - } - Task::none() - } _ => iced::Task::none(), } @@ -545,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 { @@ -597,9 +460,6 @@ where )) => { return Some(Action::WindowState(id, s)); } - wayland::Event::BlurEnabled => { - return Some(Action::BlurEnabled); - } _ => (), } } @@ -711,7 +571,8 @@ where #[cfg(feature = "multi-window")] pub fn view(&self, id: window::Id) -> Element<'_, crate::Action> { - if let Some((_, _, _, Some(v))) = self.surface_views.get(&id) { + #[cfg(all(feature = "wayland", target_os = "linux"))] + if let Some((_, _, v)) = self.surface_views.get(&id) { return v(&self.app); } if self @@ -760,7 +621,7 @@ 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 @@ -811,15 +672,35 @@ impl Cosmic { self.app.core_mut().window.is_maximized = state.intersects(WindowState::MAXIMIZED | WindowState::FULLSCREEN); } - { + if self.app.core().sync_window_border_radii_to_theme() { + use iced_runtime::platform_specific::wayland::CornerRadius; use iced_winit::platform_specific::commands::corner_radius::corner_radius; let theme = THEME.lock().unwrap(); - let rounded = !self.app.core().window.sharp_corners - && self.app.core().sync_window_border_radii_to_theme(); - - let cur_rad = self.app.core().corners(&theme, rounded); - return Task::batch([corner_radius(id, cur_rad).discard()]); + let t = theme.cosmic(); + let radii = t.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); + let cur_rad = CornerRadius { + top_left: radii[0].round() as u32, + top_right: radii[1].round() as u32, + bottom_right: radii[2].round() as u32, + bottom_left: radii[3].round() as u32, + }; + let rounded = !self.app.core().window.sharp_corners; + return Task::batch([corner_radius( + id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard()]); } } @@ -892,70 +773,18 @@ impl Cosmic { if a.distance_squared(*t_inner.accent_color()) > 0.00001 { theme = Theme::system(Arc::new(t_inner.with_accent(a))); } - } - } - - let new_blur = self.blur_enabled && { - let t = theme.cosmic(); - match self.app.core().app_type() { - crate::core::AppType::Window => t.frosted_windows, - crate::core::AppType::System => t.frosted_system_interface, - crate::core::AppType::Applet => t.frosted_applets, - } - }; - - theme.transparent = new_blur; - let mut guard = THEME.lock().unwrap(); - guard.set_theme(theme.theme_type.clone()); - guard.transparent = new_blur; - drop(guard); - - #[cfg(all(feature = "wayland", target_os = "linux"))] - { - let core = self.app.core(); - let mut cmds = Vec::with_capacity(1 + self.surface_views.len()); - let blur = if new_blur { - iced::window::enable_blur - } else { - iced::window::disable_blur }; - if core.blur(&theme, None) { - cmds.push(blur( - self.app - .core() - .main_window_id() - .unwrap_or(window::Id::RESERVED), - )); - } - for (id, wrapper, ..) in &self.surface_views { - let overriden = wrapper.2(&self.app); - if core.blur(&theme, Some(wrapper.1)) && overriden.blur.unwrap_or(true) { - cmds.push(blur(*id)); - } else if overriden.blur.is_some_and(|b| !b) { - cmds.push(iced::window::disable_blur(*id)); - } - } - return Task::batch(cmds); } + + THEME.lock().unwrap().set_theme(theme.theme_type); } - Action::SystemThemeChange(keys, mut theme) => { + Action::SystemThemeChange(keys, theme) => { let cur_is_dark = THEME.lock().unwrap().theme_type.is_dark(); // Ignore updates if the current theme mode does not match. if cur_is_dark != theme.cosmic().is_dark { return iced::Task::none(); } - // update transparent - let new_blur = self.blur_enabled && { - let t = theme.cosmic(); - match self.app.core().app_type() { - crate::core::AppType::Window => t.frosted_windows, - crate::core::AppType::System => t.frosted_system_interface, - crate::core::AppType::Applet => t.frosted_applets, - } - }; - theme.transparent = new_blur; - let cmd = self.app.system_theme_update(&keys, theme.cosmic()); // Record the last-known system theme in event that the current theme is custom. self.app.core_mut().system_theme = theme.clone(); @@ -979,45 +808,89 @@ impl Cosmic { } else { theme }; - new_theme.transparent = new_blur; new_theme.theme_type.prefer_dark(prefer_dark); cosmic_theme.set_theme(new_theme.theme_type); - cosmic_theme.transparent = new_blur; - #[cfg(all(feature = "wayland", target_os = "linux"))] - { + if self.app.core().sync_window_border_radii_to_theme() { + use iced_runtime::platform_specific::wayland::CornerRadius; use iced_winit::platform_specific::commands::corner_radius::corner_radius; - let rounded = self.app.core().sync_window_border_radii_to_theme() - && !self.app.core().window.sharp_corners; + let t = cosmic_theme.cosmic(); - let cur_rad = self.app.core().corners(&cosmic_theme, rounded); + let radii = t.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); + let cur_rad = CornerRadius { + top_left: radii[0].round() as u32, + top_right: radii[1].round() as u32, + bottom_right: radii[2].round() as u32, + bottom_left: radii[3].round() as u32, + }; + let rounded = !self.app.core().window.sharp_corners; // Update radius for the main window let main_window_id = self .app .core() .main_window_id() .unwrap_or(window::Id::RESERVED); - let mut cmds = vec![corner_radius(main_window_id, cur_rad).discard()]; + let mut cmds = vec![ + corner_radius( + main_window_id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), + ]; // Update radius for each tracked view with the window surface type - for (id, (_, surface_type, live_settings, _)) in &self.surface_views { - let overriden = live_settings(&self.app); - let cur_rad = if let Some(c) = overriden.corners { - Some(c) - } else { - corners( - *surface_type, - rounded, - &cosmic_theme, - self.app.core().auto_corner_radius, - ) - }; - if cur_rad.is_none() { - continue; + for (id, (_, surface_type, _)) in self.surface_views.iter() { + if let SurfaceIdWrapper::Window(_) = surface_type { + cmds.push( + corner_radius( + *id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), + ); } - cmds.push(corner_radius(*id, cur_rad).discard()); + } + // Update radius for all tracked windows + for id in self.tracked_windows.iter() { + cmds.push( + corner_radius( + *id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), + ); } return Task::batch(cmds); @@ -1048,7 +921,6 @@ impl Cosmic { } { return iced::Task::none(); } - let mut cmds = vec![self.app.system_theme_mode_update(&keys, &mode)]; let core = self.app.core_mut(); @@ -1077,15 +949,6 @@ impl Cosmic { } else { new_theme }; - let new_blur = self.blur_enabled && { - let t = new_theme.cosmic(); - match core.app_type() { - crate::core::AppType::Window => t.frosted_windows, - crate::core::AppType::System => t.frosted_system_interface, - crate::core::AppType::Applet => t.frosted_applets, - } - }; - new_theme.transparent = new_blur; core.system_theme = new_theme.clone(); { @@ -1094,14 +957,21 @@ impl Cosmic { // Only apply update if the theme is set to load a system theme if let ThemeType::System { .. } = cosmic_theme.theme_type { cosmic_theme.set_theme(new_theme.theme_type); - cosmic_theme.transparent = new_blur; #[cfg(all(feature = "wayland", target_os = "linux"))] - { + if self.app.core().sync_window_border_radii_to_theme() { + use iced_runtime::platform_specific::wayland::CornerRadius; use iced_winit::platform_specific::commands::corner_radius::corner_radius; - let rounded = self.app.core().sync_window_border_radii_to_theme() - && !self.app.core().window.sharp_corners; - let cur_rad = self.app.core().corners(&cosmic_theme, rounded); + let t = cosmic_theme.cosmic(); + + let radii = t.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); + let cur_rad = CornerRadius { + top_left: radii[0].round() as u32, + top_right: radii[1].round() as u32, + bottom_right: radii[2].round() as u32, + bottom_left: radii[3].round() as u32, + }; + let rounded = !self.app.core().window.sharp_corners; // Update radius for the main window let main_window_id = self @@ -1109,52 +979,64 @@ impl Cosmic { .core() .main_window_id() .unwrap_or(window::Id::RESERVED); - let mut cmds = - vec![corner_radius(main_window_id, cur_rad).discard()]; + let mut cmds = vec![ + corner_radius( + main_window_id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), + ]; // Update radius for each tracked view with the window surface type - for (id, (_, surface_type, live_settings, _)) in &self.surface_views - { - let overriden = live_settings(&self.app); - let cur_rad = if let Some(c) = overriden.corners { - Some(c) - } else { - corners( - *surface_type, - rounded, - &cosmic_theme, - self.app.core().auto_corner_radius, + for (id, (_, surface_type, _)) in self.surface_views.iter() { + if let SurfaceIdWrapper::Window(_) = surface_type { + cmds.push( + corner_radius( + *id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), + ); + } + } + // Update radius for all tracked windows + for id in self.tracked_windows.iter() { + cmds.push( + corner_radius( + *id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, ) - }; - if cur_rad.is_none() { - continue; - } - cmds.push(corner_radius(*id, cur_rad).discard()); - } - - let core = self.app.core(); - let blur = if cosmic_theme.transparent { - iced::window::enable_blur - } else { - iced::window::disable_blur - }; - - if core.blur(&cosmic_theme, None) { - cmds.push(blur( - self.app - .core() - .main_window_id() - .unwrap_or(window::Id::RESERVED), - )); - } - for (id, wrapper, ..) in &self.surface_views { - let overriden = wrapper.2(&self.app); - if core.blur(&cosmic_theme, Some(wrapper.1)) - && overriden.blur.unwrap_or(true) - { - cmds.push(blur(*id)); - } else if overriden.blur.is_some_and(|b| !b) { - cmds.push(iced::window::disable_blur(*id)); - } + .discard(), + ); } return Task::batch(cmds); @@ -1177,7 +1059,7 @@ impl Cosmic { #[allow(clippy::used_underscore_binding)] _token, ), - ); + ) } #[cfg(not(all(feature = "wayland", target_os = "linux")))] @@ -1197,9 +1079,10 @@ impl Cosmic { *v == 0 }) { self.opened_surfaces.remove(&id); + #[cfg(all(feature = "wayland", target_os = "linux"))] self.surface_views.remove(&id); + self.tracked_windows.remove(&id); } - self.surface_views.shrink_to(self.surface_views.len() * 2); let mut ret = if let Some(msg) = self.app.on_close_requested(id) { self.app.update(msg) @@ -1253,57 +1136,18 @@ impl Cosmic { if changed { core.theme_sub_counter += 1; - let mut new_theme = if is_dark { + let new_theme = if is_dark { crate::theme::system_dark() } else { crate::theme::system_light() }; - if let ThemeType::System { .. } = new_theme.theme_type { - let new_blur = self.blur_enabled && { - let t = new_theme.cosmic(); - match core.app_type() { - crate::core::AppType::Window => t.frosted_windows, - crate::core::AppType::System => t.frosted_system_interface, - crate::core::AppType::Applet => t.frosted_applets, - } - }; - new_theme.transparent = new_blur; - } core.system_theme = new_theme.clone(); - let core = self.app.core(); { let mut cosmic_theme = THEME.lock().unwrap(); // Only apply update if the theme is set to load a system theme if let ThemeType::System { theme: _, .. } = cosmic_theme.theme_type { - let mut cmds = Vec::with_capacity(1); - #[cfg(all(feature = "wayland", target_os = "linux"))] - { - let blur = if cosmic_theme.transparent { - iced::window::enable_blur - } else { - iced::window::disable_blur - }; - - if core.blur(&cosmic_theme, None) { - cmds.push(blur( - core.main_window_id().unwrap_or(window::Id::RESERVED), - )); - } - - for (id, wrapper, ..) in &self.surface_views { - let overriden = wrapper.2(&self.app); - if core.blur(&cosmic_theme, Some(wrapper.1)) - && overriden.blur.unwrap_or(true) - { - cmds.push(blur(*id)); - } else if overriden.blur.is_some_and(|b| !b) { - cmds.push(iced::window::disable_blur(*id)); - } - } - } cosmic_theme.set_theme(new_theme.theme_type); - return Task::batch(cmds); } } } @@ -1364,7 +1208,6 @@ impl Cosmic { parent, SurfaceIdWrapper::Subsurface(_) | SurfaceIdWrapper::Popup(_), _, - _, )) = self.surface_views.get(&f) { // If the parent is already focused, push the new focus @@ -1381,7 +1224,7 @@ impl Cosmic { cur = self .surface_views .get(&p) - .and_then(|(parent, _, _, _)| *parent); + .and_then(|(parent, _, _)| *parent); } parent_chain.reverse(); self.app.core_mut().focused_window = parent_chain; @@ -1405,135 +1248,44 @@ impl Cosmic { } Action::Opened(id) => { #[cfg(all(feature = "wayland", target_os = "linux"))] - { + if self.app.core().sync_window_border_radii_to_theme() { + use iced_runtime::platform_specific::wayland::CornerRadius; use iced_winit::platform_specific::commands::corner_radius::corner_radius; let theme = THEME.lock().unwrap(); - + let t = theme.cosmic(); + let radii = t.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); + let cur_rad = CornerRadius { + top_left: radii[0].round() as u32, + top_right: radii[1].round() as u32, + bottom_right: radii[2].round() as u32, + bottom_left: radii[3].round() as u32, + }; // TODO do we need per window sharp corners? - let rounded = (!self.app.core().window.sharp_corners - && self.app.core().sync_window_border_radii_to_theme()) - || self - .surface_views - .get(&id) - .is_some_and(|(_, surface_type, _, _)| { - matches!( - surface_type, - SurfaceIdWrapper::Popup(_) | SurfaceIdWrapper::LayerSurface(_) - ) - }); - let new_blur = self.blur_enabled && { - let t = theme.cosmic(); - match self.app.core().app_type() { - crate::core::AppType::Window => t.frosted_windows, - crate::core::AppType::System => t.frosted_system_interface, - crate::core::AppType::Applet => t.frosted_applets, - } - }; - - let wrapper = self.surface_views.get(&id).map(|s| s.1); - - // this will blur untracked windows as if they were the main window - let blur_cmd = if self.app.core().blur(&theme, wrapper) - && self - .surface_views - .get(&id) - .and_then(|s| s.2(&self.app).blur) - .unwrap_or(true) - { - let blur = if new_blur { - iced::window::enable_blur - } else { - iced::window::disable_blur - }; - let mut cmds = Vec::with_capacity(1 + self.surface_views.len()); - cmds.push(blur(id)); - - Task::batch(cmds) - } else { - Task::none() - }; - - let corner_task = if let Some((_, cur_rad)) = - self.surface_views.get(&id).map(|s| (s.1, &s.2)).and_then( - |(s, overriden)| { - let overriden = overriden(&self.app); - if let Some(c) = overriden.corners { - Some((s, c)) - } else { - corners(s, rounded, &theme, self.app.core().auto_corner_radius) - .map(|c| (s, c)) - } - }, - ) { - corner_radius(id, Some(cur_rad)).discard() - } else if id - == self - .app - .core() - .main_window_id() - .unwrap_or(window::Id::RESERVED) - { - corner_radius(id, self.app.core().corners(&theme, rounded)).discard() - } else { - Task::none() - }; + let rounded = !self.app.core().window.sharp_corners; return Task::batch([ - blur_cmd, - corner_task, + corner_radius( + id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), iced_runtime::window::run_with_handle(id, init_windowing_system), ]); } return iced_runtime::window::run_with_handle(id, init_windowing_system); } - #[cfg(all(feature = "wayland", target_os = "linux"))] - Action::BlurEnabled => { - // TODO do this after blur event confirms support instead of for all wayland windows - self.blur_enabled = true; - let mut t = THEME.lock().unwrap(); - - let new_blur = self.blur_enabled && { - let t = t.cosmic(); - match self.app.core().app_type() { - crate::core::AppType::Window => t.frosted_windows, - crate::core::AppType::System => t.frosted_system_interface, - crate::core::AppType::Applet => t.frosted_applets, - } - }; - - t.transparent = new_blur; - - self.app.core_mut().system_theme.transparent = new_blur; - { - let blur = if new_blur { - iced::window::enable_blur - } else { - iced::window::disable_blur - }; - let mut cmds = Vec::with_capacity(1 + self.surface_views.len()); - if self.app.core().blur(&t, None) { - cmds.push(blur( - self.app - .core() - .main_window_id() - .unwrap_or(window::Id::RESERVED), - )); - } - for (id, wrapper, ..) in &self.surface_views { - let overriden = wrapper.2(&self.app); - if self.app.core().blur(&t, Some(wrapper.1)) - && overriden.blur.unwrap_or(true) - { - cmds.push(blur(*id)); - } else if overriden.blur.is_some_and(|b| !b) { - cmds.push(iced::window::disable_blur(*id)); - } - } - return Task::batch(cmds); - } - } - _ => (), + _ => {} } iced::Task::none() @@ -1544,136 +1296,34 @@ impl Cosmic { pub fn new(app: App) -> Self { Self { app, + #[cfg(all(feature = "wayland", target_os = "linux"))] surface_views: HashMap::new(), + tracked_windows: HashSet::new(), opened_surfaces: HashMap::new(), - blur_enabled: false, } } - #[cfg(feature = "surface-message")] - /// Apply live setting overrides for a surface - pub fn apply_live_settings( - &mut self, - id_wrapper: SurfaceIdWrapper, - live_settings: &crate::surface::action::LiveSettings, - ) -> Task> { - let id = id_wrapper.inner(); - - let mut cmds = Vec::with_capacity(2); - let t = THEME.try_lock().unwrap(); - - if let Some(blur) = live_settings.blur { - let blur_cmd = if blur { - if matches!(id_wrapper, SurfaceIdWrapper::Window(_)) { - window::enable_blur(id) - } else { - #[cfg(all(feature = "wayland", target_os = "linux"))] - { - iced_winit::commands::blur::blur( - id, - Some(vec![iced::Rectangle::new( - iced::Point::ORIGIN, - iced::Size::INFINITE, - )]), - ) - .discard() - } - #[cfg(not(all(feature = "wayland", target_os = "linux")))] - { - iced::window::enable_blur(id) - } - } - } else if matches!(id_wrapper, SurfaceIdWrapper::Window(_)) { - window::disable_blur(id) - } else { - #[cfg(all(feature = "wayland", target_os = "linux"))] - { - iced_winit::commands::blur::blur(id, None).discard() - } - #[cfg(not(all(feature = "wayland", target_os = "linux")))] - { - iced::window::disable_blur(id) - } - }; - cmds.push(blur_cmd); - } else if self.app.core().blur(&t, Some(id_wrapper)) { - cmds.push(if matches!(id_wrapper, SurfaceIdWrapper::Window(_)) { - window::enable_blur(id) - } else { - #[cfg(all(feature = "wayland", target_os = "linux"))] - { - iced_winit::commands::blur::blur( - id, - Some(vec![iced::Rectangle::new( - iced::Point::ORIGIN, - iced::Size::INFINITE, - )]), - ) - .discard() - } - #[cfg(not(all(feature = "wayland", target_os = "linux")))] - { - iced::window::enable_blur(id) - } - }); - } - #[cfg(all(feature = "wayland", target_os = "linux"))] - if let Some(corners) = live_settings.corners { - cmds.push( - iced_winit::commands::corner_radius::corner_radius(id, Some(corners)).discard(), - ); - } else { - let rounded = !self.app.core().window.sharp_corners - && self.app.core().sync_window_border_radii_to_theme(); - if let Some(cur_rad) = - corners(id_wrapper, rounded, &t, self.app.core().auto_corner_radius) - { - cmds.push( - iced_winit::commands::corner_radius::corner_radius(id, Some(cur_rad)).discard(), - ); - } - } - #[cfg(all(feature = "wayland", target_os = "linux"))] - if let (SurfaceIdWrapper::LayerSurface(id), Some(padding)) = - (id_wrapper, live_settings.padding) - { - cmds.push(iced_winit::commands::layer_surface::set_padding( - id, padding, - )); - } - Task::batch(cmds) - } - #[cfg(all(feature = "wayland", target_os = "linux"))] /// Create a subsurface pub fn get_subsurface( &mut self, settings: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings, - view: Option< - Box Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync>, + view: Box< + dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync, >, ) -> Task> { use iced_winit::commands::subsurface::get_subsurface; *self.opened_surfaces.entry(settings.id).or_insert_with(|| 0) += 1; - let live_settings_task = self.apply_live_settings( - SurfaceIdWrapper::Subsurface(settings.id), - &LiveSettings { - blur: Some(self.blur_enabled), - corners: None, - padding: None, - }, - ); self.surface_views.insert( settings.id, ( Some(settings.parent), SurfaceIdWrapper::Subsurface(settings.id), - Box::new(|_| LiveSettings::default()), view, ), ); - Task::batch([live_settings_task, get_subsurface(settings)]) + get_subsurface(settings) } #[cfg(all(feature = "wayland", target_os = "linux"))] @@ -1681,27 +1331,21 @@ impl Cosmic { pub fn get_popup( &mut self, settings: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings, - live_settings: Box Fn(&'a App) -> LiveSettings + Send + Sync>, - view: Option< - Box Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync>, + view: Box< + dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync, >, ) -> Task> { use iced_winit::commands::popup::get_popup; *self.opened_surfaces.entry(settings.id).or_insert_with(|| 0) += 1; - let live_settings_task = self.apply_live_settings( - SurfaceIdWrapper::Popup(settings.id), - &live_settings(&self.app), - ); self.surface_views.insert( settings.id, ( Some(settings.parent), SurfaceIdWrapper::Popup(settings.id), - live_settings, view, ), ); - live_settings_task.chain(get_popup(settings)).discard() + get_popup(settings) } #[cfg(all(feature = "wayland", target_os = "linux"))] @@ -1710,107 +1354,23 @@ impl Cosmic { &mut self, id: iced::window::Id, settings: iced::window::Settings, - live_settings: Box Fn(&'a App) -> LiveSettings + Send + Sync>, - - view: Option< - Box Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync>, + view: Box< + dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync, >, ) -> Task> { use iced_winit::SurfaceIdWrapper; *self.opened_surfaces.entry(id).or_insert(0) += 1; - let live_settings_task = - self.apply_live_settings(SurfaceIdWrapper::Window(id), &live_settings(&self.app)); self.surface_views.insert( id, ( None, // TODO parent for window, platform specific option maybe? SurfaceIdWrapper::Window(id), - live_settings, view, ), ); - Task::batch([ - iced_runtime::task::oneshot(|channel| { - iced_runtime::Action::Window(iced_runtime::window::Action::Open( - id, settings, channel, - )) - }) - .discard(), - // We don't control window creation in the same way - live_settings_task, - ]) - } - - #[cfg(all(feature = "wayland", target_os = "linux"))] - pub fn get_layer_shell( - &mut self, - settings: iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings, - live_settings: Box Fn(&'a App) -> LiveSettings + Send + Sync>, - view: Option< - Box Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync>, - >, - ) -> Task> { - use iced_winit::SurfaceIdWrapper; - use iced_winit::platform_specific::commands::layer_surface::get_layer_surface; - *self.opened_surfaces.entry(settings.id).or_insert(0) += 1; - let live_settings_task = self.apply_live_settings( - SurfaceIdWrapper::LayerSurface(settings.id), - &live_settings(&self.app), - ); - self.surface_views.insert( - settings.id, - ( - None, // TODO parent for layer shell, platform specific option maybe? - SurfaceIdWrapper::LayerSurface(settings.id), - live_settings, - view, - ), - ); - Task::batch([live_settings_task, get_layer_surface(settings)]) + iced_runtime::task::oneshot(|channel| { + iced_runtime::Action::Window(iced_runtime::window::Action::Open(id, settings, channel)) + }) + .discard() } } - -#[cfg(all(feature = "wayland", target_os = "linux"))] -fn corners( - surface_type: SurfaceIdWrapper, - rounded: bool, - theme: &Theme, - auto_corner_radius: BitFlags, -) -> Option { - if !match surface_type { - SurfaceIdWrapper::Window(_) => auto_corner_radius.contains(Auto::Window), - SurfaceIdWrapper::LayerSurface(_) => auto_corner_radius.contains(Auto::System), - SurfaceIdWrapper::Popup(_) => auto_corner_radius.contains(Auto::Popup), - _ => false, - } { - return None; - } - let theme = theme.cosmic(); - Some(if let SurfaceIdWrapper::Popup(_) = surface_type { - let radius_m = theme.radius_m(); - iced_runtime::platform_specific::wayland::CornerRadius { - top_left: radius_m[0].round() as u32, - top_right: radius_m[1].round() as u32, - bottom_right: radius_m[2].round() as u32, - bottom_left: radius_m[3].round() as u32, - } - } else if let SurfaceIdWrapper::Window(_) = surface_type - && !rounded - { - let radius_0 = theme.radius_0(); - iced_runtime::platform_specific::wayland::CornerRadius { - top_left: radius_0[0].round() as u32, - top_right: radius_0[1].round() as u32, - bottom_right: radius_0[2].round() as u32, - bottom_left: radius_0[3].round() as u32, - } - } else { - let radius_s = theme.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); - iced_runtime::platform_specific::wayland::CornerRadius { - top_left: radius_s[0].round() as u32, - top_right: radius_s[1].round() as u32, - bottom_right: radius_s[2].round() as u32, - bottom_left: radius_s[3].round() as u32, - } - }) -} diff --git a/src/app/mod.rs b/src/app/mod.rs index bf5da95e..f78beac7 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -22,11 +22,11 @@ use crate::prelude::*; use crate::theme::THEME; use crate::widget::{container, id_container, menu, nav_bar, popover, space}; use apply::Apply; -use iced::{Color, Length, Subscription, theme, window}; +use iced::{Length, Subscription}; +use iced::{theme, window}; pub use settings::Settings; use std::borrow::Cow; -use std::cell::RefCell; -use std::rc::Rc; +use std::{cell::RefCell, rc::Rc}; #[cold] pub(crate) fn iced_settings( @@ -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, @@ -389,22 +389,10 @@ where let mut nav = crate::widget::nav_bar(nav_model, |id| crate::Action::Cosmic(Action::NavBar(id))) .on_context(|id| crate::Action::Cosmic(Action::NavBarContext(id))) - .context_menu(self.nav_context_menu()); - #[cfg(all( - feature = "multi-window", - feature = "wayland", - target_os = "linux", - feature = "surface-message" - ))] - { - nav = nav - .window_id_maybe(self.core().main_window_id()) - .on_surface_action(|m| crate::Action::Cosmic(crate::app::Action::Surface(m))) - } - let mut nav = nav - .into_container() - .width(iced::Length::Shrink) - .height(iced::Length::Fill); + .context_menu(self.nav_context_menu(self.core().nav_bar_context())) + .into_container() + .width(iced::Length::Shrink) + .height(iced::Length::Fill); if !self.core().is_condensed() { nav = nav.max_width(280); @@ -414,7 +402,10 @@ where } /// Shows a context menu for the active nav bar item. - fn nav_context_menu(&self) -> Option>>> { + fn nav_context_menu( + &self, + id: nav_bar::Id, + ) -> Option>>> { None } @@ -624,7 +615,6 @@ impl ApplicationExt for App { let content_container = core.window.content_container; let show_context = core.window.show_context; let nav_bar_active = core.nav_bar_active(); - let transparent_header = core.window.transparent_header; let focused = core .focus_chain() .iter() @@ -645,8 +635,10 @@ impl ApplicationExt for App { let mut widgets = Vec::with_capacity(3); // Insert nav bar onto the left side of the window. - let has_nav = if let Some(nav) = self.nav_bar() { - let nav = id_container(nav, iced_core::id::Id::new("COSMIC_nav_bar")); + let has_nav = if let Some(nav) = self + .nav_bar() + .map(|nav| id_container(nav, iced_core::id::Id::new("COSMIC_nav_bar"))) + { widgets.push( container(nav) .padding([ @@ -781,6 +773,7 @@ impl ApplicationExt for App { .focused(focused) .maximized(maximized) .sharp_corners(sharp_corners) + .transparent(content_container) .title(&core.window.header_title) .on_drag(crate::Action::Cosmic(Action::Drag)) .on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu)) @@ -833,11 +826,7 @@ impl ApplicationExt for App { let cosmic = theme.cosmic(); container::Style { background: Some(iced::Background::Color( - if transparent_header { - Color::TRANSPARENT - } else { - cosmic.background(theme.transparent).base.into() - }, + cosmic.background.base.into(), )), border: iced::Border { radius: [ @@ -866,7 +855,7 @@ impl ApplicationExt for App { container::Style { background: if content_container { Some(iced::Background::Color( - theme.cosmic().background(theme.transparent).base.into(), + theme.cosmic().background.base.into(), )) } else { None diff --git a/src/applet/column.rs b/src/applet/column.rs index 1ec9c731..9657b566 100644 --- a/src/applet/column.rs +++ b/src/applet/column.rs @@ -2,10 +2,14 @@ use crate::iced; use iced::core::alignment::{self, Alignment}; use iced::core::event::{self, Event}; +use iced::core::layout; +use iced::core::mouse; +use iced::core::overlay; +use iced::core::renderer; use iced::core::widget::{Operation, Tree}; use iced::core::{ Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, - layout, mouse, overlay, renderer, widget, + widget, }; /// A container that distributes its contents vertically. diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 37635bf4..48721e1c 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -1,31 +1,37 @@ #[cfg(feature = "applet-token")] pub mod token; -use crate::app::{BootData, BootDataInner, cosmic, iced_settings}; -use crate::cctk::sctk; -use crate::theme::{self, Button, THEME, system_dark, system_light}; -use crate::widget::autosize::{self, Autosize, autosize}; -use crate::widget::column::Column; -use crate::widget::row::Row; -use crate::widget::space::{horizontal, vertical}; -use crate::widget::{self, layer_container}; -use crate::{Application, Element, Renderer}; +use crate::app::{BootData, BootDataInner, cosmic}; +use crate::{ + Application, Element, Renderer, + app::iced_settings, + cctk::sctk, + theme::{self, Button, THEME, system_dark, system_light}, + widget::{ + self, + autosize::{self, Autosize, autosize}, + column::Column, + layer_container, + row::Row, + space::horizontal, + space::vertical, + }, +}; pub use cosmic_panel_config; use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize}; -use iced::alignment::{Alignment, Horizontal, Vertical}; -use iced::widget::Container; -use iced::{self, Color, Length, Limits, Rectangle, window}; +use iced::{ + self, Color, Length, Limits, Rectangle, + alignment::{Alignment, Horizontal, Vertical}, + widget::Container, + window, +}; use iced_core::{Padding, Shadow}; use iced_runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner}; use iced_widget::Text; use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity}; -use std::borrow::Cow; use std::cell::RefCell; -use std::num::NonZeroU32; -use std::rc::Rc; -use std::sync::LazyLock; -use std::time::Duration; +use std::{borrow::Cow, num::NonZeroU32, rc::Rc, sync::LazyLock, time::Duration}; use tracing::info; pub mod column; @@ -221,7 +227,7 @@ impl Context { let icon = widget::icon(icon) .class(if symbolic { theme::Svg::Custom(Rc::new(|theme| iced_widget::svg::Style { - color: Some(theme.cosmic().background(theme.transparent).on.into()), + color: Some(theme.cosmic().background.on.into()), })) } else { theme::Svg::default() @@ -372,17 +378,16 @@ impl Context { Container::::new(content).style(|theme| { let cosmic = theme.cosmic(); let corners = cosmic.corner_radii; - let mut bg = cosmic.background(theme.transparent).base; iced_widget::container::Style { - text_color: Some(cosmic.background(theme.transparent).on.into()), - background: Some(Color::from(bg).into()), + text_color: Some(cosmic.background.on.into()), + background: Some(Color::from(cosmic.background.base).into()), border: iced::Border { radius: corners.radius_m.into(), width: 1.0, - color: cosmic.background(theme.transparent).divider.into(), + color: cosmic.background.divider.into(), }, shadow: Shadow::default(), - icon_color: Some(cosmic.background(theme.transparent).on.into()), + icon_color: Some(cosmic.background.on.into()), snap: true, } }), @@ -560,7 +565,6 @@ pub fn run(flags: App::Flags) -> iced::Result { core.window.show_maximize = false; core.window.show_minimize = false; core.window.use_template = false; - core.app_type = crate::core::AppType::Applet; window_settings.decorations = false; window_settings.exit_on_close_request = true; diff --git a/src/applet/row.rs b/src/applet/row.rs index 888c68ef..a6745d1c 100644 --- a/src/applet/row.rs +++ b/src/applet/row.rs @@ -1,13 +1,16 @@ //! 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; +use iced::core::renderer; use iced::core::widget::{Operation, Tree}; use iced::core::{ - Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, mouse, - overlay, renderer, widget, + 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/applet/token/subscription.rs b/src/applet/token/subscription.rs index 64be0ce2..07c528ea 100644 --- a/src/applet/token/subscription.rs +++ b/src/applet/token/subscription.rs @@ -1,12 +1,13 @@ use crate::iced; use cctk::sctk::reexports::calloop; -use futures::channel::mpsc::{UnboundedReceiver, unbounded}; -use futures::{SinkExt, StreamExt}; +use futures::{ + SinkExt, StreamExt, + channel::mpsc::{UnboundedReceiver, unbounded}, +}; use iced::Subscription; -use iced_futures::{futures, stream}; -use std::fmt::Debug; -use std::hash::Hash; -use std::thread::JoinHandle; +use iced_futures::futures; +use iced_futures::stream; +use std::{fmt::Debug, hash::Hash, thread::JoinHandle}; use super::wayland_handler::wayland_handler; diff --git a/src/applet/token/wayland_handler.rs b/src/applet/token/wayland_handler.rs index 3bbaf573..3db84fc4 100644 --- a/src/applet/token/wayland_handler.rs +++ b/src/applet/token/wayland_handler.rs @@ -1,20 +1,27 @@ -use std::os::fd::{FromRawFd, RawFd}; -use std::os::unix::net::UnixStream; +use std::os::{ + fd::{FromRawFd, RawFd}, + unix::net::UnixStream, +}; use super::subscription::{TokenRequest, TokenUpdate}; -use cctk::sctk::activation::{RequestData, RequestDataExt}; -use cctk::sctk::reexports::calloop; -use cctk::sctk::reexports::calloop_wayland_source::WaylandSource; -use cctk::sctk::seat::{SeatHandler, SeatState}; -use cctk::sctk::{self}; -use cctk::wayland_client::protocol::wl_seat::WlSeat; -use cctk::wayland_client::protocol::wl_surface::WlSurface; -use cctk::wayland_client::{self}; +use cctk::{ + sctk::{ + self, + activation::{RequestData, RequestDataExt}, + reexports::{calloop, calloop_wayland_source::WaylandSource}, + seat::{SeatHandler, SeatState}, + }, + wayland_client::{ + self, + protocol::{wl_seat::WlSeat, wl_surface::WlSurface}, + }, +}; use iced_futures::futures::channel::mpsc::UnboundedSender; -use sctk::activation::{ActivationHandler, ActivationState}; -use sctk::registry::{ProvidesRegistryState, RegistryState}; -use wayland_client::globals::registry_queue_init; -use wayland_client::{Connection, QueueHandle}; +use sctk::{ + activation::{ActivationHandler, ActivationState}, + registry::{ProvidesRegistryState, RegistryState}, +}; +use wayland_client::{Connection, QueueHandle, globals::registry_queue_init}; struct AppData { exit: bool, @@ -164,18 +171,7 @@ pub(crate) fn wayland_handler( if app_data.exit { break; } - - if let Err(why) = event_loop.dispatch(None, &mut app_data) { - if let calloop::Error::IoError(ref why) = why - && why.kind() == std::io::ErrorKind::BrokenPipe - { - tracing::info!("Connection to panel has ended. The applet will now exit with it."); - break; - } - - tracing::error!(?why, "dispatch error on Wayland connection to panel"); - break; - } + event_loop.dispatch(None, &mut app_data).unwrap(); } } diff --git a/src/command.rs b/src/command.rs index fee60ec5..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) } @@ -63,6 +65,7 @@ pub fn file_transfer_send( /// Returns a list of file paths. #[cfg(feature = "xdg-portal")] pub fn file_transfer_receive(key: String) -> iced::Task>> { + dbg!(&key); iced::Task::future(async move { let file_transfer = ashpd::documents::FileTransfer::new().await?; file_transfer.retrieve_files(&key).await diff --git a/src/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 8845d2af..970a5351 100644 --- a/src/core.rs +++ b/src/core.rs @@ -6,7 +6,6 @@ use std::collections::HashMap; use crate::widget::nav_bar; use cosmic_config::CosmicConfigEntry; use cosmic_theme::ThemeMode; -use enumflags2::{self, BitFlags, bitflags}; use iced::{Limits, Size, window}; use iced_core::window::Id; use palette::Srgba; @@ -39,24 +38,11 @@ pub struct Window { pub show_close: bool, pub show_maximize: bool, pub show_minimize: bool, - pub transparent_header: bool, pub is_maximized: bool, height: f32, width: f32, } -#[bitflags] -#[repr(u8)] -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum Auto { - /// Automatically apply effect to regular windows - Window, - /// Automatically apply effect to popups - Popup, - /// Automatically apply effect to system interface elements (layer shell surfaces) - System, -} - /// COSMIC-specific application settings #[derive(Clone)] pub struct Core { @@ -92,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, @@ -111,21 +99,8 @@ pub struct Core { pub(crate) menu_bars: HashMap, - pub(crate) auto_blur: BitFlags, - - pub(crate) auto_corner_radius: BitFlags, - - pub(crate) app_type: AppType, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum AppType { - /// A regular application - Window, - /// A system application - System, - /// An applet - Applet, + #[cfg(all(feature = "wayland", target_os = "linux"))] + pub(crate) sync_window_border_radii_to_theme: bool, } impl Default for Core { @@ -168,7 +143,6 @@ impl Default for Core { show_minimize: true, show_window_menu: false, is_maximized: false, - transparent_header: false, height: 0., width: 0., }, @@ -181,12 +155,12 @@ 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(), - auto_blur: Auto::System | Auto::Popup | Auto::Window, - auto_corner_radius: Auto::System | Auto::Popup | Auto::Window, - app_type: AppType::Window, + #[cfg(all(feature = "wayland", target_os = "linux"))] + sync_window_border_radii_to_theme: true, } } } @@ -458,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(); @@ -465,6 +440,7 @@ impl Core { crate::command::drag(id) } + #[cfg(feature = "winit")] pub fn maximize( &self, id: Option, @@ -476,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(); @@ -483,6 +460,7 @@ impl Core { crate::command::minimize(id) } + #[cfg(feature = "winit")] pub fn set_title( &self, id: Option, @@ -494,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(); @@ -501,6 +480,7 @@ impl Core { crate::command::set_windowed(id) } + #[cfg(feature = "winit")] pub fn toggle_maximize( &self, id: Option, @@ -512,112 +492,14 @@ impl Core { crate::command::toggle_maximize(id) } + // TODO should we emit tasks setting the corner radius or unsetting it if this is changed? + #[cfg(all(feature = "wayland", target_os = "linux"))] + pub fn set_sync_window_border_radii_to_theme(&mut self, sync: bool) { + self.sync_window_border_radii_to_theme = sync; + } + #[cfg(all(feature = "wayland", target_os = "linux"))] pub fn sync_window_border_radii_to_theme(&self) -> bool { - match self.app_type { - AppType::Window => self.auto_corner_radius.contains(Auto::Window), - AppType::System => self.auto_corner_radius.contains(Auto::System), - AppType::Applet => false, - } - } - - pub fn set_auto_blur(&mut self, auto_blur: BitFlags) { - self.auto_blur = auto_blur; - } - - pub fn auto_blur(&self) -> BitFlags { - self.auto_blur - } - - pub fn set_auto_corner_radius(&mut self, auto_corner_radius: BitFlags) { - self.auto_corner_radius = auto_corner_radius; - } - - pub fn auto_corner_radius(&self) -> BitFlags { - self.auto_corner_radius - } - - pub fn set_app_type(&mut self, app_type: AppType) { - self.app_type = app_type; - } - - pub fn app_type(&self) -> AppType { - self.app_type - } - - #[must_use] - pub fn blur( - &self, - theme: &Theme, - surface_id_wrapper: Option, - ) -> bool { - use iced_winit::SurfaceIdWrapper; - let theme = theme.cosmic(); - match surface_id_wrapper { - Some(SurfaceIdWrapper::LayerSurface(_)) => { - theme.frosted_system_interface && self.auto_blur.contains(Auto::System) - } - Some(SurfaceIdWrapper::Window(_)) => { - theme.frosted_windows && self.auto_blur.contains(Auto::Window) - } - Some(SurfaceIdWrapper::Popup(_)) - if matches!(self.app_type, AppType::Window | AppType::System) => - { - theme.frosted_windows && self.auto_blur.contains(Auto::Popup) - } - Some(SurfaceIdWrapper::Popup(_)) if matches!(self.app_type, AppType::Applet) => { - theme.frosted_applets && self.auto_blur.contains(Auto::Popup) - } - None => match self.app_type { - AppType::Window => theme.frosted_windows && self.auto_blur.contains(Auto::Window), - AppType::System => { - theme.frosted_system_interface && self.auto_blur.contains(Auto::System) - } - AppType::Applet => false, - }, - _ => false, - } - } - - /// Calculate suggested corners for each app type main window - #[must_use] - #[cfg(all(feature = "wayland", target_os = "linux"))] - pub fn corners( - &self, - theme: &Theme, - rounded: bool, - ) -> Option { - if !self.sync_window_border_radii_to_theme() { - return None; - } - let theme = theme.cosmic(); - let ret = if let AppType::Applet = self.app_type { - let radius_l = theme.radius_l(); - iced_runtime::platform_specific::wayland::CornerRadius { - top_left: radius_l[0].round() as u32, - top_right: radius_l[1].round() as u32, - bottom_right: radius_l[2].round() as u32, - bottom_left: radius_l[3].round() as u32, - } - } else if let AppType::Window = self.app_type - && !rounded - { - let radius_0 = theme.radius_0(); - iced_runtime::platform_specific::wayland::CornerRadius { - top_left: radius_0[0].round() as u32, - top_right: radius_0[1].round() as u32, - bottom_right: radius_0[2].round() as u32, - bottom_left: radius_0[3].round() as u32, - } - } else { - let radius_s = theme.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); - iced_runtime::platform_specific::wayland::CornerRadius { - top_left: radius_s[0].round() as u32, - top_right: radius_s[1].round() as u32, - bottom_right: radius_s[2].round() as u32, - bottom_left: radius_s[3].round() as u32, - } - }; - Some(ret) + self.sync_window_border_radii_to_theme } } diff --git a/src/dbus_activation.rs b/src/dbus_activation.rs index 880db634..99e2f9f0 100644 --- a/src/dbus_activation.rs +++ b/src/dbus_activation.rs @@ -1,15 +1,17 @@ // Copyright 2024 System76 // SPDX-License-Identifier: MPL-2.0 -use crate::ApplicationExt; -use iced::Subscription; -use iced_futures::futures::SinkExt; -use iced_futures::futures::channel::mpsc::{Receiver, Sender}; -use std::any::TypeId; -use std::collections::HashMap; -use url::Url; -use zbus::zvariant::Value; -use zbus::{interface, proxy}; +use { + crate::ApplicationExt, + iced::Subscription, + iced_futures::futures::{ + SinkExt, + channel::mpsc::{Receiver, Sender}, + }, + std::{any::TypeId, collections::HashMap}, + url::Url, + zbus::{interface, proxy, zvariant::Value}, +}; #[cold] pub fn subscription() -> Subscription> { @@ -43,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(), ), @@ -876,8 +872,7 @@ trait SystemdManger { #[cfg(all(test, not(windows)))] mod tests { use super::*; - use std::path::{Path, PathBuf}; - use std::{env, fs}; + use std::{env, fs, path::Path, path::PathBuf}; use tempfile::tempdir; struct EnvVarGuard { 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 e22a3a1f..02623799 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,93 +3,11 @@ #![allow(clippy::module_name_repetitions)] #![cfg_attr(target_os = "redox", feature(lazy_cell))] -#![cfg_attr(docsrs, feature(doc_cfg))] - -//! # The COSMIC Toolkit -//! -//! Quickly code your project with modular elements created with customization in mind. -//! Panels, applets, theming, tiling, launcher, app library, keyboard shortcuts and -//! dynamic or pinned workspaces are all made flexible to bend to your users needs. -//! -//! [COSMIC](https://system76.com/cosmic) empowers frictionless development by being -//! approachable, easy to maintain, and modular. Use the same language and toolkit to -//! build apps and applets, with helpful templates provided. Even shell components and -//! the compositor use the same toolkit. Learn once and use your knowledge anywhere in -//! the desktop. -//! -//! ## Architecture -//! -//! Based on [iced](https://iced.rs/), COSMIC apps and applets are modeled with the -//! MVU (Model-View-Update) pattern from [The Elm Architecture](https://guide.elm-lang.org/architecture/). -//! For more details, see the [architecture page of the iced book](https://book.iced.rs/architecture.html). -//! -//! An application will consist of: -//! -//! * An application **model** for holding persistent application state which implements -//! the [Application] trait. -//! * A [view](Application::view) function which borrows data from the model to construct -//! a view with stateless widgets -//! * An [update](Application::update) function which receives application messages from -//! widgets, tasks, and subscriptions. -//! -//! Messages handled by the update function are used to update the -//! application model and spawn background tasks that can emit messages back to the -//! app's update function. -//! -//! ### Tasks -//! -//! Tasks returned by the update function are scheduled for concurrent execution on a -//! background thread managed by the application's async executor, which is tokio by default. -//! They can be constructed from futures and may also stream events back to the application -//! asynchronously. -//! -//! ### Subscriptions -//! -//! Applications may also use a [subscription](Application::subscription) function to subscribe to external -//! asynchronous event streams. These can run perpetually from application start; or -//! optionally started, stopped, and restarted based on changes to the application model -//! between updates. Such as: -//! -//! * Conditionally starting and stopping a subscription based on the state of a boolean -//! value or enum -//! * Restarting a subscription when the hash of its borrowed data changes -//! * Dynamically spawning a subscription for each item in a list. -//! -//! ## Templates -//! -//! Get started using [cargo-generate](https://github.com/cargo-generate/cargo-generate) -//! with one of the following templates. The app template is for developing desktop -//! applications and the applet template is for developing COSMIC applets. -//! -//! - [App Template](https://github.com/pop-os/cosmic-app-template/) -//! - [Applet Template](https://github.com/pop-os/cosmic-applet-template/) -//! -//! ## Widgets -//! -//! Reference the [`widget`] module for available widgets for use in the view function. -//! Widgets are composable and can be configured through chainable builder methods. -//! Compose widgets together to create complex interfaces and higher level widgets. -//! -//! Composed widgets may be managed by their own custom type with its own view and -//! update functions. It is a common pattern to use these functions within the -//! application's own view and update functions. If using a custom type, implement -//! [From] for the [Element] type to have API treat a composed widget the same as a -//! native custom widget. -//! -//! If a widget does not exist for a specific use case, use the [Widget](iced::advanced::Widget) -//! trait to create an advanced custom widget. This can then be used to composed higher -//! level widgets by chaining composable widgets together. -//! -//! ## Core -//! -//! Every application model requires a [cosmic::Core](app::Core). This contains -//! application state which is managed by libcosmic's runtime for its generated -//! interfaces. Such as the context drawner, nav bar, and the headerbar. This can be -//! used by the app to subscribe to configuration changes and to emit events to the -//! libcosmic-managed portion of the application's state and view. +#![cfg_attr(docsrs, feature(doc_auto_cfg))] /// 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}; @@ -103,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}; @@ -146,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/localize.rs b/src/localize.rs index d63d9706..95a31655 100644 --- a/src/localize.rs +++ b/src/localize.rs @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only -use i18n_embed::fluent::{FluentLanguageLoader, fluent_language_loader}; -use i18n_embed::{DefaultLocalizer, LanguageLoader, Localizer}; +use i18n_embed::{ + DefaultLocalizer, LanguageLoader, Localizer, + fluent::{FluentLanguageLoader, fluent_language_loader}, +}; use rust_embed::RustEmbed; use std::sync::{LazyLock, OnceLock}; 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/process.rs b/src/process.rs index e2cb0f70..2b6c4e0e 100644 --- a/src/process.rs +++ b/src/process.rs @@ -5,7 +5,7 @@ use smol::io::AsyncReadExt; use std::io; use std::os::fd::OwnedFd; -use std::process::{Command, Stdio}; +use std::process::{Command, Stdio, exit}; #[cfg(feature = "tokio")] use tokio::io::AsyncReadExt; @@ -56,33 +56,24 @@ pub async fn spawn(mut command: Command) -> Option { match unsafe { libc::fork() } { // Parent process - fork_pid @ 1..=i32::MAX => { + 1.. => { // Drop copy of write end, then read PID from pipe drop(write); let pid = read_from_pipe(read).await; - _ = rustix::process::waitpid( - rustix::process::Pid::from_raw(fork_pid), - rustix::process::WaitOptions::empty(), - ); + // wait to prevent zombie + _ = rustix::process::wait(rustix::process::WaitOptions::empty()); pid } // Child process 0 => { let _res = rustix::process::setsid(); - let exit_status = if let Ok(child) = command.spawn() { + if let Ok(child) = command.spawn() { // Write PID to pipe let _ = rustix::io::write(write, &child.id().to_be_bytes()); - 0 - } else { - 1 - }; - - // # Safety - // Required for child fork to exit without affecting the parent. - unsafe { - libc::_exit(exit_status); } + + exit(0) } ..=-1 => { 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 2d86b007..50e2b4a9 100644 --- a/src/surface/action.rs +++ b/src/surface/action.rs @@ -2,15 +2,11 @@ // SPDX-License-Identifier: MPL-2.0 use super::Action; +#[cfg(feature = "winit")] use crate::Application; use iced::window; -#[cfg(all(feature = "wayland", target_os = "linux"))] -use iced_runtime::platform_specific::wayland::CornerRadius; -#[cfg(all(feature = "wayland", target_os = "linux"))] -use iced_runtime::platform_specific::wayland::layer_surface::IcedMargin; -use std::any::Any; -use std::sync::Arc; +use std::{any::Any, sync::Arc}; /// Used to produce a destroy popup message from within a widget. #[cfg(all(feature = "wayland", target_os = "linux"))] @@ -31,40 +27,18 @@ pub fn destroy_window(id: iced_core::window::Id) -> Action { Action::DestroyWindow(id) } -#[cfg(all(feature = "wayland", target_os = "linux"))] -#[must_use] -pub fn destroy_layer_shell(id: iced_core::window::Id) -> Action { - Action::DestroyLayerShell(id) -} - -#[derive(Debug, Default, Copy, Clone)] -pub struct LiveSettings { - #[cfg(all(feature = "wayland", target_os = "linux"))] - /// Override the surface padding value for the surface type. - pub padding: Option, - #[cfg(all(feature = "wayland", target_os = "linux"))] - /// Override the default corner radius value for the surface type. - pub corners: Option, - /// Override the default blur setting for the surface type. - pub blur: Option, -} - -#[cfg(all(feature = "wayland", target_os = "linux"))] -type BoxedView = Option< - Box< - dyn Fn(&App) -> crate::Element<'_, crate::Action<::Message>> - + Send - + Sync - + 'static, - >, ->; - -#[cfg(all(feature = "wayland", target_os = "linux"))] +#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[must_use] pub fn app_window( - live_settings: impl Fn(&App) -> LiveSettings + Send + Sync + 'static, settings: impl Fn(&mut App) -> window::Settings + Send + Sync + 'static, - view: BoxedView, + view: Option< + Box< + dyn for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action> + + Send + + Sync + + 'static, + >, + >, ) -> (window::Id, Action) { let id = window::Id::unique(); @@ -72,16 +46,11 @@ pub fn app_window( Box::new(settings); let boxed: Box = Box::new(boxed); - let boxed_live: Box LiveSettings + Send + Sync + 'static> = - Box::new(live_settings); - let boxed_live: Box = Box::new(boxed_live); - ( id, Action::AppWindow( id, Arc::new(boxed), - Arc::new(boxed_live), view.map(|view| { let boxed: Box = Box::new(view); Arc::new(boxed) @@ -91,10 +60,9 @@ 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( - live_settings: impl Fn() -> LiveSettings + Send + Sync + 'static, settings: impl Fn() -> window::Settings + Send + Sync + 'static, view: Option< impl Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, @@ -105,15 +73,11 @@ pub fn simple_window( let boxed: Box window::Settings + Send + Sync + 'static> = Box::new(settings); let boxed: Box = Box::new(boxed); - let boxed_live: Box LiveSettings + Send + Sync + 'static> = Box::new(live_settings); - let boxed_live: Box = Box::new(boxed_live); - ( id, Action::Window( id, Arc::new(boxed), - Arc::new(boxed_live), view.map(|view| { let boxed: Box< dyn Fn() -> crate::Element<'static, crate::Action> @@ -128,15 +92,21 @@ 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( - live_settings: impl Fn(&App) -> LiveSettings + Send + Sync + 'static, settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync + 'static, - view: BoxedView, + view: Option< + Box< + dyn for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action> + + Send + + Sync + + 'static, + >, + >, ) -> Action { let boxed: Box< dyn Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings @@ -146,13 +116,8 @@ pub fn app_popup( > = Box::new(settings); let boxed: Box = Box::new(boxed); - let boxed_live: Box LiveSettings + Send + Sync + 'static> = - Box::new(live_settings); - let boxed_live: Box = Box::new(boxed_live); - Action::AppPopup( Arc::new(boxed), - Arc::new(boxed_live), view.map(|view| { let boxed: Box = Box::new(view); Arc::new(boxed) @@ -161,9 +126,9 @@ 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( +pub fn simple_subsurface( settings: impl Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync @@ -182,7 +147,6 @@ pub fn simple_subsurface( Action::Subsurface( Arc::new(boxed), - Arc::new(Box::new(LiveSettings::default)), view.map(|view| { let boxed: Box = Box::new(view); Arc::new(boxed) @@ -191,10 +155,9 @@ 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( - live_settings: impl Fn() -> LiveSettings + Send + Sync + 'static, settings: impl Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync @@ -211,12 +174,8 @@ pub fn simple_popup( > = Box::new(settings); let boxed: Box = Box::new(boxed); - let boxed_live: Box LiveSettings + Send + Sync + 'static> = Box::new(live_settings); - let boxed_live: Box = Box::new(boxed_live); - Action::Popup( Arc::new(boxed), - Arc::new(boxed_live), view.map(|view| { let boxed: Box< dyn Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, @@ -227,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( @@ -238,7 +197,14 @@ pub fn subsurface( + Sync + 'static, // XXX Boxed trait object is required for less cumbersome type inference, but we box it anyways. - view: BoxedView, + view: Option< + Box< + dyn for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action> + + Send + + Sync + + 'static, + >, + >, ) -> Action { let boxed: Box< dyn Fn( @@ -253,82 +219,6 @@ pub fn subsurface( Action::AppSubsurface( Arc::new(boxed), - Arc::new(Box::new(|_: &App| LiveSettings::default())), - view.map(|view| { - let boxed: Box = Box::new(view); - Arc::new(boxed) - }), - ) -} - -#[cfg(all(feature = "wayland", target_os = "linux"))] -#[must_use] -pub fn simple_layer_shell( - live_settings: impl Fn() -> LiveSettings + Send + Sync + 'static, - settings: impl Fn() - -> iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings - + Send - + Sync - + 'static, - view: Option< - impl Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, - >, -) -> Action { - let boxed: Box< - dyn Fn() - -> iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings - + Send - + Sync - + 'static, - > = Box::new(settings); - let boxed: Box = Box::new(boxed); - let boxed_live: Box LiveSettings + Send + Sync + 'static> = Box::new(live_settings); - let boxed_live: Box = Box::new(boxed_live); - Action::LayerShell( - Arc::new(boxed), - Arc::new(boxed_live), - view.map(|view| { - let boxed: Box< - dyn Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, - > = Box::new(view); - let boxed: Box = Box::new(boxed); - Arc::new(boxed) - }), - ) -} - -#[cfg(all(feature = "wayland", target_os = "linux"))] -#[must_use] -pub fn app_layer_shell( - live_settings: impl Fn(&App) -> LiveSettings + Send + Sync + 'static, - settings: impl Fn( - &mut App, - ) - -> iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings - + Send - + Sync - + 'static, - // XXX Boxed trait object is required for less cumbersome type inference, but we box it anyways. - view: BoxedView, -) -> Action { - let boxed: Box< - dyn Fn( - &mut App, - ) - -> iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings - + Send - + Sync - + 'static, - > = Box::new(settings); - let boxed: Box = Box::new(boxed); - - let boxed_live: Box LiveSettings + Send + Sync + 'static> = - Box::new(live_settings); - let boxed_live: Box = Box::new(boxed_live); - - Action::AppLayerShell( - Arc::new(boxed), - Arc::new(boxed_live), view.map(|view| { let boxed: Box = Box::new(view); Arc::new(boxed) diff --git a/src/surface/corner_radius.rs b/src/surface/corner_radius.rs deleted file mode 100644 index 1eda578d..00000000 --- a/src/surface/corner_radius.rs +++ /dev/null @@ -1,86 +0,0 @@ -use iced::Rectangle; -use iced_runtime::platform_specific::wayland::CornerRadius; - -#[must_use] -pub fn rounded_rect_strips(rect: Rectangle, radius: CornerRadius) -> Vec> { - let mut out = Vec::new(); - - let w = rect.width.max(0.0); - let h = rect.height.max(0.0); - - if w <= 0.0 || h <= 0.0 { - return out; - } - - let tl = radius.top_left as i32; - let tr = radius.top_right as i32; - let bl = radius.bottom_left as i32; - let br = radius.bottom_right as i32; - - let max_top = tl.max(tr); - let max_bottom = bl.max(br); - - let center_y = rect.y + max_top as f32; - let center_h = (h as i32 - max_top - max_bottom).max(0) as f32; - - if center_h > 0.0 { - out.push(Rectangle { - x: rect.x, - y: center_y, - width: w, - height: center_h, - }); - } - - for y in 0..max_top { - let left = if y < tl { circle_inset(tl, y) } else { 0 }; - - let right = if y < tr { circle_inset(tr, y) } else { 0 }; - - let strip_x = rect.x + left as f32; - let strip_w = (w as i32 - left - right).max(0) as f32; - - if strip_w > 0.0 { - out.push(Rectangle { - x: strip_x, - y: rect.y + y as f32, - width: strip_w, - height: 1.0, - }); - } - } - - for y in 0..max_bottom { - let cy = max_bottom - 1 - y; - let left = if cy < bl { circle_inset(bl, cy) } else { 0 }; - - let right = if cy < br { circle_inset(br, cy) } else { 0 }; - - let strip_x = rect.x + left as f32; - let strip_w = (w as i32 - left - right).max(0) as f32; - - if strip_w > 0.0 { - out.push(Rectangle { - x: strip_x, - y: rect.y + h - max_bottom as f32 + y as f32, - width: strip_w, - height: 1.0, - }); - } - } - - out -} - -fn circle_inset(radius: i32, y: i32) -> i32 { - if radius <= 0 { - return 0; - } - - let fy = y as f32 + 0.5; - let r = radius as f32; - - let x = r - (r * r - (r - fy) * (r - fy)).sqrt(); - - x.floor() as i32 -} diff --git a/src/surface/mod.rs b/src/surface/mod.rs index d34d684f..0dad6459 100644 --- a/src/surface/mod.rs +++ b/src/surface/mod.rs @@ -2,28 +2,38 @@ // SPDX-License-Identifier: MPL-2.0 pub mod action; -#[cfg(all(feature = "wayland", target_os = "linux"))] -pub mod corner_radius; -use iced::{Limits, Size, Task}; -use std::any::Any; +use iced::Limits; +use iced::Size; +use iced::Task; +use std::future::Future; use std::sync::Arc; -type BoxedSetting = Arc>; - /// Ignore this message in your application. It will be intercepted. #[derive(Clone)] pub enum Action { /// Create a subsurface with a view function accepting the App as a parameter - AppSubsurface(BoxedSetting, BoxedSetting, Option), + AppSubsurface( + std::sync::Arc>, + Option>>, + ), /// Create a subsurface with a view function - Subsurface(BoxedSetting, BoxedSetting, Option), + Subsurface( + std::sync::Arc>, + Option>>, + ), /// Destroy a subsurface with a view function DestroySubsurface(iced::window::Id), /// Create a popup with a view function accepting the App as a parameter - AppPopup(BoxedSetting, BoxedSetting, Option), + AppPopup( + std::sync::Arc>, + Option>>, + ), /// Create a popup - Popup(BoxedSetting, BoxedSetting, Option), + Popup( + std::sync::Arc>, + Option>>, + ), /// Destroy a subsurface with a view function DestroyPopup(iced::window::Id), /// Destroys the global tooltip popup subsurface @@ -32,29 +42,18 @@ pub enum Action { /// Create a window with a view function accepting the App as a parameter AppWindow( iced::window::Id, - BoxedSetting, - BoxedSetting, - Option, + std::sync::Arc>, + Option>>, ), /// Create a window with a view function Window( iced::window::Id, - BoxedSetting, - BoxedSetting, - Option, + std::sync::Arc>, + Option>>, ), /// Destroy a window DestroyWindow(iced::window::Id), - /// Create a layer shell surface with a view function accepting the App as a parameter - AppLayerShell(BoxedSetting, BoxedSetting, Option), - - /// Create a layer shell surface with a view function - LayerShell(BoxedSetting, BoxedSetting, Option), - - /// Destroy a layer shell surface - DestroyLayerShell(iced::window::Id), - /// Responsive menu bar update ResponsiveMenuBar { /// Id of the menu bar @@ -65,45 +64,28 @@ pub enum Action { size: Size, }, Ignore, - SyncLiveSettings(iced::window::Id), Task(Arc Task + Send + Sync>), } -pub fn surface_task(action: Action) -> Task> { - crate::task::message(crate::Action::Cosmic(crate::app::Action::Surface(action))) -} - impl std::fmt::Debug for Action { #[cold] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::AppSubsurface(arg0, arg1, arg2) => f + Self::AppSubsurface(arg0, arg1) => f .debug_tuple("AppSubsurface") .field(arg0) .field(arg1) - .field(arg2) - .finish(), - Self::Subsurface(arg0, arg1, arg2) => f - .debug_tuple("Subsurface") - .field(arg0) - .field(arg1) - .field(arg2) .finish(), + Self::Subsurface(arg0, arg1) => { + f.debug_tuple("Subsurface").field(arg0).field(arg1).finish() + } Self::DestroySubsurface(arg0) => { f.debug_tuple("DestroySubsurface").field(arg0).finish() } - Self::AppPopup(arg0, arg1, arg2) => f - .debug_tuple("AppPopup") - .field(arg0) - .field(arg1) - .field(arg2) - .finish(), - Self::Popup(arg0, arg1, arg2) => f - .debug_tuple("Popup") - .field(arg0) - .field(arg1) - .field(arg2) - .finish(), + Self::AppPopup(arg0, arg1) => { + f.debug_tuple("AppPopup").field(arg0).field(arg1).finish() + } + Self::Popup(arg0, arg1) => f.debug_tuple("Popup").field(arg0).field(arg1).finish(), Self::DestroyPopup(arg0) => f.debug_tuple("DestroyPopup").field(arg0).finish(), Self::DestroyTooltipPopup => f.debug_tuple("DestroyTooltipPopup").finish(), Self::ResponsiveMenuBar { @@ -117,38 +99,20 @@ impl std::fmt::Debug for Action { .field("size", size) .finish(), Self::Ignore => write!(f, "Ignore"), - Self::AppWindow(id, arg0, arg1, arg2) => f + Self::AppWindow(id, arg0, arg1) => f .debug_tuple("AppWindow") .field(id) .field(arg0) .field(arg1) - .field(arg2) .finish(), - Self::Window(id, arg0, arg1, arg2) => f + Self::Window(id, arg0, arg1) => f .debug_tuple("Window") .field(id) .field(arg0) .field(arg1) - .field(arg2) .finish(), Self::DestroyWindow(arg0) => f.debug_tuple("DestroyWindow").field(arg0).finish(), Self::Task(_) => f.debug_tuple("Future").finish(), - Self::AppLayerShell(arg, arg1, arg2) => f - .debug_tuple("AppLayerShell") - .field(arg) - .field(arg1) - .field(arg2) - .finish(), - Self::LayerShell(arg, arg1, arg2) => f - .debug_tuple("LayerShell") - .field(arg) - .field(arg1) - .field(arg2) - .finish(), - Self::DestroyLayerShell(arg0) => { - f.debug_tuple("DestroyLayerShell").field(arg0).finish() - } - Self::SyncLiveSettings(arg0) => f.debug_tuple("SyncLiveSettings").field(arg0).finish(), } } } diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 1fd19cf3..093bac05 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -7,9 +7,13 @@ pub mod portal; pub mod style; -use ::iced::Alignment; use cosmic_config::CosmicConfigEntry; -use cosmic_theme::{Component, LayeredTheme, Spacing, ThemeMode}; +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::*; @@ -46,8 +50,6 @@ pub static TRANSPARENT_COMPONENT: LazyLock = LazyLock::new(|| Compone pub(crate) static THEME: Mutex = Mutex::new(Theme { theme_type: ThemeType::Dark, layer: cosmic_theme::Layer::Background, - transparent: false, - list_item_position: None, }); /// Currently-defined theme. @@ -84,6 +86,34 @@ pub fn is_high_contrast() -> bool { active_type().is_high_contrast() } +// /// Watches for changes to the system's theme preference. +// #[cold] +// pub fn subscription(is_dark: bool) -> Subscription { +// config_subscription::<_, crate::cosmic_theme::Theme>( +// ( +// std::any::TypeId::of::(), +// is_dark, +// ), +// if is_dark { +// cosmic_theme::DARK_THEME_ID +// } else { +// cosmic_theme::LIGHT_THEME_ID +// } +// .into(), +// crate::cosmic_theme::Theme::VERSION, +// ) +// .map(|res| { +// for error in res.errors.into_iter().filter(cosmic_config::Error::is_err) { +// tracing::error!( +// ?error, +// "error while watching system theme preference changes" +// ); +// } + +// Theme::system(Arc::new(res.config)) +// }) +// } + pub fn system_dark() -> Theme { let Ok(helper) = crate::cosmic_theme::Theme::dark_config() else { return Theme::dark(); @@ -183,9 +213,6 @@ impl ThemeType { pub struct Theme { pub theme_type: ThemeType, pub layer: cosmic_theme::Layer, - pub transparent: bool, - /// Only meaningful for widgets that must be in a list. Otherwise it should be ignored. - pub list_item_position: Option<(Alignment, usize)>, } impl Theme { @@ -256,9 +283,9 @@ impl Theme { /// can be used in a component that is intended to be a child of a `CosmicContainer` pub fn current_container(&self) -> &cosmic_theme::Container { match self.layer { - cosmic_theme::Layer::Background => self.cosmic().background(self.transparent), - cosmic_theme::Layer::Primary => self.cosmic().primary(self.transparent), - cosmic_theme::Layer::Secondary => self.cosmic().secondary(self.transparent), + cosmic_theme::Layer::Background => &self.cosmic().background, + cosmic_theme::Layer::Primary => &self.cosmic().primary, + cosmic_theme::Layer::Secondary => &self.cosmic().secondary, } } @@ -267,14 +294,6 @@ impl Theme { pub fn set_theme(&mut self, theme: ThemeType) { self.theme_type = theme; } - - #[inline] - /// Clone theme with a list position - pub fn with_list_item_position(&self, position: Option<(Alignment, usize)>) -> Self { - let mut new = self.clone(); - new.list_item_position = position; - new - } } impl LayeredTheme for Theme { diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index 723d1b6b..bb52d9a6 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -6,8 +6,10 @@ use cosmic_theme::Component; use iced_core::{Background, Color}; -use crate::theme::TRANSPARENT_COMPONENT; -use crate::widget::button::{Catalog, Style}; +use crate::{ + theme::TRANSPARENT_COMPONENT, + widget::button::{Catalog, Style}, +}; #[derive(Default)] pub enum Button { @@ -126,20 +128,20 @@ pub fn appearance( let (background, _, _) = color(&cosmic.text_button); appearance.background = Some(Background::Color(background)); - appearance.icon_color = Some(cosmic.background(theme.transparent).on.into()); - appearance.text_color = Some(cosmic.background(theme.transparent).on.into()); + appearance.icon_color = Some(cosmic.background.on.into()); + appearance.text_color = Some(cosmic.background.on.into()); corner_radii = &cosmic.corner_radii.radius_0; } Button::AppletIcon => { let (background, _, _) = color(&cosmic.text_button); appearance.background = Some(Background::Color(background)); - appearance.icon_color = Some(cosmic.background(theme.transparent).on.into()); - appearance.text_color = Some(cosmic.background(theme.transparent).on.into()); + appearance.icon_color = Some(cosmic.background.on.into()); + appearance.text_color = Some(cosmic.background.on.into()); } Button::MenuFolder => { // Menu folders cannot be disabled, ignore customized icon and text color - let component = &cosmic.background(theme.transparent).component; + let component = &cosmic.background.component; let (background, _, _) = color(component); appearance.background = Some(Background::Color(background)); appearance.icon_color = Some(component.on.into()); @@ -148,12 +150,11 @@ pub fn appearance( } Button::ListItem(radii) => { corner_radii = radii; - let (background, text, icon) = color(&cosmic.list_button); + let (background, text, icon) = color(&cosmic.background.component); if selected { - appearance.background = Some(Background::Color( - cosmic.primary(theme.transparent).component.hover.into(), - )); + appearance.background = + Some(Background::Color(cosmic.primary.component.hover.into())); appearance.icon_color = Some(cosmic.accent.base.into()); appearance.text_color = Some(cosmic.accent_text_color().into()); } else { @@ -163,7 +164,7 @@ pub fn appearance( } } Button::MenuItem => { - let (background, text, icon) = color(&cosmic.background(theme.transparent).component); + let (background, text, icon) = color(&cosmic.background.component); appearance.background = Some(Background::Color(background)); appearance.icon_color = icon; appearance.text_color = text; @@ -196,7 +197,7 @@ impl Catalog for crate::Theme { return active(focused, self); } - appearance(self, focused, selected, false, style, move |component| { + let mut s = appearance(self, focused, selected, false, style, move |component| { let text_color = if matches!( style, Button::Icon | Button::IconVertical | Button::HeaderBar @@ -208,7 +209,15 @@ impl Catalog for crate::Theme { }; (component.base.into(), text_color, text_color) - }) + }); + + if let Button::ListItem(_) = style { + if !selected { + s.background = None; + } + } + + s } fn disabled(&self, style: &Self::Class) -> Style { @@ -236,7 +245,7 @@ impl Catalog for crate::Theme { return hovered(focused, self); } - appearance( + let mut s = appearance( self, focused || matches!(style, Button::Image), selected, @@ -255,7 +264,15 @@ impl Catalog for crate::Theme { (component.hover.into(), text_color, text_color) }, - ) + ); + + if let Button::ListItem(_) = style { + if !selected { + s.background = None; + } + } + + s } fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style { @@ -279,6 +296,6 @@ impl Catalog for crate::Theme { } fn selection_background(&self) -> Background { - Background::Color(self.cosmic().primary(self.transparent).base.into()) + Background::Color(self.cosmic().primary.base.into()) } } diff --git a/src/theme/style/dropdown.rs b/src/theme/style/dropdown.rs index 56f5536e..cc89a399 100644 --- a/src/theme/style/dropdown.rs +++ b/src/theme/style/dropdown.rs @@ -13,28 +13,18 @@ impl dropdown::menu::StyleSheet for Theme { dropdown::menu::Appearance { text_color: cosmic.on_bg_color().into(), - background: Background::Color( - cosmic.background(self.transparent).component.base.into(), - ), + background: Background::Color(cosmic.background.component.base.into()), border_width: 0.0, border_radius: cosmic.corner_radii.radius_m.into(), border_color: Color::TRANSPARENT, hovered_text_color: cosmic.on_bg_color().into(), - hovered_background: Background::Color( - cosmic.primary(self.transparent).component.hover.into(), - ), + hovered_background: Background::Color(cosmic.primary.component.hover.into()), selected_text_color: cosmic.accent_text_color().into(), - selected_background: Background::Color( - cosmic.primary(self.transparent).component.hover.into(), - ), + selected_background: Background::Color(cosmic.primary.component.hover.into()), - description_color: cosmic - .primary(self.transparent) - .component - .on_disabled - .into(), + description_color: cosmic.primary.component.on_disabled.into(), } } } diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 38030e4b..aa6f4b33 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -5,17 +5,18 @@ use crate::theme::{CosmicComponent, TRANSPARENT_COMPONENT, Theme}; use cosmic_theme::composite::over; -use iced::overlay::menu; -use iced::theme::Base; -use iced::widget::slider::{self, Rail}; -use iced::widget::{ - button as iced_button, checkbox as iced_checkbox, combo_box, container as iced_container, - pane_grid, pick_list, progress_bar, radio, rule, scrollable, svg, toggler, +use iced::{ + overlay::menu, + theme::Base, + widget::{ + button as iced_button, checkbox as iced_checkbox, combo_box, container as iced_container, + pane_grid, pick_list, progress_bar, radio, rule, scrollable, + slider::{self, Rail}, + svg, toggler, + }, }; use iced_core::{Background, Border, Color, Shadow, Vector}; -use iced_widget::pane_grid::Highlight; -use iced_widget::scrollable::AutoScroll; -use iced_widget::{text_editor, text_input}; +use iced_widget::{pane_grid::Highlight, scrollable::AutoScroll, text_editor, text_input}; use palette::WithAlpha; use std::rc::Rc; @@ -170,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; @@ -224,11 +228,11 @@ impl iced_checkbox::Catalog for Theme { }, Checkbox::Secondary => iced_checkbox::Style { background: Background::Color(if is_checked { - cosmic.background(self.transparent).component.base.into() + cosmic.background.component.base.into() } else { self.current_container().small_widget.into() }), - icon_color: cosmic.background(self.transparent).on.into(), + icon_color: cosmic.background.on.into(), border: Border { radius: corners.radius_xs.into(), width: if is_checked { 0.0 } else { 1.0 }, @@ -386,11 +390,9 @@ pub enum Container<'a> { WindowBackground, Background, Card, - ContextDrawer { - transparent: bool, - }, + ContextDrawer, Custom(Box iced_container::Style + 'a>), - Dialog(bool), + Dialog, Dropdown, HeaderBar { focused: bool, @@ -411,13 +413,11 @@ impl<'a> Container<'a> { } #[must_use] - pub fn background(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style { + pub fn background(theme: &cosmic_theme::Theme) -> iced_container::Style { iced_container::Style { - icon_color: Some(Color::from(theme.background(transparent).on)), - text_color: Some(Color::from(theme.background(transparent).on)), - background: Some(iced::Background::Color( - theme.background(transparent).base.into(), - )), + icon_color: Some(Color::from(theme.background.on)), + text_color: Some(Color::from(theme.background.on)), + background: Some(iced::Background::Color(theme.background.base.into())), border: Border { radius: theme.corner_radii.radius_s.into(), ..Default::default() @@ -428,13 +428,11 @@ impl<'a> Container<'a> { } #[must_use] - pub fn primary(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style { + pub fn primary(theme: &cosmic_theme::Theme) -> iced_container::Style { iced_container::Style { - icon_color: Some(Color::from(theme.primary(transparent).on)), - text_color: Some(Color::from(theme.primary(transparent).on)), - background: Some(iced::Background::Color( - theme.primary(transparent).base.into(), - )), + icon_color: Some(Color::from(theme.primary.on)), + text_color: Some(Color::from(theme.primary.on)), + background: Some(iced::Background::Color(theme.primary.base.into())), border: Border { radius: theme.corner_radii.radius_s.into(), ..Default::default() @@ -445,13 +443,11 @@ impl<'a> Container<'a> { } #[must_use] - pub fn secondary(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style { + pub fn secondary(theme: &cosmic_theme::Theme) -> iced_container::Style { iced_container::Style { - icon_color: Some(Color::from(theme.secondary(transparent).on)), - text_color: Some(Color::from(theme.secondary(transparent).on)), - background: Some(iced::Background::Color( - theme.secondary(transparent).base.into(), - )), + icon_color: Some(Color::from(theme.secondary.on)), + text_color: Some(Color::from(theme.secondary.on)), + background: Some(iced::Background::Color(theme.secondary.base.into())), border: Border { radius: theme.corner_radii.radius_s.into(), ..Default::default() @@ -482,30 +478,14 @@ impl iced_container::Catalog for Theme { let window_corner_radius = cosmic.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); match class { - Container::Transparent => { - let component = &self.current_container().component; - - iced_container::Style { - icon_color: Some(component.on.into()), - text_color: Some(component.on.into()), - background: None, - border: Border { - radius: 0.into(), - ..Default::default() - }, - shadow: Shadow::default(), - snap: true, - } - } + Container::Transparent => iced_container::Style::default(), Container::Custom(f) => f(self), Container::WindowBackground => iced_container::Style { - icon_color: Some(Color::from(cosmic.background(self.transparent).on)), - text_color: Some(Color::from(cosmic.background(self.transparent).on)), - background: Some(iced::Background::Color( - cosmic.background(self.transparent).base.into(), - )), + icon_color: Some(Color::from(cosmic.background.on)), + text_color: Some(Color::from(cosmic.background.on)), + background: Some(iced::Background::Color(cosmic.background.base.into())), border: Border { radius: [ cosmic.corner_radii.radius_0[0], @@ -543,13 +523,12 @@ impl iced_container::Catalog for Theme { let (icon_color, text_color) = if *focused { ( Color::from(cosmic.accent_text_color()), - Color::from(cosmic.background(self.transparent).on), + Color::from(cosmic.background.on), ) } else { use crate::ext::ColorExt; - let unfocused_color = - Color::from(cosmic.background(self.transparent).component.on) - .blend_alpha(cosmic.background(self.transparent).base.into(), 0.5); + let unfocused_color = Color::from(cosmic.background.component.on) + .blend_alpha(cosmic.background.base.into(), 0.5); (unfocused_color, unfocused_color) }; @@ -559,9 +538,7 @@ impl iced_container::Catalog for Theme { background: if *transparent { None } else { - Some(iced::Background::Color( - cosmic.background(self.transparent).base.into(), - )) + Some(iced::Background::Color(cosmic.background.base.into())) }, border: Border { radius: [ @@ -586,21 +563,21 @@ impl iced_container::Catalog for Theme { } } - Container::ContextDrawer { transparent } => { - let mut a = Container::primary(cosmic, self.transparent && *transparent); + Container::ContextDrawer => { + let mut a = Container::primary(cosmic); if cosmic.is_high_contrast { a.border.width = 1.; - a.border.color = cosmic.primary(self.transparent).divider.into(); + a.border.color = cosmic.primary.divider.into(); } a } - Container::Background => Container::background(cosmic, self.transparent), + Container::Background => Container::background(cosmic), - Container::Primary => Container::primary(cosmic, self.transparent), + Container::Primary => Container::primary(cosmic), - Container::Secondary => Container::secondary(cosmic, self.transparent), + Container::Secondary => Container::secondary(cosmic), Container::Dropdown => iced_container::Style { icon_color: None, @@ -632,14 +609,10 @@ impl iced_container::Catalog for Theme { match self.layer { cosmic_theme::Layer::Background => iced_container::Style { - icon_color: Some(Color::from( - cosmic.background(self.transparent).component.on, - )), - text_color: Some(Color::from( - cosmic.background(self.transparent).component.on, - )), + icon_color: Some(Color::from(cosmic.background.component.on)), + text_color: Some(Color::from(cosmic.background.component.on)), background: Some(iced::Background::Color( - cosmic.background(self.transparent).component.base.into(), + cosmic.background.component.base.into(), )), border: Border { radius: cosmic.corner_radii.radius_s.into(), @@ -649,14 +622,10 @@ impl iced_container::Catalog for Theme { snap: true, }, cosmic_theme::Layer::Primary => iced_container::Style { - icon_color: Some(Color::from( - cosmic.primary(self.transparent).component.on, - )), - text_color: Some(Color::from( - cosmic.primary(self.transparent).component.on, - )), + icon_color: Some(Color::from(cosmic.primary.component.on)), + text_color: Some(Color::from(cosmic.primary.component.on)), background: Some(iced::Background::Color( - cosmic.primary(self.transparent).component.base.into(), + cosmic.primary.component.base.into(), )), border: Border { radius: cosmic.corner_radii.radius_s.into(), @@ -666,14 +635,10 @@ impl iced_container::Catalog for Theme { snap: true, }, cosmic_theme::Layer::Secondary => iced_container::Style { - icon_color: Some(Color::from( - cosmic.secondary(self.transparent).component.on, - )), - text_color: Some(Color::from( - cosmic.secondary(self.transparent).component.on, - )), + icon_color: Some(Color::from(cosmic.secondary.component.on)), + text_color: Some(Color::from(cosmic.secondary.component.on)), background: Some(iced::Background::Color( - cosmic.secondary(self.transparent).component.base.into(), + cosmic.secondary.component.base.into(), )), border: Border { radius: cosmic.corner_radii.radius_s.into(), @@ -685,17 +650,12 @@ impl iced_container::Catalog for Theme { } } - Container::Dialog(is_overlay) => iced_container::Style { - icon_color: Some(Color::from(cosmic.primary(self.transparent).on)), - text_color: Some(Color::from(cosmic.primary(self.transparent).on)), - background: Some(iced::Background::Color( - cosmic.primary(self.transparent && !is_overlay).base.into(), - )), + Container::Dialog => iced_container::Style { + icon_color: Some(Color::from(cosmic.primary.on)), + text_color: Some(Color::from(cosmic.primary.on)), + background: Some(iced::Background::Color(cosmic.primary.base.into())), border: Border { - color: cosmic - .primary(self.transparent && !is_overlay) - .divider - .into(), + color: cosmic.primary.divider.into(), width: 1.0, radius: cosmic.corner_radii.radius_m.into(), }, @@ -832,20 +792,18 @@ 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 { text_color: cosmic.on_bg_color().into(), - background: Background::Color(cosmic.background(self.transparent).base.into()), + background: Background::Color(cosmic.background.base.into()), border: Border { radius: cosmic.corner_radii.radius_m.into(), ..Default::default() }, selected_text_color: cosmic.accent_text_color().into(), - selected_background: Background::Color( - cosmic.background(self.transparent).component.hover.into(), - ), + selected_background: Background::Color(cosmic.background.component.hover.into()), shadow: Default::default(), } } @@ -858,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(); @@ -883,7 +841,7 @@ impl pick_list::Catalog for Theme { match status { pick_list::Status::Active => appearance, pick_list::Status::Hovered => pick_list::Style { - background: Background::Color(cosmic.background(self.transparent).base.into()), + background: Background::Color(cosmic.background.base.into()), ..appearance }, pick_list::Status::Opened { is_hovered: _ } => appearance, @@ -899,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(); @@ -952,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); @@ -980,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 { @@ -1000,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 @@ -1017,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 { @@ -1079,10 +1037,7 @@ impl progress_bar::Catalog for Theme { }, ) } else { - ( - theme.accent.base, - theme.background(self.transparent).divider, - ) + (theme.accent.base, theme.background.divider) }; let border = Border { radius: theme.corner_radii.radius_xl.into(), @@ -1188,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); @@ -1238,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); @@ -1306,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(), }, }; @@ -1348,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(), @@ -1387,16 +1342,9 @@ impl iced_widget::text::Catalog for Theme { match class { Text::Accent => iced_widget::text::Style { color: Some(self.cosmic().accent_text_color().into()), - ..Default::default() - }, - Text::Default => iced_widget::text::Style { - color: None, - ..Default::default() - }, - Text::Color(c) => iced_widget::text::Style { - color: Some(*c), - ..Default::default() }, + Text::Default => iced_widget::text::Style { color: None }, + Text::Color(c) => iced_widget::text::Style { color: Some(*c) }, Text::Custom(f) => f(self), } } @@ -1485,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 { @@ -1562,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(self.transparent).on.into(); + let icon: Color = cosmic.background.on.into(); // TODO do we need to add icon color back? match status { @@ -1579,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 { @@ -1682,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/menu_bar.rs b/src/theme/style/menu_bar.rs index 37e14486..ed0e657a 100644 --- a/src/theme/style/menu_bar.rs +++ b/src/theme/style/menu_bar.rs @@ -31,7 +31,7 @@ pub trait StyleSheet { type Style: Default; /// Produces the [`Appearance`] of a menu bar and its menus. - fn appearance(&self, style: &Self::Style, is_overlay: bool) -> Appearance; + fn appearance(&self, style: &Self::Style) -> Appearance; } /// The style of a menu bar and its menus @@ -54,7 +54,7 @@ impl From Appearance> for MenuBarStyle { impl StyleSheet for fn(&Theme) -> Appearance { type Style = Theme; - fn appearance(&self, style: &Self::Style, _is_overlay: bool) -> Appearance { + fn appearance(&self, style: &Self::Style) -> Appearance { (self)(style) } } @@ -62,22 +62,21 @@ impl StyleSheet for fn(&Theme) -> Appearance { impl StyleSheet for Theme { type Style = MenuBarStyle; - fn appearance(&self, style: &Self::Style, is_overlay: bool) -> Appearance { + fn appearance(&self, style: &Self::Style) -> Appearance { let cosmic = self.cosmic(); - let component = &cosmic.background(!is_overlay && self.transparent).component; - let bg = component.base; + let component = &cosmic.background.component; match style { MenuBarStyle::Default => Appearance { - background: bg.into(), + background: component.base.into(), border_width: 1.0, bar_border_radius: cosmic.corner_radii.radius_xl, - menu_border_radius: cosmic.corner_radii.radius_s, + menu_border_radius: cosmic.corner_radii.radius_s.map(|x| x + 2.0), border_color: component.divider.into(), background_expand: [1; 4], path: component.hover.into(), }, - MenuBarStyle::Custom(c) => c.appearance(self, is_overlay), + MenuBarStyle::Custom(c) => c.appearance(self), } } } 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 45fcc0b8..b9863c88 100644 --- a/src/theme/style/segmented_button.rs +++ b/src/theme/style/segmented_button.rs @@ -3,13 +3,10 @@ //! Contains stylesheet implementation for [`crate::widget::segmented_button`]. -use crate::theme::Theme; -use crate::widget::segmented_button::{ - Appearance, ItemAppearance, ItemStatusAppearance, StyleSheet, -}; +use crate::widget::segmented_button::{Appearance, ItemAppearance, StyleSheet}; +use crate::{theme::Theme, widget::segmented_button::ItemStatusAppearance}; use iced::Border; -use iced_core::Background; -use iced_core::border::Radius; +use iced_core::{Background, border::Radius}; use palette::WithAlpha; #[derive(Default)] @@ -146,8 +143,7 @@ mod horizontal { use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance}; use cosmic_theme::{Component, Container}; use iced::Border; - use iced_core::Background; - use iced_core::border::Radius; + use iced_core::{Background, border::Radius}; use palette::WithAlpha; pub fn tab_bar(cosmic: &cosmic_theme::Theme, container: &Container) -> Appearance { @@ -185,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; @@ -254,8 +250,7 @@ mod vertical { use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance}; use cosmic_theme::{Component, Container}; use iced::Border; - use iced_core::Background; - use iced_core::border::Radius; + use iced_core::{Background, border::Radius}; use palette::WithAlpha; pub fn tab_bar(cosmic: &cosmic_theme::Theme, container: &Container) -> Appearance { @@ -280,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/about.rs b/src/widget/about.rs index 490b0378..9b21e93a 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -1,6 +1,8 @@ -use crate::iced::{Alignment, Length}; -use crate::widget::{self, list}; -use crate::{Apply, Element, fl}; +use crate::{ + Apply, Element, fl, + iced::{Alignment, Length}, + widget::{self, list}, +}; use std::rc::Rc; #[derive(Debug, Default, Clone, derive_setters::Setters)] diff --git a/src/widget/aspect_ratio.rs b/src/widget/aspect_ratio.rs index 5cd8b9c0..577bea95 100644 --- a/src/widget/aspect_ratio.rs +++ b/src/widget/aspect_ratio.rs @@ -3,10 +3,13 @@ use iced::Size; use iced::widget::Container; use iced_core::event::Event; +use iced_core::layout; +use iced_core::mouse; +use iced_core::overlay; +use iced_core::renderer; use iced_core::widget::Tree; use iced_core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, - layout, mouse, overlay, renderer, }; pub use iced_widget::container::{Catalog, Style}; diff --git a/src/widget/autosize.rs b/src/widget/autosize.rs index c52367db..69fd9c83 100644 --- a/src/widget/autosize.rs +++ b/src/widget/autosize.rs @@ -1,11 +1,12 @@ //! Autosize Container, which will resize the window to its contents. use iced_core::event::{self, Event}; +use iced_core::layout; +use iced_core::mouse; +use iced_core::overlay; +use iced_core::renderer; use iced_core::widget::{Id, Operation, Tree}; -use iced_core::{ - Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay, - renderer, -}; +use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; pub use iced_widget::container::{Catalog, Style}; pub fn autosize<'a, Message: 'static, Theme, E>( diff --git a/src/widget/button/icon.rs b/src/widget/button/icon.rs index 47713536..04d2bdd5 100644 --- a/src/widget/button/icon.rs +++ b/src/widget/button/icon.rs @@ -3,13 +3,9 @@ use super::{Builder, ButtonClass}; use crate::Element; -use crate::widget::icon::Handle; -use crate::widget::tooltip; +use crate::widget::{icon::Handle, tooltip}; use apply::Apply; -use iced_core::font::Weight; -use iced_core::text::LineHeight; -use iced_core::widget::Id; -use iced_core::{Alignment, Length, Padding}; +use iced_core::{Alignment, Length, Padding, font::Weight, text::LineHeight, widget::Id}; use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Icon>; @@ -156,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) @@ -173,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 915cc532..ab51e667 100644 --- a/src/widget/button/image.rs +++ b/src/widget/button/image.rs @@ -2,12 +2,11 @@ // SPDX-License-Identifier: MPL-2.0 use super::Builder; -use crate::Element; -use crate::widget::image::Handle; -use crate::widget::{self}; -use iced_core::font::Weight; -use iced_core::widget::Id; -use iced_core::{Length, Padding}; +use crate::{ + Element, + widget::{self, image::Handle}, +}; +use iced_core::{Length, Padding, font::Weight, widget::Id}; use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Image<'a, Handle, Message>>; @@ -84,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) @@ -92,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 e6f8ac7e..9ce81268 100644 --- a/src/widget/button/link.rs +++ b/src/widget/button/link.rs @@ -3,15 +3,14 @@ //! Hyperlink button widget -use super::{Builder, ButtonClass}; +use super::Builder; +use super::ButtonClass; use crate::Element; use crate::prelude::*; use crate::widget::icon::{self, Handle}; use crate::widget::{button, row, tooltip}; -use iced_core::font::Weight; use iced_core::text::LineHeight; -use iced_core::widget::Id; -use iced_core::{Alignment, Length, Padding}; +use iced_core::{Alignment, Length, Padding, font::Weight, widget::Id}; use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Hyperlink>; @@ -67,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()) @@ -95,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/style.rs b/src/widget/button/style.rs index f8df8c62..21afa08b 100644 --- a/src/widget/button/style.rs +++ b/src/widget/button/style.rs @@ -2,8 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 //! Change the apperance of a button. -use iced_core::border::Radius; -use iced_core::{Background, Color, Vector}; +use iced_core::{Background, Color, Vector, border::Radius}; use crate::theme::THEME; diff --git a/src/widget/button/text.rs b/src/widget/button/text.rs index 55623ff8..bcdd02ba 100644 --- a/src/widget/button/text.rs +++ b/src/widget/button/text.rs @@ -4,10 +4,7 @@ use super::{Builder, ButtonClass}; use crate::widget::{icon, row, tooltip}; use crate::{Apply, Element}; -use iced_core::font::Weight; -use iced_core::text::LineHeight; -use iced_core::widget::Id; -use iced_core::{Alignment, Length, Padding}; +use iced_core::{Alignment, Length, Padding, font::Weight, text::LineHeight, widget::Id}; use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Text>; @@ -122,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. @@ -141,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 72113b87..4acf3f2d 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -6,18 +6,20 @@ //! //! A [`Button`] has some local [`State`]. -use iced::Alignment; 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; use iced_core::widget::tree::{self, Tree}; use iced_core::{ - Background, Border, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shadow, Shell, - Vector, Widget, layout, mouse, overlay, svg, touch, + Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget, }; +use iced_core::{Border, mouse}; +use iced_core::{Shadow, overlay}; +use iced_core::{layout, svg}; use iced_renderer::core::widget::operation; use crate::theme::THEME; @@ -379,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; } + } } _ => (), @@ -449,7 +452,7 @@ impl<'a, Message: 'a + Clone> Widget let state = tree.state.downcast_ref::(); - let mut styling = if !is_enabled { + let styling = if !is_enabled { theme.disabled(&self.style) } else if is_mouse_over { if state.is_pressed { @@ -471,24 +474,6 @@ impl<'a, Message: 'a + Clone> Widget theme.active(state.is_focused, self.selected, &self.style) }; - if matches!( - self.style, - crate::theme::Button::MenuItem | crate::theme::Button::MenuFolder - ) { - match theme.list_item_position { - Some((Alignment::Start, _)) => { - styling.border_radius = - styling.border_radius.bottom(theme.cosmic().radius_0()[3]); - } - Some((Alignment::End, _)) => { - styling.border_radius = styling.border_radius.top(theme.cosmic().radius_0()[0]); - } - Some((Alignment::Center, _)) => {} - None => { - styling.border_radius = theme.cosmic().radius_0().into(); - } - } - } let mut icon_color = styling.icon_color.unwrap_or(renderer_style.icon_color); @@ -575,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 { @@ -609,6 +594,8 @@ impl<'a, Message: 'a + Clone> Widget }, ); } + } + } }); } } @@ -660,8 +647,10 @@ impl<'a, Message: 'a + Clone> Widget state: &Tree, p: mouse::Cursor, ) -> iced_accessibility::A11yTree { - use iced_accessibility::accesskit::{Action, Node, NodeId, Rect, Role}; - use iced_accessibility::{A11yNode, A11yTree}; + use iced_accessibility::{ + A11yNode, A11yTree, + accesskit::{Action, Node, NodeId, Rect, Role}, + }; // TODO why is state None sometimes? if matches!(state.state, iced_core::widget::tree::State::None) { tracing::info!("Button state is missing."); @@ -678,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); @@ -759,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) { @@ -804,6 +799,7 @@ pub fn update<'a, Message: Clone>( } shell.capture_event(); + return; } } } @@ -823,6 +819,7 @@ pub fn update<'a, Message: Clone>( } shell.capture_event(); + return; } } else if on_press_down.is_some() { let state = state(); @@ -830,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) @@ -842,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 { @@ -852,6 +850,7 @@ pub fn update<'a, Message: Clone>( shell.publish(msg); shell.capture_event(); + return; } } } @@ -871,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/calendar.rs b/src/widget/calendar.rs index 35632c3a..91c601d3 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -8,8 +8,10 @@ use crate::widget::{button, column, grid, icon, row, text}; use apply::Apply; use iced::alignment::Vertical; use iced_core::{Alignment, Length}; -use jiff::ToSpan; -use jiff::civil::{Date, Weekday}; +use jiff::{ + ToSpan, + civil::{Date, Weekday}, +}; /// A widget that displays an interactive calendar. pub fn calendar( diff --git a/src/widget/card/style.rs b/src/widget/card/style.rs index d8e95277..0e63e846 100644 --- a/src/widget/card/style.rs +++ b/src/widget/card/style.rs @@ -31,26 +31,16 @@ impl crate::widget::card::style::Catalog for crate::Theme { match self.layer { cosmic_theme::Layer::Background => crate::widget::card::style::Style { - card_1: Background::Color( - cosmic.background(self.transparent).component.hover.into(), - ), - card_2: Background::Color( - cosmic.background(self.transparent).component.pressed.into(), - ), + card_1: Background::Color(cosmic.background.component.hover.into()), + card_2: Background::Color(cosmic.background.component.pressed.into()), }, cosmic_theme::Layer::Primary => crate::widget::card::style::Style { - card_1: Background::Color(cosmic.primary(self.transparent).component.hover.into()), - card_2: Background::Color( - cosmic.primary(self.transparent).component.pressed.into(), - ), + card_1: Background::Color(cosmic.primary.component.hover.into()), + card_2: Background::Color(cosmic.primary.component.pressed.into()), }, cosmic_theme::Layer::Secondary => crate::widget::card::style::Style { - card_1: Background::Color( - cosmic.secondary(self.transparent).component.hover.into(), - ), - card_2: Background::Color( - cosmic.secondary(self.transparent).component.pressed.into(), - ), + card_1: Background::Color(cosmic.secondary.component.hover.into()), + card_2: Background::Color(cosmic.secondary.component.pressed.into()), }, } } diff --git a/src/widget/cards.rs b/src/widget/cards.rs index 5c8852a8..66267a73 100644 --- a/src/widget/cards.rs +++ b/src/widget/cards.rs @@ -1,24 +1,30 @@ //! An expandable stack of cards use std::time::Duration; -use crate::anim; -use crate::theme::THEME; -use crate::widget::icon::{self, Handle}; -use crate::widget::{button, column, row, text}; +use crate::{ + anim, + widget::{ + button, + card::style::Style, + column, + icon::{self, Handle}, + row, text, + }, +}; use float_cmp::approx_eq; use iced::widget; -use iced_core::border::Radius; -use iced_core::id::Id; -use iced_core::layout::Node; -use iced_core::renderer::Quad; -use iced_core::widget::{Tree, tree}; -use iced_core::{Border, Element, Event, Length, Shadow, Size, Vector, Widget, window}; +use iced_core::{ + Border, Element, Event, Length, Shadow, Size, Vector, Widget, border::Radius, id::Id, + layout::Node, renderer::Quad, widget::Tree, +}; +use iced_core::{widget::tree, window}; const ICON_SIZE: u16 = 16; const TOP_SPACING: u16 = 4; const VERTICAL_SPACING: f32 = 8.0; const PADDING: u16 = 16; const BG_CARD_VISIBLE_HEIGHT: f32 = 4.0; +const BG_CARD_BORDER_RADIUS: f32 = 8.0; const BG_CARD_MARGIN_STEP: f32 = 8.0; /// get an expandable stack of cards @@ -111,7 +117,7 @@ where Self { can_show_more, - id, + id: Id::unique(), show_less_button: { let mut show_less_children = Vec::with_capacity(3); if let Some(source) = show_less_icon { @@ -336,15 +342,14 @@ where for i in 1..self.elements.len().min(3) { // height must be 16px for 8px padding // but we only want 4px visible - let guard = THEME.lock().unwrap(); - let radius_xs = guard.cosmic().radius_xs(); let margin = f32::from(u8::try_from(i).unwrap()) * BG_CARD_MARGIN_STEP; - let node = Node::new(Size::new(width - 2.0 * margin, radius_xs[0] * 2.0)) - .translate(Vector::new( - margin, - size.height - radius_xs[0] * 2.0 + BG_CARD_VISIBLE_HEIGHT, - )); + let node = + Node::new(Size::new(width - 2.0 * margin, BG_CARD_BORDER_RADIUS * 2.0)) + .translate(Vector::new( + margin, + size.height - BG_CARD_BORDER_RADIUS * 2.0 + BG_CARD_VISIBLE_HEIGHT, + )); size.height += BG_CARD_VISIBLE_HEIGHT; children.push(node); } @@ -424,19 +429,22 @@ where _ = tree_children.next(); } - let radius_xs = theme.cosmic().radius_xs(); // Draw first to appear behind if fully_unexpanded { - use crate::widget::card::style::Catalog as _; let card_layout = layout.next().unwrap(); - let appearance = theme.default(); + let appearance = Style::default(); let bg_layout = layout.collect::>(); - for (i, layout) in (0..2).zip(bg_layout).rev() { + for (i, layout) in (0..2).zip(bg_layout.into_iter()).rev() { renderer.fill_quad( Quad { bounds: layout.bounds(), border: Border { - radius: Radius::from([0.0, 0.0, radius_xs[2], radius_xs[3]]), + radius: Radius::from([ + 0.0, + 0.0, + BG_CARD_BORDER_RADIUS, + BG_CARD_BORDER_RADIUS, + ]), ..Default::default() }, shadow: Shadow::default(), @@ -524,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, ); } @@ -534,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 3ec2ff35..318e943b 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -5,18 +5,17 @@ use std::borrow::Cow; use std::rc::Rc; +use std::sync::LazyLock; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{Duration, Instant}; use crate::Element; use crate::theme::iced::Slider; -use crate::theme::{Button, THEME, Theme}; -use crate::widget::button::Catalog; -use crate::widget::segmented_button::Entity; -use crate::widget::{container, slider}; +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}; @@ -26,8 +25,10 @@ use iced_core::{ }; use iced_widget::slider::HandleShape; -use iced_widget::space::{horizontal, vertical}; -use iced_widget::{Row, canvas, column, row, scrollable}; +use iced_widget::{ + Row, canvas, column, row, scrollable, + space::{horizontal, vertical}, +}; use palette::{FromColor, RgbHue}; use super::divider::horizontal; @@ -38,6 +39,37 @@ use super::{Icon, button, segmented_control, text, text_input, tooltip}; #[doc(inline)] pub use ColorPickerModel as Model; +// TODO is this going to look correct enough? +pub static HSV_RAINBOW: LazyLock> = LazyLock::new(|| { + (0u16..8) + .map(|h| { + Color::from(palette::Srgba::from_color(palette::Hsv::new_srgb_const( + RgbHue::new(f32::from(h) * 360.0 / 7.0), + 1.0, + 1.0, + ))) + }) + .collect() +}); + +fn hsv_rainbow(low_hue: f32, high_hue: f32) -> Vec { + let mut colors = Vec::new(); + let steps: u8 = 7; + let step_size = (high_hue - low_hue) / f32::from(steps); + for i in 0..=steps { + let hue = low_hue + step_size * f32::from(i); + colors.push(ColorStop { + color: Color::from(palette::Srgba::from_color(palette::Hsv::new_srgb_const( + RgbHue::new(hue), + 1.0, + 1.0, + ))), + offset: f32::from(i) / f32::from(steps), + }); + } + colors +} + const MAX_RECENT: usize = 20; #[derive(Debug, Clone)] @@ -269,67 +301,23 @@ where copy_to_clipboard_label: T, copied_to_clipboard_label: T, ) -> ColorPicker<'a, Message> { + fn rail_backgrounds(hue: f32) -> (Background, Background) { + let low_range = hsv_rainbow(0., hue); + let high_range = hsv_rainbow(hue, 360.); + + ( + Background::Gradient(iced::Gradient::Linear( + Linear::new(Radians(90.0)).add_stops(low_range), + )), + Background::Gradient(iced::Gradient::Linear( + Linear::new(Radians(90.0)).add_stops(high_range), + )), + ) + } + let on_update = self.on_update; let spacing = THEME.lock().unwrap().cosmic().spacing; - let color_slider_style = Rc::new(move |t: &Theme| { - let cosmic = t.cosmic(); - let mut a = slider::Catalog::style(t, &Slider::default(), slider::Status::Active); - a.rail.backgrounds = ( - // active track - Background::Gradient(iced::Gradient::Linear( - Linear::new(Radians(90.0)).add_stops((0..8_u8).map(|index| { - let offset; - let hue = self.active_color.hue.into_positive_degrees(); - let new_hue: f32; - if hue <= f32::from(index) * 360.0 / 7.0 { - offset = 1.0; - new_hue = hue; - } else { - offset = (f32::from(index) / 7.0) * (360.0 / hue); - new_hue = f32::from(index) * 360.0 / 7.0; - } - ColorStop { - color: Color::from(palette::Srgba::from_color( - palette::Hsv::new_srgb_const(RgbHue::new(new_hue), 1.0, 1.0), - )), - offset, - } - })), - )), - // inactive track - Background::Gradient(iced::Gradient::Linear( - Linear::new(Radians(90.0)).add_stops((0..8_u8).map(|index| { - let offset; - let hue = self.active_color.hue.into_positive_degrees(); - let new_hue: f32; - if hue >= f32::from(index) * 360.0 / 7.0 { - offset = 0.0; - new_hue = hue; - } else { - offset = - ((f32::from(index) / 7.0) - (hue / 360.0)) / (1.0 - (hue / 360.0)); - new_hue = f32::from(index) * 360.0 / 7.0; - } - ColorStop { - color: Color::from(palette::Srgba::from_color( - palette::Hsv::new_srgb_const(RgbHue::new(new_hue), 1.0, 1.0), - )), - offset, - } - })), - )), - ); - a.rail.width = 8.0; - a.handle.background = Background::Color(Color::from(palette::Srgba::from_color( - palette::Hsv::new_srgb_const(self.active_color.hue, 1.0, 1.0), - ))); - a.handle.shape = HandleShape::Circle { radius: 8.0 }; - a.handle.border_color = cosmic.palette.neutral_10.into(); - a.handle.border_width = 4.0; - a - }); - let mut inner = column![ // segmented buttons segmented_control::horizontal(self.model) @@ -344,22 +332,56 @@ where .width(self.width) .height(self.height), slider( - 0.0..=359.99, + 0.001..=359.99, self.active_color.hue.into_positive_degrees(), move |v| { let mut new = self.active_color; new.hue = v.into(); on_update(ColorPickerUpdate::ActiveColor(new)) - }, + } ) .on_release(on_update(ColorPickerUpdate::ActionFinished)) .class(Slider::Custom { - active: color_slider_style.clone(), - hovered: color_slider_style.clone(), - dragging: color_slider_style, + active: Rc::new(move |t| { + let cosmic = t.cosmic(); + let mut a = + slider::Catalog::style(t, &Slider::default(), slider::Status::Active); + let hue = self.active_color.hue.into_positive_degrees(); + a.rail.backgrounds = rail_backgrounds(hue); + a.rail.width = 8.0; + a.handle.background = Color::TRANSPARENT.into(); + a.handle.shape = HandleShape::Circle { radius: 8.0 }; + a.handle.border_color = cosmic.palette.neutral_10.into(); + a.handle.border_width = 4.0; + a + }), + hovered: Rc::new(move |t| { + let cosmic = t.cosmic(); + let mut a = + slider::Catalog::style(t, &Slider::default(), slider::Status::Active); + let hue = self.active_color.hue.into_positive_degrees(); + a.rail.backgrounds = rail_backgrounds(hue); + a.rail.width = 8.0; + a.handle.background = Color::TRANSPARENT.into(); + a.handle.shape = HandleShape::Circle { radius: 8.0 }; + a.handle.border_color = cosmic.palette.neutral_10.into(); + a.handle.border_width = 4.0; + a + }), + dragging: Rc::new(move |t| { + let cosmic = t.cosmic(); + let mut a = + slider::Catalog::style(t, &Slider::default(), slider::Status::Active); + let hue = self.active_color.hue.into_positive_degrees(); + a.rail.backgrounds = rail_backgrounds(hue); + a.rail.width = 8.0; + a.handle.background = Color::TRANSPARENT.into(); + a.handle.shape = HandleShape::Circle { radius: 8.0 }; + a.handle.border_color = cosmic.palette.neutral_10.into(); + a.handle.border_width = 4.0; + a + }), }) - .step(4.0 / 17.0) - .shift_step(64.0 / 17.0) .width(self.width), text_input("", self.input_color) .on_input(move |s| on_update(ColorPickerUpdate::Input(s))) @@ -597,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 @@ -723,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, @@ -736,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_drawer/overlay.rs b/src/widget/context_drawer/overlay.rs index 01f5cfcf..39b34217 100644 --- a/src/widget/context_drawer/overlay.rs +++ b/src/widget/context_drawer/overlay.rs @@ -5,7 +5,8 @@ use crate::Element; use iced::advanced::layout::{self, Layout}; use iced::advanced::widget::{self, Operation}; -use iced::advanced::{Clipboard, Shell, overlay, renderer}; +use iced::advanced::{Clipboard, Shell}; +use iced::advanced::{overlay, renderer}; use iced::{Event, Point, Size, mouse}; use iced_core::{Renderer, touch}; diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index b743c4eb..7420738c 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -6,10 +6,11 @@ use crate::widget::{self, LayerContainer, button, column, container, icon, row, use crate::{Apply, Element, Renderer, Theme, fl}; use std::borrow::Cow; +use iced_core::Alignment; use iced_core::event::Event; use iced_core::widget::{Operation, Tree}; use iced_core::{ - Alignment, Clipboard, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, + Clipboard, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay as iced_overlay, renderer, }; @@ -31,24 +32,6 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { on_close: Message, max_width: f32, ) -> Element<'a, Message> - where - Drawer: Into>, - { - Self::new_inner_overlay( - title, actions, header, footer, drawer, on_close, max_width, false, - ) - } - - pub fn new_inner_overlay( - title: Option>, - actions: Option>, - header: Option>, - footer: Option>, - drawer: Drawer, - on_close: Message, - max_width: f32, - overlay: bool, - ) -> Element<'a, Message> where Drawer: Into>, { @@ -61,7 +44,6 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { drawer: Element<'a, Message>, on_close: Message, max_width: f32, - overlay: bool, ) -> Element<'a, Message> { let cosmic_theme::Spacing { space_xxs, @@ -124,9 +106,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { container( LayerContainer::new(pane) .layer(cosmic_theme::Layer::Primary) - .class(crate::style::Container::ContextDrawer { - transparent: !overlay, - }) + .class(crate::style::Container::ContextDrawer) .width(Length::Fill) .height(Length::Fill) .max_width(max_width), @@ -145,7 +125,6 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { drawer.into(), on_close, max_width, - overlay, ) } @@ -164,9 +143,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { Content: Into>, Drawer: Into>, { - let drawer = Self::new_inner_overlay( - title, actions, header, footer, drawer, on_close, max_width, true, - ); + let drawer = Self::new_inner(title, actions, header, footer, drawer, on_close, max_width); ContextDrawer { id: None, diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 0d3ba1fb..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)] @@ -79,10 +81,7 @@ impl ContextMenu<'_, Message> { my_state: &mut LocalState, ) { if self.window_id != window::Id::NONE && self.on_surface_action.is_some() { - use crate::surface::action::{LiveSettings, destroy_popup}; - use crate::theme::THEME; - use crate::widget::menu::{Menu, StyleSheet as _}; - use iced_runtime::platform_specific::wayland::CornerRadius; + use crate::{surface::action::destroy_popup, widget::menu::Menu}; use iced_runtime::platform_specific::wayland::popup::{ SctkPopupSettings, SctkPositioner, }; @@ -91,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(); @@ -149,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); @@ -185,22 +184,8 @@ impl ContextMenu<'_, Message> { ..Default::default() }; let parent = self.window_id; - let t = THEME.lock().unwrap(); - let styling = t.appearance(&crate::theme::menu_bar::MenuBarStyle::Default, false); - drop(t); - let rad = styling.menu_border_radius; - shell.publish((self.on_surface_action.as_ref().unwrap())( crate::surface::action::simple_popup( - move || LiveSettings { - corners: Some(CornerRadius { - top_left: rad[0] as u32, - top_right: rad[1] as u32, - bottom_left: rad[2] as u32, - bottom_right: rad[3] as u32, - }), - ..Default::default() - }, move || SctkPopupSettings { parent, id, @@ -392,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)) @@ -435,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)) { @@ -456,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)) @@ -494,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/dialog.rs b/src/widget/dialog.rs index 4ee027bd..7d084626 100644 --- a/src/widget/dialog.rs +++ b/src/widget/dialog.rs @@ -1,5 +1,8 @@ -use crate::iced::{Length, Pixels}; -use crate::{Element, style, theme, widget}; +use crate::{ + Element, + iced::{Length, Pixels}, + style, theme, widget, +}; use std::borrow::Cow; pub fn dialog<'a, Message>() -> Dialog<'a, Message> { @@ -18,7 +21,6 @@ pub struct Dialog<'a, Message> { height: Option, max_width: Option, max_height: Option, - is_overlay: bool, } impl Default for Dialog<'_, Message> { @@ -41,7 +43,6 @@ impl<'a, Message> Dialog<'a, Message> { height: None, max_width: None, max_height: None, - is_overlay: true, } } @@ -99,11 +100,6 @@ impl<'a, Message> Dialog<'a, Message> { self.max_width = Some(max_width.into()); self } - - pub fn is_overlay(mut self, is_overlay: bool) -> Self { - self.is_overlay = is_overlay; - self - } } impl<'a, Message: Clone + 'static> From> for Element<'a, Message> { @@ -164,7 +160,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes let mut container = widget::container( widget::column::with_children([content_row.into(), button_row.into()]).spacing(space_l), ) - .class(style::Container::Dialog(dialog.is_overlay)) + .class(style::Container::Dialog) .padding(space_m) .width(dialog.width.unwrap_or(Length::Fixed(570.0))); diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index afe135bc..10bf7a8b 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -1,17 +1,29 @@ -use std::borrow::Cow; -use std::sync::atomic::{AtomicU64, Ordering}; +use std::{ + borrow::Cow, + sync::atomic::{AtomicU64, Ordering}, +}; use iced::Vector; -use crate::Element; -use crate::widget::{Id, Widget}; +use crate::{ + Element, + widget::{Id, Widget}, +}; -use iced::clipboard::dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent}; -use iced::clipboard::mime::AllowedMimeTypes; -use iced::id::Internal; -use iced::{Event, Length, Rectangle, event, mouse, overlay}; -use iced_core::widget::{Tree, tree}; -use iced_core::{self, Clipboard, Shell, layout}; +use iced::{ + Event, Length, Rectangle, + clipboard::{ + dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent}, + mime::AllowedMimeTypes, + }, + event, + id::Internal, + mouse, overlay, +}; +use iced_core::{ + self, Clipboard, Shell, layout, + widget::{Tree, tree}, +}; pub fn dnd_destination<'a, Message: 'static>( child: impl Into>, @@ -357,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 {:?})", @@ -396,6 +408,7 @@ impl Widget ); } shell.capture_event(); + return; } Event::Dnd(DndEvent::Offer(_, OfferEvent::Leave)) => { log::trace!( @@ -423,6 +436,7 @@ impl Widget viewport, ); } + return; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Motion { x, y })) if *id == Some(my_id) => { log::trace!( @@ -457,6 +471,7 @@ impl Widget ); } shell.capture_event(); + return; } Event::Dnd(DndEvent::Offer(_, OfferEvent::LeaveDestination)) => { log::trace!( @@ -469,6 +484,7 @@ impl Widget { shell.publish(msg); } + return; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Drop)) if *id == Some(my_id) => { log::trace!( @@ -481,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) => @@ -498,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) => @@ -537,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 7b706478..980723e3 100644 --- a/src/widget/dnd_source.rs +++ b/src/widget/dnd_source.rs @@ -1,14 +1,20 @@ use std::any::Any; -use iced_core::widget::Operation; -use iced_core::window; +use iced_core::{widget::Operation, window}; -use crate::Element; -use crate::widget::{Id, Widget, container}; -use iced::clipboard::dnd::{DndAction, DndEvent, SourceEvent}; -use iced::{Event, Length, Point, Rectangle, Vector, mouse, overlay}; -use iced_core::widget::{Tree, tree}; -use iced_core::{self, Clipboard, Shell, layout, renderer}; +use crate::{ + Element, + widget::{Id, Widget, container}, +}; +use iced::{ + Event, Length, Point, Rectangle, Vector, + clipboard::dnd::{DndAction, DndEvent, SourceEvent}, + event, mouse, overlay, +}; +use iced_core::{ + self, Clipboard, Shell, layout, renderer, + widget::{Tree, tree}, +}; pub fn dnd_source< 'a, diff --git a/src/widget/dropdown/menu/appearance.rs b/src/widget/dropdown/menu/appearance.rs index 2c32c011..d1bed21c 100644 --- a/src/widget/dropdown/menu/appearance.rs +++ b/src/widget/dropdown/menu/appearance.rs @@ -3,8 +3,7 @@ // SPDX-License-Identifier: MPL-2.0 AND MIT //! Change the appearance of menus. -use iced_core::border::Radius; -use iced_core::{Background, Color}; +use iced_core::{Background, Color, border::Radius}; /// The appearance of a menu. #[derive(Debug, Clone, Copy)] diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index 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 c7a46592..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, Shadow, Shell, Size, Vector, Widget, - alignment, keyboard, layout, mouse, overlay, renderer, svg, touch, + 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 2fa742e9..2ff9c92f 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -8,15 +8,16 @@ 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::{ - Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shadow, Shell, Size, Vector, Widget, - alignment, keyboard, layout, mouse, overlay, renderer, svg, touch, + Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, }; +use iced_core::{Shadow, alignment, keyboard, layout, mouse, overlay, renderer, svg, touch}; use iced_widget::pick_list::{self, Catalog}; use std::borrow::Cow; +use std::ffi::OsStr; 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) @@ -579,8 +580,6 @@ pub fn update< use iced_runtime::platform_specific::wayland::popup::{ SctkPopupSettings, SctkPositioner, }; - - use crate::surface::action::LiveSettings; let bounds = layout.bounds(); let anchor_rect = Rectangle { x: bounds.x as i32, @@ -607,7 +606,6 @@ pub fn update< let on_surface_action_clone = on_surface_action.clone(); let translation = layout.virtual_offset(); let get_popup_action = surface::action::simple_popup::( - || LiveSettings::default(), move || { SctkPopupSettings { parent, @@ -660,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))); } @@ -683,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))); } @@ -728,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/frames.rs b/src/widget/frames.rs index bf985ce2..a542cec6 100644 --- a/src/widget/frames.rs +++ b/src/widget/frames.rs @@ -2,12 +2,14 @@ //! Based on use std::ffi::OsStr; +use std::fmt; +use std::io; use std::path::Path; use std::time::{Duration, Instant}; -use std::{fmt, io}; use ::image as image_rs; -use iced::{Task, mouse}; +use iced::Task; +use iced::mouse; use iced_core::image::Renderer as ImageRenderer; use iced_core::mouse::Cursor; use iced_core::widget::{Tree, tree}; 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 82a7e8e9..a772f7d2 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -5,8 +5,7 @@ use crate::cosmic_theme::{Density, Spacing}; use crate::{Element, theme, widget}; use apply::Apply; use derive_setters::Setters; -use iced_core::widget::tree; -use iced_core::{Length, Size, Vector, Widget, layout, text}; +use iced_core::{Length, Size, Vector, Widget, layout, text, widget::tree}; use std::borrow::Cow; #[must_use] @@ -28,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 @@ -114,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> { @@ -401,25 +370,16 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { space_xxs, .. } = theme::spacing(); - let is_ssd = self.is_ssd; // 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 is_ssd { + let padding = if self.is_ssd { [2, 8, 2, 8] } else { match ( @@ -464,7 +424,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .class(theme::Container::HeaderBar { focused: self.focused, sharp_corners: self.sharp_corners, - transparent: if is_ssd { false } else { true }, + transparent: self.transparent, }) .height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32)) .padding(padding) @@ -487,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) @@ -504,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/icon/named.rs b/src/widget/icon/named.rs index 89050301..dfd66cf5 100644 --- a/src/widget/icon/named.rs +++ b/src/widget/icon/named.rs @@ -2,10 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 use super::{Handle, Icon}; -use std::borrow::Cow; -use std::ffi::OsStr; -use std::path::PathBuf; -use std::sync::Arc; +use std::{borrow::Cow, ffi::OsStr, path::PathBuf, sync::Arc}; #[derive(Debug, Clone, Default, Hash)] /// Fallback icon to use if the icon was not found. @@ -39,10 +36,6 @@ pub struct Named { /// Prioritizes SVG over PNG pub prefer_svg: bool, - - /// Extra directories to search as flat paths before the icon theme chain. - #[setters(skip)] - pub extra_paths: Vec, } impl Named { @@ -56,30 +49,19 @@ impl Named { size: None, scale: None, prefer_svg: symbolic, - extra_paths: Vec::new(), } } - pub fn with_extra_paths(mut self, paths: Vec) -> Self { - self.extra_paths = paths; - self - } - #[cfg(all(unix, not(target_os = "macos")))] #[must_use] pub fn path(self) -> Option { let name = &*self.name; let fallback = &self.fallback; - let extra_paths = &self.extra_paths; let locate = |theme: &str, name| { let mut lookup = freedesktop_icons::lookup(name) .with_theme(theme.as_ref()) .with_cache(); - if !extra_paths.is_empty() { - lookup = lookup.with_extra_paths(extra_paths); - } - if let Some(scale) = self.scale { lookup = lookup.with_scale(scale); } diff --git a/src/widget/id_container.rs b/src/widget/id_container.rs index 7c149203..716ee138 100644 --- a/src/widget/id_container.rs +++ b/src/widget/id_container.rs @@ -1,9 +1,10 @@ -use iced_core::event::Event; +use iced_core::event::{self, Event}; +use iced_core::layout; +use iced_core::mouse; +use iced_core::overlay; +use iced_core::renderer; use iced_core::widget::{Id, Operation, Tree}; -use iced_core::{ - Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay, - renderer, -}; +use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; pub use iced_widget::container::{Catalog, Style}; pub fn id_container<'a, Message: 'static, Theme, E>( diff --git a/src/widget/layer_container.rs b/src/widget/layer_container.rs index 91a6729c..110af518 100644 --- a/src/widget/layer_container.rs +++ b/src/widget/layer_container.rs @@ -1,11 +1,14 @@ 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; +use iced_core::renderer; use iced_core::widget::Tree; use iced_core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, - layout, mouse, overlay, renderer, }; pub use iced_widget::container::{Catalog, Style}; diff --git a/src/widget/list/list_column.rs b/src/widget/list/list_column.rs index 41892f69..4ef3fc01 100644 --- a/src/widget/list/list_column.rs +++ b/src/widget/list/list_column.rs @@ -2,8 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 use crate::widget::container::Catalog; -use crate::widget::space::vertical; -use crate::widget::{DndDestination, DndSource, button, column, container, divider, row}; +use crate::widget::{button, column, container, divider, row, space::vertical}; use crate::{Apply, Element, theme}; use iced::{Length, Padding}; @@ -11,27 +10,14 @@ use iced::{Length, Padding}; pub struct ListButton<'a, Message> { content: Element<'a, Message>, on_press: Option, - dnd_source_builder: Option>>, - dnd_destination_builder: Option>>, selected: bool, } -/// Builds a DndSource, wrapping an element -pub type DndSourceBuilder<'a, Message> = - dyn FnOnce( - Element<'a, Message>, - ) -> DndSource<'a, Message, Box>; -/// Builds a DndDestination, wrapping an element -pub type DndDestinationBuilder<'a, Message> = - dyn FnOnce(Element<'a, Message>) -> DndDestination<'a, Message>; - /// Creates a [`ListButton`] with the given content. pub fn button<'a, Message>(content: impl Into>) -> ListButton<'a, Message> { ListButton { content: content.into(), on_press: None, - dnd_source_builder: None, - dnd_destination_builder: None, selected: false, } } @@ -51,19 +37,6 @@ impl<'a, Message: 'static> ListButton<'a, Message> { self.selected = selected; self } - - pub fn with_dnd_source(mut self, builder: Box>) -> Self { - self.dnd_source_builder = Some(builder); - self - } - - pub fn with_dnd_destination( - mut self, - builder: Box>, - ) -> Self { - self.dnd_destination_builder = Some(builder); - self - } } pub enum ListItem<'a, Message> { @@ -201,10 +174,8 @@ impl<'a, Message: Clone + 'static> ListColumn<'a, Message> { content, on_press, selected, - dnd_source_builder, - dnd_destination_builder, - }) => { - let mut button: Element<'a, Message> = content_row(content) + }) => col.push( + content_row(content) .apply(button::custom) .padding(item_padding) .width(Length::Fill) @@ -214,18 +185,8 @@ impl<'a, Message: Clone + 'static> ListColumn<'a, Message> { radius_s, i == 0, i == last_index, - ))) - .into(); - if let Some(builder) = dnd_source_builder { - button = builder(button).into(); - } - - if let Some(builder) = dnd_destination_builder { - button = builder(button).into(); - } - - col.push(button) - } + ))), + ), }; } diff --git a/src/widget/menu.rs b/src/widget/menu.rs index 17d47cdd..9d4ce4b1 100644 --- a/src/widget/menu.rs +++ b/src/widget/menu.rs @@ -66,13 +66,10 @@ mod menu_bar; pub(crate) use menu_bar::MenuBarState; pub use menu_bar::{MenuBar, menu_bar as bar}; -pub mod menu_column; - mod menu_inner; mod menu_tree; pub use menu_tree::{ MenuItem as Item, MenuTree as Tree, menu_button, menu_items as items, menu_root as root, - nav_context, }; pub use crate::style::menu_bar::{Appearance, StyleSheet}; diff --git a/src/widget/menu/flex.rs b/src/widget/menu/flex.rs index e4a32870..4a58f13a 100644 --- a/src/widget/menu/flex.rs +++ b/src/widget/menu/flex.rs @@ -1,9 +1,11 @@ // From iced_aw, license MIT -use iced_core::Widget; -use iced_core::widget::Tree; -use iced_widget::core::layout::{Limits, Node}; -use iced_widget::core::{Alignment, Element, Padding, Point, Size, renderer}; +use iced_core::{Widget, widget::Tree}; +use iced_widget::core::{ + Alignment, Element, Padding, Point, Size, + layout::{Limits, Node}, + renderer, +}; use crate::widget::RcElementWrapper; @@ -48,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, @@ -247,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, @@ -270,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, @@ -313,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/key_bind.rs b/src/widget/menu/key_bind.rs index 2202125f..8b4ed227 100644 --- a/src/widget/menu/key_bind.rs +++ b/src/widget/menu/key_bind.rs @@ -1,4 +1,3 @@ -use iced_core::keyboard::key::{Code, Physical}; use iced_core::keyboard::{Key, Modifiers}; use std::fmt; @@ -28,116 +27,28 @@ pub struct KeyBind { } impl KeyBind { - /// Checks if the given key and modifiers match the `KeyBind`, with an - /// optional fallback to the physical key position for non-Latin keyboard - /// layouts. + /// Checks if the given key and modifiers match the `KeyBind`. /// /// # Arguments /// /// * `modifiers` - A `Modifiers` instance representing the current active modifiers. /// * `key` - A reference to the `Key` that is being checked. - /// * `physical_key` - An optional reference to the physical key position, - /// used as a fallback when the logical `key` does not match (e.g. on - /// Cyrillic or other non-Latin layouts). Can be `None` for keys where - /// the physical position is not relevant (e.g. `Key::Named`). /// /// # Returns /// /// * `bool` - `true` if the key and modifiers match the `KeyBind`, `false` otherwise. - pub fn matches( - &self, - modifiers: Modifiers, - key: &Key, - physical_key: Option<&Physical>, - ) -> bool { - let key_eq = self.key_eq(key) - || (!is_latin_shortcut_key(key) - && physical_key - .and_then(physical_key_to_latin) - .is_some_and(|latin| self.key_eq(&latin))); + pub fn matches(&self, modifiers: Modifiers, key: &Key) -> bool { + let key_eq = match (key, &self.key) { + // CapsLock and Shift change the case of Key::Character, so we compare these in a case insensitive way + (Key::Character(a), Key::Character(b)) => a.eq_ignore_ascii_case(b), + (a, b) => a.eq(b), + }; key_eq && modifiers.logo() == self.modifiers.contains(&Modifier::Super) && modifiers.control() == self.modifiers.contains(&Modifier::Ctrl) && modifiers.alt() == self.modifiers.contains(&Modifier::Alt) && modifiers.shift() == self.modifiers.contains(&Modifier::Shift) } - - fn key_eq(&self, key: &Key) -> bool { - match (key, &self.key) { - // CapsLock and Shift change the case of Key::Character, so we compare these in a case insensitive way - (Key::Character(a), Key::Character(b)) => a.eq_ignore_ascii_case(b), - (a, b) => a.eq(b), - } - } -} - -fn is_latin_shortcut_key(key: &Key) -> bool { - let Key::Character(s) = key else { - return false; - }; - - let mut chars = s.chars(); - let Some(ch) = chars.next() else { - return false; - }; - - chars.next().is_none() && (ch.is_ascii_graphic() || ch == ' ') -} - -/// Converts a physical key code to the corresponding US-layout Latin `Key`. -/// -/// This mapping is intentionally limited to keys that may produce different -/// characters on non-Latin keyboard layouts (letters and punctuation). Keys -/// like digits are not included because they remain the same across layouts. -/// -/// Only used as a fallback when the primary key comparison in -/// [`KeyBind::matches`] does not match. -fn physical_key_to_latin(physical_key: &Physical) -> Option { - let code = match physical_key { - Physical::Code(code) => code, - Physical::Unidentified(_) => return None, - }; - let ch = match code { - Code::KeyA => "a", - Code::KeyB => "b", - Code::KeyC => "c", - Code::KeyD => "d", - Code::KeyE => "e", - Code::KeyF => "f", - Code::KeyG => "g", - Code::KeyH => "h", - Code::KeyI => "i", - Code::KeyJ => "j", - Code::KeyK => "k", - Code::KeyL => "l", - Code::KeyM => "m", - Code::KeyN => "n", - Code::KeyO => "o", - Code::KeyP => "p", - Code::KeyQ => "q", - Code::KeyR => "r", - Code::KeyS => "s", - Code::KeyT => "t", - Code::KeyU => "u", - Code::KeyV => "v", - Code::KeyW => "w", - Code::KeyX => "x", - Code::KeyY => "y", - Code::KeyZ => "z", - Code::Minus => "-", - Code::Equal => "=", - Code::BracketLeft => "[", - Code::BracketRight => "]", - Code::Backslash => "\\", - Code::Semicolon => ";", - Code::Quote => "'", - Code::Backquote => "`", - Code::Comma => ",", - Code::Period => ".", - Code::Slash => "/", - _ => return None, - }; - Some(Key::Character(ch.into())) } impl fmt::Display for KeyBind { @@ -146,100 +57,9 @@ impl fmt::Display for KeyBind { write!(f, "{:?} + ", modifier)?; } match &self.key { - Key::Character(c) if c.as_str() == " " => write!(f, "Space"), Key::Character(c) => write!(f, "{}", c.to_uppercase()), Key::Named(named) => write!(f, "{:?}", named), other => write!(f, "{:?}", other), } } } - -#[cfg(test)] -mod test { - use super::*; - - fn bind_ctrl_w() -> KeyBind { - KeyBind { - modifiers: vec![Modifier::Ctrl], - key: Key::Character("w".into()), - } - } - - #[test] - fn ctrl_w() { - assert!(bind_ctrl_w().matches( - Modifiers::CTRL, - &Key::Character("w".into()), - Some(&Physical::Code(Code::KeyW)), - )); - } - - #[test] - fn ctrl_w_no_fallback_to_dvorak_comma() { - assert!(!bind_ctrl_w().matches( - Modifiers::CTRL, - &Key::Character(",".into()), - Some(&Physical::Code(Code::KeyW)), - )); - } - - #[test] - fn non_latin_layout_fallback() { - assert!(bind_ctrl_w().matches( - Modifiers::CTRL, - &Key::Character("ц".into()), - Some(&Physical::Code(Code::KeyW)), - )); - - let bind = KeyBind { - modifiers: vec![Modifier::Ctrl], - key: Key::Character("s".into()), - }; - - assert!(bind.matches( - Modifiers::CTRL, - &Key::Character("ы".into()), - Some(&Physical::Code(Code::KeyS)), - )); - - assert!(!bind.matches( - Modifiers::CTRL, - &Key::Character("ц".into()), - Some(&Physical::Code(Code::KeyQ)), - )); - } - - #[test] - fn ctrl_space() { - let bind = KeyBind { - modifiers: vec![Modifier::Ctrl], - key: Key::Character(" ".into()), - }; - - assert!(bind.matches(Modifiers::CTRL, &Key::Character(" ".into()), None,)); - assert!(format!("{}", bind) == String::from("Ctrl + Space")) - } - - #[test] - fn ctrl_space_no_fallback() { - assert!(!bind_ctrl_w().matches( - Modifiers::CTRL, - &Key::Character(" ".into()), - Some(&Physical::Code(Code::KeyW)), - )); - } - - #[test] - fn ctrl_a_no_fallback_to_french_azerty_q() { - let bind = KeyBind { - modifiers: vec![Modifier::Ctrl], - key: Key::Character("a".into()), - }; - - assert!(!bind.matches( - Modifiers::CTRL, - &Key::Character("q".into()), - Some(&Physical::Code(Code::KeyA)), - )); - } -} diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 01b91b59..981446e8 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -1,34 +1,42 @@ // From iced_aw, license MIT //! A widget that handles menu trees -use std::collections::HashMap; -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; -use super::menu_inner::{ - CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight, +use super::{ + menu_inner::{ + CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight, + }, + menu_tree::MenuTree, }; -use super::menu_tree::MenuTree; -use crate::Renderer; #[cfg(all( feature = "multi-window", feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; -use crate::style::menu_bar::StyleSheet; -use crate::widget::RcWrapper; -use crate::widget::menu::menu_inner::init_root_menu; +use crate::{ + Renderer, + 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::layout::{Limits, Node}; -use iced_widget::core::mouse::{self, Cursor}; -use iced_widget::core::renderer::{self, Renderer as IcedRenderer}; -use iced_widget::core::widget::{Tree, tree}; use iced_widget::core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, event, - overlay, touch, + layout::{Limits, Node}, + mouse::{self, Cursor}, + overlay, + renderer::{self, Renderer as IcedRenderer}, + touch, + widget::{Tree, tree}, }; /// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing. @@ -48,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, @@ -84,6 +93,7 @@ impl Default for MenuBarStateInner { vertical_direction: Direction::Positive, menu_states: Vec::new(), popup_id: HashMap::new(), + bar_pressed: false, } } } @@ -160,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)] @@ -181,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, @@ -216,10 +235,11 @@ where path_highlight: Some(PathHighlight::MenuActive), menu_roots, style: ::Style::default(), - window_id: window::Id::RESERVED, + window_id: window::Id::NONE, #[cfg(all( feature = "multi-window", feature = "wayland", + feature = "winit", target_os = "linux" ))] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), @@ -318,6 +338,7 @@ where #[cfg(all( feature = "multi-window", feature = "wayland", + feature = "winit", target_os = "linux" ))] pub fn with_positioner( @@ -355,6 +376,7 @@ where feature = "multi-window", feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] #[allow(clippy::too_many_lines)] @@ -368,9 +390,7 @@ where my_state: &mut MenuBarState, ) { if self.window_id != window::Id::NONE && self.on_surface_action.is_some() { - use crate::surface::action::{LiveSettings, destroy_popup}; - use crate::theme::THEME; - use iced_runtime::platform_specific::wayland::CornerRadius; + use crate::surface::action::destroy_popup; use iced_runtime::platform_specific::wayland::popup::{ SctkPopupSettings, SctkPositioner, }; @@ -485,22 +505,7 @@ where ..Default::default() }; let parent = self.window_id; - - let t = THEME.lock().unwrap(); - let styling = t.appearance(&crate::theme::menu_bar::MenuBarStyle::Default, false); - drop(t); - let rad = styling.menu_border_radius; - shell.publish((surface_action)(crate::surface::action::simple_popup( - move || LiveSettings { - corners: Some(CornerRadius { - top_left: rad[0] as u32, - top_right: rad[1] as u32, - bottom_left: rad[2] as u32, - bottom_right: rad[3] as u32, - }), - ..Default::default() - }, move || SctkPopupSettings { parent, id, @@ -585,8 +590,7 @@ where viewport: &Rectangle, ) { use event::Event::{Mouse, Touch}; - use mouse::Button::Left; - use mouse::Event::ButtonReleased; + use mouse::{Button::Left, Event::ButtonReleased}; use touch::Event::{FingerLifted, FingerLost}; process_root_events( @@ -610,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 }); @@ -641,6 +647,7 @@ where #[cfg(all( feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] { @@ -664,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)) { @@ -678,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)) { @@ -709,20 +718,7 @@ where // draw path highlight if self.path_highlight.is_some() { - let mut is_overlay = true; - #[cfg(all( - feature = "multi-window", - feature = "wayland", - target_os = "linux", - feature = "surface-message" - ))] - if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) - && self.on_surface_action.is_some() - && self.window_id != window::Id::NONE - { - is_overlay = true; - }; - let styling = theme.appearance(&self.style, is_overlay); + let styling = theme.appearance(&self.style); if let Some(active) = state.active_root.first() { let active_bounds = layout .children() @@ -766,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_column.rs b/src/widget/menu/menu_column.rs deleted file mode 100644 index 3e7455d6..00000000 --- a/src/widget/menu/menu_column.rs +++ /dev/null @@ -1,411 +0,0 @@ -//! Distribute content vertically. -use crate::iced; -use iced::core::alignment::{self, Alignment}; -use iced::core::event::Event; -use iced::core::widget::{Operation, Tree}; -use iced::core::{ - Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, - layout, mouse, overlay, renderer, -}; - -#[allow(missing_debug_implementations)] -#[must_use] -pub struct MenuColumn<'a, Message, Renderer = iced::Renderer> { - spacing: f32, - padding: Padding, - width: Length, - height: Length, - max_width: f32, - align: Alignment, - clip: bool, - children: Vec>, -} - -impl<'a, Message, Renderer> MenuColumn<'a, Message, Renderer> -where - Renderer: iced::core::Renderer, -{ - /// Creates an empty [`MenuColumn`]. - pub fn new() -> Self { - Self::from_vec(Vec::new()) - } - - /// Creates a [`MenuColumn`] with the given capacity. - pub fn with_capacity(capacity: usize) -> Self { - Self::from_vec(Vec::with_capacity(capacity)) - } - - /// Creates a [`MenuColumn`] with the given elements. - pub fn with_children( - children: impl IntoIterator>, - ) -> Self { - let iterator = children.into_iter(); - - Self::with_capacity(iterator.size_hint().0).extend(iterator) - } - - /// Creates a [`MenuColumn`] from an already allocated [`Vec`]. - /// - /// Keep in mind that the [`MenuColumn`] will not inspect the [`Vec`], which means - /// it won't automatically adapt to the sizing strategy of its contents. - /// - /// If any of the children have a [`Length::Fill`] strategy, you will need to - /// call [`MenuColumn::width`] or [`MenuColumn::height`] accordingly. - pub fn from_vec(children: Vec>) -> Self { - Self { - spacing: 0.0, - padding: Padding::ZERO, - width: Length::Shrink, - height: Length::Shrink, - max_width: f32::INFINITY, - align: Alignment::Start, - clip: false, - children, - } - } - - /// Sets the vertical spacing _between_ elements. - /// - /// Custom margins per element do not exist in iced. You should use this - /// method instead! While less flexible, it helps you keep spacing between - /// elements consistent. - pub fn spacing(mut self, amount: impl Into) -> Self { - self.spacing = amount.into().0; - self - } - - /// Sets the [`Padding`] of the [`MenuColumn`]. - pub fn padding>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the width of the [`MenuColumn`]. - pub fn width(mut self, width: impl Into) -> Self { - self.width = width.into(); - self - } - - /// Sets the height of the [`MenuColumn`]. - pub fn height(mut self, height: impl Into) -> Self { - self.height = height.into(); - self - } - - /// Sets the maximum width of the [`MenuColumn`]. - pub fn max_width(mut self, max_width: impl Into) -> Self { - self.max_width = max_width.into().0; - self - } - - /// Sets the horizontal alignment of the contents of the [`MenuColumn`] . - pub fn align_x(mut self, align: impl Into) -> Self { - self.align = Alignment::from(align.into()); - self - } - - /// Sets whether the contents of the [`MenuColumn`] should be clipped on - /// overflow. - pub fn clip(mut self, clip: bool) -> Self { - self.clip = clip; - self - } - - /// Adds an element to the [`MenuColumn`]. - pub fn push(mut self, child: impl Into>) -> Self { - let child = child.into(); - let child_size = child.as_widget().size_hint(); - - self.width = self.width.enclose(child_size.width); - self.height = self.height.enclose(child_size.height); - - self.children.push(child); - self - } - - /// Adds an element to the [`MenuColumn`], if `Some`. - #[must_use] - pub fn push_maybe( - self, - child: Option>>, - ) -> Self { - if let Some(child) = child { - self.push(child) - } else { - self - } - } - - /// Extends the [`MenuColumn`] with the given children. - pub fn extend( - self, - children: impl IntoIterator>, - ) -> Self { - children.into_iter().fold(self, Self::push) - } -} - -impl Default for MenuColumn<'_, Message, Renderer> -where - Renderer: iced::core::Renderer, -{ - fn default() -> Self { - Self::new() - } -} - -impl<'a, Message, Renderer: iced::core::Renderer> - FromIterator> - for MenuColumn<'a, Message, Renderer> -{ - fn from_iter>>( - iter: T, - ) -> Self { - Self::with_children(iter) - } -} - -impl Widget - for MenuColumn<'_, Message, Renderer> -where - Renderer: iced::core::Renderer, -{ - fn children(&self) -> Vec { - self.children.iter().map(Tree::new).collect() - } - - fn diff(&mut self, tree: &mut Tree) { - tree.diff_children(self.children.as_mut_slice()); - } - - fn size(&self) -> Size { - Size { - width: self.width, - height: self.height, - } - } - - fn layout( - &mut self, - tree: &mut Tree, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let limits = limits.max_width(self.max_width); - - layout::flex::resolve( - layout::flex::Axis::Vertical, - renderer, - &limits, - self.width, - self.height, - self.padding, - self.spacing, - self.align, - &mut self.children, - &mut tree.children, - ) - } - - 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), c_layout)| { - child.as_widget_mut().operate( - state, - c_layout.with_virtual_offset(layout.virtual_offset()), - renderer, - operation, - ); - }); - }); - } - - fn update( - &mut self, - tree: &mut Tree, - event: &Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - viewport: &Rectangle, - ) { - for (((_i, child), state), c_layout) in self - .children - .iter_mut() - .enumerate() - .zip(&mut tree.children) - .zip(layout.children()) - { - child.as_widget_mut().update( - state, - &event, - c_layout.with_virtual_offset(layout.virtual_offset()), - cursor, - renderer, - clipboard, - shell, - viewport, - ); - } - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor: mouse::Cursor, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.children - .iter() - .zip(&tree.children) - .zip(layout.children()) - .map(|((child, state), c_layout)| { - child.as_widget().mouse_interaction( - state, - c_layout.with_virtual_offset(layout.virtual_offset()), - cursor, - viewport, - renderer, - ) - }) - .max() - .unwrap_or_default() - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &crate::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor: mouse::Cursor, - viewport: &Rectangle, - ) { - if let Some(clipped_viewport) = layout.bounds().intersection(viewport) { - let viewport = if self.clip { - &clipped_viewport - } else { - viewport - }; - - for (i, ((child, state), c_layout)) in self - .children - .iter() - .zip(&tree.children) - .zip(layout.children()) - .filter(|(_, layout)| layout.bounds().intersects(viewport)) - .enumerate() - { - let t = theme.with_list_item_position(if self.children.len() == 1 { - Some((Alignment::Center, i)) - } else if 0 == i { - Some((Alignment::Start, i)) - } else if i == self.children.len() - 1 { - Some((Alignment::End, i)) - } else { - None - }); - child.as_widget().draw( - state, - renderer, - &t, - style, - c_layout.with_virtual_offset(layout.virtual_offset()), - 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, - ) - } - - #[cfg(feature = "a11y")] - /// get the a11y nodes for the widget - fn a11y_nodes( - &self, - layout: Layout<'_>, - state: &Tree, - cursor: mouse::Cursor, - ) -> iced_accessibility::A11yTree { - use iced_accessibility::A11yTree; - A11yTree::join( - self.children - .iter() - .zip(layout.children()) - .zip(state.children.iter()) - .map(|((c, c_layout), state)| { - c.as_widget().a11y_nodes( - c_layout.with_virtual_offset(layout.virtual_offset()), - state, - cursor, - ) - }), - ) - } - - fn drag_destinations( - &self, - state: &Tree, - layout: Layout<'_>, - renderer: &Renderer, - dnd_rectangles: &mut iced::core::clipboard::DndDestinationRectangles, - ) { - for ((e, c_layout), state) in self - .children - .iter() - .zip(layout.children()) - .zip(state.children.iter()) - { - e.as_widget().drag_destinations( - state, - c_layout.with_virtual_offset(layout.virtual_offset()), - renderer, - dnd_rectangles, - ); - } - } -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, crate::Theme, Renderer> -where - Message: 'a, - Renderer: iced::core::Renderer + 'a, -{ - fn from(column: MenuColumn<'a, Message, Renderer>) -> Self { - Self::new(column) - } -} diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index d5e50b92..74afe60f 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -1,28 +1,27 @@ // From iced_aw, license MIT //! Menu tree overlay -use std::borrow::Cow; -use std::sync::Arc; +use std::{borrow::Cow, sync::Arc}; -use super::menu_bar::MenuBarState; -use super::menu_tree::MenuTree; +use super::{menu_bar::MenuBarState, menu_tree::MenuTree}; #[cfg(all( feature = "multi-window", feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; use crate::style::menu_bar::StyleSheet; -use iced::{Alignment, window}; +use iced::window; use iced_core::{Border, Renderer as IcedRenderer, Shadow, Widget}; -use iced_widget::core::layout::{Limits, Node}; -use iced_widget::core::mouse::{self, Cursor}; -use iced_widget::core::widget::Tree; use iced_widget::core::{ - Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, event, overlay, - renderer, touch, + Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, event, + layout::{Limits, Node}, + mouse::{self, Cursor}, + overlay, renderer, touch, + widget::Tree, }; /// The condition of when to close a menu @@ -524,7 +523,6 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { ); let node_size = children_node.size(); intrinsic_size.height += node_size.height; - intrinsic_size.width = intrinsic_size.width.max(node_size.width); nodes.push(children_node); @@ -561,10 +559,14 @@ 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 mouse::Button::Left; - use mouse::Event::{ButtonPressed, ButtonReleased, CursorMoved, WheelScrolled}; + use event::{ + Event::{Mouse, Touch}, + Status::{Captured, Ignored}, + }; + use mouse::{ + Button::Left, + Event::{ButtonPressed, ButtonReleased, CursorMoved, WheelScrolled}, + }; use touch::Event::{FingerLifted, FingerMoved, FingerPressed}; if !self @@ -580,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(), }; @@ -680,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)) @@ -741,7 +744,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { Rectangle::new(Point::ORIGIN, Size::INFINITE) }; - let styling = theme.appearance(&self.style, self.is_overlay); + let styling = theme.appearance(&self.style); let roots = active_root.iter().skip(1).fold( &self.menu_roots[active_root[0]].children, |mt, next_active_root| &mt[*next_active_root].children, @@ -816,27 +819,13 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { .children() .nth(active.saturating_sub(start_index)) { - let i = active.saturating_sub(start_index); - let mut rad = styling.menu_border_radius; - let rad_0 = theme.cosmic().radius_0(); - if start_index != end_index { - if 0 == i { - rad[0] = rad_0[0]; - rad[1] = rad_0[1]; - } else if i == end_index - start_index { - rad[2] = rad_0[2]; - rad[3] = rad_0[3]; - } else { - rad = rad_0; - } - } let path_quad = renderer::Quad { bounds: active_layout .bounds() .intersection(&viewport) .unwrap_or_default(), border: Border { - radius: rad.into(), + radius: styling.menu_border_radius.into(), ..Default::default() }, shadow: Shadow::default(), @@ -849,25 +838,12 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { // draw item menu_roots[start_index..=end_index] .iter() - .enumerate() .zip(children_layout.children()) - .for_each(|((i, mt), clo)| { - let t = theme.with_list_item_position( - if start_index == end_index { - Some((Alignment::Center, i)) - } else if 0 == i { - Some((Alignment::Start, i)) - } else if i == end_index - start_index { - Some((Alignment::End, i)) - } else { - None - }, - ); - + .for_each(|(mt, clo)| { mt.item.draw( &state.tree.children[active_root[0]].children[mt.index], r, - &t, + theme, style, clo, view_cursor, @@ -977,7 +953,7 @@ impl Widget, cursor: mouse::Cursor, @@ -991,29 +967,26 @@ impl Widget Widget Widget( feature = "multi-window", feature = "wayland", target_os = "linux", + feature = "winit", feature = "surface-message" ))] pub(super) fn init_root_popup_menu( @@ -1568,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); @@ -1647,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/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index bd4f1e6c..41cf1dff 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -230,11 +230,10 @@ pub fn menu_items< } fn key_style(theme: &crate::Theme) -> TextStyle { - let mut color = theme.cosmic().background(theme.transparent).component.on; + let mut color = theme.cosmic().background.component.on; color.alpha *= 0.75; TextStyle { color: Some(color.into()), - ..Default::default() } } let key_class = theme::Text::Custom(key_style); @@ -401,19 +400,3 @@ pub fn menu_items< }) .collect() } - -/// Create a menu tree from a widget and a vector of sub trees -pub fn nav_context< - A: MenuAction, - L: Into> + From<&'static str> + 'static, - Message: 'static + std::clone::Clone, ->( - key_binds: &HashMap, - children: Vec>>, -) -> Vec> { - let menus = children - .into_iter() - .map(|m| MenuItem::::Folder(L::from(""), m)); - let root = vec![MenuItem::::Folder(L::from(""), menus.collect())]; - menu_items(key_binds, root) -} diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 5764ab44..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)] @@ -152,7 +154,7 @@ pub use dialog::{Dialog, dialog}; pub mod divider { /// Horizontal variant of a divider. pub mod horizontal { - use iced::widget::{Rule, rule}; + use iced::{widget::Rule, widget::rule}; /// Horizontal divider with default thickness #[must_use] @@ -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/nav_bar.rs b/src/widget/nav_bar.rs index 6f9d0210..ad6f9206 100644 --- a/src/widget/nav_bar.rs +++ b/src/widget/nav_bar.rs @@ -6,9 +6,10 @@ //! For details on the model, see the [`segmented_button`] module for more details. use apply::Apply; -use iced::clipboard::dnd::DndAction; -use iced::clipboard::mime::AllowedMimeTypes; -use iced::{Background, Length, window}; +use iced::{ + Background, Length, + clipboard::{dnd::DndAction, mime::AllowedMimeTypes}, +}; use iced_core::{Border, Color, Shadow}; use crate::widget::{Container, Icon, container, menu, scrollable, segmented_button}; @@ -55,7 +56,7 @@ where } #[must_use] -pub struct NavBar<'a, Message: Clone + 'static> { +pub struct NavBar<'a, Message> { segmented_button: segmented_button::VerticalSegmentedButton<'a, segmented_button::SingleSelect, Message>, } @@ -133,41 +134,6 @@ impl<'a, Message: Clone + 'static> NavBar<'a, Message> { self.segmented_button = self.segmented_button.on_dnd_leave(handler); self } - - #[cfg(all( - feature = "multi-window", - feature = "wayland", - target_os = "linux" - ))] - pub fn with_positioner( - mut self, - positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, - ) -> Self { - self.segmented_button = self.segmented_button.with_positioner(positioner); - self - } - - #[must_use] - pub fn window_id(mut self, id: window::Id) -> Self { - self.segmented_button = self.segmented_button.window_id(id); - self - } - - #[must_use] - pub fn window_id_maybe(mut self, id: Option) -> Self { - self.segmented_button = self.segmented_button.window_id_maybe(id); - - self - } - - #[must_use] - pub fn on_surface_action( - mut self, - handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static, - ) -> Self { - self.segmented_button = self.segmented_button.on_surface_action(handler); - self - } } impl<'a, Message: Clone + 'static> From> @@ -207,9 +173,7 @@ pub fn nav_bar_style(theme: &Theme) -> iced_widget::container::Style { iced_widget::container::Style { icon_color: Some(cosmic.on_bg_color().into()), text_color: Some(cosmic.on_bg_color().into()), - background: Some(Background::Color( - cosmic.primary(theme.transparent).base.into(), - )), + background: Some(Background::Color(cosmic.primary.base.into())), border: Border { width: 0.0, color: Color::TRANSPARENT, diff --git a/src/widget/popover.rs b/src/widget/popover.rs index fb904248..af5370a8 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -4,11 +4,15 @@ //! 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; +use iced_core::renderer; +use iced_core::touch; use iced_core::widget::{Operation, Tree}; use iced_core::{ - Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, layout, - mouse, overlay, renderer, touch, + Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, }; pub use iced_widget::container::{Catalog, Style}; @@ -160,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 { .. }) @@ -169,6 +173,7 @@ where { shell.publish(on_close.clone()); } + } } // Hide cursor from background content when modal popup is active @@ -401,26 +406,6 @@ where && matches!(event, Event::Mouse(_) | Event::Touch(_)) && !cursor_position.is_over(layout.bounds()) { - // Swallow new presses outside the popup, but still forward other - // events so an interaction started inside it (such as a selection - // drag) receives its release once the cursor leaves the bounds. - let is_press = matches!( - event, - Event::Mouse(mouse::Event::ButtonPressed(_)) - | Event::Touch(touch::Event::FingerPressed { .. }) - ); - if !is_press { - self.content.as_widget_mut().update( - self.tree, - event, - layout, - cursor_position, - renderer, - clipboard, - shell, - &layout.bounds(), - ); - } shell.capture_event(); return; } @@ -491,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/progress_bar/animation.rs b/src/widget/progress_bar/animation.rs deleted file mode 100644 index 500ba4ac..00000000 --- a/src/widget/progress_bar/animation.rs +++ /dev/null @@ -1,106 +0,0 @@ -use crate::anim::smootherstep; -use iced::time::Instant; -use std::time::Duration; - -const LAG: f32 = 0.1; - -#[derive(Default)] -pub struct Progress { - pub current: f32, - last: Option, -} - -impl Progress { - /// Chases `target` using exponential decay. - /// Returns `true` if a redraw should be requested. - pub fn update(&mut self, target: f32, now: Instant) -> bool { - // Don't animate on start - let Some(last) = self.last else { - self.current = target; - self.last = Some(now); - return false; - }; - - let dt = (now - last).as_secs_f32().min(1.0 / 60.0); - self.last = Some(now); - let diff = target - self.current; - - if diff.abs() > 0.001 { - self.current += diff * (1.0 - (-dt / LAG).exp()); - true - } else { - self.current = target; - false - } - } -} - -#[derive(Clone, Copy)] -pub struct Animation { - expanding: bool, - start: Instant, - last: Instant, - offset: u32, -} - -impl Default for Animation { - fn default() -> Self { - let now = Instant::now(); - Self { - expanding: true, - start: now, - last: now, - offset: 0, - } - } -} - -impl Animation { - pub fn timed_transition( - &self, - cycle_duration: Duration, - period: Duration, - wrap: f32, - now: Instant, - ) -> Self { - let additional = - ((now - self.last).as_secs_f32() / period.as_secs_f32() * u32::MAX as f32) as u32; - let new_offset = self.offset.wrapping_add(additional); - - if !cycle_duration.is_zero() && now.duration_since(self.start) > cycle_duration { - let offset = if self.expanding { - new_offset - } else { - new_offset.wrapping_add((wrap * u32::MAX as f32) as u32) - }; - Self { - expanding: !self.expanding, - start: now, - last: now, - offset, - } - } else { - Self { - last: now, - offset: new_offset, - ..*self - } - } - } - - pub fn bar_positions(&self, cycle_duration: Duration, min: f32, wrap: f32) -> (f32, f32) { - let offset = self.offset as f32 / u32::MAX as f32; - let progress = if !cycle_duration.is_zero() { - smootherstep( - self.last.duration_since(self.start).as_secs_f32() / cycle_duration.as_secs_f32(), - ) - } else { - 1.0 - }; - if self.expanding { - (offset, offset + min + wrap * progress) - } else { - (offset + wrap * progress, offset + min + wrap) - } - } -} diff --git a/src/widget/progress_bar/circular.rs b/src/widget/progress_bar/circular.rs index 3318d7a3..fa8c38fe 100644 --- a/src/widget/progress_bar/circular.rs +++ b/src/widget/progress_bar/circular.rs @@ -1,16 +1,20 @@ //! Show a circular progress indicator. -use super::animation::{Animation, Progress}; use super::style::StyleSheet; +use crate::anim::smootherstep; +use iced::advanced::layout; +use iced::advanced::renderer; use iced::advanced::widget::tree::{self, Tree}; -use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer}; +use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; +use iced::mouse; +use iced::time::Instant; use iced::widget::canvas; -use iced::{Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector, mouse, window}; +use iced::window; +use iced::{Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector}; use std::f32::consts::PI; use std::time::Duration; -const MIN_GAP_ANGLE: Radians = Radians(PI / 4.0); -const MAX_WRAP: f32 = 1.0 - MIN_GAP_ANGLE.0 / (2.0 * PI); +const MIN_ANGLE: Radians = Radians(PI / 8.0); #[must_use] pub struct Circular @@ -19,9 +23,9 @@ where { size: f32, bar_height: f32, - style: Theme::Style, + style: ::Style, cycle_duration: Duration, - period: Duration, + rotation_duration: Duration, progress: Option, } @@ -34,9 +38,9 @@ where Circular { size: 40.0, bar_height: 4.0, - style: Theme::Style::default(), + style: ::Style::default(), cycle_duration: Duration::from_millis(1500), - period: Duration::from_secs(2), + rotation_duration: Duration::from_secs(2), progress: None, } } @@ -54,7 +58,7 @@ where } /// Sets the style variant of this [`Circular`]. - pub fn style(mut self, style: Theme::Style) -> Self { + pub fn style(mut self, style: ::Style) -> Self { self.style = style; self } @@ -65,10 +69,10 @@ where self } - /// Sets the base period of this [`Circular`]. This is the duration that a full rotation - /// would take if the cycle duration were set to 0.0 (no expanding or contracting) - pub fn period(mut self, duration: Duration) -> Self { - self.period = duration; + /// Sets the base rotation duration of this [`Circular`]. This is the duration that a full + /// rotation would take if the cycle rotation were set to 0.0 (no expanding or contracting) + pub fn rotation_duration(mut self, duration: Duration) -> Self { + self.rotation_duration = duration; self } @@ -77,6 +81,12 @@ where self.progress = Some(progress.clamp(0.0, 1.0)); self } + + fn min_wrap_angle(&self, track_radius: f32) -> (f32, f32) { + let cap_angle = self.bar_height / track_radius; + let gap = MIN_ANGLE.0.max(cap_angle); + (gap - cap_angle, 2.0 * PI - gap * 2.0) + } } impl Default for Circular @@ -88,11 +98,125 @@ where } } +#[derive(Clone, Copy)] +enum Animation { + Expanding { + start: Instant, + progress: f32, + rotation: u32, + last: Instant, + }, + Contracting { + start: Instant, + progress: f32, + rotation: u32, + last: Instant, + }, +} + +impl Default for Animation { + fn default() -> Self { + Self::Expanding { + start: Instant::now(), + progress: 0.0, + rotation: 0, + last: Instant::now(), + } + } +} + +impl Animation { + fn next(&self, additional_rotation: u32, wrap_angle: f32, now: Instant) -> Self { + match self { + Self::Expanding { rotation, .. } => Self::Contracting { + start: now, + progress: 0.0, + rotation: rotation.wrapping_add(additional_rotation), + last: now, + }, + Self::Contracting { rotation, .. } => Self::Expanding { + start: now, + progress: 0.0, + rotation: rotation.wrapping_add( + (f64::from((wrap_angle) / (2.0 * PI)) * f64::from(u32::MAX)) as u32, + ), + last: now, + }, + } + } + + fn start(&self) -> Instant { + match self { + Self::Expanding { start, .. } | Self::Contracting { start, .. } => *start, + } + } + + fn last(&self) -> Instant { + match self { + Self::Expanding { last, .. } | Self::Contracting { last, .. } => *last, + } + } + + fn timed_transition( + &self, + cycle_duration: Duration, + rotation_duration: Duration, + wrap_angle: f32, + now: Instant, + ) -> Self { + let elapsed = now.duration_since(self.start()); + let additional_rotation = ((now - self.last()).as_secs_f32() + / rotation_duration.as_secs_f32() + * (u32::MAX) as f32) as u32; + + match elapsed { + elapsed if elapsed > cycle_duration => self.next(additional_rotation, wrap_angle, now), + _ => self.with_elapsed(cycle_duration, additional_rotation, elapsed, now), + } + } + + fn with_elapsed( + &self, + cycle_duration: Duration, + additional_rotation: u32, + elapsed: Duration, + now: Instant, + ) -> Self { + let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32(); + match self { + Self::Expanding { + start, rotation, .. + } => Self::Expanding { + start: *start, + progress, + rotation: rotation.wrapping_add(additional_rotation), + last: now, + }, + Self::Contracting { + start, rotation, .. + } => Self::Contracting { + start: *start, + progress, + rotation: rotation.wrapping_add(additional_rotation), + last: now, + }, + } + } + + fn rotation(&self) -> f32 { + match self { + Self::Expanding { rotation, .. } | Self::Contracting { rotation, .. } => { + *rotation as f32 / u32::MAX as f32 + } + } + } +} + #[derive(Default)] struct State { animation: Animation, cache: canvas::Cache, - progress: Progress, + progress: Option, } impl Widget for Circular @@ -136,22 +260,28 @@ where _viewport: &Rectangle, ) { let state = tree.state.downcast_mut::(); - if let Event::Window(window::Event::RedrawRequested(now)) = event { - if let Some(target) = self.progress { - if state.progress.update(target, *now) { - state.cache.clear(); - shell.request_redraw(); - } - } else { - state.animation = state.animation.timed_transition( - self.cycle_duration, - self.period, - MAX_WRAP, - *now, - ); + if self.progress.is_some() { + if !float_cmp::approx_eq!( + f32, + state.progress.unwrap_or_default(), + self.progress.unwrap_or_default() + ) { + state.progress = self.progress; state.cache.clear(); - shell.request_redraw(); } + return; + } + if let Event::Window(window::Event::RedrawRequested(now)) = event { + let (_, wrap_angle) = self.min_wrap_angle(self.size / 2.0 - self.bar_height); + state.animation = state.animation.timed_transition( + self.cycle_duration, + self.rotation_duration, + wrap_angle, + *now, + ); + + state.cache.clear(); + shell.request_redraw(); } } @@ -169,13 +299,11 @@ where let state = tree.state.downcast_ref::(); let bounds = layout.bounds(); - let custom_style = Theme::appearance(theme, &self.style, self.progress.is_some(), true); + let custom_style = + ::appearance(theme, &self.style, self.progress.is_some(), true); let geometry = state.cache.draw(renderer, bounds.size(), |frame| { let track_radius = frame.width() / 2.0 - self.bar_height; - if track_radius <= 0.0 { - return; - } let track_path = canvas::Path::circle(frame.center(), track_radius); frame.stroke( @@ -185,46 +313,133 @@ where .with_width(self.bar_height), ); - // Converts a track fraction to an angle in radians, with 0 being top of circle - let to_angle = |t: f32| t * 2.0 * PI - PI / 2.0; - let draw_bar = |frame: &mut canvas::Frame, start: f32, end: f32| { + if let Some(progress) = self.progress { + // outer border + if let Some(border_color) = custom_style.border_color { + let border_path = + canvas::Path::circle(frame.center(), track_radius + self.bar_height / 2.0); + + frame.stroke( + &border_path, + canvas::Stroke::default() + .with_color(border_color) + .with_width(1.0), + ); + } + + // inner border + if let Some(border_color) = custom_style.border_color { + let border_path = + canvas::Path::circle(frame.center(), track_radius - self.bar_height / 2.0); + + frame.stroke( + &border_path, + canvas::Stroke::default() + .with_color(border_color) + .with_width(1.0), + ); + } + + // bar let mut builder = canvas::path::Builder::new(); + builder.arc(canvas::path::Arc { center: frame.center(), radius: track_radius, - start_angle: Radians(to_angle(start)), - end_angle: Radians(to_angle(end)), + start_angle: Radians(-PI / 2.0), + end_angle: Radians(-PI / 2.0 + progress * 2.0 * PI), }); + + let bar_path = builder.build(); + frame.stroke( - &builder.build(), + &bar_path, canvas::Stroke::default() .with_color(custom_style.bar_color) - .with_width(self.bar_height) - .with_line_cap(canvas::LineCap::Round), + .with_width(self.bar_height), ); - }; - if self.progress.is_some() { - if let Some(border_color) = custom_style.border_color { - for radius_offset in [self.bar_height / 2.0, -(self.bar_height / 2.0)] { - let border_path = - canvas::Path::circle(frame.center(), track_radius + radius_offset); - frame.stroke( - &border_path, - canvas::Stroke::default() - .with_color(border_color) - .with_width(1.0), - ); - } - } - draw_bar(frame, 0.0, state.progress.current); + let mut builder = canvas::path::Builder::new(); + + // get center of end of arc for rounded cap + let end_angle = -PI / 2.0 + progress * 2.0 * PI; + let end_center = + frame.center() + Vector::new(end_angle.cos(), end_angle.sin()) * track_radius; + builder.arc(canvas::path::Arc { + center: end_center, + radius: self.bar_height / 2.0, + start_angle: Radians(end_angle), + end_angle: Radians(end_angle + PI), + }); + + // get center of start of arc for rounded cap + let start_angle = -PI / 2.0; + let start_center = frame.center() + + Vector::new(start_angle.cos(), start_angle.sin()) * track_radius; + builder.arc(canvas::path::Arc { + center: start_center, + radius: self.bar_height / 2.0, + start_angle: Radians(start_angle - PI), + end_angle: Radians(start_angle), + }); + + let cap_path = builder.build(); + frame.fill(&cap_path, custom_style.bar_color); } else { - // f32::EPSILON prevents flicker when wrap angle is 0.0 - let (start, end) = - state - .animation - .bar_positions(self.cycle_duration, f32::EPSILON, MAX_WRAP); - draw_bar(frame, start, end); + let mut builder = canvas::path::Builder::new(); + + let start = state.animation.rotation() * 2.0 * PI; + let (min_angle, wrap_angle) = self.min_wrap_angle(track_radius); + let (start_angle, end_angle) = match state.animation { + Animation::Expanding { progress, .. } => ( + start, + start + min_angle + wrap_angle * smootherstep(progress), + ), + Animation::Contracting { progress, .. } => ( + start + wrap_angle * smootherstep(progress), + start + min_angle + wrap_angle, + ), + }; + builder.arc(canvas::path::Arc { + center: frame.center(), + radius: track_radius, + start_angle: Radians(start_angle), + end_angle: Radians(end_angle), + }); + + let bar_path = builder.build(); + + frame.stroke( + &bar_path, + canvas::Stroke::default() + .with_color(custom_style.bar_color) + .with_width(self.bar_height), + ); + + let mut builder = canvas::path::Builder::new(); + + // get center of end of arc for rounded cap + let end_center = + frame.center() + Vector::new(end_angle.cos(), end_angle.sin()) * track_radius; + builder.arc(canvas::path::Arc { + center: end_center, + radius: self.bar_height / 2.0, + start_angle: Radians(end_angle), + end_angle: Radians(end_angle + PI), + }); + + // get center of start of arc for rounded cap + let start_center = frame.center() + + Vector::new(start_angle.cos(), start_angle.sin()) * track_radius; + builder.arc(canvas::path::Arc { + center: start_center, + radius: self.bar_height / 2.0, + start_angle: Radians(start_angle - PI), + end_angle: Radians(start_angle), + }); + + let cap_path = builder.build(); + frame.fill(&cap_path, custom_style.bar_color); } }); diff --git a/src/widget/progress_bar/linear.rs b/src/widget/progress_bar/linear.rs index 6c06e2f7..226b2b5f 100644 --- a/src/widget/progress_bar/linear.rs +++ b/src/widget/progress_bar/linear.rs @@ -1,15 +1,19 @@ //! Show a linear progress indicator. -use super::animation::{Animation, Progress}; -use super::style::StyleSheet; +use iced::advanced::layout; +use iced::advanced::renderer::{self, Quad}; use iced::advanced::widget::tree::{self, Tree}; -use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer}; -use iced::{Border, Color, Element, Event, Length, Pixels, Rectangle, Size, mouse, window}; +use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; +use iced::mouse; +use iced::time::Instant; +use iced::window; +use iced::{Background, Element, Event, Length, Rectangle, Size}; + +use crate::anim::smootherstep; + +use super::style::StyleSheet; use std::time::Duration; -const MIN_LENGTH: f32 = 0.15; -const WRAP_LENGTH: f32 = 0.618; // avoids animation repetition - #[must_use] pub struct Linear where @@ -19,10 +23,7 @@ where girth: Length, style: Theme::Style, cycle_duration: Duration, - period: Duration, progress: Option, - markers: Vec, - segment_spacing: f32, } impl Linear @@ -36,10 +37,7 @@ where girth: Length::Fixed(4.0), style: Theme::Style::default(), cycle_duration: Duration::from_millis(1500), - period: Duration::from_secs(2), progress: None, - markers: Vec::new(), - segment_spacing: 1.0, } } @@ -67,38 +65,11 @@ where self } - /// Sets the base period of this [`Linear`]. This is the duration that a full traversal - /// would take if the cycle duration were set to 0.0 (no expanding or contracting) - pub fn period(mut self, duration: Duration) -> Self { - self.period = duration; - self - } - /// Override the default behavior by providing a determinate progress value between `0.0` and `1.0`. pub fn progress(mut self, progress: f32) -> Self { self.progress = Some(progress.clamp(0.0, 1.0)); self } - - /// Sets the markers of a determinate progress bar, which divide the bar into segments. - /// Each marker is a value between `0.0` and `1.0` that defines the position of a visual gap. - pub fn markers(mut self, markers: impl Into>) -> Self { - let mut markers = markers.into(); - for marker in &mut markers { - *marker = marker.clamp(0.0, 1.0); - } - markers.sort_by(f32::total_cmp); - markers.dedup(); - - self.markers = markers; - self - } - - /// Sets the spacing between segments at each marker. - pub fn segment_spacing(mut self, spacing: impl Into) -> Self { - self.segment_spacing = spacing.into().0.max(1.0); - self - } } impl Default for Linear @@ -110,10 +81,63 @@ where } } -#[derive(Default)] -struct State { - animation: Animation, - progress: Progress, +#[derive(Clone, Copy)] +enum State { + Expanding { start: Instant, progress: f32 }, + Contracting { start: Instant, progress: f32 }, +} + +impl Default for State { + fn default() -> Self { + Self::Expanding { + start: Instant::now(), + progress: 0.0, + } + } +} + +impl State { + fn next(&self, now: Instant) -> Self { + match self { + Self::Expanding { .. } => Self::Contracting { + start: now, + progress: 0.0, + }, + Self::Contracting { .. } => Self::Expanding { + start: now, + progress: 0.0, + }, + } + } + + fn start(&self) -> Instant { + match self { + Self::Expanding { start, .. } | Self::Contracting { start, .. } => *start, + } + } + + fn timed_transition(&self, cycle_duration: Duration, now: Instant) -> Self { + let elapsed = now.duration_since(self.start()); + + match elapsed { + elapsed if elapsed > cycle_duration => self.next(now), + _ => self.with_elapsed(cycle_duration, elapsed), + } + } + + fn with_elapsed(&self, cycle_duration: Duration, elapsed: Duration) -> Self { + let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32(); + match self { + Self::Expanding { start, .. } => Self::Expanding { + start: *start, + progress, + }, + Self::Contracting { start, .. } => Self::Contracting { + start: *start, + progress, + }, + } + } } impl Widget for Linear @@ -157,21 +181,16 @@ where shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) { + if self.progress.is_some() { + return; + } + let state = tree.state.downcast_mut::(); + if let Event::Window(window::Event::RedrawRequested(now)) = event { - if let Some(target) = self.progress { - if state.progress.update(target, *now) { - shell.request_redraw(); - } - } else { - state.animation = state.animation.timed_transition( - self.cycle_duration, - self.period, - WRAP_LENGTH, - *now, - ); - shell.request_redraw(); - } + *state = state.timed_transition(self.cycle_duration, *now); + + shell.request_redraw(); } } @@ -189,118 +208,89 @@ where let custom_style = theme.appearance(&self.style, self.progress.is_some(), false); let state = tree.state.downcast_ref::(); - let border_width = if custom_style.border_color.is_some() { - 1.0 - } else { - 0.0 - }; - let border_color = custom_style.border_color.unwrap_or(custom_style.bar_color); - let radius = custom_style.border_radius; - let track_color = custom_style.track_color; - let bar_color = custom_style.bar_color; + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x, + y: bounds.y, + width: bounds.width, + height: bounds.height, + }, + border: iced::Border { + width: if custom_style.border_color.is_some() { + 1.0 + } else { + 0.0 + }, + color: custom_style.border_color.unwrap_or(custom_style.bar_color), + radius: custom_style.border_radius.into(), + }, + snap: true, + ..renderer::Quad::default() + }, + Background::Color(custom_style.track_color), + ); - let draw_quad = |renderer: &mut Renderer, rect: Rectangle, border: Border, color: Color| { + if let Some(progress) = self.progress { renderer.fill_quad( renderer::Quad { - bounds: rect, - border, + bounds: Rectangle { + x: bounds.x, + y: bounds.y, + width: progress * bounds.width, + height: bounds.height, + }, + border: iced::Border { + width: 0., + color: iced::Color::TRANSPARENT, + radius: custom_style.border_radius.into(), + }, snap: true, ..renderer::Quad::default() }, - color, + Background::Color(custom_style.bar_color), ); - }; - let to_rect = |x: f32, width: f32| Rectangle { - x: bounds.x + x * bounds.width, - y: bounds.y, - width: width * bounds.width, - height: bounds.height, - }; + } else { + match state { + State::Expanding { progress, .. } => renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x, + y: bounds.y, + width: smootherstep(*progress) * bounds.width, + height: bounds.height, + }, + border: iced::Border { + width: 0., + color: iced::Color::TRANSPARENT, + radius: custom_style.border_radius.into(), + }, + snap: true, + ..renderer::Quad::default() + }, + Background::Color(custom_style.bar_color), + ), - // determinate progress bar - if self.progress.is_some() { - let current_p = state.progress.current; - let len = self.markers.len(); - let spacing = self.segment_spacing; - let gap = spacing / bounds.width; - let drawable = 1.0 - gap * len as f32; - let radius_inner = radius.min(spacing); - - for i in 0..=len { - let (seg_lo, r_left) = if i == 0 { - (0.0, radius) - } else { - (self.markers[i - 1], radius_inner) - }; - let (seg_hi, r_right) = if i == len { - (1.0, radius) - } else { - (self.markers[i], radius_inner) - }; - let x_start = seg_lo * drawable + i as f32 * gap; - let x_width = (seg_hi - seg_lo) * drawable; - let rect = to_rect(x_start, x_width); - let segment_radius = [r_left, r_right, r_right, r_left].into(); - - let border = Border { - width: border_width, - color: border_color, - radius: segment_radius, - }; - - // empty segment - if current_p < seg_lo { - draw_quad(renderer, rect, border, track_color); - } - // filled segment - else if current_p > seg_hi { - draw_quad(renderer, rect, border, bar_color); - } - // partially filled segment - else { - let fill = (current_p - seg_lo) / (seg_hi - seg_lo); - draw_quad(renderer, rect, border, track_color); - renderer.with_layer(to_rect(x_start, x_width * fill), |renderer| { - draw_quad(renderer, rect, border, bar_color); - }); - } + State::Contracting { progress, .. } => renderer.fill_quad( + Quad { + bounds: Rectangle { + x: bounds.x + smootherstep(*progress) * bounds.width, + y: bounds.y, + width: (1.0 - smootherstep(*progress)) * bounds.width, + height: bounds.height, + }, + border: iced::Border { + width: 0., + color: iced::Color::TRANSPARENT, + radius: custom_style.border_radius.into(), + }, + snap: true, + ..renderer::Quad::default() + }, + Background::Color(custom_style.bar_color), + ), } } - // indeterminate progress bar - else { - // draw track - draw_quad( - renderer, - bounds, - Border { - width: border_width, - color: border_color, - radius: radius.into(), - }, - track_color, - ); - - // draw bar - let (bar_start, bar_end) = - state - .animation - .bar_positions(self.cycle_duration, MIN_LENGTH, WRAP_LENGTH); - let length = bar_end - bar_start; - let start = bar_start % 1.0; - let right_width = (1.0 - start).min(length); - let left_width = length - right_width; - let border = Border { - radius: radius.into(), - ..Border::default() - }; - - renderer.with_layer(to_rect(start, right_width), |renderer| { - draw_quad(renderer, bounds, border, bar_color); - }); - renderer.with_layer(to_rect(0.0, left_width), |renderer| { - draw_quad(renderer, bounds, border, bar_color); - }); - } } } diff --git a/src/widget/progress_bar/mod.rs b/src/widget/progress_bar/mod.rs index cb24adac..4e277b0a 100644 --- a/src/widget/progress_bar/mod.rs +++ b/src/widget/progress_bar/mod.rs @@ -1,4 +1,3 @@ -mod animation; pub mod circular; pub mod linear; pub mod style; diff --git a/src/widget/radio.rs b/src/widget/radio.rs index edf5a3c7..c3f115c0 100644 --- a/src/widget/radio.rs +++ b/src/widget/radio.rs @@ -1,11 +1,15 @@ //! 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; +use iced_core::renderer; +use iced_core::touch; use iced_core::widget::tree::Tree; use iced_core::{ Border, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Vector, Widget, - layout, mouse, overlay, renderer, touch, }; use iced_widget::radio as iced_radio; @@ -262,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 8863309b..b3066ecb 100644 --- a/src/widget/rectangle_tracker/mod.rs +++ b/src/widget/rectangle_tracker/mod.rs @@ -5,14 +5,14 @@ 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; +use iced_core::renderer; use iced_core::widget::Tree; -use iced_core::{ - Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, layout, - mouse, overlay, renderer, -}; -use std::fmt::Debug; -use std::hash::Hash; +use iced_core::{Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget}; +use std::{fmt::Debug, hash::Hash}; pub use iced_widget::container::{Catalog, Style}; diff --git a/src/widget/rectangle_tracker/subscription.rs b/src/widget/rectangle_tracker/subscription.rs index 22b42441..02fa4329 100644 --- a/src/widget/rectangle_tracker/subscription.rs +++ b/src/widget/rectangle_tracker/subscription.rs @@ -1,10 +1,13 @@ -use iced::Rectangle; -use iced::futures::channel::mpsc::{UnboundedReceiver, unbounded}; -use iced::futures::{StreamExt, stream}; +use iced::{ + Rectangle, + futures::{ + StreamExt, + channel::mpsc::{UnboundedReceiver, unbounded}, + stream, + }, +}; use iced_futures::Subscription; -use std::collections::HashMap; -use std::fmt::Debug; -use std::hash::Hash; +use std::{collections::HashMap, fmt::Debug, hash::Hash}; use super::RectangleTracker; 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 b30bb848..b9b6a289 100644 --- a/src/widget/responsive_container.rs +++ b/src/widget/responsive_container.rs @@ -1,12 +1,13 @@ //! 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; +use iced_core::renderer; use iced_core::widget::{Id, Operation, Tree, tree}; -use iced_core::{ - Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay, - renderer, -}; +use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; pub(crate) fn responsive_container<'a, Message: 'static, Theme, E>( content: E, diff --git a/src/widget/responsive_menu_bar.rs b/src/widget/responsive_menu_bar.rs index de323c13..b5dd556d 100644 --- a/src/widget/responsive_menu_bar.rs +++ b/src/widget/responsive_menu_bar.rs @@ -2,8 +2,10 @@ use std::collections::HashMap; use apply::Apply; -use crate::widget::{button, icon, responsive_container}; -use crate::{Core, Element}; +use crate::{ + Core, Element, + widget::{button, icon, responsive_container}, +}; use super::menu::{self, ItemHeight, ItemWidth}; @@ -23,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) @@ -32,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/horizontal.rs b/src/widget/segmented_button/horizontal.rs index 62205264..5fd67649 100644 --- a/src/widget/segmented_button/horizontal.rs +++ b/src/widget/segmented_button/horizontal.rs @@ -21,7 +21,7 @@ pub struct Horizontal; /// Horizontal implementation of the [`SegmentedButton`]. /// /// For details on the model, see the [`segmented_button`](super) module for more details. -pub fn horizontal( +pub fn horizontal( model: &Model, ) -> SegmentedButton<'_, Horizontal, SelectionMode, Message> where @@ -30,7 +30,7 @@ where SegmentedButton::new(model) } -impl SegmentedVariant +impl SegmentedVariant for SegmentedButton<'_, Horizontal, SelectionMode, Message> where Model: Selectable, diff --git a/src/widget/segmented_button/model/mod.rs b/src/widget/segmented_button/model/mod.rs index 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/vertical.rs b/src/widget/segmented_button/vertical.rs index 26cc2bea..5458cd0a 100644 --- a/src/widget/segmented_button/vertical.rs +++ b/src/widget/segmented_button/vertical.rs @@ -20,7 +20,7 @@ pub type VerticalSegmentedButton<'a, SelectionMode, Message> = /// Vertical implementation of the [`SegmentedButton`]. /// /// For details on the model, see the [`segmented_button`](super) module for more details. -pub fn vertical( +pub fn vertical( model: &Model, ) -> SegmentedButton<'_, Vertical, SelectionMode, Message> where @@ -30,7 +30,7 @@ where SegmentedButton::new(model) } -impl SegmentedVariant +impl SegmentedVariant for SegmentedButton<'_, Vertical, SelectionMode, Message> where Model: Selectable, diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 6b2633fb..44ca8574 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -9,7 +9,7 @@ use crate::widget::menu::{ self, CloseCondition, ItemHeight, ItemWidth, MenuBarState, PathHighlight, menu_roots_children, menu_roots_diff, }; -use crate::widget::{Icon, WindowControlsPosition, icon}; +use crate::widget::{Icon, icon}; use crate::{Element, Renderer}; use derive_setters::Setters; use iced::clipboard::dnd::{ @@ -25,11 +25,9 @@ use iced_core::id::Internal; use iced_core::mouse::ScrollDelta; use iced_core::text::{self, Ellipsize, LineHeight, Renderer as TextRenderer, Shaping, Wrapping}; use iced_core::widget::operation::Focusable; -use iced_core::widget::{self, Tree, operation, tree}; -use iced_core::{ - Border, Clipboard, Layout, Point, Renderer as IcedRenderer, Shadow, Shell, Text, Widget, - layout, renderer, -}; +use iced_core::widget::{self, operation, tree}; +use iced_core::{Border, Point, Renderer as IcedRenderer, Shadow, Text}; +use iced_core::{Clipboard, Layout, Shell, Widget, layout, renderer, widget::Tree}; use iced_runtime::{Action, task}; use slotmap::{Key, SecondaryMap}; use std::borrow::Cow; @@ -38,7 +36,6 @@ use std::collections::HashSet; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; -use std::sync::Arc; use std::time::{Duration, Instant}; thread_local! { @@ -108,7 +105,7 @@ pub trait SegmentedVariant { /// A conjoined group of items that function together as a button. #[derive(Setters)] #[must_use] -pub struct SegmentedButton<'a, Variant, SelectionMode, Message: Clone + 'static> +pub struct SegmentedButton<'a, Variant, SelectionMode, Message> where Model: Selectable, SelectionMode: Default, @@ -176,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>>, @@ -194,27 +187,14 @@ where pub(super) tab_drag: Option>, #[setters(skip)] pub(super) on_drop_hint: Option) -> Message + 'static>>, - #[setters(skip)] pub(super) on_reorder: Option Message + 'static>>, #[setters(skip)] - window_id: window::Id, - #[cfg(all( - feature = "multi-window", - feature = "wayland", - target_os = "linux" - ))] - positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, - #[setters(skip)] - pub(crate) on_surface_action: - Option Message + Send + Sync + 'static>>, - /// Defines the implementation of this struct variant: PhantomData, } -impl<'a, Variant, SelectionMode, Message: Clone + 'static> - SegmentedButton<'a, Variant, SelectionMode, Message> +impl<'a, Variant, SelectionMode, Message> SegmentedButton<'a, Variant, SelectionMode, Message> where Self: SegmentedVariant, Model: Selectable, @@ -252,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, @@ -262,14 +241,6 @@ where tab_drag: None, on_drop_hint: None, on_reorder: None, - window_id: window::Id::RESERVED, - #[cfg(all( - feature = "multi-window", - feature = "wayland", - target_os = "linux" - ))] - positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), - on_surface_action: None, } } @@ -333,7 +304,12 @@ where where Message: Clone + 'static, { - self.context_menu = context_menu; + self.context_menu = context_menu.map(|menus| { + vec![menu::Tree::with_children( + crate::Element::from(crate::widget::Row::new()), + menus, + )] + }); if let Some(ref mut context_menu) = self.context_menu { context_menu.iter_mut().for_each(menu::Tree::set_index); @@ -378,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)); @@ -425,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, @@ -711,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 += @@ -895,268 +861,6 @@ where |id| id.0, ) } - - #[cfg(all( - feature = "multi-window", - feature = "wayland", - target_os = "linux" - ))] - pub fn with_positioner( - mut self, - positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, - ) -> Self { - self.positioner = positioner; - self - } - - #[must_use] - pub fn window_id(mut self, id: window::Id) -> Self { - self.window_id = id; - self - } - - #[must_use] - pub fn window_id_maybe(mut self, id: Option) -> Self { - if let Some(id) = id { - self.window_id = id; - } - self - } - - #[must_use] - pub fn on_surface_action( - mut self, - handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static, - ) -> Self { - self.on_surface_action = Some(Arc::new(handler)); - self - } - - #[cfg(all( - feature = "multi-window", - feature = "wayland", - target_os = "linux", - feature = "surface-message" - ))] - #[allow(clippy::too_many_lines)] - fn create_popup<'b>( - &mut self, - layout: Layout<'b>, - view_cursor: mouse::Cursor, - renderer: &'b Renderer, - shell: &'b mut Shell<'_, Message>, - viewport: &'b Rectangle, - tree: &'b mut Tree, - ) { - let state = tree.state.downcast_mut::(); - let my_state = state.menu_state.clone(); - - if self.window_id != window::Id::NONE { - use crate::surface::action::{LiveSettings, destroy_popup}; - use crate::widget::menu::StyleSheet; - use iced_runtime::platform_specific::wayland::CornerRadius; - use iced_runtime::platform_specific::wayland::popup::{ - SctkPopupSettings, SctkPositioner, - }; - - let Some(surface_action) = self.on_surface_action.as_ref() else { - return; - }; - - let id = my_state.inner.with_data_mut(|state| { - if let Some(id) = state.popup_id.get(&self.window_id).copied() { - // close existing popups - state.menu_states.clear(); - state.active_root.clear(); - shell.publish(surface_action(destroy_popup(id))); - state.view_cursor = view_cursor; - id - } else { - window::Id::unique() - } - }); - let Some(entity) = state.show_context else { - return; - }; - - let Some((mut bounds, i)) = self - .variant_bounds(state, layout.bounds()) - .filter_map(|item| match item { - ItemBounds::Button(entity, bounds) => Some((bounds, entity)), - _ => None, - }) - .enumerate() - .find_map(|(i, (bounds, e))| if e == entity { Some((bounds, i)) } else { None }) - else { - return; - }; - - assert!( - self.context_menu - .as_ref() - .is_none_or(|m| m[0].children.len() == self.model.len()), - "model length must match the number of context menus" - ); - let menu = self - .context_menu - .as_mut() - .map(|m| m[0].children[i].clone()) - .unwrap(); - - bounds.x = state.context_cursor.x; - bounds.y = state.context_cursor.y; - - let mut popup_menu: menu::Menu<'static, _> = menu::Menu { - tree: my_state.clone(), - menu_roots: std::borrow::Cow::Owned(vec![menu]), - bounds_expand: 0, - menu_overlays_parent: false, - close_condition: CloseCondition { - leave: false, - click_outside: true, - click_inside: true, - }, - item_width: ItemWidth::Uniform(240), - item_height: ItemHeight::Dynamic(40), - bar_bounds: bounds, - main_offset: 0, - cross_offset: 0, - root_bounds_list: vec![bounds], - path_highlight: Some(PathHighlight::MenuActive), - style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default), - position: Point::new(0., 0.), - is_overlay: false, - window_id: id, - depth: 0, - on_surface_action: self.on_surface_action.clone(), - }; - - menu::init_root_menu( - &mut popup_menu, - renderer, - shell, - view_cursor.position().unwrap(), - viewport.size(), - Vector::new(0., 0.), - bounds, - 0., // TODO offset? - ); - let (anchor_rect, gravity) = my_state.inner.with_data_mut(|state| { - state.popup_id.insert(self.window_id, id); - (state - .menu_states - .iter() - .find(|s| s.index.is_none()) - .map(|s| s.menu_bounds.parent_bounds) - .map_or_else( - || { - let bounds = layout.bounds(); - Rectangle { - x: bounds.x as i32, - y: bounds.y as i32, - width: 1, - height: 1, - } - }, - |r| Rectangle { - x: r.x as i32, - y: r.y as i32, - width: 1, - height: 1, - }, - ), match (state.horizontal_direction, state.vertical_direction) { - (menu::Direction::Positive, menu::Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight, - (menu::Direction::Positive, menu::Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight, - (menu::Direction::Negative, menu::Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft, - (menu::Direction::Negative, menu::Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft, - }) - }); - - let menu_node = - popup_menu.layout(renderer, layout::Limits::NONE.min_width(1.).min_height(1.)); - let popup_size = menu_node.size(); - let positioner = SctkPositioner { - size: Some(( - popup_size.width.ceil() as u32 + 2, - popup_size.height.ceil() as u32 + 2, - )), - anchor_rect, - anchor: - cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::BottomLeft, - gravity, - reactive: true, - ..Default::default() - }; - let parent = self.window_id; - - let t = THEME.lock().unwrap(); - let styling = t.appearance(&crate::theme::menu_bar::MenuBarStyle::Default, false); - drop(t); - let rad = styling.menu_border_radius; - - /// Used to create a popup message from within a widget. - #[cfg(all(feature = "wayland", target_os = "linux"))] - #[must_use] - pub fn simple_popup( - live_settings: impl Fn() -> LiveSettings + Send + Sync + 'static, - settings: impl Fn() - -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings - + Send - + Sync - + 'static, - view: Option crate::Element<'static, Message> + Send + Sync + 'static>, - ) -> crate::surface::Action { - use std::any::Any; - - let boxed: Box< - dyn Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings - + Send - + Sync - + 'static, - > = Box::new(settings); - let boxed: Box = Box::new(boxed); - - let boxed_live: Box LiveSettings + Send + Sync + 'static> = - Box::new(live_settings); - let boxed_live: Box = Box::new(boxed_live); - - crate::surface::Action::Popup( - Arc::new(boxed), - Arc::new(boxed_live), - view.map(|view| { - let boxed: Box< - dyn Fn() -> crate::Element<'static, Message> + Send + Sync + 'static, - > = Box::new(view); - let boxed: Box = Box::new(boxed); - Arc::new(boxed) - }), - ) - } - shell.publish((surface_action)(simple_popup( - move || LiveSettings { - corners: Some(CornerRadius { - top_left: rad[0] as u32, - top_right: rad[1] as u32, - bottom_left: rad[2] as u32, - bottom_right: rad[3] as u32, - }), - ..Default::default() - }, - move || SctkPopupSettings { - parent, - id, - positioner: positioner.clone(), - parent_size: None, - grab: true, - close_with_children: false, - input_zone: None, - }, - Some(move || { - Element::from(crate::widget::container(popup_menu.clone()).center(Length::Fill)) - }), - ))); - } - } } impl Widget @@ -1208,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(), @@ -1269,10 +972,10 @@ where mut event: &Event, layout: Layout<'_>, cursor_position: mouse::Cursor, - renderer: &Renderer, + _renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - viewport: &iced::Rectangle, + _viewport: &iced::Rectangle, ) { let my_bounds = layout.bounds(); let state = tree.state.downcast_mut::(); @@ -1363,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:?}" @@ -1442,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 => { @@ -1480,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) @@ -1608,11 +1306,8 @@ where } } - let close_button_bounds = close_bounds( - bounds, - f32::from(self.close_icon.size), - crate::config::window_controls_position(), - ); + let close_button_bounds = + close_bounds(bounds, f32::from(self.close_icon.size)); let over_close_button = self.model.items[key].closable && cursor_position.is_over(close_button_bounds); @@ -1621,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(); @@ -1676,132 +1371,42 @@ where } } - if is_lifted(event) { + if is_lifted(&event) { state.unfocus(); } - #[cfg(all( - feature = "multi-window", - feature = "wayland", - target_os = "linux", - feature = "surface-message" - ))] - if is_pressed(event) - && let Some(_on_context) = self.on_context.as_ref() - { - let (was_open, id) = state.menu_state.inner.with_data_mut(|data| { - let was_open = data.open; - data.reset(); - data.open = false; - data.view_cursor = cursor_position; - let root = data.popup_id.remove(&self.window_id); - data.popup_id.clear(); - (was_open, root) - }); - if let Some(w) = id - && let Some(surface_action) = self.on_surface_action.as_ref() - && was_open - { - use crate::surface::action::destroy_popup; - - shell.publish((surface_action)(destroy_popup(w))); - return; - } - } - - #[cfg(all( - feature = "multi-window", - feature = "wayland", - target_os = "linux", - feature = "surface-message" - ))] 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; } } // Present a context menu on a right click event. - // Yoda: on_context must fire even without a widget-side - // context_menu — consumers (cosmic-term tab rename) rely - // on the message alone and provide their own dialog. - if let Some(on_context) = self.on_context.as_ref() - && (right_button_released(event) - || (touch_lifted(event) && fingers_pressed == 2)) + 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)) { - if self.context_menu.is_some() { - state.show_context = Some(key); - state.context_cursor = - cursor_position.position().unwrap_or_default(); + state.show_context = Some(key); + state.context_cursor = cursor_position.position().unwrap_or_default(); - state.menu_state.inner.with_data_mut(|data| { - // Clear stale MenuBounds from any previous context menu before opening a new one. - data.reset(); - data.open = true; - data.view_cursor = cursor_position; - }); - } + state.menu_state.inner.with_data_mut(|data| { + data.open = true; + data.view_cursor = cursor_position; + }); shell.publish(on_context(key)); shell.capture_event(); - - #[cfg(all( - feature = "multi-window", - feature = "wayland", - target_os = "linux", - feature = "surface-message" - ))] - if self.context_menu.is_some() - && matches!( - crate::app::cosmic::WINDOWING_SYSTEM.get(), - Some(crate::app::cosmic::WindowingSystem::Wayland) - ) - { - self.create_popup( - layout, - cursor_position, - renderer, - shell, - viewport, - tree, - ); - } return; } + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) = event { @@ -1883,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; } } @@ -1919,33 +1524,6 @@ where } } - if (matches!(event, Event::Mouse(mouse::Event::ButtonReleased(_))) || (touch_lifted(event))) - && let Some(_id) = state - .menu_state - .inner - .with_data_mut(|ms| ms.popup_id.remove(&self.window_id)) - { - #[cfg(all( - feature = "wayland", - target_os = "linux", - feature = "surface-message" - ))] - { - let surface_action = self.on_surface_action.as_ref().unwrap(); - shell.capture_event(); - - shell.publish(surface_action(crate::surface::action::destroy_popup(_id))); - } - state.show_context = None; - - state.menu_state.inner.with_data_mut(|data| { - // Clear stale MenuBounds from any previous context menu before opening a new one. - data.reset(); - data.open = false; - data.view_cursor = cursor_position; - }); - } - if matches!( event, Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) @@ -2423,25 +2001,6 @@ where } } - // Whether to show the close button on this tab. - let show_close_button = - (key_is_active || !self.show_close_icon_on_hover || key_is_hovered) - && self.model.is_closable(key); - - // Width of the icon used by the close button, which we will subtract from the text bounds. - let close_icon_width = if show_close_button { - f32::from(self.close_icon.size) - } else { - 0.0 - }; - - // With macOS-style controls the close button precedes the contents, so - // shift them right to make room for it. - let close_position = crate::config::window_controls_position(); - if show_close_button && close_position == WindowControlsPosition::Start { - bounds.x += close_icon_width + f32::from(self.button_spacing); - } - // Draw the image beside the text. if let Some(icon) = self.model.icon(key) { let mut image_bounds = bounds; @@ -2499,17 +2058,21 @@ where } } - // Room reserved after the contents: only needed when the close button - // trails them, since a leading one is already counted in `bounds.x`. - let trailing_close_width = if close_position == WindowControlsPosition::End { - close_icon_width + // Whether to show the close button on this tab. + let show_close_button = + (key_is_active || !self.show_close_icon_on_hover || key_is_hovered) + && self.model.is_closable(key); + + // Width of the icon used by the close button, which we will subtract from the text bounds. + let close_icon_width = if show_close_button { + f32::from(self.close_icon.size) } else { 0.0 }; bounds.width = original_bounds.width - (bounds.x - original_bounds.x) - - trailing_close_width + - close_icon_width - f32::from(self.button_padding[2]); bounds.y = center_y; @@ -2535,8 +2098,7 @@ where // Draw a close button if set. if show_close_button { - let close_button_bounds = - close_bounds(original_bounds, close_icon_width, close_position); + let close_button_bounds = close_bounds(original_bounds, close_icon_width); draw_icon::( renderer, @@ -2550,8 +2112,8 @@ where ); } - if show_drop_hint_marker - && matches!( + if show_drop_hint_marker { + if matches!( drop_hint_marker, Some(DropHint { entity, @@ -2566,6 +2128,7 @@ where appearance.active.text_color, ); } + } nth += 1; }); @@ -2579,44 +2142,19 @@ where _viewport: &iced_core::Rectangle, translation: Vector, ) -> Option> { - #[cfg(all( - feature = "multi-window", - feature = "wayland", - target_os = "linux", - feature = "surface-message" - ))] - if matches!( - crate::app::cosmic::WINDOWING_SYSTEM.get(), - Some(crate::app::cosmic::WindowingSystem::Wayland) - ) && self.on_surface_action.is_some() - && self.window_id != window::Id::NONE - { - return None; - } - let state = tree.state.downcast_mut::(); let menu_state = state.menu_state.clone(); let entity = state.show_context?; - let (mut bounds, i) = self - .variant_bounds(state, layout.bounds()) - .filter_map(|item| match item { - ItemBounds::Button(entity, bounds) => Some((bounds, entity)), - _ => None, - }) - .enumerate() - .find_map(|(i, (bounds, e))| if e == entity { Some((bounds, i)) } else { None })?; + let mut bounds = + self.variant_bounds(state, layout.bounds()) + .find_map(|item| match item { + ItemBounds::Button(e, bounds) if e == entity => Some(bounds), + _ => None, + })?; - assert!( - self.context_menu - .as_ref() - .is_none_or(|m| m[0].children.len() == self.model.len()) - ); - let menu = self - .context_menu - .as_mut() - .map(|m| m[0].children[i].clone())?; + let context_menu = self.context_menu.as_mut()?; if !menu_state.inner.with_data(|data| data.open) { // If the menu is not open, we don't need to show it. @@ -2634,7 +2172,7 @@ where Some( crate::widget::menu::Menu { tree: menu_state, - menu_roots: std::borrow::Cow::Owned(vec![menu]), + menu_roots: std::borrow::Cow::Owned(context_menu.clone()), bounds_expand: 16, menu_overlays_parent: true, close_condition: CloseCondition { @@ -2775,7 +2313,7 @@ where } } -pub(super) struct TabDragSource { +struct TabDragSource { mime: String, threshold: f32, _marker: PhantomData, @@ -2826,6 +2364,7 @@ struct TabDragCandidate { #[derive(Debug, Clone, Copy)] struct Focus { updated_at: Instant, + now: Instant, } /// State that is maintained by each individual widget. @@ -2848,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 @@ -2898,6 +2434,7 @@ impl LocalState { self.focused = Some(Focus { updated_at: now, + now, }); } } @@ -2920,7 +2457,6 @@ mod tests { where Model: Selectable, SelectionMode: Default, - Message: Clone, { const VERTICAL: bool = false; @@ -2996,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, @@ -3089,16 +2624,9 @@ impl From for widget::Id { } /// Calculates the bounds of the close button within the area of an item. -fn close_bounds( - area: Rectangle, - icon_size: f32, - position: WindowControlsPosition, -) -> Rectangle { +fn close_bounds(area: Rectangle, icon_size: f32) -> Rectangle { Rectangle { - x: match position { - WindowControlsPosition::Start => area.x + 8.0, - WindowControlsPosition::End => area.x + area.width - icon_size - 8.0, - }, + x: area.x + area.width - icon_size - 8.0, y: area.center_y() - (icon_size / 2.0), width: icon_size, height: icon_size, diff --git a/src/widget/segmented_control.rs b/src/widget/segmented_control.rs index cecc3eb1..046956c7 100644 --- a/src/widget/segmented_control.rs +++ b/src/widget/segmented_control.rs @@ -14,7 +14,7 @@ use super::segmented_button::{ /// The data for the widget comes from a model that is maintained the application. /// /// For details on the model, see the [`segmented_button`] module for more details. -pub fn horizontal( +pub fn horizontal( model: &Model, ) -> HorizontalSegmentedButton<'_, SelectionMode, Message> where @@ -37,7 +37,7 @@ where /// The data for the widget comes from a model that is maintained the application. /// /// For details on the model, see the [`segmented_button`] module for more details. -pub fn vertical( +pub fn vertical( model: &Model, ) -> VerticalSegmentedButton<'_, SelectionMode, Message> where diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index 4eb5bb17..5abb464c 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -3,11 +3,12 @@ use std::borrow::Cow; -use crate::widget::{FlexRow, Row, column, container, flex_row, list, row, text}; -use crate::{Element, Theme, theme}; +use crate::{ + Element, Theme, theme, + widget::{FlexRow, Row, column, container, flex_row, list, row, text}, +}; use derive_setters::Setters; -use iced_core::Length; -use iced_core::text::Wrapping; +use iced_core::{Length, text::Wrapping}; use iced_widget::space; use taffy::AlignContent; diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index b2528861..833e90b8 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -3,10 +3,13 @@ //! A control for incremental adjustments of a value. -use crate::widget::{button, column, container, icon, row, text}; -use crate::{Element, theme}; +use crate::{ + Element, theme, + widget::{button, column, container, icon, row, text}, +}; use apply::Apply; -use iced::{Alignment, Border, Length, Shadow}; +use iced::{Alignment, Length}; +use iced::{Border, Shadow}; use std::borrow::Cow; use std::ops::{Add, Sub}; @@ -23,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, @@ -34,11 +37,9 @@ where ); #[cfg(feature = "a11y")] - let button = { - let mut button = button; + { button = button.name(name.into()); - button - }; + } button } @@ -56,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/tab_bar.rs b/src/widget/tab_bar.rs index 358f1d4b..a08128b4 100644 --- a/src/widget/tab_bar.rs +++ b/src/widget/tab_bar.rs @@ -14,7 +14,7 @@ use super::segmented_button::{ /// The data for the widget comes from a model supplied by the application. /// /// For details on the model, see the [`segmented_button`] module for more details. -pub fn horizontal( +pub fn horizontal( model: &Model, ) -> HorizontalSegmentedButton<'_, SelectionMode, Message> where @@ -35,7 +35,7 @@ where /// /// The data for the widget comes from a model that is maintained the application. /// For details on the model, see the [`segmented_button`] module for more details. -pub fn vertical( +pub fn vertical( model: &Model, ) -> VerticalSegmentedButton<'_, SelectionMode, Message> where diff --git a/src/widget/table/mod.rs b/src/widget/table/mod.rs index dfde3810..c546383c 100644 --- a/src/widget/table/mod.rs +++ b/src/widget/table/mod.rs @@ -2,9 +2,12 @@ //! pub mod model; -pub use model::category::{ItemCategory, ItemInterface}; -pub use model::selection::{MultiSelect, SingleSelect}; -pub use model::{Entity, Model}; +pub use model::{ + Entity, Model, + category::ItemCategory, + category::ItemInterface, + selection::{MultiSelect, SingleSelect}, +}; pub mod widget; pub use widget::compact::CompactTableView; pub use widget::standard::TableView; diff --git a/src/widget/table/model/entity.rs b/src/widget/table/model/entity.rs index 910ccd2f..51c60609 100644 --- a/src/widget/table/model/entity.rs +++ b/src/widget/table/model/entity.rs @@ -3,8 +3,10 @@ use slotmap::{SecondaryMap, SparseSecondaryMap}; -use super::category::{ItemCategory, ItemInterface}; -use super::{Entity, Model, Selectable}; +use super::{ + Entity, Model, Selectable, + category::{ItemCategory, ItemInterface}, +}; /// A newly-inserted item which may have additional actions applied to it. pub struct EntityMut< diff --git a/src/widget/table/model/mod.rs b/src/widget/table/model/mod.rs index f0e5e61c..d6250eaf 100644 --- a/src/widget/table/model/mod.rs +++ b/src/widget/table/model/mod.rs @@ -2,8 +2,10 @@ pub mod category; pub mod entity; pub mod selection; -use std::any::{Any, TypeId}; -use std::collections::{HashMap, VecDeque}; +use std::{ + any::{Any, TypeId}, + collections::{HashMap, VecDeque}, +}; use category::{ItemCategory, ItemInterface}; use entity::EntityMut; @@ -98,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/model/selection.rs b/src/widget/table/model/selection.rs index 3e3d46b5..20a07248 100644 --- a/src/widget/table/model/selection.rs +++ b/src/widget/table/model/selection.rs @@ -3,8 +3,10 @@ //! Describes logic specific to the single-select and multi-select modes of a model. -use super::category::{ItemCategory, ItemInterface}; -use super::{Entity, Model}; +use super::{ + Entity, Model, + category::{ItemCategory, ItemInterface}, +}; use std::collections::HashSet; /// Describes a type that has selectable items. diff --git a/src/widget/table/widget/compact.rs b/src/widget/table/widget/compact.rs index 6d027caf..65ac9058 100644 --- a/src/widget/table/widget/compact.rs +++ b/src/widget/table/widget/compact.rs @@ -1,10 +1,14 @@ use derive_setters::Setters; -use crate::widget::table::model::category::{ItemCategory, ItemInterface}; -use crate::widget::table::model::selection::Selectable; -use crate::widget::table::model::{Entity, Model}; -use crate::widget::{self, container, menu}; -use crate::{Apply, Element, theme}; +use crate::widget::table::model::{ + Entity, Model, + category::{ItemCategory, ItemInterface}, + selection::Selectable, +}; +use crate::{ + Apply, Element, theme, + widget::{self, container, menu}, +}; use iced::{Alignment, Border, Padding}; #[derive(Setters)] diff --git a/src/widget/table/widget/standard.rs b/src/widget/table/widget/standard.rs index e21cd22f..9ab76c9d 100644 --- a/src/widget/table/widget/standard.rs +++ b/src/widget/table/widget/standard.rs @@ -1,10 +1,14 @@ use derive_setters::Setters; -use crate::widget::table::model::category::{ItemCategory, ItemInterface}; -use crate::widget::table::model::selection::Selectable; -use crate::widget::table::model::{Entity, Model}; -use crate::widget::{self, container, divider, menu}; -use crate::{Apply, Element, theme}; +use crate::widget::table::model::{ + Entity, Model, + category::{ItemCategory, ItemInterface}, + selection::Selectable, +}; +use crate::{ + Apply, Element, theme, + widget::{self, container, divider, menu}, +}; use iced::{Alignment, Border, Length, Padding}; // THIS IS A PLACEHOLDER UNTIL A MORE SOPHISTICATED WIDGET CAN BE DEVELOPED @@ -84,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/editor.rs b/src/widget/text_input/editor.rs index c9c2ca7a..b8144761 100644 --- a/src/widget/text_input/editor.rs +++ b/src/widget/text_input/editor.rs @@ -2,8 +2,7 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MIT -use super::cursor::Cursor; -use super::value::Value; +use super::{cursor::Cursor, value::Value}; pub struct Editor<'a> { value: &'a mut Value, diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index cecd7ae7..4336c757 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -21,21 +21,25 @@ 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; use iced_core::widget::operation::{self, Operation}; use iced_core::widget::tree::{self, Tree}; +use iced_core::window; +use iced_core::{Background, alignment}; +use iced_core::{Border, Shadow, keyboard}; use iced_core::{ - Background, Border, Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, - Rectangle, Shadow, Shell, Size, Vector, Widget, alignment, keyboard, layout, overlay, touch, - window, + Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size, + Vector, Widget, }; +use iced_core::{layout, overlay}; use iced_runtime::{Action, Task, task}; thread_local! { @@ -666,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() { @@ -822,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()); @@ -888,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; @@ -901,6 +908,7 @@ where shell.publish((on_edit)(f.focused)); } } + } // Calculates the layout of the trailing icon button element. if !tree.children.is_empty() { @@ -911,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, @@ -928,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); @@ -1646,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; @@ -1674,6 +1685,7 @@ pub fn update<'a, Message: Clone + 'static>( state.last_click = Some(click); shell.capture_event(); + return; } else { state.unfocus(); @@ -1709,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, .. }) => { @@ -1791,6 +1804,7 @@ pub fn update<'a, Message: Clone + 'static>( } shell.capture_event(); + return; } } Event::Keyboard(keyboard::Event::KeyPressed { @@ -1811,30 +1825,18 @@ pub fn update<'a, Message: Clone + 'static>( focus.updated_at = Instant::now(); LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at)); - // Ctrl/Command+A/C/V/X, plus the traditional alternate clipboard - let clip_key = match key.as_ref() { - keyboard::Key::Named(keyboard::key::Named::Insert) if modifiers.shift() => { - Some('v') - } - keyboard::Key::Named(keyboard::key::Named::Insert) if modifiers.command() => { - Some('c') - } - keyboard::Key::Named(keyboard::key::Named::Delete) if modifiers.shift() => { - Some('x') - } - _ if modifiers.command() => key.to_latin(*physical_key), - _ => None, - }; - { - match clip_key { + // Check if Ctrl/Command+A/C/V/X was pressed. + if state.keyboard_modifiers.command() { + match key.to_latin(*physical_key) { Some('c') => { - if !is_secure - && 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 @@ -2089,6 +2091,7 @@ pub fn update<'a, Message: Clone + 'static>( } shell.capture_event(); + return; } } Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => { @@ -2108,6 +2111,7 @@ pub fn update<'a, Message: Clone + 'static>( } shell.capture_event(); + return; } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { @@ -2123,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() { @@ -2132,6 +2137,7 @@ pub fn update<'a, Message: Clone + 'static>( text_size: Some(size.into()), }); shell.capture_event(); + return; } } input_method::Event::Commit(text) => { @@ -2149,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); @@ -2169,6 +2175,7 @@ pub fn update<'a, Message: Clone + 'static>( update_cache(state, &value); shell.capture_event(); + return; } } } @@ -2198,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"))] @@ -2207,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, }); @@ -2220,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; } } @@ -2247,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(); @@ -2275,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 { @@ -2291,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(); @@ -2310,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 })) @@ -2346,7 +2359,9 @@ pub fn update<'a, Message: Clone + 'static>( }; update_cache(state, &value); shell.capture_event(); + return; } + return; } _ => {} } @@ -2596,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 { @@ -2779,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/style.rs b/src/widget/text_input/style.rs index 52b98784..8af5e63e 100644 --- a/src/widget/text_input/style.rs +++ b/src/widget/text_input/style.rs @@ -4,8 +4,7 @@ //! Change the appearance of a text input. -use iced_core::border::Radius; -use iced_core::{Background, Color}; +use iced_core::{Background, Color, border::Radius}; /// The appearance of a text input. #[derive(Debug, Clone, Copy)] 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/mod.rs b/src/widget/toaster/mod.rs index 43acc5c1..bafaa9f9 100644 --- a/src/widget/toaster/mod.rs +++ b/src/widget/toaster/mod.rs @@ -6,13 +6,16 @@ use std::collections::VecDeque; use std::rc::Rc; -use crate::widget::{Column, container}; +use crate::widget::Column; +use crate::widget::container; use iced::Task; use iced_core::Element; -use slotmap::{SlotMap, new_key_type}; +use slotmap::SlotMap; +use slotmap::new_key_type; use widget::Toaster; -use super::{button, column, icon, row, text}; +use super::column; +use super::{button, icon, row, text}; mod widget; diff --git a/src/widget/toaster/widget.rs b/src/widget/toaster/widget.rs index 03bdf1bd..de47a9bd 100644 --- a/src/widget/toaster/widget.rs +++ b/src/widget/toaster/widget.rs @@ -4,14 +4,16 @@ use iced::{Limits, Size}; use iced_core::layout::Node; -use iced_core::event::Event; +use iced_core::Element; +use iced_core::Overlay; +use iced_core::event::{self, Event}; +use iced_core::layout; +use iced_core::mouse; +use iced_core::overlay; use iced_core::renderer::{self}; use iced_core::widget::Operation; use iced_core::widget::tree::Tree; -use iced_core::{ - Clipboard, Element, Layout, Length, Overlay, Point, Rectangle, Shell, Vector, Widget, layout, - mouse, overlay, -}; +use iced_core::{Clipboard, Layout, Length, Point, Rectangle, Shell, Vector, Widget}; pub struct Toaster<'a, Message, Theme, Renderer> { toasts: Element<'a, Message, Theme, Renderer>, @@ -152,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 6d17503c..b95b596e 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -1,17 +1,19 @@ //! Show toggle controls using togglers. -use std::time::Duration; +use std::time::{Duration, Instant}; use crate::{Element, anim}; -use iced_core::renderer::{self, Renderer}; -use iced_core::widget::{self, Tree, tree}; use iced_core::{ - Border, Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment, layout, mouse, text, touch, window, + Border, Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment, + event, layout, mouse, + renderer::{self, Renderer}, + text, touch, + widget::{self, Tree, tree}, + window, }; -use iced_widget::Id; -use iced_widget::toggler::Status; +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) @@ -181,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| { @@ -221,7 +222,8 @@ impl<'a, Message> Widget for Toggler<'a, } }, |_| layout::Node::new(Size::new(48., 24.)), - ) + ); + res } fn update( @@ -259,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(); @@ -369,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/mod.rs b/src/widget/wayland/tooltip/mod.rs index aa80fad6..947d1e83 100644 --- a/src/widget/wayland/tooltip/mod.rs +++ b/src/widget/wayland/tooltip/mod.rs @@ -5,8 +5,7 @@ pub mod widget; // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -use iced_core::border::Radius; -use iced_core::{Background, Color, Vector}; +use iced_core::{Background, Color, Vector, border::Radius}; use crate::theme::THEME; diff --git a/src/widget/wayland/tooltip/widget.rs b/src/widget/wayland/tooltip/widget.rs index 310491cb..7bf0991a 100644 --- a/src/widget/wayland/tooltip/widget.rs +++ b/src/widget/wayland/tooltip/widget.rs @@ -13,20 +13,29 @@ 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; use iced_core::widget::tree::{self, Tree}; use iced_core::{ - Background, Border, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shadow, Shell, - Vector, Widget, layout, mouse, overlay, renderer, touch, + Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget, }; -use iced_runtime::platform_specific::wayland::CornerRadius; - -use crate::surface::action::LiveSettings; -use crate::theme::THEME; +use iced_core::{Border, mouse}; +use iced_core::{Shadow, overlay}; +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)] #[must_use] @@ -301,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); @@ -516,26 +525,8 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>( > = Box::new(move || s(bounds)); let boxed: Box = Box::new(boxed); - - let theme = THEME.lock().unwrap(); - - let corners = theme.cosmic().corner_radii.radius_s; - let boxed_live: Box< - dyn Fn() -> LiveSettings + Send + Sync + 'static, - > = Box::new(move || LiveSettings { - corners: Some(CornerRadius { - top_left: corners[0] as u32, - top_right: corners[1] as u32, - bottom_left: corners[3] as u32, - bottom_right: corners[2] as u32, - }), - ..Default::default() - }); - let boxed_live: Box = - Box::new(boxed_live); crate::surface::Action::Popup( Arc::new(boxed), - Arc::new(boxed_live), Some({ let boxed: Box< dyn Fn() -> crate::Element< @@ -566,25 +557,9 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>( + 'static, > = Box::new(move || s(bounds)); let boxed: Box = Box::new(boxed); - let theme = THEME.lock().unwrap(); - - let corners = theme.cosmic().corner_radii.radius_s; - let boxed_live: Box LiveSettings + Send + Sync + 'static> = - Box::new(move || LiveSettings { - corners: Some(CornerRadius { - top_left: corners[0] as u32, - top_right: corners[1] as u32, - bottom_left: corners[3] as u32, - bottom_right: corners[2] as u32, - }), - ..Default::default() - }); - let boxed_live: Box = - Box::new(boxed_live); let sm = crate::surface::Action::Popup( Arc::new(boxed), - Arc::new(boxed_live), Some({ let boxed: Box< dyn Fn() -> crate::Element< diff --git a/src/widget/wrapper.rs b/src/widget/wrapper.rs index 27f75a3d..133f9b87 100644 --- a/src/widget/wrapper.rs +++ b/src/widget/wrapper.rs @@ -1,13 +1,13 @@ -use std::borrow::Borrow; -use std::cell::RefCell; -use std::rc::Rc; -use std::thread::{self, ThreadId}; +use std::{ + borrow::Borrow, + cell::RefCell, + rc::Rc, + thread::{self, ThreadId}, +}; use crate::Element; -use iced::{Length, Rectangle, Size}; -use iced_core::id::Id; -use iced_core::widget::tree; -use iced_core::{Widget, widget}; +use iced::{Length, Rectangle, Size, event}; +use iced_core::{Widget, id::Id, widget, widget::tree}; #[derive(Debug)] pub struct RcWrapper {