diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 3e3a042e..af5b059b 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -17,13 +17,13 @@ jobs: - name: Install Rust nightly uses: dtolnay/rust-toolchain@master with: - toolchain: nightly-2025-07-31 + toolchain: nightly-2026-04-27 - name: System dependencies run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev - name: Build documentation run: | RUSTDOCFLAGS="--cfg docsrs" \ - cargo +nightly-2025-07-31 doc --no-deps \ + cargo +nightly-2026-04-27 doc --no-deps \ -p cosmic-client-toolkit \ -p cosmic-protocols \ -p libcosmic \ diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 00000000..2cc7b98d --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,15 @@ +{ + "format_on_save": "on", + "lsp": { + "rust-analyzer": { + "initialization_options": { + "check": { + "command": "clippy", + }, + "rustfmt": { + "extraArgs": ["+nightly"], + }, + }, + }, + }, +} diff --git a/Cargo.toml b/Cargo.toml index 64172899..d220eed3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ name = "libcosmic-yoda" version = "0.1.0-yoda.2" edition = "2024" -rust-version = "1.90" +rust-version = "1.93" [lib] name = "cosmic" @@ -128,9 +128,9 @@ async-std = [ apply = "0.3.0" ashpd = { version = "0.12.3", default-features = false, optional = true } async-fs = { version = "2.2", optional = true } -async-std = { version = "1.13", optional = true } +async-std = { workspace = true, optional = true } auto_enums = "0.8.8" -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "c253ec1", optional = true } jiff = "0.2" cosmic-config = { path = "cosmic-config" } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } @@ -153,25 +153,26 @@ image-extras = { version = "0.1.0", default-features = false, features = [ "xpm", "xbm", ], optional = true } -libc = { version = "0.2.183", optional = true } +libc = { version = "0.2.186", optional = true } log = "0.4" mime = { version = "0.3.17", optional = true } -palette = "0.7.6" +palette.workspace = true rfd = { version = "0.16.0", default-features = false, features = [ "xdg-portal", ], optional = true } rustix = { version = "1.1", features = ["pipe", "process"], optional = true } -serde = { version = "1.0.228", features = ["derive"] } +serde = { workspace = true, features = ["derive"] } slotmap = "1.1.1" smol = { version = "2.0.2", optional = true } -thiserror = "2.0.18" +thiserror.workspace = true taffy = { version = "0.9.2", features = ["grid"] } -tokio = { version = "1.50.0", optional = true } -tracing = "0.1.44" -unicode-segmentation = "1.12" +tokio = { workspace = true, optional = true } +tracing.workspace = true +unicode-segmentation = "1.13" url = "2.5.8" -zbus = { version = "5.14.0", default-features = false, optional = true } +zbus = { workspace = true, optional = true } float-cmp = "0.10.0" +ron = { workspace = true, optional = true } # Enable DBus feature on Linux targets [target.'cfg(target_os = "linux")'.dependencies] @@ -247,10 +248,6 @@ git = "https://github.com/pop-os/cosmic-panel" # path = "../cosmic-panel/cosmic-panel-config" optional = true -[dependencies.ron] -version = "0.12" -optional = true - [workspace] members = [ "cosmic-config", @@ -262,7 +259,15 @@ members = [ exclude = ["iced", "examples"] [workspace.dependencies] -dirs = "6.0.0" +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 diff --git a/README.md b/README.md index 23da97bc..698316db 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,24 @@ 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/Cargo.toml b/cosmic-config/Cargo.toml index 2a626f6b..a81d13fd 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -15,16 +15,16 @@ 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 = "0.12.0" -serde = "1.0.228" +ron.workspace = true +serde.workspace = true cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true } iced = { path = "../iced/", default-features = false, optional = true } iced_futures = { path = "../iced/futures/", default-features = false, optional = true } futures-util = { version = "0.3", optional = true } dirs.workspace = true -tokio = { version = "1.50", optional = true, features = ["time"] } -async-std = { version = "1.13", optional = true } -tracing = "0.1" +tokio = { workspace = true, optional = true, features = ["time"] } +async-std = { workspace = true, optional = true } +tracing.workspace = true [target.'cfg(unix)'.dependencies] xdg = "3.0" diff --git a/cosmic-config/src/dbus.rs b/cosmic-config/src/dbus.rs index da7bcb68..9b6e8697 100644 --- a/cosmic-config/src/dbus.rs +++ b/cosmic-config/src/dbus.rs @@ -1,13 +1,12 @@ -use std::{any::TypeId, ops::Deref}; +use std::any::TypeId; +use std::ops::Deref; use crate::{CosmicConfigEntry, Update}; use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy}; use futures_util::SinkExt; -use iced_futures::{ - Subscription, - futures::{self, StreamExt, future::pending}, - stream, -}; +use iced_futures::futures::future::pending; +use iced_futures::futures::{self, StreamExt}; +use iced_futures::{Subscription, 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 c8eda064..2af498ff 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -1,16 +1,13 @@ //! Integrations for cosmic-config — the cosmic configuration system. -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, -}; +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}; /// Get the config directory, with Flatpak sandbox support. /// In Flatpak, HOST_XDG_CONFIG_HOME points to the real user config directory, @@ -54,6 +51,22 @@ 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")] @@ -266,6 +279,24 @@ impl Config { }) } + /// Get data for the given application name and config version. + pub fn new_data(name: &str, version: u64) -> 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), + }) + } + // Start a transaction (to set multiple configs at the same time) #[inline] pub fn transaction(&self) -> ConfigTransaction<'_> { diff --git a/cosmic-config/src/subscription.rs b/cosmic-config/src/subscription.rs index 8246a12d..f0387877 100644 --- a/cosmic-config/src/subscription.rs +++ b/cosmic-config/src/subscription.rs @@ -1,7 +1,9 @@ -use iced_futures::futures::SinkExt; -use iced_futures::{futures::channel::mpsc, stream}; +use iced_futures::futures::channel::mpsc; +use iced_futures::futures::{SinkExt, Stream}; +use iced_futures::stream; use notify::RecommendedWatcher; -use std::{borrow::Cow, hash::Hash}; +use std::borrow::Cow; +use std::hash::Hash; use crate::{Config, CosmicConfigEntry}; @@ -77,7 +79,8 @@ async fn start_listening, output: &mut mpsc::Sender>, ) -> ConfigState { - use iced_futures::futures::{StreamExt, future::pending}; + use iced_futures::futures::StreamExt; + use iced_futures::futures::future::pending; match state { ConfigState::Init(config_id, version, is_state) => { diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index 9ee641e8..b5bffa17 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -15,13 +15,13 @@ export = ["serde_json"] no-default = [] [dependencies] -palette = { version = "0.7.6", features = ["serializing"] } +palette = { workspace = true, features = ["serializing"] } almost = "0.2" -serde = { version = "1.0.228", features = ["derive"] } +serde = { workspace = true, features = ["derive"] } serde_json = { version = "1.0.149", optional = true, features = [ "preserve_order", ] } -ron = "0.12.0" +ron.workspace = true csscolorparser = { version = "0.8.3", features = ["serde"] } cosmic-config = { path = "../cosmic-config/", default-features = false, features = [ "subscription", @@ -29,7 +29,7 @@ cosmic-config = { path = "../cosmic-config/", default-features = false, features ] } configparser = "3.1.0" dirs.workspace = true -thiserror = "2.0.18" +thiserror.workspace = true [dev-dependencies] insta = "1.47.2" diff --git a/cosmic-theme/src/model/corner.rs b/cosmic-theme/src/model/corner.rs index ecd18c0b..f2fa95ec 100644 --- a/cosmic-theme/src/model/corner.rs +++ b/cosmic-theme/src/model/corner.rs @@ -29,3 +29,51 @@ 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/density.rs b/cosmic-theme/src/model/density.rs deleted file mode 100644 index 7655361c..00000000 --- a/cosmic-theme/src/model/density.rs +++ /dev/null @@ -1,69 +0,0 @@ -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/mod.rs b/cosmic-theme/src/model/mod.rs index f48d1a8d..19370dee 100644 --- a/cosmic-theme/src/model/mod.rs +++ b/cosmic-theme/src/model/mod.rs @@ -1,6 +1,5 @@ pub use corner::*; pub use cosmic_palette::*; -pub use density::*; pub use derivation::*; pub use mode::*; pub use spacing::*; @@ -8,7 +7,6 @@ pub use theme::*; 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 93b1bf43..f02cf510 100644 --- a/cosmic-theme/src/model/spacing.rs +++ b/cosmic-theme/src/model/spacing.rs @@ -41,3 +41,59 @@ 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 36480f9f..0c06005e 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -1,13 +1,13 @@ +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::{ - IntoColor, Oklcha, Srgb, Srgba, WithAlpha, color_difference::Wcag21RelativeContrast, rgb::Rgb, -}; +use palette::color_difference::Wcag21RelativeContrast; +use palette::rgb::Rgb; +use palette::{IntoColor, Oklcha, Srgb, Srgba, WithAlpha}; use serde::{Deserialize, Serialize}; use std::num::NonZeroUsize; @@ -75,6 +75,8 @@ 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 @@ -1291,6 +1293,15 @@ 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], diff --git a/cosmic-theme/src/output/mod.rs b/cosmic-theme/src/output/mod.rs index 19f7bc5b..04dce39f 100644 --- a/cosmic-theme/src/output/mod.rs +++ b/cosmic-theme/src/output/mod.rs @@ -1,5 +1,6 @@ use configparser::ini::WriteOptions; -use palette::{Srgba, rgb::Rgba}; +use palette::Srgba; +use palette::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 43a45470..16df211f 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::{Mix, Srgba, WithAlpha, blend::Compose, rgb::Rgba}; -use std::{ - fs::{self, File}, - io::Write, - path::PathBuf, - vec, -}; +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 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 d42d553b..84614e7b 100644 --- a/cosmic-theme/src/output/qt_output.rs +++ b/cosmic-theme/src/output/qt_output.rs @@ -1,12 +1,11 @@ use crate::Theme; use configparser::ini::Ini; use cosmic_config::CosmicConfigEntry; -use palette::{Mix, Srgba, blend::Compose}; -use std::{ - fs::{self, File}, - io::{self, Write}, - path::{Path, PathBuf}, -}; +use palette::blend::Compose; +use palette::{Mix, Srgba}; +use std::fs::{self, File}; +use std::io::{self, Write}; +use std::path::{Path, PathBuf}; use super::{OutputError, qt_settings_ini_style}; diff --git a/cosmic-theme/src/steps.rs b/cosmic-theme/src/steps.rs index 6ebf1015..d156722c 100644 --- a/cosmic-theme/src/steps.rs +++ b/cosmic-theme/src/steps.rs @@ -1,7 +1,8 @@ use std::num::NonZeroUsize; use almost::equal; -use palette::{ClampAssign, FromColor, Lch, Oklcha, Srgb, Srgba, convert::FromColorUnclamped}; +use palette::convert::FromColorUnclamped; +use palette::{ClampAssign, FromColor, Lch, Oklcha, Srgb, Srgba}; /// Get an array of 100 colors with a specific hue and chroma /// over the full range of lightness. diff --git a/examples/about/src/main.rs b/examples/about/src/main.rs index c25a9b9a..d1ea475e 100644 --- a/examples/about/src/main.rs +++ b/examples/about/src/main.rs @@ -8,7 +8,8 @@ use cosmic::app::{Core, Settings, Task}; use cosmic::executor; use cosmic::iced::{alignment, Length, Size}; use cosmic::prelude::*; -use cosmic::widget::{self, about::About, nav_bar}; +use cosmic::widget::about::About; +use cosmic::widget::{self, nav_bar}; /// Runs application with these settings #[rustfmt::skip] diff --git a/examples/applet/src/window.rs b/examples/applet/src/window.rs index 22903eac..57546ab1 100644 --- a/examples/applet/src/window.rs +++ b/examples/applet/src/window.rs @@ -4,7 +4,8 @@ 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, list_column, settings, toggler}; +use cosmic::widget::dropdown::popup_dropdown; +use cosmic::widget::{list_column, settings, toggler}; use cosmic::Element; const ID: &str = "com.system76.CosmicAppletExample"; diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index f6e571e0..05841f5b 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -5,9 +5,10 @@ use cosmic::app::Settings; use cosmic::iced::{Alignment, Length, Size}; +use cosmic::prelude::*; use cosmic::widget::menu::{self, KeyBind}; use cosmic::widget::nav_bar; -use cosmic::{executor, iced, prelude::*, widget, Core}; +use cosmic::{executor, iced, widget, Core}; use std::collections::HashMap; use std::sync::LazyLock; @@ -239,7 +240,9 @@ impl cosmic::Application for App { widget::progress_bar::linear::Linear::new() .girth(10.0) .progress(self.progress) - .width(Length::Fill), + .width(Length::Fill) + .markers([0.25, 0.5, 0.75]) + .segment_spacing(2), ) .push( widget::progress_bar::circular::Circular::new() diff --git a/examples/config/src/main.rs b/examples/config/src/main.rs index f6fb5c0d..dfcc5b99 100644 --- a/examples/config/src/main.rs +++ b/examples/config/src/main.rs @@ -88,4 +88,7 @@ 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/cosmic/src/window.rs b/examples/cosmic/src/window.rs index 9fce8767..4168718d 100644 --- a/examples/cosmic/src/window.rs +++ b/examples/cosmic/src/window.rs @@ -23,15 +23,11 @@ use cosmic::{ Element, }; use cosmic_time::{Instant, Timeline}; -use std::{ - cell::RefCell, - rc::Rc, - sync::{ - atomic::{AtomicU32, Ordering}, - Arc, - }, - vec, -}; +use std::cell::RefCell; +use std::rc::Rc; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::Arc; +use std::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 1b5892f6..2878b46a 100644 --- a/examples/cosmic/src/window/bluetooth.rs +++ b/examples/cosmic/src/window/bluetooth.rs @@ -1,9 +1,7 @@ use super::{Page, Window}; -use cosmic::{ - iced::widget::{column, text}, - widget::{list_column, settings, toggler}, - Element, -}; +use cosmic::iced::widget::{column, text}; +use cosmic::widget::{list_column, settings, toggler}; +use cosmic::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 0d31fa93..20a730b5 100644 --- a/examples/cosmic/src/window/demo.rs +++ b/examples/cosmic/src/window/demo.rs @@ -1,19 +1,17 @@ -use std::{cell::RefCell, rc::Rc}; +use std::cell::RefCell; +use std::rc::Rc; use apply::Apply; -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::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, 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 46a4e5b8..a087de9e 100644 --- a/examples/cosmic/src/window/desktop.rs +++ b/examples/cosmic/src/window/desktop.rs @@ -1,10 +1,7 @@ -use cosmic::{ - iced::widget::{column, container, horizontal_space, image, row, svg, text}, - iced::Length, - theme, - widget::{list_column, settings, toggler}, - Element, -}; +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 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 ed1bd004..5f498857 100644 --- a/examples/cosmic/src/window/system_and_accounts.rs +++ b/examples/cosmic/src/window/system_and_accounts.rs @@ -1,9 +1,7 @@ -use cosmic::{ - iced::widget::{horizontal_space, row, text}, - iced::Length, - widget::{icon, list_column, settings}, - Element, -}; +use cosmic::iced::widget::{horizontal_space, row, text}; +use cosmic::iced::Length; +use cosmic::widget::{icon, list_column, settings}; +use cosmic::Element; use super::{Message, Page, SubPage, Window}; diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs index da0c3231..900d0322 100644 --- a/examples/menu/src/main.rs +++ b/examples/menu/src/main.rs @@ -9,11 +9,9 @@ use std::{env, process}; use cosmic::app::{Core, Settings, Task}; use cosmic::iced::alignment::{Horizontal, Vertical}; use cosmic::iced::keyboard::Key; -use cosmic::iced::window; -use cosmic::iced::{Length, Size}; +use cosmic::iced::{window, Length, Size}; use cosmic::widget::menu::action::MenuAction; -use cosmic::widget::menu::key_bind::KeyBind; -use cosmic::widget::menu::key_bind::Modifier; +use cosmic::widget::menu::key_bind::{KeyBind, Modifier}; use cosmic::widget::menu::{self, ItemHeight, ItemWidth}; use cosmic::widget::RcElementWrapper; use cosmic::{executor, Element}; diff --git a/examples/multi-window/src/window.rs b/examples/multi-window/src/window.rs index 754a0d86..a6d40d36 100644 --- a/examples/multi-window/src/window.rs +++ b/examples/multi-window/src/window.rs @@ -1,13 +1,11 @@ use std::collections::HashMap; -use cosmic::{ - app::Core, - iced::core::{id, Alignment, Length, Point}, - iced::widget::{column, container, scrollable, text}, - iced::{self, event, window, Subscription}, - prelude::*, - widget::{button, header_bar}, -}; +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}; #[derive(Debug, Clone, PartialEq)] pub enum Message { diff --git a/examples/spin-button/src/main.rs b/examples/spin-button/src/main.rs index 47db4dce..0228291c 100644 --- a/examples/spin-button/src/main.rs +++ b/examples/spin-button/src/main.rs @@ -1,15 +1,8 @@ -use cosmic::iced::Length; +use cosmic::app::{Core, Task}; +use cosmic::iced::alignment::{Horizontal, Vertical}; +use cosmic::iced::{self, Alignment, Length, Size}; use cosmic::widget::{column, container, spin_button}; -use cosmic::Apply; -use cosmic::{ - app::{Core, Task}, - iced::{ - self, - alignment::{Horizontal, Vertical}, - Alignment, Size, - }, - Application, Element, -}; +use cosmic::{Application, Apply, Element}; use fraction::Decimal; pub struct SpinButtonExamplApp { diff --git a/examples/subscriptions/src/main.rs b/examples/subscriptions/src/main.rs index 17e630aa..325af162 100644 --- a/examples/subscriptions/src/main.rs +++ b/examples/subscriptions/src/main.rs @@ -5,7 +5,8 @@ use cosmic::app::{Core, Settings, Task}; use cosmic::iced::Subscription; -use cosmic::{executor, prelude::*, widget}; +use cosmic::prelude::*; +use cosmic::{executor, widget}; /// Runs application with these settings fn main() -> Result<(), Box> { diff --git a/examples/table-view/src/main.rs b/examples/table-view/src/main.rs index d2478429..e518b3cc 100644 --- a/examples/table-view/src/main.rs +++ b/examples/table-view/src/main.rs @@ -9,8 +9,7 @@ use chrono::Datelike; use cosmic::app::{Core, Settings, Task}; use cosmic::iced::Size; use cosmic::prelude::*; -use cosmic::widget::table; -use cosmic::widget::{self, nav_bar}; +use cosmic::widget::{self, nav_bar, table}; use cosmic::{executor, iced}; #[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash)] diff --git a/i18n/el/libcosmic_yoda.ftl b/i18n/el/libcosmic_yoda.ftl index e69de29b..f4fe0b31 100644 --- a/i18n/el/libcosmic_yoda.ftl +++ b/i18n/el/libcosmic_yoda.ftl @@ -0,0 +1,34 @@ +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/lo/libcosmic.ftl b/i18n/lo/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/sr/libcosmic_yoda.ftl b/i18n/sr/libcosmic_yoda.ftl index e69de29b..e8b88303 100644 --- a/i18n/sr/libcosmic_yoda.ftl +++ b/i18n/sr/libcosmic_yoda.ftl @@ -0,0 +1,34 @@ +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/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..c1578aaf --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +imports_granularity = "Module" diff --git a/src/app/action.rs b/src/app/action.rs index fb982acb..166df16b 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::surface; +use crate::config::CosmicTk; use crate::theme::Theme; use crate::widget::nav_bar; -use crate::{config::CosmicTk, keyboard_nav}; +use crate::{keyboard_nav, surface}; #[cfg(all(feature = "wayland", target_os = "linux"))] use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use cosmic_theme::ThemeMode; diff --git a/src/app/mod.rs b/src/app/mod.rs index f53ee519..b2d82dcd 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::{Length, Subscription}; -use iced::{theme, window}; +use iced::{Length, Subscription, theme, window}; pub use settings::Settings; use std::borrow::Cow; -use std::{cell::RefCell, rc::Rc}; +use std::cell::RefCell; +use std::rc::Rc; #[cold] pub(crate) fn iced_settings( @@ -773,7 +773,6 @@ impl ApplicationExt for App { .focused(focused) .maximized(maximized) .sharp_corners(sharp_corners) - .transparent(content_container) .title(&core.window.header_title) .on_drag(crate::Action::Cosmic(Action::Drag)) .on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu)) diff --git a/src/applet/column.rs b/src/applet/column.rs index bb16f085..1ec9c731 100644 --- a/src/applet/column.rs +++ b/src/applet/column.rs @@ -1,15 +1,11 @@ //! Distribute content vertically. use crate::iced; use iced::core::alignment::{self, Alignment}; -use iced::core::event::Event; -use iced::core::layout; -use iced::core::mouse; -use iced::core::overlay; -use iced::core::renderer; +use iced::core::event::{self, Event}; use iced::core::widget::{Operation, Tree}; use iced::core::{ Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, - widget, + layout, mouse, overlay, renderer, widget, }; /// A container that distributes its contents vertically. diff --git a/src/applet/mod.rs b/src/applet/mod.rs index d1ae6e87..e16b0303 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -1,33 +1,31 @@ #[cfg(feature = "applet-token")] pub mod token; -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}, - layer_container, - }, -}; +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}; pub use cosmic_panel_config; use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize}; -use iced::{ - self, Color, Length, Limits, Rectangle, - alignment::{Alignment, Horizontal, Vertical}, - widget::Container, - window, -}; +use iced::alignment::{Alignment, Horizontal, Vertical}; +use iced::widget::Container; +use iced::{self, Color, Length, Limits, Rectangle, 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::{borrow::Cow, num::NonZeroU32, rc::Rc, sync::LazyLock, time::Duration}; +use std::num::NonZeroU32; +use std::rc::Rc; +use std::sync::LazyLock; +use std::time::Duration; use tracing::info; pub mod column; @@ -213,6 +211,12 @@ impl Context { icon: widget::icon::Handle, ) -> crate::widget::Button<'a, Message> { let suggested = self.suggested_size(icon.symbolic); + let (applet_padding_major_axis, applet_padding_minor_axis) = self.suggested_padding(true); + let (horizontal_padding, vertical_padding) = if self.is_horizontal() { + (applet_padding_major_axis, applet_padding_minor_axis) + } else { + (applet_padding_minor_axis, applet_padding_major_axis) + }; let symbolic = icon.symbolic; let icon = widget::icon(icon) .class(if symbolic { @@ -455,8 +459,10 @@ impl Context { &self, content: impl Into>, ) -> Autosize<'a, Message, crate::Theme, crate::Renderer> { + let force_configured = matches!(&self.panel_type, PanelType::Other(n) if n.is_empty()); let w = autosize(content, AUTOSIZE_MAIN_ID.clone()); let mut limits = Limits::NONE; + let suggested_window_size = self.suggested_window_size(); if let Some(width) = self .suggested_bounds @@ -567,7 +573,7 @@ pub fn run(flags: App::Flags) -> iced::Result { // window_settings = window_settings.clone(); core.main_window = Some(iced_core::window::Id::RESERVED); } - let app = iced::daemon( + let mut app = iced::daemon( BootData(Rc::new(RefCell::new(Some(BootDataInner:: { flags, core, diff --git a/src/applet/row.rs b/src/applet/row.rs index 3ac9f7e0..888c68ef 100644 --- a/src/applet/row.rs +++ b/src/applet/row.rs @@ -3,12 +3,10 @@ use crate::iced; use iced::core::alignment::{self, Alignment}; use iced::core::event::Event; use iced::core::layout::{self, Layout}; -use iced::core::mouse; -use iced::core::overlay; -use iced::core::renderer; use iced::core::widget::{Operation, Tree}; use iced::core::{ - Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, widget, + Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, mouse, + overlay, renderer, widget, }; /// A container that distributes its contents horizontally. diff --git a/src/applet/token/subscription.rs b/src/applet/token/subscription.rs index 07c528ea..64be0ce2 100644 --- a/src/applet/token/subscription.rs +++ b/src/applet/token/subscription.rs @@ -1,13 +1,12 @@ use crate::iced; use cctk::sctk::reexports::calloop; -use futures::{ - SinkExt, StreamExt, - channel::mpsc::{UnboundedReceiver, unbounded}, -}; +use futures::channel::mpsc::{UnboundedReceiver, unbounded}; +use futures::{SinkExt, StreamExt}; use iced::Subscription; -use iced_futures::futures; -use iced_futures::stream; -use std::{fmt::Debug, hash::Hash, thread::JoinHandle}; +use iced_futures::{futures, stream}; +use std::fmt::Debug; +use std::hash::Hash; +use std::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 3db84fc4..3bbaf573 100644 --- a/src/applet/token/wayland_handler.rs +++ b/src/applet/token/wayland_handler.rs @@ -1,27 +1,20 @@ -use std::os::{ - fd::{FromRawFd, RawFd}, - unix::net::UnixStream, -}; +use std::os::fd::{FromRawFd, RawFd}; +use std::os::unix::net::UnixStream; use super::subscription::{TokenRequest, TokenUpdate}; -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 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 iced_futures::futures::channel::mpsc::UnboundedSender; -use sctk::{ - activation::{ActivationHandler, ActivationState}, - registry::{ProvidesRegistryState, RegistryState}, -}; -use wayland_client::{Connection, QueueHandle, globals::registry_queue_init}; +use sctk::activation::{ActivationHandler, ActivationState}; +use sctk::registry::{ProvidesRegistryState, RegistryState}; +use wayland_client::globals::registry_queue_init; +use wayland_client::{Connection, QueueHandle}; struct AppData { exit: bool, @@ -171,7 +164,18 @@ pub(crate) fn wayland_handler( if app_data.exit { break; } - event_loop.dispatch(None, &mut app_data).unwrap(); + + 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; + } } } diff --git a/src/dbus_activation.rs b/src/dbus_activation.rs index 10857fac..880db634 100644 --- a/src/dbus_activation.rs +++ b/src/dbus_activation.rs @@ -1,17 +1,15 @@ // Copyright 2024 System76 // SPDX-License-Identifier: MPL-2.0 -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}, -}; +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}; #[cold] pub fn subscription() -> Subscription> { diff --git a/src/desktop.rs b/src/desktop.rs index 0a49eb8a..11276960 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -876,7 +876,8 @@ trait SystemdManger { #[cfg(all(test, not(windows)))] mod tests { use super::*; - use std::{env, fs, path::Path, path::PathBuf}; + use std::path::{Path, PathBuf}; + use std::{env, fs}; use tempfile::tempdir; struct EnvVarGuard { diff --git a/src/lib.rs b/src/lib.rs index b1f893af..9c31506d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ #![allow(clippy::module_name_repetitions)] #![cfg_attr(target_os = "redox", feature(lazy_cell))] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] /// Recommended default imports. pub mod prelude { diff --git a/src/localize.rs b/src/localize.rs index 95a31655..d63d9706 100644 --- a/src/localize.rs +++ b/src/localize.rs @@ -1,9 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only -use i18n_embed::{ - DefaultLocalizer, LanguageLoader, Localizer, - fluent::{FluentLanguageLoader, fluent_language_loader}, -}; +use i18n_embed::fluent::{FluentLanguageLoader, fluent_language_loader}; +use i18n_embed::{DefaultLocalizer, LanguageLoader, Localizer}; use rust_embed::RustEmbed; use std::sync::{LazyLock, OnceLock}; diff --git a/src/process.rs b/src/process.rs index 2b6c4e0e..e2cb0f70 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, exit}; +use std::process::{Command, Stdio}; #[cfg(feature = "tokio")] use tokio::io::AsyncReadExt; @@ -56,24 +56,33 @@ pub async fn spawn(mut command: Command) -> Option { match unsafe { libc::fork() } { // Parent process - 1.. => { + fork_pid @ 1..=i32::MAX => { // Drop copy of write end, then read PID from pipe drop(write); let pid = read_from_pipe(read).await; - // wait to prevent zombie - _ = rustix::process::wait(rustix::process::WaitOptions::empty()); + _ = rustix::process::waitpid( + rustix::process::Pid::from_raw(fork_pid), + rustix::process::WaitOptions::empty(), + ); pid } // Child process 0 => { let _res = rustix::process::setsid(); - if let Ok(child) = command.spawn() { + let exit_status = if let Ok(child) = command.spawn() { // Write PID to pipe let _ = rustix::io::write(write, &child.id().to_be_bytes()); - } + 0 + } else { + 1 + }; - exit(0) + // # Safety + // Required for child fork to exit without affecting the parent. + unsafe { + libc::_exit(exit_status); + } } ..=-1 => { diff --git a/src/surface/action.rs b/src/surface/action.rs index 72dbad66..16816fff 100644 --- a/src/surface/action.rs +++ b/src/surface/action.rs @@ -5,7 +5,8 @@ use super::Action; use crate::Application; use iced::window; -use std::{any::Any, sync::Arc}; +use std::any::Any; +use std::sync::Arc; /// Used to produce a destroy popup message from within a widget. #[cfg(all(feature = "wayland", target_os = "linux"))] diff --git a/src/surface/mod.rs b/src/surface/mod.rs index 513cb91f..307b5275 100644 --- a/src/surface/mod.rs +++ b/src/surface/mod.rs @@ -3,9 +3,8 @@ pub mod action; -use iced::Limits; -use iced::Size; -use iced::Task; +use iced::{Limits, Size, Task}; +use std::future::Future; use std::sync::Arc; /// Ignore this message in your application. It will be intercepted. diff --git a/src/theme/mod.rs b/src/theme/mod.rs index cf3beccb..a5ae5d4a 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -7,11 +7,9 @@ pub mod portal; pub mod style; -use cosmic_config::CosmicConfigEntry; -use cosmic_theme::Component; -use cosmic_theme::LayeredTheme; -use cosmic_theme::Spacing; -use cosmic_theme::ThemeMode; +use cosmic_config::{CosmicConfigEntry, config_subscription}; +use cosmic_theme::{Component, LayeredTheme, Spacing, ThemeMode}; +use iced_futures::Subscription; use iced_runtime::{Appearance, DefaultStyle}; use std::sync::{Arc, LazyLock, Mutex}; pub use style::*; diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index c405455b..f84c9e97 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -6,10 +6,8 @@ use cosmic_theme::Component; use iced_core::{Background, Color}; -use crate::{ - theme::TRANSPARENT_COMPONENT, - widget::button::{Catalog, Style}, -}; +use crate::theme::TRANSPARENT_COMPONENT; +use crate::widget::button::{Catalog, Style}; #[derive(Default)] pub enum Button { @@ -150,7 +148,7 @@ pub fn appearance( } Button::ListItem(radii) => { corner_radii = radii; - let (background, text, icon) = color(&cosmic.background.component); + let (background, text, icon) = color(&cosmic.list_button); if selected { appearance.background = @@ -197,7 +195,7 @@ impl Catalog for crate::Theme { return active(focused, self); } - let mut s = appearance(self, focused, selected, false, style, move |component| { + appearance(self, focused, selected, false, style, move |component| { let text_color = if matches!( style, Button::Icon | Button::IconVertical | Button::HeaderBar @@ -209,14 +207,7 @@ impl Catalog for crate::Theme { }; (component.base.into(), text_color, text_color) - }); - - if let Button::ListItem(_) = style - && !selected { - s.background = None; - } - - s + }) } fn disabled(&self, style: &Self::Class) -> Style { @@ -244,7 +235,7 @@ impl Catalog for crate::Theme { return hovered(focused, self); } - let mut s = appearance( + appearance( self, focused || matches!(style, Button::Image), selected, @@ -263,14 +254,7 @@ impl Catalog for crate::Theme { (component.hover.into(), text_color, text_color) }, - ); - - if let Button::ListItem(_) = style - && !selected { - s.background = None; - } - - s + ) } fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style { diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 746503e3..8f9fa461 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -5,18 +5,17 @@ use crate::theme::{CosmicComponent, TRANSPARENT_COMPONENT, Theme}; use cosmic_theme::composite::over; -use iced::{ - overlay::menu, - theme::Base, - widget::{ - button as iced_button, checkbox as iced_checkbox, combo_box, container as iced_container, - pane_grid, pick_list, progress_bar, radio, rule, scrollable, - slider::{self, Rail}, - svg, toggler, - }, +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_core::{Background, Border, Color, Shadow, Vector}; -use iced_widget::{pane_grid::Highlight, scrollable::AutoScroll, text_editor, text_input}; +use iced_widget::pane_grid::Highlight; +use iced_widget::scrollable::AutoScroll; +use iced_widget::{text_editor, text_input}; use palette::WithAlpha; use std::rc::Rc; @@ -1339,9 +1338,16 @@ 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), } } diff --git a/src/theme/style/menu_bar.rs b/src/theme/style/menu_bar.rs index ed0e657a..db6e0ba1 100644 --- a/src/theme/style/menu_bar.rs +++ b/src/theme/style/menu_bar.rs @@ -71,7 +71,7 @@ impl StyleSheet for Theme { background: component.base.into(), border_width: 1.0, bar_border_radius: cosmic.corner_radii.radius_xl, - menu_border_radius: cosmic.corner_radii.radius_s.map(|x| x + 2.0), + menu_border_radius: cosmic.corner_radii.radius_s, border_color: component.divider.into(), background_expand: [1; 4], path: component.hover.into(), diff --git a/src/theme/style/segmented_button.rs b/src/theme/style/segmented_button.rs index de8b5782..45fcc0b8 100644 --- a/src/theme/style/segmented_button.rs +++ b/src/theme/style/segmented_button.rs @@ -3,10 +3,13 @@ //! Contains stylesheet implementation for [`crate::widget::segmented_button`]. -use crate::widget::segmented_button::{Appearance, ItemAppearance, StyleSheet}; -use crate::{theme::Theme, widget::segmented_button::ItemStatusAppearance}; +use crate::theme::Theme; +use crate::widget::segmented_button::{ + Appearance, ItemAppearance, ItemStatusAppearance, StyleSheet, +}; use iced::Border; -use iced_core::{Background, border::Radius}; +use iced_core::Background; +use iced_core::border::Radius; use palette::WithAlpha; #[derive(Default)] @@ -143,7 +146,8 @@ mod horizontal { use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance}; use cosmic_theme::{Component, Container}; use iced::Border; - use iced_core::{Background, border::Radius}; + use iced_core::Background; + use iced_core::border::Radius; use palette::WithAlpha; pub fn tab_bar(cosmic: &cosmic_theme::Theme, container: &Container) -> Appearance { @@ -250,7 +254,8 @@ mod vertical { use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance}; use cosmic_theme::{Component, Container}; use iced::Border; - use iced_core::{Background, border::Radius}; + use iced_core::Background; + use iced_core::border::Radius; use palette::WithAlpha; pub fn tab_bar(cosmic: &cosmic_theme::Theme, container: &Container) -> Appearance { diff --git a/src/widget/about.rs b/src/widget/about.rs index 9b21e93a..490b0378 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -1,8 +1,6 @@ -use crate::{ - Apply, Element, fl, - iced::{Alignment, Length}, - widget::{self, list}, -}; +use crate::iced::{Alignment, Length}; +use crate::widget::{self, list}; +use crate::{Apply, Element, fl}; 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 577bea95..5cd8b9c0 100644 --- a/src/widget/aspect_ratio.rs +++ b/src/widget/aspect_ratio.rs @@ -3,13 +3,10 @@ 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 69fd9c83..c52367db 100644 --- a/src/widget/autosize.rs +++ b/src/widget/autosize.rs @@ -1,12 +1,11 @@ //! 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}; +use iced_core::{ + Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay, + renderer, +}; 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 a0e06323..47713536 100644 --- a/src/widget/button/icon.rs +++ b/src/widget/button/icon.rs @@ -3,9 +3,13 @@ use super::{Builder, ButtonClass}; use crate::Element; -use crate::widget::{icon::Handle, tooltip}; +use crate::widget::icon::Handle; +use crate::widget::tooltip; use apply::Apply; -use iced_core::{Alignment, Length, Padding, font::Weight, text::LineHeight, widget::Id}; +use iced_core::font::Weight; +use iced_core::text::LineHeight; +use iced_core::widget::Id; +use iced_core::{Alignment, Length, Padding}; use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Icon>; diff --git a/src/widget/button/image.rs b/src/widget/button/image.rs index c5a40fd5..915cc532 100644 --- a/src/widget/button/image.rs +++ b/src/widget/button/image.rs @@ -2,11 +2,12 @@ // SPDX-License-Identifier: MPL-2.0 use super::Builder; -use crate::{ - Element, - widget::{self, image::Handle}, -}; -use iced_core::{Length, Padding, font::Weight, widget::Id}; +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 std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Image<'a, Handle, Message>>; diff --git a/src/widget/button/link.rs b/src/widget/button/link.rs index 9bcd7487..e6f8ac7e 100644 --- a/src/widget/button/link.rs +++ b/src/widget/button/link.rs @@ -3,14 +3,15 @@ //! Hyperlink button widget -use super::Builder; -use super::ButtonClass; +use super::{Builder, 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::{Alignment, Length, Padding, font::Weight, widget::Id}; +use iced_core::widget::Id; +use iced_core::{Alignment, Length, Padding}; use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Hyperlink>; diff --git a/src/widget/button/style.rs b/src/widget/button/style.rs index 21afa08b..f8df8c62 100644 --- a/src/widget/button/style.rs +++ b/src/widget/button/style.rs @@ -2,7 +2,8 @@ // SPDX-License-Identifier: MPL-2.0 //! Change the apperance of a button. -use iced_core::{Background, Color, Vector, border::Radius}; +use iced_core::border::Radius; +use iced_core::{Background, Color, Vector}; use crate::theme::THEME; diff --git a/src/widget/button/text.rs b/src/widget/button/text.rs index 71cd1f53..55623ff8 100644 --- a/src/widget/button/text.rs +++ b/src/widget/button/text.rs @@ -4,7 +4,10 @@ use super::{Builder, ButtonClass}; use crate::widget::{icon, row, tooltip}; use crate::{Apply, Element}; -use iced_core::{Alignment, Length, Padding, font::Weight, text::LineHeight, widget::Id}; +use iced_core::font::Weight; +use iced_core::text::LineHeight; +use iced_core::widget::Id; +use iced_core::{Alignment, Length, Padding}; use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Text>; diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index cdb2b5d6..e6b83d7b 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -11,15 +11,12 @@ use iced_runtime::{Action, Task, keyboard, task}; use iced_core::event::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, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget, + Background, Border, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shadow, Shell, + Vector, Widget, layout, mouse, overlay, svg, touch, }; -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; @@ -644,10 +641,8 @@ impl<'a, Message: 'a + Clone> Widget state: &Tree, p: mouse::Cursor, ) -> iced_accessibility::A11yTree { - use iced_accessibility::{ - A11yNode, A11yTree, - accesskit::{Action, Node, NodeId, Rect, Role}, - }; + use iced_accessibility::accesskit::{Action, Node, NodeId, Rect, Role}; + use iced_accessibility::{A11yNode, A11yTree}; // TODO why is state None sometimes? if matches!(state.state, iced_core::widget::tree::State::None) { tracing::info!("Button state is missing."); diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index 91c601d3..35632c3a 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -8,10 +8,8 @@ use crate::widget::{button, column, grid, icon, row, text}; use apply::Apply; use iced::alignment::Vertical; use iced_core::{Alignment, Length}; -use jiff::{ - ToSpan, - civil::{Date, Weekday}, -}; +use jiff::ToSpan; +use jiff::civil::{Date, Weekday}; /// A widget that displays an interactive calendar. pub fn calendar( diff --git a/src/widget/cards.rs b/src/widget/cards.rs index 72300cac..4d6770fa 100644 --- a/src/widget/cards.rs +++ b/src/widget/cards.rs @@ -1,23 +1,18 @@ //! An expandable stack of cards use std::time::Duration; -use crate::{ - anim, - widget::{ - button, - card::style::Style, - column, - icon::{self, Handle}, - row, text, - }, -}; +use crate::anim; +use crate::widget::card::style::Style; +use crate::widget::icon::{self, Handle}; +use crate::widget::{button, column, row, text}; use float_cmp::approx_eq; use iced::widget; -use iced_core::{ - Border, Element, Event, Length, Shadow, Size, Vector, Widget, border::Radius, id::Id, - layout::Node, renderer::Quad, widget::Tree, -}; -use iced_core::{widget::tree, window}; +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}; const ICON_SIZE: u16 = 16; const TOP_SPACING: u16 = 4; diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 0de31fd6..879cc604 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -12,7 +12,9 @@ use std::time::{Duration, Instant}; use crate::Element; use crate::theme::iced::Slider; use crate::theme::{Button, THEME}; -use crate::widget::{button::Catalog, container, segmented_button::Entity, slider}; +use crate::widget::button::Catalog; +use crate::widget::segmented_button::Entity; +use crate::widget::{container, slider}; use derive_setters::Setters; use iced::Task; use iced_core::event::Event; @@ -25,10 +27,8 @@ use iced_core::{ }; use iced_widget::slider::HandleShape; -use iced_widget::{ - Row, canvas, column, row, scrollable, - space::{horizontal, vertical}, -}; +use iced_widget::space::{horizontal, vertical}; +use iced_widget::{Row, canvas, column, row, scrollable}; use palette::{FromColor, RgbHue}; use super::divider::horizontal; diff --git a/src/widget/context_drawer/overlay.rs b/src/widget/context_drawer/overlay.rs index 39b34217..01f5cfcf 100644 --- a/src/widget/context_drawer/overlay.rs +++ b/src/widget/context_drawer/overlay.rs @@ -5,8 +5,7 @@ use crate::Element; use iced::advanced::layout::{self, Layout}; use iced::advanced::widget::{self, Operation}; -use iced::advanced::{Clipboard, Shell}; -use iced::advanced::{overlay, renderer}; +use iced::advanced::{Clipboard, Shell, 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 7420738c..9a7448eb 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -6,11 +6,10 @@ 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::{ - Clipboard, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, + Alignment, Clipboard, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay as iced_overlay, renderer, }; diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 37fce504..9256c954 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -79,7 +79,8 @@ impl ContextMenu<'_, Message> { my_state: &mut LocalState, ) { if self.window_id != window::Id::NONE && self.on_surface_action.is_some() { - use crate::{surface::action::destroy_popup, widget::menu::Menu}; + use crate::surface::action::destroy_popup; + use crate::widget::menu::Menu; use iced_runtime::platform_specific::wayland::popup::{ SctkPopupSettings, SctkPositioner, }; diff --git a/src/widget/dialog.rs b/src/widget/dialog.rs index 7d084626..72ab11f4 100644 --- a/src/widget/dialog.rs +++ b/src/widget/dialog.rs @@ -1,8 +1,5 @@ -use crate::{ - Element, - iced::{Length, Pixels}, - style, theme, widget, -}; +use crate::iced::{Length, Pixels}; +use crate::{Element, style, theme, widget}; use std::borrow::Cow; pub fn dialog<'a, Message>() -> Dialog<'a, Message> { diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index 683a766b..afe135bc 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -1,29 +1,17 @@ -use std::{ - borrow::Cow, - sync::atomic::{AtomicU64, Ordering}, -}; +use std::borrow::Cow; +use std::sync::atomic::{AtomicU64, Ordering}; use iced::Vector; -use crate::{ - Element, - widget::{Id, Widget}, -}; +use crate::Element; +use crate::widget::{Id, Widget}; -use iced::{ - Event, Length, Rectangle, - clipboard::{ - dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent}, - mime::AllowedMimeTypes, - }, - event, - id::Internal, - mouse, overlay, -}; -use iced_core::{ - self, Clipboard, Shell, layout, - widget::{Tree, tree}, -}; +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}; pub fn dnd_destination<'a, Message: 'static>( child: impl Into>, diff --git a/src/widget/dnd_source.rs b/src/widget/dnd_source.rs index 170d6940..3cc9fa71 100644 --- a/src/widget/dnd_source.rs +++ b/src/widget/dnd_source.rs @@ -1,19 +1,14 @@ use std::any::Any; -use iced_core::{widget::Operation, window}; +use iced_core::widget::Operation; +use iced_core::window; -use crate::{ - Element, - widget::{Id, Widget, container}, -}; -use iced::{ - Event, Length, Point, Rectangle, Vector, - clipboard::dnd::{DndAction, DndEvent, SourceEvent}, mouse, overlay, -}; -use iced_core::{ - self, Clipboard, Shell, layout, renderer, - widget::{Tree, tree}, -}; +use crate::Element; +use crate::widget::{Id, Widget, container}; +use iced::clipboard::dnd::{DndAction, DndEvent, SourceEvent}; +use iced::{Event, Length, Point, Rectangle, Vector, event, mouse, overlay}; +use iced_core::widget::{Tree, tree}; +use iced_core::{self, Clipboard, Shell, layout, renderer}; pub fn dnd_source< 'a, diff --git a/src/widget/dropdown/menu/appearance.rs b/src/widget/dropdown/menu/appearance.rs index d1bed21c..2c32c011 100644 --- a/src/widget/dropdown/menu/appearance.rs +++ b/src/widget/dropdown/menu/appearance.rs @@ -3,7 +3,8 @@ // SPDX-License-Identifier: MPL-2.0 AND MIT //! Change the appearance of menus. -use iced_core::{Background, Color, border::Radius}; +use iced_core::border::Radius; +use iced_core::{Background, Color}; /// The appearance of a menu. #[derive(Debug, Clone, Copy)] diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index dd877def..c7a46592 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -9,9 +9,9 @@ use iced_core::event::Event; use iced_core::text::{self, Text}; use iced_core::widget::tree::{self, Tree}; use iced_core::{ - Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, + Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shadow, Shell, Size, Vector, Widget, + alignment, keyboard, layout, mouse, overlay, renderer, svg, touch, }; -use iced_core::{Shadow, alignment, keyboard, layout, mouse, overlay, renderer, svg, touch}; use iced_widget::pick_list; pub use iced_widget::pick_list::{Catalog, Style}; diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 0ba04c15..ebcf175a 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -12,9 +12,9 @@ use iced_core::event::Event; use iced_core::text::{self, Paragraph, Text}; use iced_core::widget::tree::{self, Tree}; use iced_core::{ - Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, + Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shadow, Shell, Size, Vector, Widget, + alignment, keyboard, layout, mouse, overlay, renderer, svg, touch, }; -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::hash::{DefaultHasher, Hash, Hasher}; diff --git a/src/widget/frames.rs b/src/widget/frames.rs index a542cec6..bf985ce2 100644 --- a/src/widget/frames.rs +++ b/src/widget/frames.rs @@ -2,14 +2,12 @@ //! 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; -use iced::mouse; +use iced::{Task, mouse}; use iced_core::image::Renderer as ImageRenderer; use iced_core::mouse::Cursor; use iced_core::widget::{Tree, tree}; diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 0fcbc6c6..82a7e8e9 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -5,7 +5,8 @@ use crate::cosmic_theme::{Density, Spacing}; use crate::{Element, theme, widget}; use apply::Apply; use derive_setters::Setters; -use iced_core::{Length, Size, Vector, Widget, layout, text, widget::tree}; +use iced_core::widget::tree; +use iced_core::{Length, Size, Vector, Widget, layout, text}; use std::borrow::Cow; #[must_use] @@ -400,6 +401,7 @@ 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); @@ -417,7 +419,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { WindowControlsPosition::Start => start.insert(0, controls), } - let padding = if self.is_ssd { + let padding = if is_ssd { [2, 8, 2, 8] } else { match ( @@ -462,7 +464,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .class(theme::Container::HeaderBar { focused: self.focused, sharp_corners: self.sharp_corners, - transparent: self.transparent, + transparent: if is_ssd { false } else { true }, }) .height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32)) .padding(padding) diff --git a/src/widget/icon/named.rs b/src/widget/icon/named.rs index dfd66cf5..89050301 100644 --- a/src/widget/icon/named.rs +++ b/src/widget/icon/named.rs @@ -2,7 +2,10 @@ // SPDX-License-Identifier: MPL-2.0 use super::{Handle, Icon}; -use std::{borrow::Cow, ffi::OsStr, path::PathBuf, sync::Arc}; +use std::borrow::Cow; +use std::ffi::OsStr; +use std::path::PathBuf; +use std::sync::Arc; #[derive(Debug, Clone, Default, Hash)] /// Fallback icon to use if the icon was not found. @@ -36,6 +39,10 @@ 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 { @@ -49,19 +56,30 @@ 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 8f280cab..6b0d13ce 100644 --- a/src/widget/id_container.rs +++ b/src/widget/id_container.rs @@ -1,10 +1,9 @@ -use iced_core::event::Event; -use iced_core::layout; -use iced_core::mouse; -use iced_core::overlay; -use iced_core::renderer; +use iced_core::event::{self, Event}; use iced_core::widget::{Id, Operation, Tree}; -use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; +use iced_core::{ + Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay, + renderer, +}; 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 577261f6..3958b03c 100644 --- a/src/widget/layer_container.rs +++ b/src/widget/layer_container.rs @@ -1,14 +1,11 @@ use crate::Theme; use cosmic_theme::LayeredTheme; 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::event::{self, Event}; 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 4ef3fc01..9e4204cc 100644 --- a/src/widget/list/list_column.rs +++ b/src/widget/list/list_column.rs @@ -2,7 +2,8 @@ // SPDX-License-Identifier: MPL-2.0 use crate::widget::container::Catalog; -use crate::widget::{button, column, container, divider, row, space::vertical}; +use crate::widget::space::vertical; +use crate::widget::{button, column, container, divider, row}; use crate::{Apply, Element, theme}; use iced::{Length, Padding}; diff --git a/src/widget/menu/flex.rs b/src/widget/menu/flex.rs index 09dab466..e4a32870 100644 --- a/src/widget/menu/flex.rs +++ b/src/widget/menu/flex.rs @@ -1,11 +1,9 @@ // From iced_aw, license MIT -use iced_core::{Widget, widget::Tree}; -use iced_widget::core::{ - Alignment, Element, Padding, Point, Size, - layout::{Limits, Node}, - renderer, -}; +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 crate::widget::RcElementWrapper; diff --git a/src/widget/menu/key_bind.rs b/src/widget/menu/key_bind.rs index 8b4ed227..ef87b344 100644 --- a/src/widget/menu/key_bind.rs +++ b/src/widget/menu/key_bind.rs @@ -1,3 +1,4 @@ +use iced_core::keyboard::key::{Code, Physical}; use iced_core::keyboard::{Key, Modifiers}; use std::fmt; @@ -27,28 +28,102 @@ pub struct KeyBind { } impl KeyBind { - /// Checks if the given key and modifiers match the `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. /// /// # 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) -> 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), - }; + pub fn matches( + &self, + modifiers: Modifiers, + key: &Key, + physical_key: Option<&Physical>, + ) -> bool { + let key_eq = self.key_eq(key) + || physical_key + .and_then(physical_key_to_latin) + .is_some_and(|latin| self.key_eq(&latin)); 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), + } + } +} + +/// 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 { diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 981053b3..3843c349 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -1,14 +1,14 @@ // From iced_aw, license MIT //! A widget that handles menu trees -use std::{collections::HashMap, sync::Arc}; +use std::collections::HashMap; +use std::sync::Arc; -use super::{ - menu_inner::{ - CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight, - }, - menu_tree::MenuTree, +use super::menu_inner::{ + CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight, }; +use super::menu_tree::MenuTree; +use crate::Renderer; #[cfg(all( feature = "multi-window", feature = "wayland", @@ -16,25 +16,21 @@ use super::{ feature = "surface-message" ))] use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; -use crate::{ - Renderer, - style::menu_bar::StyleSheet, - widget::{ - RcWrapper, - menu::menu_inner::init_root_menu, - }, -}; +use crate::style::menu_bar::StyleSheet; +use crate::widget::RcWrapper; +use crate::widget::dropdown::menu::{self, State}; +use crate::widget::menu::menu_inner::init_root_menu; +use iced::event::Status; use iced::{Point, Shadow, Vector, 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, - layout::{Limits, Node}, - mouse::{self, Cursor}, - overlay, - renderer::{self, Renderer as IcedRenderer}, - touch, - widget::{Tree, tree}, + overlay, touch, }; /// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing. @@ -574,7 +570,8 @@ where viewport: &Rectangle, ) { use event::Event::{Mouse, Touch}; - use mouse::{Button::Left, Event::ButtonReleased}; + use mouse::Button::Left; + use mouse::Event::ButtonReleased; use touch::Event::{FingerLifted, FingerLost}; process_root_events( @@ -741,7 +738,7 @@ where tree: &'b mut Tree, layout: Layout<'b>, _renderer: &Renderer, - _viewport: &Rectangle, + viewport: &Rectangle, translation: Vector, ) -> Option> { #[cfg(all( diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index af0b3add..c534c5b7 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -1,9 +1,11 @@ // From iced_aw, license MIT //! Menu tree overlay -use std::{borrow::Cow, sync::Arc}; +use std::borrow::Cow; +use std::sync::Arc; -use super::{menu_bar::MenuBarState, menu_tree::MenuTree}; +use super::menu_bar::MenuBarState; +use super::menu_tree::MenuTree; #[cfg(all( feature = "multi-window", feature = "wayland", @@ -15,12 +17,12 @@ use crate::style::menu_bar::StyleSheet; 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, - layout::{Limits, Node}, - mouse::{self, Cursor}, - overlay, renderer, touch, - widget::Tree, + Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, event, overlay, + renderer, touch, }; /// The condition of when to close a menu @@ -559,10 +561,9 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { shell: &mut Shell<'_, Message>, ) -> Option<(usize, MenuState)> { use event::Event::{Mouse, Touch}; - use mouse::{ - Button::Left, - Event::{ButtonPressed, ButtonReleased, CursorMoved, WheelScrolled}, - }; + use event::Status::{Captured, Ignored}; + use mouse::Button::Left; + use mouse::Event::{ButtonPressed, ButtonReleased, CursorMoved, WheelScrolled}; use touch::Event::{FingerLifted, FingerMoved, FingerPressed}; if !self @@ -578,7 +579,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(), }; @@ -948,7 +949,7 @@ impl Widget, cursor: mouse::Cursor, @@ -966,21 +967,21 @@ impl Widget Widget( ) 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 41cf1dff..efcf9800 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -234,6 +234,7 @@ pub fn menu_items< color.alpha *= 0.75; TextStyle { color: Some(color.into()), + ..Default::default() } } let key_class = theme::Text::Custom(key_style); diff --git a/src/widget/mod.rs b/src/widget/mod.rs index d83a74e2..5764ab44 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -152,7 +152,7 @@ pub use dialog::{Dialog, dialog}; pub mod divider { /// Horizontal variant of a divider. pub mod horizontal { - use iced::{widget::Rule, widget::rule}; + use iced::widget::{Rule, rule}; /// Horizontal divider with default thickness #[must_use] diff --git a/src/widget/nav_bar.rs b/src/widget/nav_bar.rs index ad6f9206..b7391abb 100644 --- a/src/widget/nav_bar.rs +++ b/src/widget/nav_bar.rs @@ -6,10 +6,9 @@ //! For details on the model, see the [`segmented_button`] module for more details. use apply::Apply; -use iced::{ - Background, Length, - clipboard::{dnd::DndAction, mime::AllowedMimeTypes}, -}; +use iced::clipboard::dnd::DndAction; +use iced::clipboard::mime::AllowedMimeTypes; +use iced::{Background, Length}; use iced_core::{Border, Color, Shadow}; use crate::widget::{Container, Icon, container, menu, scrollable, segmented_button}; diff --git a/src/widget/popover.rs b/src/widget/popover.rs index 7ca9eeff..138d5a19 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -4,15 +4,11 @@ //! A container which displays an overlay when a popup widget is attached. use iced::widget; -use iced_core::event::Event; -use iced_core::layout; -use iced_core::mouse; -use iced_core::overlay; -use iced_core::renderer; -use iced_core::touch; +use iced_core::event::{self, Event}; use iced_core::widget::{Operation, Tree}; use iced_core::{ - Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, + Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, layout, + mouse, overlay, renderer, touch, }; pub use iced_widget::container::{Catalog, Style}; diff --git a/src/widget/progress_bar/animation.rs b/src/widget/progress_bar/animation.rs new file mode 100644 index 00000000..a9d52831 --- /dev/null +++ b/src/widget/progress_bar/animation.rs @@ -0,0 +1,115 @@ +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, + target: Option, + last: Option, +} + +impl Progress { + /// Smoothly 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.target = Some(target); + self.last = Some(now); + return false; + }; + + // Sync animation clock when target changes + if self.target != Some(target) { + self.target = Some(target); + self.last = Some(now); + return true; + } + + let dt = (now - last).as_secs_f32(); + 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 fa8c38fe..3a3fedc4 100644 --- a/src/widget/progress_bar/circular.rs +++ b/src/widget/progress_bar/circular.rs @@ -1,15 +1,10 @@ //! 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}; -use iced::mouse; -use iced::time::Instant; +use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer}; use iced::widget::canvas; -use iced::window; -use iced::{Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector}; +use iced::{Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector, mouse, window}; use std::f32::consts::PI; use std::time::Duration; @@ -23,9 +18,9 @@ where { size: f32, bar_height: f32, - style: ::Style, + style: Theme::Style, cycle_duration: Duration, - rotation_duration: Duration, + period: Duration, progress: Option, } @@ -38,9 +33,9 @@ where Circular { size: 40.0, bar_height: 4.0, - style: ::Style::default(), + style: Theme::Style::default(), cycle_duration: Duration::from_millis(1500), - rotation_duration: Duration::from_secs(2), + period: Duration::from_secs(2), progress: None, } } @@ -58,7 +53,7 @@ where } /// Sets the style variant of this [`Circular`]. - pub fn style(mut self, style: ::Style) -> Self { + pub fn style(mut self, style: Theme::Style) -> Self { self.style = style; self } @@ -69,10 +64,10 @@ where self } - /// Sets the base rotation duration of this [`Circular`]. This is the duration that a full - /// rotation would take if the cycle rotation were set to 0.0 (no expanding or contracting) - pub fn rotation_duration(mut self, duration: Duration) -> Self { - self.rotation_duration = duration; + /// 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; self } @@ -82,10 +77,10 @@ where self } - fn min_wrap_angle(&self, track_radius: f32) -> (f32, f32) { + fn min_wrap(&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) + ((gap - cap_angle) / (2.0 * PI), 1.0 - gap / PI) } } @@ -98,125 +93,11 @@ 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: Option, + progress: Progress, } impl Widget for Circular @@ -260,28 +141,21 @@ where _viewport: &Rectangle, ) { let state = tree.state.downcast_mut::(); - if self.progress.is_some() { - if !float_cmp::approx_eq!( - f32, - state.progress.unwrap_or_default(), - self.progress.unwrap_or_default() - ) { - state.progress = self.progress; - state.cache.clear(); - } - return; - } if let Event::Window(window::Event::RedrawRequested(now)) = event { - 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(); + if let Some(target) = self.progress { + if state.progress.update(target, *now) { + state.cache.clear(); + shell.request_redraw(); + } + } else { + let (_, wrap) = self.min_wrap(self.size / 2.0 - self.bar_height); + state.animation = + state + .animation + .timed_transition(self.cycle_duration, self.period, wrap, *now); + state.cache.clear(); + shell.request_redraw(); + } } } @@ -299,11 +173,13 @@ where let state = tree.state.downcast_ref::(); let bounds = layout.bounds(); - let custom_style = - ::appearance(theme, &self.style, self.progress.is_some(), true); + let custom_style = Theme::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( @@ -313,133 +189,65 @@ where .with_width(self.bar_height), ); - if let Some(progress) = self.progress { - // outer border - if let Some(border_color) = custom_style.border_color { - let border_path = - canvas::Path::circle(frame.center(), track_radius + self.bar_height / 2.0); + // 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; - frame.stroke( - &border_path, - canvas::Stroke::default() - .with_color(border_color) - .with_width(1.0), - ); - } - - // inner border - if let Some(border_color) = custom_style.border_color { - let border_path = - canvas::Path::circle(frame.center(), track_radius - self.bar_height / 2.0); - - frame.stroke( - &border_path, - canvas::Stroke::default() - .with_color(border_color) - .with_width(1.0), - ); - } - - // bar - let mut builder = canvas::path::Builder::new(); - - builder.arc(canvas::path::Arc { - center: frame.center(), - radius: track_radius, - start_angle: Radians(-PI / 2.0), - end_angle: Radians(-PI / 2.0 + progress * 2.0 * PI), - }); - - let bar_path = builder.build(); - - frame.stroke( - &bar_path, - canvas::Stroke::default() - .with_color(custom_style.bar_color) - .with_width(self.bar_height), - ); - - let mut builder = canvas::path::Builder::new(); - - // get center of end of arc for rounded cap - let end_angle = -PI / 2.0 + progress * 2.0 * PI; - let end_center = - frame.center() + Vector::new(end_angle.cos(), end_angle.sin()) * track_radius; - builder.arc(canvas::path::Arc { - center: end_center, - radius: self.bar_height / 2.0, - start_angle: Radians(end_angle), - end_angle: Radians(end_angle + PI), - }); - - // get center of start of arc for rounded cap - let start_angle = -PI / 2.0; - let start_center = frame.center() - + Vector::new(start_angle.cos(), start_angle.sin()) * track_radius; - builder.arc(canvas::path::Arc { - center: start_center, - radius: self.bar_height / 2.0, - start_angle: Radians(start_angle - PI), - end_angle: Radians(start_angle), - }); - - let cap_path = builder.build(); - frame.fill(&cap_path, custom_style.bar_color); - } else { - let mut builder = canvas::path::Builder::new(); - - let start = 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, - ), + let draw_cap = |frame: &mut canvas::Frame, t: f32, flip: bool| { + let angle = to_angle(t); + let center = frame.center() + Vector::new(angle.cos(), angle.sin()) * track_radius; + let (start_angle, end_angle) = if flip { + (angle - PI, angle) + } else { + (angle, angle + PI) }; + let mut builder = canvas::path::Builder::new(); builder.arc(canvas::path::Arc { - center: frame.center(), - radius: track_radius, + center, + radius: self.bar_height / 2.0, start_angle: Radians(start_angle), end_angle: Radians(end_angle), }); + frame.fill(&builder.build(), custom_style.bar_color); + }; - let bar_path = builder.build(); - + let draw_bar = |frame: &mut canvas::Frame, start: f32, end: f32| { + 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)), + }); frame.stroke( - &bar_path, + &builder.build(), canvas::Stroke::default() .with_color(custom_style.bar_color) .with_width(self.bar_height), ); + draw_cap(frame, end, false); + draw_cap(frame, start, true); + }; - 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); + 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); + } else { + let (min, wrap) = self.min_wrap(track_radius); + let (start, end) = state + .animation + .bar_positions(self.cycle_duration, min, wrap); + draw_bar(frame, start, end); } }); diff --git a/src/widget/progress_bar/linear.rs b/src/widget/progress_bar/linear.rs index 226b2b5f..0ebe402f 100644 --- a/src/widget/progress_bar/linear.rs +++ b/src/widget/progress_bar/linear.rs @@ -1,19 +1,15 @@ //! Show a linear progress indicator. -use iced::advanced::layout; -use iced::advanced::renderer::{self, Quad}; -use iced::advanced::widget::tree::{self, Tree}; -use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; -use iced::mouse; -use iced::time::Instant; -use iced::window; -use iced::{Background, Element, Event, Length, Rectangle, Size}; - -use crate::anim::smootherstep; - +use super::animation::{Animation, Progress}; use super::style::StyleSheet; +use iced::advanced::widget::tree::{self, Tree}; +use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer}; +use iced::{Background, Element, Event, Length, Pixels, Rectangle, Size, mouse, window}; 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 @@ -23,7 +19,10 @@ where girth: Length, style: Theme::Style, cycle_duration: Duration, + period: Duration, progress: Option, + markers: Vec, + segment_spacing: f32, } impl Linear @@ -37,7 +36,10 @@ 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: 0.0, } } @@ -65,11 +67,38 @@ 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 value is a progress fraction between `0.0` and `1.0 at which a visual gap is inserted. + pub fn markers(mut self, markers: impl Into>) -> Self { + let mut markers = markers.into(); + for bp in &mut markers { + *bp = bp.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; + self + } } impl Default for Linear @@ -81,63 +110,10 @@ where } } -#[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, - }, - } - } +#[derive(Default)] +struct State { + animation: Animation, + progress: Progress, } impl Widget for Linear @@ -181,16 +157,21 @@ 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 { - *state = state.timed_transition(self.cycle_duration, *now); - - shell.request_redraw(); + 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(); + } } } @@ -208,88 +189,148 @@ where let custom_style = theme.appearance(&self.style, self.progress.is_some(), false); let state = tree.state.downcast_ref::(); - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x, - y: bounds.y, - width: bounds.width, - height: bounds.height, - }, - border: iced::Border { - width: if custom_style.border_color.is_some() { - 1.0 - } else { - 0.0 - }, - color: custom_style.border_color.unwrap_or(custom_style.bar_color), - radius: custom_style.border_radius.into(), - }, - snap: true, - ..renderer::Quad::default() - }, - Background::Color(custom_style.track_color), - ); - - if let Some(progress) = self.progress { - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x, - y: bounds.y, - width: progress * bounds.width, - height: bounds.height, - }, - border: iced::Border { - width: 0., - color: iced::Color::TRANSPARENT, - radius: custom_style.border_radius.into(), - }, - snap: true, - ..renderer::Quad::default() - }, - Background::Color(custom_style.bar_color), - ); + let border_width = if custom_style.border_color.is_some() { + 1.0 } else { - match state { - State::Expanding { progress, .. } => renderer.fill_quad( + 0.0 + }; + let border_color = custom_style.border_color.unwrap_or(custom_style.bar_color); + let radius = custom_style.border_radius; + + let mut draw_quad = |x: f32, width: f32, color: iced::Color, border: iced::Border| { + // don't draw if width is less than 0.1 pixels + if width * bounds.width > 0.1 { + renderer.fill_quad( renderer::Quad { bounds: Rectangle { - x: bounds.x, + x: bounds.x + x * bounds.width, y: bounds.y, - width: smootherstep(*progress) * bounds.width, + width: width * bounds.width, height: bounds.height, }, - border: iced::Border { - width: 0., - color: iced::Color::TRANSPARENT, - radius: custom_style.border_radius.into(), - }, + border, snap: true, ..renderer::Quad::default() }, - Background::Color(custom_style.bar_color), - ), - - State::Contracting { progress, .. } => renderer.fill_quad( - Quad { - bounds: Rectangle { - x: bounds.x + smootherstep(*progress) * bounds.width, - y: bounds.y, - width: (1.0 - smootherstep(*progress)) * bounds.width, - height: bounds.height, - }, - border: iced::Border { - width: 0., - color: iced::Color::TRANSPARENT, - radius: custom_style.border_radius.into(), - }, - snap: true, - ..renderer::Quad::default() - }, - Background::Color(custom_style.bar_color), - ), + Background::Color(color), + ); } + }; + + if self.progress.is_some() { + let spacing = self.segment_spacing.max(1.0); + let radius_inner = radius.min(spacing); + + let gap = if self.markers.is_empty() { + 0.0 + } else { + spacing / bounds.width + }; + let drawable = 1.0 - gap * self.markers.len() as f32; + let num_segments = self.markers.len() + 1; + + let segment_bounds = |i: usize| { + let seg_lo = if i == 0 { 0.0 } else { self.markers[i - 1] }; + let seg_hi = if i == num_segments - 1 { + 1.0 + } else { + self.markers[i] + }; + (seg_lo, seg_hi) + }; + let get_radius = |i: usize| { + let r_left = if i == 0 { radius } else { radius_inner }; + let r_right = if i == num_segments - 1 { + radius + } else { + radius_inner + }; + [r_left, r_right, r_right, r_left].into() + }; + + // draw track segments + for i in 0..num_segments { + let (seg_lo, seg_hi) = segment_bounds(i); + let x_start = seg_lo * drawable + i as f32 * gap; + let x_width = (seg_hi - seg_lo) * drawable; + + draw_quad( + x_start, + x_width, + custom_style.track_color, + iced::Border { + width: border_width, + color: border_color, + radius: get_radius(i), + }, + ); + } + + // draw bar segments + let current_p = state.progress.current; + for i in 0..num_segments { + let (seg_lo, seg_hi) = segment_bounds(i); + + // don't iterate over non-filled segments + if current_p < seg_lo { + break; + } + + let x_start = seg_lo * drawable + i as f32 * gap; + let x_width = (seg_hi - seg_lo) * drawable; + let fill = ((current_p - seg_lo) / (seg_hi - seg_lo)).clamp(0.0, 1.0); + + draw_quad( + x_start, + x_width * fill, + custom_style.bar_color, + iced::Border { + radius: get_radius(i), + ..iced::Border::default() + }, + ); + } + } else { + // draw track + draw_quad( + 0.0, + 1.0, + custom_style.track_color, + iced::Border { + width: border_width, + color: border_color, + radius: radius.into(), + }, + ); + + // 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; + + draw_quad( + start, + right_width, + custom_style.bar_color, + iced::Border { + radius: radius.into(), + ..iced::Border::default() + }, + ); + draw_quad( + 0.0, + left_width, + custom_style.bar_color, + iced::Border { + radius: radius.into(), + ..iced::Border::default() + }, + ); } } } diff --git a/src/widget/progress_bar/mod.rs b/src/widget/progress_bar/mod.rs index 4e277b0a..cb24adac 100644 --- a/src/widget/progress_bar/mod.rs +++ b/src/widget/progress_bar/mod.rs @@ -1,3 +1,4 @@ +mod animation; pub mod circular; pub mod linear; pub mod style; diff --git a/src/widget/radio.rs b/src/widget/radio.rs index 51ed00a4..f8e174e1 100644 --- a/src/widget/radio.rs +++ b/src/widget/radio.rs @@ -1,15 +1,11 @@ //! Create choices using radio buttons. use crate::{Theme, theme}; use iced::border; -use iced_core::event::Event; -use iced_core::layout; -use iced_core::mouse; -use iced_core::overlay; -use iced_core::renderer; -use iced_core::touch; +use iced_core::event::{self, Event}; 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; diff --git a/src/widget/rectangle_tracker/mod.rs b/src/widget/rectangle_tracker/mod.rs index f5969589..839fa9a7 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::layout; -use iced_core::mouse; -use iced_core::overlay; -use iced_core::renderer; +use iced_core::event::{self, Event}; use iced_core::widget::Tree; -use iced_core::{Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget}; -use std::{fmt::Debug, hash::Hash}; +use iced_core::{ + Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, layout, + mouse, overlay, renderer, +}; +use std::fmt::Debug; +use std::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 02fa4329..22b42441 100644 --- a/src/widget/rectangle_tracker/subscription.rs +++ b/src/widget/rectangle_tracker/subscription.rs @@ -1,13 +1,10 @@ -use iced::{ - Rectangle, - futures::{ - StreamExt, - channel::mpsc::{UnboundedReceiver, unbounded}, - stream, - }, -}; +use iced::Rectangle; +use iced::futures::channel::mpsc::{UnboundedReceiver, unbounded}; +use iced::futures::{StreamExt, stream}; use iced_futures::Subscription; -use std::{collections::HashMap, fmt::Debug, hash::Hash}; +use std::collections::HashMap; +use std::fmt::Debug; +use std::hash::Hash; use super::RectangleTracker; diff --git a/src/widget/responsive_container.rs b/src/widget/responsive_container.rs index 14b7a030..c8925cc2 100644 --- a/src/widget/responsive_container.rs +++ b/src/widget/responsive_container.rs @@ -1,13 +1,12 @@ //! Responsive Container, which will notify of size changes. use iced::{Limits, Size}; -use iced_core::event::Event; -use iced_core::layout; -use iced_core::mouse; -use iced_core::overlay; -use iced_core::renderer; +use iced_core::event::{self, Event}; use iced_core::widget::{Id, Operation, Tree, tree}; -use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; +use iced_core::{ + Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay, + renderer, +}; 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 86b7f0d2..de323c13 100644 --- a/src/widget/responsive_menu_bar.rs +++ b/src/widget/responsive_menu_bar.rs @@ -2,10 +2,8 @@ use std::collections::HashMap; use apply::Apply; -use crate::{ - Core, Element, - widget::{button, icon, responsive_container}, -}; +use crate::widget::{button, icon, responsive_container}; +use crate::{Core, Element}; use super::menu::{self, ItemHeight, ItemWidth}; diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 9edf3e3b..5e0b4f7f 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -25,9 +25,11 @@ 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, 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_core::widget::{self, Tree, operation, tree}; +use iced_core::{ + Border, Clipboard, Layout, Point, Renderer as IcedRenderer, Shadow, Shell, Text, Widget, + layout, renderer, +}; use iced_runtime::{Action, task}; use slotmap::{Key, SecondaryMap}; use std::borrow::Cow; @@ -1446,6 +1448,8 @@ where 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; }); diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index 5abb464c..4eb5bb17 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -3,12 +3,11 @@ use std::borrow::Cow; -use crate::{ - Element, Theme, theme, - widget::{FlexRow, Row, column, container, flex_row, list, row, text}, -}; +use crate::widget::{FlexRow, Row, column, container, flex_row, list, row, text}; +use crate::{Element, Theme, theme}; use derive_setters::Setters; -use iced_core::{Length, text::Wrapping}; +use iced_core::Length; +use iced_core::text::Wrapping; use iced_widget::space; use taffy::AlignContent; diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs index 3ff0e7bf..b2528861 100644 --- a/src/widget/spin_button.rs +++ b/src/widget/spin_button.rs @@ -3,13 +3,10 @@ //! A control for incremental adjustments of a value. -use crate::{ - Element, theme, - widget::{button, column, container, icon, row, text}, -}; +use crate::widget::{button, column, container, icon, row, text}; +use crate::{Element, theme}; use apply::Apply; -use iced::{Alignment, Length}; -use iced::{Border, Shadow}; +use iced::{Alignment, Border, Length, Shadow}; use std::borrow::Cow; use std::ops::{Add, Sub}; diff --git a/src/widget/table/mod.rs b/src/widget/table/mod.rs index c546383c..dfde3810 100644 --- a/src/widget/table/mod.rs +++ b/src/widget/table/mod.rs @@ -2,12 +2,9 @@ //! pub mod model; -pub use model::{ - Entity, Model, - category::ItemCategory, - category::ItemInterface, - selection::{MultiSelect, SingleSelect}, -}; +pub use model::category::{ItemCategory, ItemInterface}; +pub use model::selection::{MultiSelect, SingleSelect}; +pub use model::{Entity, Model}; 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 51c60609..910ccd2f 100644 --- a/src/widget/table/model/entity.rs +++ b/src/widget/table/model/entity.rs @@ -3,10 +3,8 @@ use slotmap::{SecondaryMap, SparseSecondaryMap}; -use super::{ - Entity, Model, Selectable, - category::{ItemCategory, ItemInterface}, -}; +use super::category::{ItemCategory, ItemInterface}; +use super::{Entity, Model, Selectable}; /// 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 749860f8..f0e5e61c 100644 --- a/src/widget/table/model/mod.rs +++ b/src/widget/table/model/mod.rs @@ -2,10 +2,8 @@ pub mod category; pub mod entity; pub mod selection; -use std::{ - any::{Any, TypeId}, - collections::{HashMap, VecDeque}, -}; +use std::any::{Any, TypeId}; +use std::collections::{HashMap, VecDeque}; use category::{ItemCategory, ItemInterface}; use entity::EntityMut; diff --git a/src/widget/table/model/selection.rs b/src/widget/table/model/selection.rs index 20a07248..3e3d46b5 100644 --- a/src/widget/table/model/selection.rs +++ b/src/widget/table/model/selection.rs @@ -3,10 +3,8 @@ //! Describes logic specific to the single-select and multi-select modes of a model. -use super::{ - Entity, Model, - category::{ItemCategory, ItemInterface}, -}; +use super::category::{ItemCategory, ItemInterface}; +use super::{Entity, Model}; 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 65ac9058..6d027caf 100644 --- a/src/widget/table/widget/compact.rs +++ b/src/widget/table/widget/compact.rs @@ -1,14 +1,10 @@ use derive_setters::Setters; -use crate::widget::table::model::{ - Entity, Model, - category::{ItemCategory, ItemInterface}, - selection::Selectable, -}; -use crate::{ - Apply, Element, theme, - widget::{self, container, menu}, -}; +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 iced::{Alignment, Border, Padding}; #[derive(Setters)] diff --git a/src/widget/table/widget/standard.rs b/src/widget/table/widget/standard.rs index c4cd648c..e21cd22f 100644 --- a/src/widget/table/widget/standard.rs +++ b/src/widget/table/widget/standard.rs @@ -1,14 +1,10 @@ use derive_setters::Setters; -use crate::widget::table::model::{ - Entity, Model, - category::{ItemCategory, ItemInterface}, - selection::Selectable, -}; -use crate::{ - Apply, Element, theme, - widget::{self, container, divider, menu}, -}; +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 iced::{Alignment, Border, Length, Padding}; // THIS IS A PLACEHOLDER UNTIL A MORE SOPHISTICATED WIDGET CAN BE DEVELOPED diff --git a/src/widget/text_input/editor.rs b/src/widget/text_input/editor.rs index b8144761..c9c2ca7a 100644 --- a/src/widget/text_input/editor.rs +++ b/src/widget/text_input/editor.rs @@ -2,7 +2,8 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MIT -use super::{cursor::Cursor, value::Value}; +use super::cursor::Cursor; +use super::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 903c81d1..730fae25 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -28,18 +28,14 @@ use iced_core::overlay::Group; use iced_core::renderer::{self, Renderer as CoreRenderer}; use iced_core::text::{self, Paragraph, Renderer, Text}; use iced_core::time::{Duration, Instant}; -use iced_core::touch; use iced_core::widget::Id; use iced_core::widget::operation::{self, Operation}; use iced_core::widget::tree::{self, Tree}; -use iced_core::window; -use iced_core::{Background, alignment}; -use iced_core::{Border, Shadow, keyboard}; use iced_core::{ - Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size, - Vector, Widget, + Background, Border, Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, + Rectangle, Shadow, Shell, Size, Vector, Widget, alignment, keyboard, layout, overlay, touch, + window, }; -use iced_core::{layout, overlay}; use iced_runtime::{Action, Task, task}; thread_local! { diff --git a/src/widget/text_input/style.rs b/src/widget/text_input/style.rs index 8af5e63e..52b98784 100644 --- a/src/widget/text_input/style.rs +++ b/src/widget/text_input/style.rs @@ -4,7 +4,8 @@ //! Change the appearance of a text input. -use iced_core::{Background, Color, border::Radius}; +use iced_core::border::Radius; +use iced_core::{Background, Color}; /// The appearance of a text input. #[derive(Debug, Clone, Copy)] diff --git a/src/widget/toaster/mod.rs b/src/widget/toaster/mod.rs index bafaa9f9..43acc5c1 100644 --- a/src/widget/toaster/mod.rs +++ b/src/widget/toaster/mod.rs @@ -6,16 +6,13 @@ use std::collections::VecDeque; use std::rc::Rc; -use crate::widget::Column; -use crate::widget::container; +use crate::widget::{Column, container}; use iced::Task; use iced_core::Element; -use slotmap::SlotMap; -use slotmap::new_key_type; +use slotmap::{SlotMap, new_key_type}; use widget::Toaster; -use super::column; -use super::{button, icon, row, text}; +use super::{button, column, icon, row, text}; mod widget; diff --git a/src/widget/toaster/widget.rs b/src/widget/toaster/widget.rs index 9b6939e7..922c1433 100644 --- a/src/widget/toaster/widget.rs +++ b/src/widget/toaster/widget.rs @@ -4,16 +4,14 @@ use iced::{Limits, Size}; use iced_core::layout::Node; -use iced_core::Element; -use iced_core::Overlay; -use iced_core::event::Event; -use iced_core::layout; -use iced_core::mouse; -use iced_core::overlay; +use iced_core::event::{self, Event}; use iced_core::renderer::{self}; use iced_core::widget::Operation; use iced_core::widget::tree::Tree; -use iced_core::{Clipboard, Layout, Length, Point, Rectangle, Shell, Vector, Widget}; +use iced_core::{ + Clipboard, Element, Layout, Length, Overlay, Point, Rectangle, Shell, Vector, Widget, layout, + mouse, overlay, +}; pub struct Toaster<'a, Message, Theme, Renderer> { toasts: Element<'a, Message, Theme, Renderer>, @@ -154,7 +152,7 @@ where translation, ) } else { - let _bounds = layout.bounds(); + let bounds = layout.bounds(); Some(overlay::Element::new(Box::new(ToasterOverlay::new( &mut state.children[1], diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index 5e5bc2ee..2254759e 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -1,18 +1,18 @@ //! 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, - renderer::{self, Renderer}, - text, touch, - widget::{self, Tree, tree}, - window, + Border, Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment, + event, layout, mouse, text, touch, window, }; -use iced_widget::{Id, toggler::Status}; +use iced_widget::Id; +use iced_widget::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) @@ -260,7 +260,7 @@ impl<'a, Message> Widget for Toggler<'a, shell.capture_event(); } } - Event::Window(window::Event::RedrawRequested(_now)) => { + Event::Window(window::Event::RedrawRequested(now)) => { state.anim.anim_done(self.duration); if state.anim.last_change.is_some() { shell.request_redraw(); @@ -370,7 +370,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 947d1e83..aa80fad6 100644 --- a/src/widget/wayland/tooltip/mod.rs +++ b/src/widget/wayland/tooltip/mod.rs @@ -5,7 +5,8 @@ pub mod widget; // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 -use iced_core::{Background, Color, Vector, border::Radius}; +use iced_core::border::Radius; +use iced_core::{Background, Color, Vector}; use crate::theme::THEME; diff --git a/src/widget/wayland/tooltip/widget.rs b/src/widget/wayland/tooltip/widget.rs index 0526e8db..203a2010 100644 --- a/src/widget/wayland/tooltip/widget.rs +++ b/src/widget/wayland/tooltip/widget.rs @@ -13,20 +13,24 @@ use std::time::Duration; use iced::Task; use iced_runtime::core::widget::Id; -use iced_core::event::Event; -use iced_core::renderer; -use iced_core::touch; +use iced_core::event::{self, Event}; use iced_core::widget::Operation; use iced_core::widget::tree::{self, Tree}; use iced_core::{ - Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget, + Background, Border, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shadow, Shell, + Vector, Widget, layout, mouse, overlay, renderer, svg, touch, }; -use iced_core::{Border, mouse}; -use iced_core::{Shadow, overlay}; -use iced_core::layout; pub use super::{Catalog, Style}; +/// Internally defines different button widget variants. +enum Variant { + Normal, + Image { + close_icon: svg::Handle, + on_remove: Option, + }, +} /// A generic button which emits a message when pressed. #[allow(missing_debug_implementations)] @@ -302,7 +306,7 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone> } let content_layout = layout.children().next().unwrap(); - let _state = tree.state.downcast_ref::(); + let state = tree.state.downcast_ref::(); let styling = theme.style(&self.style); @@ -456,6 +460,7 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>( shell.publish(on_leave.clone()); shell.capture_event(); + return; } } @@ -483,8 +488,8 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>( } } else { *guard = cursor.is_over(bounds); - if *guard - && let Some(settings) = settings { + if *guard { + if let Some(settings) = settings { if let Some(delay) = delay { let s = settings.clone(); let view = view.clone(); @@ -568,6 +573,7 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>( shell.publish((on_surface_action)(sm)); } } + } } } _ => {} diff --git a/src/widget/wrapper.rs b/src/widget/wrapper.rs index b27bb9ff..aee06e5a 100644 --- a/src/widget/wrapper.rs +++ b/src/widget/wrapper.rs @@ -1,13 +1,13 @@ -use std::{ - borrow::Borrow, - cell::RefCell, - rc::Rc, - thread::{self, ThreadId}, -}; +use std::borrow::Borrow; +use std::cell::RefCell; +use std::rc::Rc; +use std::thread::{self, ThreadId}; use crate::Element; -use iced::{Length, Rectangle, Size}; -use iced_core::{Widget, id::Id, widget, widget::tree}; +use iced::{Length, Rectangle, Size, event}; +use iced_core::id::Id; +use iced_core::widget::tree; +use iced_core::{Widget, widget}; #[derive(Debug)] pub struct RcWrapper {