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 {