Compare commits
47 commits
b284e754fd
...
f3db54342c
| Author | SHA1 | Date | |
|---|---|---|---|
| f3db54342c | |||
| 6722f1e581 | |||
| 5f50d74477 | |||
| 7a191cf086 | |||
| 38acba82b4 | |||
| ef8f69134f | |||
| 597aba3e9f | |||
| e3dcdf1fce | |||
| 10422b8f4a | |||
| 87510782ae | |||
| ea17ada931 | |||
|
|
8fa6a01d04 | ||
|
|
113e0ae1f9 | ||
|
|
2f022280f2 | ||
|
|
e2b172e0bc | ||
|
|
b279d82240 | ||
|
|
147306d581 | ||
|
|
466e25d9f8 | ||
|
|
6f863f1905 | ||
|
|
5782cb927e | ||
|
|
631c81b170 | ||
|
|
cab9d5aff2 | ||
|
|
2aa9133f9d | ||
|
|
4fab6c777d | ||
|
|
b105e9a108 | ||
|
|
528513725d | ||
|
|
0e0960f3c7 | ||
|
|
9c2a86a8f4 | ||
|
|
9abc4d483b | ||
|
|
34326b3925 | ||
|
|
15a8a535dd | ||
|
|
564ef834ce | ||
|
|
99c038a66a | ||
|
|
cae05f3ffb | ||
|
|
8768bce2e2 | ||
|
|
49846d9f28 | ||
|
|
51be7dc42d | ||
|
|
5825afb0da | ||
|
|
f3fc763b5f | ||
|
|
21977ef617 | ||
|
|
58b7532849 | ||
|
|
17291536a1 | ||
|
|
dad5f1e273 | ||
|
|
8b4c8adec8 | ||
|
|
4d39cf3d7b | ||
|
|
dc84488cd8 | ||
|
|
6dba6a2561 |
117 changed files with 1257 additions and 1095 deletions
4
.github/workflows/pages.yml
vendored
4
.github/workflows/pages.yml
vendored
|
|
@ -17,13 +17,13 @@ jobs:
|
||||||
- name: Install Rust nightly
|
- name: Install Rust nightly
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: nightly-2025-07-31
|
toolchain: nightly-2026-04-27
|
||||||
- name: System dependencies
|
- name: System dependencies
|
||||||
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
|
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
|
||||||
- name: Build documentation
|
- name: Build documentation
|
||||||
run: |
|
run: |
|
||||||
RUSTDOCFLAGS="--cfg docsrs" \
|
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-client-toolkit \
|
||||||
-p cosmic-protocols \
|
-p cosmic-protocols \
|
||||||
-p libcosmic \
|
-p libcosmic \
|
||||||
|
|
|
||||||
15
.zed/settings.json
Normal file
15
.zed/settings.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"format_on_save": "on",
|
||||||
|
"lsp": {
|
||||||
|
"rust-analyzer": {
|
||||||
|
"initialization_options": {
|
||||||
|
"check": {
|
||||||
|
"command": "clippy",
|
||||||
|
},
|
||||||
|
"rustfmt": {
|
||||||
|
"extraArgs": ["+nightly"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
37
Cargo.toml
37
Cargo.toml
|
|
@ -7,7 +7,7 @@
|
||||||
name = "libcosmic-yoda"
|
name = "libcosmic-yoda"
|
||||||
version = "0.1.0-yoda.2"
|
version = "0.1.0-yoda.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.90"
|
rust-version = "1.93"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "cosmic"
|
name = "cosmic"
|
||||||
|
|
@ -128,9 +128,9 @@ async-std = [
|
||||||
apply = "0.3.0"
|
apply = "0.3.0"
|
||||||
ashpd = { version = "0.12.3", default-features = false, optional = true }
|
ashpd = { version = "0.12.3", default-features = false, optional = true }
|
||||||
async-fs = { version = "2.2", 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"
|
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"
|
jiff = "0.2"
|
||||||
cosmic-config = { path = "cosmic-config" }
|
cosmic-config = { path = "cosmic-config" }
|
||||||
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true }
|
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",
|
"xpm",
|
||||||
"xbm",
|
"xbm",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
libc = { version = "0.2.183", optional = true }
|
libc = { version = "0.2.186", optional = true }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = { version = "0.3.17", optional = true }
|
mime = { version = "0.3.17", optional = true }
|
||||||
palette = "0.7.6"
|
palette.workspace = true
|
||||||
rfd = { version = "0.16.0", default-features = false, features = [
|
rfd = { version = "0.16.0", default-features = false, features = [
|
||||||
"xdg-portal",
|
"xdg-portal",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
rustix = { version = "1.1", features = ["pipe", "process"], 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"
|
slotmap = "1.1.1"
|
||||||
smol = { version = "2.0.2", optional = true }
|
smol = { version = "2.0.2", optional = true }
|
||||||
thiserror = "2.0.18"
|
thiserror.workspace = true
|
||||||
taffy = { version = "0.9.2", features = ["grid"] }
|
taffy = { version = "0.9.2", features = ["grid"] }
|
||||||
tokio = { version = "1.50.0", optional = true }
|
tokio = { workspace = true, optional = true }
|
||||||
tracing = "0.1.44"
|
tracing.workspace = true
|
||||||
unicode-segmentation = "1.12"
|
unicode-segmentation = "1.13"
|
||||||
url = "2.5.8"
|
url = "2.5.8"
|
||||||
zbus = { version = "5.14.0", default-features = false, optional = true }
|
zbus = { workspace = true, optional = true }
|
||||||
float-cmp = "0.10.0"
|
float-cmp = "0.10.0"
|
||||||
|
ron = { workspace = true, optional = true }
|
||||||
|
|
||||||
# Enable DBus feature on Linux targets
|
# Enable DBus feature on Linux targets
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
|
@ -247,10 +248,6 @@ git = "https://github.com/pop-os/cosmic-panel"
|
||||||
# path = "../cosmic-panel/cosmic-panel-config"
|
# path = "../cosmic-panel/cosmic-panel-config"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.ron]
|
|
||||||
version = "0.12"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"cosmic-config",
|
"cosmic-config",
|
||||||
|
|
@ -262,7 +259,15 @@ members = [
|
||||||
exclude = ["iced", "examples"]
|
exclude = ["iced", "examples"]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[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
|
# Speed up snapshot diffing in cosmic-theme tests. Cargo silently ignores
|
||||||
# [profile.*] blocks in non-root manifests, so this lives at the
|
# [profile.*] blocks in non-root manifests, so this lives at the
|
||||||
|
|
|
||||||
18
README.md
18
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
|
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 `<id>com.system76.CosmicApplication</id>` to the provides section of the project's metainfo.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<provides>
|
||||||
|
<id>com.system76.CosmicApplication</id>
|
||||||
|
</provides>
|
||||||
|
```
|
||||||
|
|
||||||
|
For COSMIC Applets, use `com.system76.CosmicApplet`.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<provides>
|
||||||
|
<id>com.system76.CosmicApplet</id>
|
||||||
|
</provides>
|
||||||
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Some examples are included in the [examples](./examples) directory to to kickstart your
|
Some examples are included in the [examples](./examples) directory to to kickstart your
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,16 @@ zbus = { version = "5.14.0", default-features = false, optional = true }
|
||||||
atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" }
|
atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" }
|
||||||
calloop = { version = "0.14.4", optional = true }
|
calloop = { version = "0.14.4", optional = true }
|
||||||
notify = "8.2.0"
|
notify = "8.2.0"
|
||||||
ron = "0.12.0"
|
ron.workspace = true
|
||||||
serde = "1.0.228"
|
serde.workspace = true
|
||||||
cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true }
|
cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true }
|
||||||
iced = { path = "../iced/", default-features = false, optional = true }
|
iced = { path = "../iced/", default-features = false, optional = true }
|
||||||
iced_futures = { path = "../iced/futures/", default-features = false, optional = true }
|
iced_futures = { path = "../iced/futures/", default-features = false, optional = true }
|
||||||
futures-util = { version = "0.3", optional = true }
|
futures-util = { version = "0.3", optional = true }
|
||||||
dirs.workspace = true
|
dirs.workspace = true
|
||||||
tokio = { version = "1.50", optional = true, features = ["time"] }
|
tokio = { workspace = true, optional = true, features = ["time"] }
|
||||||
async-std = { version = "1.13", optional = true }
|
async-std = { workspace = true, optional = true }
|
||||||
tracing = "0.1"
|
tracing.workspace = true
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
xdg = "3.0"
|
xdg = "3.0"
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
use std::{any::TypeId, ops::Deref};
|
use std::any::TypeId;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
use crate::{CosmicConfigEntry, Update};
|
use crate::{CosmicConfigEntry, Update};
|
||||||
use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy};
|
use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy};
|
||||||
use futures_util::SinkExt;
|
use futures_util::SinkExt;
|
||||||
use iced_futures::{
|
use iced_futures::futures::future::pending;
|
||||||
Subscription,
|
use iced_futures::futures::{self, StreamExt};
|
||||||
futures::{self, StreamExt, future::pending},
|
use iced_futures::{Subscription, stream};
|
||||||
stream,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn settings_daemon_proxy() -> zbus::Result<CosmicSettingsDaemonProxy<'static>> {
|
pub async fn settings_daemon_proxy() -> zbus::Result<CosmicSettingsDaemonProxy<'static>> {
|
||||||
let conn = zbus::Connection::session().await?;
|
let conn = zbus::Connection::session().await?;
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,13 @@
|
||||||
//! Integrations for cosmic-config — the cosmic configuration system.
|
//! Integrations for cosmic-config — the cosmic configuration system.
|
||||||
|
|
||||||
use notify::{
|
use notify::event::{EventKind, ModifyKind, RenameMode};
|
||||||
RecommendedWatcher, Watcher,
|
use notify::{RecommendedWatcher, Watcher};
|
||||||
event::{EventKind, ModifyKind, RenameMode},
|
use serde::Serialize;
|
||||||
};
|
use serde::de::DeserializeOwned;
|
||||||
use serde::{Serialize, de::DeserializeOwned};
|
use std::io::Write;
|
||||||
use std::{
|
use std::path::{Path, PathBuf};
|
||||||
env, fmt, fs,
|
use std::sync::Mutex;
|
||||||
io::Write,
|
use std::{env, fmt, fs};
|
||||||
path::{Path, PathBuf},
|
|
||||||
sync::Mutex,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Get the config directory, with Flatpak sandbox support.
|
/// Get the config directory, with Flatpak sandbox support.
|
||||||
/// In Flatpak, HOST_XDG_CONFIG_HOME points to the real user config directory,
|
/// In Flatpak, HOST_XDG_CONFIG_HOME points to the real user config directory,
|
||||||
|
|
@ -54,6 +51,22 @@ fn get_state_dir() -> Option<PathBuf> {
|
||||||
dirs::state_dir()
|
dirs::state_dir()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the data directory, with Flatpak sandbox support.
|
||||||
|
fn get_data_dir() -> Option<PathBuf> {
|
||||||
|
// 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")]
|
#[cfg(feature = "subscription")]
|
||||||
mod subscription;
|
mod subscription;
|
||||||
#[cfg(feature = "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<Self, Error> {
|
||||||
|
// 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)
|
// Start a transaction (to set multiple configs at the same time)
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn transaction(&self) -> ConfigTransaction<'_> {
|
pub fn transaction(&self) -> ConfigTransaction<'_> {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
use iced_futures::futures::SinkExt;
|
use iced_futures::futures::channel::mpsc;
|
||||||
use iced_futures::{futures::channel::mpsc, stream};
|
use iced_futures::futures::{SinkExt, Stream};
|
||||||
|
use iced_futures::stream;
|
||||||
use notify::RecommendedWatcher;
|
use notify::RecommendedWatcher;
|
||||||
use std::{borrow::Cow, hash::Hash};
|
use std::borrow::Cow;
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
use crate::{Config, CosmicConfigEntry};
|
use crate::{Config, CosmicConfigEntry};
|
||||||
|
|
||||||
|
|
@ -77,7 +79,8 @@ async fn start_listening<T: 'static + Send + Sync + PartialEq + Clone + CosmicCo
|
||||||
state: ConfigState<T>,
|
state: ConfigState<T>,
|
||||||
output: &mut mpsc::Sender<crate::Update<T>>,
|
output: &mut mpsc::Sender<crate::Update<T>>,
|
||||||
) -> ConfigState<T> {
|
) -> ConfigState<T> {
|
||||||
use iced_futures::futures::{StreamExt, future::pending};
|
use iced_futures::futures::StreamExt;
|
||||||
|
use iced_futures::futures::future::pending;
|
||||||
|
|
||||||
match state {
|
match state {
|
||||||
ConfigState::Init(config_id, version, is_state) => {
|
ConfigState::Init(config_id, version, is_state) => {
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@ export = ["serde_json"]
|
||||||
no-default = []
|
no-default = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
palette = { version = "0.7.6", features = ["serializing"] }
|
palette = { workspace = true, features = ["serializing"] }
|
||||||
almost = "0.2"
|
almost = "0.2"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = { version = "1.0.149", optional = true, features = [
|
serde_json = { version = "1.0.149", optional = true, features = [
|
||||||
"preserve_order",
|
"preserve_order",
|
||||||
] }
|
] }
|
||||||
ron = "0.12.0"
|
ron.workspace = true
|
||||||
csscolorparser = { version = "0.8.3", features = ["serde"] }
|
csscolorparser = { version = "0.8.3", features = ["serde"] }
|
||||||
cosmic-config = { path = "../cosmic-config/", default-features = false, features = [
|
cosmic-config = { path = "../cosmic-config/", default-features = false, features = [
|
||||||
"subscription",
|
"subscription",
|
||||||
|
|
@ -29,7 +29,7 @@ cosmic-config = { path = "../cosmic-config/", default-features = false, features
|
||||||
] }
|
] }
|
||||||
configparser = "3.1.0"
|
configparser = "3.1.0"
|
||||||
dirs.workspace = true
|
dirs.workspace = true
|
||||||
thiserror = "2.0.18"
|
thiserror.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = "1.47.2"
|
insta = "1.47.2"
|
||||||
|
|
|
||||||
|
|
@ -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<Roundness> 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<CornerRadii> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<Density> 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<Spacing> 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
pub use corner::*;
|
pub use corner::*;
|
||||||
pub use cosmic_palette::*;
|
pub use cosmic_palette::*;
|
||||||
pub use density::*;
|
|
||||||
pub use derivation::*;
|
pub use derivation::*;
|
||||||
pub use mode::*;
|
pub use mode::*;
|
||||||
pub use spacing::*;
|
pub use spacing::*;
|
||||||
|
|
@ -8,7 +7,6 @@ pub use theme::*;
|
||||||
|
|
||||||
mod corner;
|
mod corner;
|
||||||
mod cosmic_palette;
|
mod cosmic_palette;
|
||||||
mod density;
|
|
||||||
mod derivation;
|
mod derivation;
|
||||||
mod mode;
|
mod mode;
|
||||||
mod spacing;
|
mod spacing;
|
||||||
|
|
|
||||||
|
|
@ -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<Density> 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<Spacing> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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::{
|
use crate::{
|
||||||
Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, DARK_PALETTE,
|
Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, DARK_PALETTE,
|
||||||
LIGHT_PALETTE, NAME, Spacing, ThemeMode,
|
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 cosmic_config::{Config, CosmicConfigEntry};
|
||||||
use palette::{
|
use palette::color_difference::Wcag21RelativeContrast;
|
||||||
IntoColor, Oklcha, Srgb, Srgba, WithAlpha, color_difference::Wcag21RelativeContrast, rgb::Rgb,
|
use palette::rgb::Rgb;
|
||||||
};
|
use palette::{IntoColor, Oklcha, Srgb, Srgba, WithAlpha};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
|
|
@ -75,6 +75,8 @@ pub struct Theme {
|
||||||
pub icon_button: Component,
|
pub icon_button: Component,
|
||||||
/// link button element colors
|
/// link button element colors
|
||||||
pub link_button: Component,
|
pub link_button: Component,
|
||||||
|
/// list button element colors
|
||||||
|
pub list_button: Component,
|
||||||
/// text button element colors
|
/// text button element colors
|
||||||
pub text_button: Component,
|
pub text_button: Component,
|
||||||
/// button component styling
|
/// button component styling
|
||||||
|
|
@ -1291,6 +1293,15 @@ impl ThemeBuilder {
|
||||||
component.on_disabled = over(component.on.with_alpha(0.5), component.base);
|
component.on_disabled = over(component.on.with_alpha(0.5), component.base);
|
||||||
component
|
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: Component::colored_component(
|
||||||
success,
|
success,
|
||||||
control_steps_array[0],
|
control_steps_array[0],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use configparser::ini::WriteOptions;
|
use configparser::ini::WriteOptions;
|
||||||
use palette::{Srgba, rgb::Rgba};
|
use palette::Srgba;
|
||||||
|
use palette::rgb::Rgba;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::Theme;
|
use crate::Theme;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use crate::Theme;
|
use crate::Theme;
|
||||||
use configparser::ini::Ini;
|
use configparser::ini::Ini;
|
||||||
use palette::{Mix, Srgba, WithAlpha, blend::Compose, rgb::Rgba};
|
use palette::blend::Compose;
|
||||||
use std::{
|
use palette::rgb::Rgba;
|
||||||
fs::{self, File},
|
use palette::{Mix, Srgba, WithAlpha};
|
||||||
io::Write,
|
use std::fs::{self, File};
|
||||||
path::PathBuf,
|
use std::io::Write;
|
||||||
vec,
|
use std::path::PathBuf;
|
||||||
};
|
use std::vec;
|
||||||
|
|
||||||
use super::{OutputError, qt_settings_ini_style};
|
use super::{OutputError, qt_settings_ini_style};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
use crate::Theme;
|
use crate::Theme;
|
||||||
use configparser::ini::Ini;
|
use configparser::ini::Ini;
|
||||||
use cosmic_config::CosmicConfigEntry;
|
use cosmic_config::CosmicConfigEntry;
|
||||||
use palette::{Mix, Srgba, blend::Compose};
|
use palette::blend::Compose;
|
||||||
use std::{
|
use palette::{Mix, Srgba};
|
||||||
fs::{self, File},
|
use std::fs::{self, File};
|
||||||
io::{self, Write},
|
use std::io::{self, Write};
|
||||||
path::{Path, PathBuf},
|
use std::path::{Path, PathBuf};
|
||||||
};
|
|
||||||
|
|
||||||
use super::{OutputError, qt_settings_ini_style};
|
use super::{OutputError, qt_settings_ini_style};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use almost::equal;
|
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
|
/// Get an array of 100 colors with a specific hue and chroma
|
||||||
/// over the full range of lightness.
|
/// over the full range of lightness.
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ use cosmic::app::{Core, Settings, Task};
|
||||||
use cosmic::executor;
|
use cosmic::executor;
|
||||||
use cosmic::iced::{alignment, Length, Size};
|
use cosmic::iced::{alignment, Length, Size};
|
||||||
use cosmic::prelude::*;
|
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
|
/// Runs application with these settings
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ use cosmic::iced::core::window;
|
||||||
use cosmic::iced::window::Id;
|
use cosmic::iced::window::Id;
|
||||||
use cosmic::iced::{Length, Rectangle};
|
use cosmic::iced::{Length, Rectangle};
|
||||||
use cosmic::surface::action::{app_popup, destroy_popup};
|
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;
|
use cosmic::Element;
|
||||||
|
|
||||||
const ID: &str = "com.system76.CosmicAppletExample";
|
const ID: &str = "com.system76.CosmicAppletExample";
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,10 @@
|
||||||
|
|
||||||
use cosmic::app::Settings;
|
use cosmic::app::Settings;
|
||||||
use cosmic::iced::{Alignment, Length, Size};
|
use cosmic::iced::{Alignment, Length, Size};
|
||||||
|
use cosmic::prelude::*;
|
||||||
use cosmic::widget::menu::{self, KeyBind};
|
use cosmic::widget::menu::{self, KeyBind};
|
||||||
use cosmic::widget::nav_bar;
|
use cosmic::widget::nav_bar;
|
||||||
use cosmic::{executor, iced, prelude::*, widget, Core};
|
use cosmic::{executor, iced, widget, Core};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
|
@ -239,7 +240,9 @@ impl cosmic::Application for App {
|
||||||
widget::progress_bar::linear::Linear::new()
|
widget::progress_bar::linear::Linear::new()
|
||||||
.girth(10.0)
|
.girth(10.0)
|
||||||
.progress(self.progress)
|
.progress(self.progress)
|
||||||
.width(Length::Fill),
|
.width(Length::Fill)
|
||||||
|
.markers([0.25, 0.5, 0.75])
|
||||||
|
.segment_spacing(2),
|
||||||
)
|
)
|
||||||
.push(
|
.push(
|
||||||
widget::progress_bar::circular::Circular::new()
|
widget::progress_bar::circular::Circular::new()
|
||||||
|
|
|
||||||
|
|
@ -88,4 +88,7 @@ pub fn main() {
|
||||||
|
|
||||||
println!("Testing state");
|
println!("Testing state");
|
||||||
test_config(Config::new_state("com.system76.Example", 1).unwrap());
|
test_config(Config::new_state("com.system76.Example", 1).unwrap());
|
||||||
|
|
||||||
|
println!("Testing data");
|
||||||
|
test_config(Config::new_data("com.system76.Example", 1).unwrap());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,15 +23,11 @@ use cosmic::{
|
||||||
Element,
|
Element,
|
||||||
};
|
};
|
||||||
use cosmic_time::{Instant, Timeline};
|
use cosmic_time::{Instant, Timeline};
|
||||||
use std::{
|
use std::cell::RefCell;
|
||||||
cell::RefCell,
|
use std::rc::Rc;
|
||||||
rc::Rc,
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
sync::{
|
use std::sync::Arc;
|
||||||
atomic::{AtomicU32, Ordering},
|
use std::vec;
|
||||||
Arc,
|
|
||||||
},
|
|
||||||
vec,
|
|
||||||
};
|
|
||||||
|
|
||||||
// XXX The use of button is removed because it assigns the same ID to multiple buttons, causing a crash when a11y is enabled...
|
// 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<id::Id> = Lazy::new(|| id::Id::new("BTN"));
|
// static BTN: Lazy<id::Id> = Lazy::new(|| id::Id::new("BTN"));
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
use super::{Page, Window};
|
use super::{Page, Window};
|
||||||
use cosmic::{
|
use cosmic::iced::widget::{column, text};
|
||||||
iced::widget::{column, text},
|
use cosmic::widget::{list_column, settings, toggler};
|
||||||
widget::{list_column, settings, toggler},
|
use cosmic::Element;
|
||||||
Element,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,17 @@
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
use cosmic::{
|
use cosmic::iced::widget::{checkbox, column, progress_bar, radio, slider, text};
|
||||||
cosmic_theme,
|
use cosmic::iced::{Alignment, Length};
|
||||||
iced::widget::{checkbox, column, progress_bar, radio, slider, text},
|
use cosmic::iced_core::id;
|
||||||
iced::{Alignment, Length},
|
use cosmic::theme::ThemeType;
|
||||||
iced_core::id,
|
use cosmic::widget::color_picker::ColorPickerUpdate;
|
||||||
theme::ThemeType,
|
use cosmic::widget::{
|
||||||
widget::{
|
button, dropdown, icon, layer_container as container, segmented_button, segmented_control,
|
||||||
button, color_picker::ColorPickerUpdate, dropdown, icon, layer_container as container,
|
settings, spin_button, tab_bar, toggler, ColorPickerModel,
|
||||||
segmented_button, segmented_control, settings, spin_button, tab_bar, toggler,
|
|
||||||
ColorPickerModel,
|
|
||||||
},
|
|
||||||
Element,
|
|
||||||
};
|
};
|
||||||
|
use cosmic::{cosmic_theme, Element};
|
||||||
use cosmic_time::{anim, chain, Timeline};
|
use cosmic_time::{anim, chain, Timeline};
|
||||||
use fraction::{Decimal, ToPrimitive};
|
use fraction::{Decimal, ToPrimitive};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
use cosmic::{
|
use cosmic::iced::widget::{column, container, horizontal_space, image, row, svg, text};
|
||||||
iced::widget::{column, container, horizontal_space, image, row, svg, text},
|
use cosmic::iced::Length;
|
||||||
iced::Length,
|
use cosmic::widget::{list_column, settings, toggler};
|
||||||
theme,
|
use cosmic::{theme, Element};
|
||||||
widget::{list_column, settings, toggler},
|
|
||||||
Element,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{Page, SubPage, Window};
|
use super::{Page, SubPage, Window};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
use cosmic::{
|
use cosmic::iced::widget::{horizontal_space, row, text};
|
||||||
iced::widget::{horizontal_space, row, text},
|
use cosmic::iced::Length;
|
||||||
iced::Length,
|
use cosmic::widget::{icon, list_column, settings};
|
||||||
widget::{icon, list_column, settings},
|
use cosmic::Element;
|
||||||
Element,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{Message, Page, SubPage, Window};
|
use super::{Message, Page, SubPage, Window};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,9 @@ use std::{env, process};
|
||||||
use cosmic::app::{Core, Settings, Task};
|
use cosmic::app::{Core, Settings, Task};
|
||||||
use cosmic::iced::alignment::{Horizontal, Vertical};
|
use cosmic::iced::alignment::{Horizontal, Vertical};
|
||||||
use cosmic::iced::keyboard::Key;
|
use cosmic::iced::keyboard::Key;
|
||||||
use cosmic::iced::window;
|
use cosmic::iced::{window, Length, Size};
|
||||||
use cosmic::iced::{Length, Size};
|
|
||||||
use cosmic::widget::menu::action::MenuAction;
|
use cosmic::widget::menu::action::MenuAction;
|
||||||
use cosmic::widget::menu::key_bind::KeyBind;
|
use cosmic::widget::menu::key_bind::{KeyBind, Modifier};
|
||||||
use cosmic::widget::menu::key_bind::Modifier;
|
|
||||||
use cosmic::widget::menu::{self, ItemHeight, ItemWidth};
|
use cosmic::widget::menu::{self, ItemHeight, ItemWidth};
|
||||||
use cosmic::widget::RcElementWrapper;
|
use cosmic::widget::RcElementWrapper;
|
||||||
use cosmic::{executor, Element};
|
use cosmic::{executor, Element};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::app::Core;
|
||||||
app::Core,
|
use cosmic::iced::core::{id, Alignment, Length, Point};
|
||||||
iced::core::{id, Alignment, Length, Point},
|
use cosmic::iced::widget::{column, container, scrollable, text};
|
||||||
iced::widget::{column, container, scrollable, text},
|
use cosmic::iced::{self, event, window, Subscription};
|
||||||
iced::{self, event, window, Subscription},
|
use cosmic::prelude::*;
|
||||||
prelude::*,
|
use cosmic::widget::{button, header_bar};
|
||||||
widget::{button, header_bar},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
|
|
|
||||||
|
|
@ -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::widget::{column, container, spin_button};
|
||||||
use cosmic::Apply;
|
use cosmic::{Application, Apply, Element};
|
||||||
use cosmic::{
|
|
||||||
app::{Core, Task},
|
|
||||||
iced::{
|
|
||||||
self,
|
|
||||||
alignment::{Horizontal, Vertical},
|
|
||||||
Alignment, Size,
|
|
||||||
},
|
|
||||||
Application, Element,
|
|
||||||
};
|
|
||||||
use fraction::Decimal;
|
use fraction::Decimal;
|
||||||
|
|
||||||
pub struct SpinButtonExamplApp {
|
pub struct SpinButtonExamplApp {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@
|
||||||
|
|
||||||
use cosmic::app::{Core, Settings, Task};
|
use cosmic::app::{Core, Settings, Task};
|
||||||
use cosmic::iced::Subscription;
|
use cosmic::iced::Subscription;
|
||||||
use cosmic::{executor, prelude::*, widget};
|
use cosmic::prelude::*;
|
||||||
|
use cosmic::{executor, widget};
|
||||||
|
|
||||||
/// Runs application with these settings
|
/// Runs application with these settings
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,7 @@ use chrono::Datelike;
|
||||||
use cosmic::app::{Core, Settings, Task};
|
use cosmic::app::{Core, Settings, Task};
|
||||||
use cosmic::iced::Size;
|
use cosmic::iced::Size;
|
||||||
use cosmic::prelude::*;
|
use cosmic::prelude::*;
|
||||||
use cosmic::widget::table;
|
use cosmic::widget::{self, nav_bar, table};
|
||||||
use cosmic::widget::{self, nav_bar};
|
|
||||||
use cosmic::{executor, iced};
|
use cosmic::{executor, iced};
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash)]
|
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash)]
|
||||||
|
|
|
||||||
|
|
@ -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 = Κυρ
|
||||||
0
i18n/lo/libcosmic.ftl
Normal file
0
i18n/lo/libcosmic.ftl
Normal file
|
|
@ -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 = Нед
|
||||||
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
imports_granularity = "Module"
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright 2023 System76 <info@system76.com>
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use crate::surface;
|
use crate::config::CosmicTk;
|
||||||
use crate::theme::Theme;
|
use crate::theme::Theme;
|
||||||
use crate::widget::nav_bar;
|
use crate::widget::nav_bar;
|
||||||
use crate::{config::CosmicTk, keyboard_nav};
|
use crate::{keyboard_nav, surface};
|
||||||
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState};
|
use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState};
|
||||||
use cosmic_theme::ThemeMode;
|
use cosmic_theme::ThemeMode;
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,11 @@ use crate::prelude::*;
|
||||||
use crate::theme::THEME;
|
use crate::theme::THEME;
|
||||||
use crate::widget::{container, id_container, menu, nav_bar, popover, space};
|
use crate::widget::{container, id_container, menu, nav_bar, popover, space};
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
use iced::{Length, Subscription};
|
use iced::{Length, Subscription, theme, window};
|
||||||
use iced::{theme, window};
|
|
||||||
pub use settings::Settings;
|
pub use settings::Settings;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
#[cold]
|
#[cold]
|
||||||
pub(crate) fn iced_settings<App: Application>(
|
pub(crate) fn iced_settings<App: Application>(
|
||||||
|
|
@ -773,7 +773,6 @@ impl<App: Application> ApplicationExt for App {
|
||||||
.focused(focused)
|
.focused(focused)
|
||||||
.maximized(maximized)
|
.maximized(maximized)
|
||||||
.sharp_corners(sharp_corners)
|
.sharp_corners(sharp_corners)
|
||||||
.transparent(content_container)
|
|
||||||
.title(&core.window.header_title)
|
.title(&core.window.header_title)
|
||||||
.on_drag(crate::Action::Cosmic(Action::Drag))
|
.on_drag(crate::Action::Cosmic(Action::Drag))
|
||||||
.on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu))
|
.on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu))
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
//! Distribute content vertically.
|
//! Distribute content vertically.
|
||||||
use crate::iced;
|
use crate::iced;
|
||||||
use iced::core::alignment::{self, Alignment};
|
use iced::core::alignment::{self, Alignment};
|
||||||
use iced::core::event::Event;
|
use iced::core::event::{self, Event};
|
||||||
use iced::core::layout;
|
|
||||||
use iced::core::mouse;
|
|
||||||
use iced::core::overlay;
|
|
||||||
use iced::core::renderer;
|
|
||||||
use iced::core::widget::{Operation, Tree};
|
use iced::core::widget::{Operation, Tree};
|
||||||
use iced::core::{
|
use iced::core::{
|
||||||
Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget,
|
Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget,
|
||||||
widget,
|
layout, mouse, overlay, renderer, widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A container that distributes its contents vertically.
|
/// A container that distributes its contents vertically.
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,31 @@
|
||||||
#[cfg(feature = "applet-token")]
|
#[cfg(feature = "applet-token")]
|
||||||
pub mod token;
|
pub mod token;
|
||||||
|
|
||||||
use crate::app::{BootData, BootDataInner, cosmic};
|
use crate::app::{BootData, BootDataInner, cosmic, iced_settings};
|
||||||
use crate::{
|
use crate::cctk::sctk;
|
||||||
Application, Element, Renderer,
|
use crate::theme::{self, Button, THEME, system_dark, system_light};
|
||||||
app::iced_settings,
|
use crate::widget::autosize::{self, Autosize, autosize};
|
||||||
cctk::sctk,
|
use crate::widget::column::Column;
|
||||||
theme::{self, Button, THEME, system_dark, system_light},
|
use crate::widget::row::Row;
|
||||||
widget::{
|
use crate::widget::space::{horizontal, vertical};
|
||||||
self,
|
use crate::widget::{self, layer_container};
|
||||||
autosize::{self, Autosize, autosize},
|
use crate::{Application, Element, Renderer};
|
||||||
layer_container,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use cosmic_panel_config;
|
pub use cosmic_panel_config;
|
||||||
use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize};
|
use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize};
|
||||||
use iced::{
|
use iced::alignment::{Alignment, Horizontal, Vertical};
|
||||||
self, Color, Length, Limits, Rectangle,
|
use iced::widget::Container;
|
||||||
alignment::{Alignment, Horizontal, Vertical},
|
use iced::{self, Color, Length, Limits, Rectangle, window};
|
||||||
widget::Container,
|
|
||||||
window,
|
|
||||||
};
|
|
||||||
use iced_core::{Padding, Shadow};
|
use iced_core::{Padding, Shadow};
|
||||||
use iced_runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner};
|
use iced_runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner};
|
||||||
use iced_widget::Text;
|
use iced_widget::Text;
|
||||||
use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity};
|
use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity};
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::cell::RefCell;
|
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;
|
use tracing::info;
|
||||||
|
|
||||||
pub mod column;
|
pub mod column;
|
||||||
|
|
@ -213,6 +211,12 @@ impl Context {
|
||||||
icon: widget::icon::Handle,
|
icon: widget::icon::Handle,
|
||||||
) -> crate::widget::Button<'a, Message> {
|
) -> crate::widget::Button<'a, Message> {
|
||||||
let suggested = self.suggested_size(icon.symbolic);
|
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 symbolic = icon.symbolic;
|
||||||
let icon = widget::icon(icon)
|
let icon = widget::icon(icon)
|
||||||
.class(if symbolic {
|
.class(if symbolic {
|
||||||
|
|
@ -455,8 +459,10 @@ impl Context {
|
||||||
&self,
|
&self,
|
||||||
content: impl Into<Element<'a, Message>>,
|
content: impl Into<Element<'a, Message>>,
|
||||||
) -> Autosize<'a, Message, crate::Theme, crate::Renderer> {
|
) -> 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 w = autosize(content, AUTOSIZE_MAIN_ID.clone());
|
||||||
let mut limits = Limits::NONE;
|
let mut limits = Limits::NONE;
|
||||||
|
let suggested_window_size = self.suggested_window_size();
|
||||||
|
|
||||||
if let Some(width) = self
|
if let Some(width) = self
|
||||||
.suggested_bounds
|
.suggested_bounds
|
||||||
|
|
@ -567,7 +573,7 @@ pub fn run<App: Application>(flags: App::Flags) -> iced::Result {
|
||||||
// window_settings = window_settings.clone();
|
// window_settings = window_settings.clone();
|
||||||
core.main_window = Some(iced_core::window::Id::RESERVED);
|
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::<App> {
|
BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> {
|
||||||
flags,
|
flags,
|
||||||
core,
|
core,
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,10 @@ use crate::iced;
|
||||||
use iced::core::alignment::{self, Alignment};
|
use iced::core::alignment::{self, Alignment};
|
||||||
use iced::core::event::Event;
|
use iced::core::event::Event;
|
||||||
use iced::core::layout::{self, Layout};
|
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::widget::{Operation, Tree};
|
||||||
use iced::core::{
|
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.
|
/// A container that distributes its contents horizontally.
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
use crate::iced;
|
use crate::iced;
|
||||||
use cctk::sctk::reexports::calloop;
|
use cctk::sctk::reexports::calloop;
|
||||||
use futures::{
|
use futures::channel::mpsc::{UnboundedReceiver, unbounded};
|
||||||
SinkExt, StreamExt,
|
use futures::{SinkExt, StreamExt};
|
||||||
channel::mpsc::{UnboundedReceiver, unbounded},
|
|
||||||
};
|
|
||||||
use iced::Subscription;
|
use iced::Subscription;
|
||||||
use iced_futures::futures;
|
use iced_futures::{futures, stream};
|
||||||
use iced_futures::stream;
|
use std::fmt::Debug;
|
||||||
use std::{fmt::Debug, hash::Hash, thread::JoinHandle};
|
use std::hash::Hash;
|
||||||
|
use std::thread::JoinHandle;
|
||||||
|
|
||||||
use super::wayland_handler::wayland_handler;
|
use super::wayland_handler::wayland_handler;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,20 @@
|
||||||
use std::os::{
|
use std::os::fd::{FromRawFd, RawFd};
|
||||||
fd::{FromRawFd, RawFd},
|
use std::os::unix::net::UnixStream;
|
||||||
unix::net::UnixStream,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::subscription::{TokenRequest, TokenUpdate};
|
use super::subscription::{TokenRequest, TokenUpdate};
|
||||||
use cctk::{
|
use cctk::sctk::activation::{RequestData, RequestDataExt};
|
||||||
sctk::{
|
use cctk::sctk::reexports::calloop;
|
||||||
self,
|
use cctk::sctk::reexports::calloop_wayland_source::WaylandSource;
|
||||||
activation::{RequestData, RequestDataExt},
|
use cctk::sctk::seat::{SeatHandler, SeatState};
|
||||||
reexports::{calloop, calloop_wayland_source::WaylandSource},
|
use cctk::sctk::{self};
|
||||||
seat::{SeatHandler, SeatState},
|
use cctk::wayland_client::protocol::wl_seat::WlSeat;
|
||||||
},
|
use cctk::wayland_client::protocol::wl_surface::WlSurface;
|
||||||
wayland_client::{
|
use cctk::wayland_client::{self};
|
||||||
self,
|
|
||||||
protocol::{wl_seat::WlSeat, wl_surface::WlSurface},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use iced_futures::futures::channel::mpsc::UnboundedSender;
|
use iced_futures::futures::channel::mpsc::UnboundedSender;
|
||||||
use sctk::{
|
use sctk::activation::{ActivationHandler, ActivationState};
|
||||||
activation::{ActivationHandler, ActivationState},
|
use sctk::registry::{ProvidesRegistryState, RegistryState};
|
||||||
registry::{ProvidesRegistryState, RegistryState},
|
use wayland_client::globals::registry_queue_init;
|
||||||
};
|
use wayland_client::{Connection, QueueHandle};
|
||||||
use wayland_client::{Connection, QueueHandle, globals::registry_queue_init};
|
|
||||||
|
|
||||||
struct AppData {
|
struct AppData {
|
||||||
exit: bool,
|
exit: bool,
|
||||||
|
|
@ -171,7 +164,18 @@ pub(crate) fn wayland_handler(
|
||||||
if app_data.exit {
|
if app_data.exit {
|
||||||
break;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
// Copyright 2024 System76 <info@system76.com>
|
// Copyright 2024 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use {
|
use crate::ApplicationExt;
|
||||||
crate::ApplicationExt,
|
use iced::Subscription;
|
||||||
iced::Subscription,
|
use iced_futures::futures::SinkExt;
|
||||||
iced_futures::futures::{
|
use iced_futures::futures::channel::mpsc::{Receiver, Sender};
|
||||||
SinkExt,
|
use std::any::TypeId;
|
||||||
channel::mpsc::{Receiver, Sender},
|
use std::collections::HashMap;
|
||||||
},
|
use url::Url;
|
||||||
std::{any::TypeId, collections::HashMap},
|
use zbus::zvariant::Value;
|
||||||
url::Url,
|
use zbus::{interface, proxy};
|
||||||
zbus::{interface, proxy, zvariant::Value},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cold]
|
#[cold]
|
||||||
pub fn subscription<App: ApplicationExt>() -> Subscription<crate::Action<App::Message>> {
|
pub fn subscription<App: ApplicationExt>() -> Subscription<crate::Action<App::Message>> {
|
||||||
|
|
|
||||||
|
|
@ -876,7 +876,8 @@ trait SystemdManger {
|
||||||
#[cfg(all(test, not(windows)))]
|
#[cfg(all(test, not(windows)))]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::{env, fs, path::Path, path::PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::{env, fs};
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
struct EnvVarGuard {
|
struct EnvVarGuard {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
#![allow(clippy::module_name_repetitions)]
|
#![allow(clippy::module_name_repetitions)]
|
||||||
#![cfg_attr(target_os = "redox", feature(lazy_cell))]
|
#![cfg_attr(target_os = "redox", feature(lazy_cell))]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
|
|
||||||
/// Recommended default imports.
|
/// Recommended default imports.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use i18n_embed::{
|
use i18n_embed::fluent::{FluentLanguageLoader, fluent_language_loader};
|
||||||
DefaultLocalizer, LanguageLoader, Localizer,
|
use i18n_embed::{DefaultLocalizer, LanguageLoader, Localizer};
|
||||||
fluent::{FluentLanguageLoader, fluent_language_loader},
|
|
||||||
};
|
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
use std::sync::{LazyLock, OnceLock};
|
use std::sync::{LazyLock, OnceLock};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
use smol::io::AsyncReadExt;
|
use smol::io::AsyncReadExt;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::os::fd::OwnedFd;
|
use std::os::fd::OwnedFd;
|
||||||
use std::process::{Command, Stdio, exit};
|
use std::process::{Command, Stdio};
|
||||||
#[cfg(feature = "tokio")]
|
#[cfg(feature = "tokio")]
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
|
|
||||||
|
|
@ -56,24 +56,33 @@ pub async fn spawn(mut command: Command) -> Option<u32> {
|
||||||
|
|
||||||
match unsafe { libc::fork() } {
|
match unsafe { libc::fork() } {
|
||||||
// Parent process
|
// Parent process
|
||||||
1.. => {
|
fork_pid @ 1..=i32::MAX => {
|
||||||
// Drop copy of write end, then read PID from pipe
|
// Drop copy of write end, then read PID from pipe
|
||||||
drop(write);
|
drop(write);
|
||||||
let pid = read_from_pipe(read).await;
|
let pid = read_from_pipe(read).await;
|
||||||
// wait to prevent zombie
|
_ = rustix::process::waitpid(
|
||||||
_ = rustix::process::wait(rustix::process::WaitOptions::empty());
|
rustix::process::Pid::from_raw(fork_pid),
|
||||||
|
rustix::process::WaitOptions::empty(),
|
||||||
|
);
|
||||||
pid
|
pid
|
||||||
}
|
}
|
||||||
|
|
||||||
// Child process
|
// Child process
|
||||||
0 => {
|
0 => {
|
||||||
let _res = rustix::process::setsid();
|
let _res = rustix::process::setsid();
|
||||||
if let Ok(child) = command.spawn() {
|
let exit_status = if let Ok(child) = command.spawn() {
|
||||||
// Write PID to pipe
|
// Write PID to pipe
|
||||||
let _ = rustix::io::write(write, &child.id().to_be_bytes());
|
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 => {
|
..=-1 => {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ use super::Action;
|
||||||
use crate::Application;
|
use crate::Application;
|
||||||
|
|
||||||
use iced::window;
|
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.
|
/// Used to produce a destroy popup message from within a widget.
|
||||||
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@
|
||||||
|
|
||||||
pub mod action;
|
pub mod action;
|
||||||
|
|
||||||
use iced::Limits;
|
use iced::{Limits, Size, Task};
|
||||||
use iced::Size;
|
use std::future::Future;
|
||||||
use iced::Task;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Ignore this message in your application. It will be intercepted.
|
/// Ignore this message in your application. It will be intercepted.
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,9 @@
|
||||||
pub mod portal;
|
pub mod portal;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
|
|
||||||
use cosmic_config::CosmicConfigEntry;
|
use cosmic_config::{CosmicConfigEntry, config_subscription};
|
||||||
use cosmic_theme::Component;
|
use cosmic_theme::{Component, LayeredTheme, Spacing, ThemeMode};
|
||||||
use cosmic_theme::LayeredTheme;
|
use iced_futures::Subscription;
|
||||||
use cosmic_theme::Spacing;
|
|
||||||
use cosmic_theme::ThemeMode;
|
|
||||||
use iced_runtime::{Appearance, DefaultStyle};
|
use iced_runtime::{Appearance, DefaultStyle};
|
||||||
use std::sync::{Arc, LazyLock, Mutex};
|
use std::sync::{Arc, LazyLock, Mutex};
|
||||||
pub use style::*;
|
pub use style::*;
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,8 @@
|
||||||
use cosmic_theme::Component;
|
use cosmic_theme::Component;
|
||||||
use iced_core::{Background, Color};
|
use iced_core::{Background, Color};
|
||||||
|
|
||||||
use crate::{
|
use crate::theme::TRANSPARENT_COMPONENT;
|
||||||
theme::TRANSPARENT_COMPONENT,
|
use crate::widget::button::{Catalog, Style};
|
||||||
widget::button::{Catalog, Style},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub enum Button {
|
pub enum Button {
|
||||||
|
|
@ -150,7 +148,7 @@ pub fn appearance(
|
||||||
}
|
}
|
||||||
Button::ListItem(radii) => {
|
Button::ListItem(radii) => {
|
||||||
corner_radii = radii;
|
corner_radii = radii;
|
||||||
let (background, text, icon) = color(&cosmic.background.component);
|
let (background, text, icon) = color(&cosmic.list_button);
|
||||||
|
|
||||||
if selected {
|
if selected {
|
||||||
appearance.background =
|
appearance.background =
|
||||||
|
|
@ -197,7 +195,7 @@ impl Catalog for crate::Theme {
|
||||||
return active(focused, self);
|
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!(
|
let text_color = if matches!(
|
||||||
style,
|
style,
|
||||||
Button::Icon | Button::IconVertical | Button::HeaderBar
|
Button::Icon | Button::IconVertical | Button::HeaderBar
|
||||||
|
|
@ -209,14 +207,7 @@ impl Catalog for crate::Theme {
|
||||||
};
|
};
|
||||||
|
|
||||||
(component.base.into(), text_color, text_color)
|
(component.base.into(), text_color, text_color)
|
||||||
});
|
})
|
||||||
|
|
||||||
if let Button::ListItem(_) = style
|
|
||||||
&& !selected {
|
|
||||||
s.background = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn disabled(&self, style: &Self::Class) -> Style {
|
fn disabled(&self, style: &Self::Class) -> Style {
|
||||||
|
|
@ -244,7 +235,7 @@ impl Catalog for crate::Theme {
|
||||||
return hovered(focused, self);
|
return hovered(focused, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut s = appearance(
|
appearance(
|
||||||
self,
|
self,
|
||||||
focused || matches!(style, Button::Image),
|
focused || matches!(style, Button::Image),
|
||||||
selected,
|
selected,
|
||||||
|
|
@ -263,14 +254,7 @@ impl Catalog for crate::Theme {
|
||||||
|
|
||||||
(component.hover.into(), text_color, text_color)
|
(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 {
|
fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,17 @@
|
||||||
|
|
||||||
use crate::theme::{CosmicComponent, TRANSPARENT_COMPONENT, Theme};
|
use crate::theme::{CosmicComponent, TRANSPARENT_COMPONENT, Theme};
|
||||||
use cosmic_theme::composite::over;
|
use cosmic_theme::composite::over;
|
||||||
use iced::{
|
use iced::overlay::menu;
|
||||||
overlay::menu,
|
use iced::theme::Base;
|
||||||
theme::Base,
|
use iced::widget::slider::{self, Rail};
|
||||||
widget::{
|
use iced::widget::{
|
||||||
button as iced_button, checkbox as iced_checkbox, combo_box, container as iced_container,
|
button as iced_button, checkbox as iced_checkbox, combo_box, container as iced_container,
|
||||||
pane_grid, pick_list, progress_bar, radio, rule, scrollable,
|
pane_grid, pick_list, progress_bar, radio, rule, scrollable, svg, toggler,
|
||||||
slider::{self, Rail},
|
|
||||||
svg, toggler,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use iced_core::{Background, Border, Color, Shadow, Vector};
|
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 palette::WithAlpha;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
|
@ -1339,9 +1338,16 @@ impl iced_widget::text::Catalog for Theme {
|
||||||
match class {
|
match class {
|
||||||
Text::Accent => iced_widget::text::Style {
|
Text::Accent => iced_widget::text::Style {
|
||||||
color: Some(self.cosmic().accent_text_color().into()),
|
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),
|
Text::Custom(f) => f(self),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ impl StyleSheet for Theme {
|
||||||
background: component.base.into(),
|
background: component.base.into(),
|
||||||
border_width: 1.0,
|
border_width: 1.0,
|
||||||
bar_border_radius: cosmic.corner_radii.radius_xl,
|
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(),
|
border_color: component.divider.into(),
|
||||||
background_expand: [1; 4],
|
background_expand: [1; 4],
|
||||||
path: component.hover.into(),
|
path: component.hover.into(),
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,13 @@
|
||||||
|
|
||||||
//! Contains stylesheet implementation for [`crate::widget::segmented_button`].
|
//! Contains stylesheet implementation for [`crate::widget::segmented_button`].
|
||||||
|
|
||||||
use crate::widget::segmented_button::{Appearance, ItemAppearance, StyleSheet};
|
use crate::theme::Theme;
|
||||||
use crate::{theme::Theme, widget::segmented_button::ItemStatusAppearance};
|
use crate::widget::segmented_button::{
|
||||||
|
Appearance, ItemAppearance, ItemStatusAppearance, StyleSheet,
|
||||||
|
};
|
||||||
use iced::Border;
|
use iced::Border;
|
||||||
use iced_core::{Background, border::Radius};
|
use iced_core::Background;
|
||||||
|
use iced_core::border::Radius;
|
||||||
use palette::WithAlpha;
|
use palette::WithAlpha;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -143,7 +146,8 @@ mod horizontal {
|
||||||
use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance};
|
use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance};
|
||||||
use cosmic_theme::{Component, Container};
|
use cosmic_theme::{Component, Container};
|
||||||
use iced::Border;
|
use iced::Border;
|
||||||
use iced_core::{Background, border::Radius};
|
use iced_core::Background;
|
||||||
|
use iced_core::border::Radius;
|
||||||
use palette::WithAlpha;
|
use palette::WithAlpha;
|
||||||
|
|
||||||
pub fn tab_bar(cosmic: &cosmic_theme::Theme, container: &Container) -> Appearance {
|
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 crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance};
|
||||||
use cosmic_theme::{Component, Container};
|
use cosmic_theme::{Component, Container};
|
||||||
use iced::Border;
|
use iced::Border;
|
||||||
use iced_core::{Background, border::Radius};
|
use iced_core::Background;
|
||||||
|
use iced_core::border::Radius;
|
||||||
use palette::WithAlpha;
|
use palette::WithAlpha;
|
||||||
|
|
||||||
pub fn tab_bar(cosmic: &cosmic_theme::Theme, container: &Container) -> Appearance {
|
pub fn tab_bar(cosmic: &cosmic_theme::Theme, container: &Container) -> Appearance {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
use crate::{
|
use crate::iced::{Alignment, Length};
|
||||||
Apply, Element, fl,
|
use crate::widget::{self, list};
|
||||||
iced::{Alignment, Length},
|
use crate::{Apply, Element, fl};
|
||||||
widget::{self, list},
|
|
||||||
};
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, derive_setters::Setters)]
|
#[derive(Debug, Default, Clone, derive_setters::Setters)]
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,10 @@
|
||||||
use iced::Size;
|
use iced::Size;
|
||||||
use iced::widget::Container;
|
use iced::widget::Container;
|
||||||
use iced_core::event::Event;
|
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::widget::Tree;
|
||||||
use iced_core::{
|
use iced_core::{
|
||||||
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget,
|
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget,
|
||||||
|
layout, mouse, overlay, renderer,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use iced_widget::container::{Catalog, Style};
|
pub use iced_widget::container::{Catalog, Style};
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
//! Autosize Container, which will resize the window to its contents.
|
//! Autosize Container, which will resize the window to its contents.
|
||||||
|
|
||||||
use iced_core::event::{self, Event};
|
use iced_core::event::{self, Event};
|
||||||
use iced_core::layout;
|
|
||||||
use iced_core::mouse;
|
|
||||||
use iced_core::overlay;
|
|
||||||
use iced_core::renderer;
|
|
||||||
use iced_core::widget::{Id, Operation, Tree};
|
use iced_core::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 use iced_widget::container::{Catalog, Style};
|
||||||
|
|
||||||
pub fn autosize<'a, Message: 'static, Theme, E>(
|
pub fn autosize<'a, Message: 'static, Theme, E>(
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,13 @@
|
||||||
|
|
||||||
use super::{Builder, ButtonClass};
|
use super::{Builder, ButtonClass};
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use crate::widget::{icon::Handle, tooltip};
|
use crate::widget::icon::Handle;
|
||||||
|
use crate::widget::tooltip;
|
||||||
use apply::Apply;
|
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;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub type Button<'a, Message> = Builder<'a, Message, Icon>;
|
pub type Button<'a, Message> = Builder<'a, Message, Icon>;
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use super::Builder;
|
use super::Builder;
|
||||||
use crate::{
|
use crate::Element;
|
||||||
Element,
|
use crate::widget::image::Handle;
|
||||||
widget::{self, image::Handle},
|
use crate::widget::{self};
|
||||||
};
|
use iced_core::font::Weight;
|
||||||
use iced_core::{Length, Padding, font::Weight, widget::Id};
|
use iced_core::widget::Id;
|
||||||
|
use iced_core::{Length, Padding};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub type Button<'a, Message> = Builder<'a, Message, Image<'a, Handle, Message>>;
|
pub type Button<'a, Message> = Builder<'a, Message, Image<'a, Handle, Message>>;
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,15 @@
|
||||||
|
|
||||||
//! Hyperlink button widget
|
//! Hyperlink button widget
|
||||||
|
|
||||||
use super::Builder;
|
use super::{Builder, ButtonClass};
|
||||||
use super::ButtonClass;
|
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::widget::icon::{self, Handle};
|
use crate::widget::icon::{self, Handle};
|
||||||
use crate::widget::{button, row, tooltip};
|
use crate::widget::{button, row, tooltip};
|
||||||
|
use iced_core::font::Weight;
|
||||||
use iced_core::text::LineHeight;
|
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;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub type Button<'a, Message> = Builder<'a, Message, Hyperlink>;
|
pub type Button<'a, Message> = Builder<'a, Message, Hyperlink>;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
//! Change the apperance of a button.
|
//! 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;
|
use crate::theme::THEME;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@
|
||||||
use super::{Builder, ButtonClass};
|
use super::{Builder, ButtonClass};
|
||||||
use crate::widget::{icon, row, tooltip};
|
use crate::widget::{icon, row, tooltip};
|
||||||
use crate::{Apply, Element};
|
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;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub type Button<'a, Message> = Builder<'a, Message, Text>;
|
pub type Button<'a, Message> = Builder<'a, Message, Text>;
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,12 @@ use iced_runtime::{Action, Task, keyboard, task};
|
||||||
|
|
||||||
use iced_core::event::Event;
|
use iced_core::event::Event;
|
||||||
use iced_core::renderer::{self, Quad, Renderer};
|
use iced_core::renderer::{self, Quad, Renderer};
|
||||||
use iced_core::touch;
|
|
||||||
use iced_core::widget::Operation;
|
use iced_core::widget::Operation;
|
||||||
use iced_core::widget::tree::{self, Tree};
|
use iced_core::widget::tree::{self, Tree};
|
||||||
use iced_core::{
|
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 iced_renderer::core::widget::operation;
|
||||||
|
|
||||||
use crate::theme::THEME;
|
use crate::theme::THEME;
|
||||||
|
|
@ -644,10 +641,8 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
state: &Tree,
|
state: &Tree,
|
||||||
p: mouse::Cursor,
|
p: mouse::Cursor,
|
||||||
) -> iced_accessibility::A11yTree {
|
) -> iced_accessibility::A11yTree {
|
||||||
use iced_accessibility::{
|
use iced_accessibility::accesskit::{Action, Node, NodeId, Rect, Role};
|
||||||
A11yNode, A11yTree,
|
use iced_accessibility::{A11yNode, A11yTree};
|
||||||
accesskit::{Action, Node, NodeId, Rect, Role},
|
|
||||||
};
|
|
||||||
// TODO why is state None sometimes?
|
// TODO why is state None sometimes?
|
||||||
if matches!(state.state, iced_core::widget::tree::State::None) {
|
if matches!(state.state, iced_core::widget::tree::State::None) {
|
||||||
tracing::info!("Button state is missing.");
|
tracing::info!("Button state is missing.");
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,8 @@ use crate::widget::{button, column, grid, icon, row, text};
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
use iced::alignment::Vertical;
|
use iced::alignment::Vertical;
|
||||||
use iced_core::{Alignment, Length};
|
use iced_core::{Alignment, Length};
|
||||||
use jiff::{
|
use jiff::ToSpan;
|
||||||
ToSpan,
|
use jiff::civil::{Date, Weekday};
|
||||||
civil::{Date, Weekday},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A widget that displays an interactive calendar.
|
/// A widget that displays an interactive calendar.
|
||||||
pub fn calendar<M>(
|
pub fn calendar<M>(
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,18 @@
|
||||||
//! An expandable stack of cards
|
//! An expandable stack of cards
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::{
|
use crate::anim;
|
||||||
anim,
|
use crate::widget::card::style::Style;
|
||||||
widget::{
|
use crate::widget::icon::{self, Handle};
|
||||||
button,
|
use crate::widget::{button, column, row, text};
|
||||||
card::style::Style,
|
|
||||||
column,
|
|
||||||
icon::{self, Handle},
|
|
||||||
row, text,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use float_cmp::approx_eq;
|
use float_cmp::approx_eq;
|
||||||
use iced::widget;
|
use iced::widget;
|
||||||
use iced_core::{
|
use iced_core::border::Radius;
|
||||||
Border, Element, Event, Length, Shadow, Size, Vector, Widget, border::Radius, id::Id,
|
use iced_core::id::Id;
|
||||||
layout::Node, renderer::Quad, widget::Tree,
|
use iced_core::layout::Node;
|
||||||
};
|
use iced_core::renderer::Quad;
|
||||||
use iced_core::{widget::tree, window};
|
use iced_core::widget::{Tree, tree};
|
||||||
|
use iced_core::{Border, Element, Event, Length, Shadow, Size, Vector, Widget, window};
|
||||||
|
|
||||||
const ICON_SIZE: u16 = 16;
|
const ICON_SIZE: u16 = 16;
|
||||||
const TOP_SPACING: u16 = 4;
|
const TOP_SPACING: u16 = 4;
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@ use std::time::{Duration, Instant};
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use crate::theme::iced::Slider;
|
use crate::theme::iced::Slider;
|
||||||
use crate::theme::{Button, THEME};
|
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 derive_setters::Setters;
|
||||||
use iced::Task;
|
use iced::Task;
|
||||||
use iced_core::event::Event;
|
use iced_core::event::Event;
|
||||||
|
|
@ -25,10 +27,8 @@ use iced_core::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use iced_widget::slider::HandleShape;
|
use iced_widget::slider::HandleShape;
|
||||||
use iced_widget::{
|
use iced_widget::space::{horizontal, vertical};
|
||||||
Row, canvas, column, row, scrollable,
|
use iced_widget::{Row, canvas, column, row, scrollable};
|
||||||
space::{horizontal, vertical},
|
|
||||||
};
|
|
||||||
use palette::{FromColor, RgbHue};
|
use palette::{FromColor, RgbHue};
|
||||||
|
|
||||||
use super::divider::horizontal;
|
use super::divider::horizontal;
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,7 @@ use crate::Element;
|
||||||
|
|
||||||
use iced::advanced::layout::{self, Layout};
|
use iced::advanced::layout::{self, Layout};
|
||||||
use iced::advanced::widget::{self, Operation};
|
use iced::advanced::widget::{self, Operation};
|
||||||
use iced::advanced::{Clipboard, Shell};
|
use iced::advanced::{Clipboard, Shell, overlay, renderer};
|
||||||
use iced::advanced::{overlay, renderer};
|
|
||||||
use iced::{Event, Point, Size, mouse};
|
use iced::{Event, Point, Size, mouse};
|
||||||
use iced_core::{Renderer, touch};
|
use iced_core::{Renderer, touch};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,10 @@ use crate::widget::{self, LayerContainer, button, column, container, icon, row,
|
||||||
use crate::{Apply, Element, Renderer, Theme, fl};
|
use crate::{Apply, Element, Renderer, Theme, fl};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use iced_core::Alignment;
|
|
||||||
use iced_core::event::Event;
|
use iced_core::event::Event;
|
||||||
use iced_core::widget::{Operation, Tree};
|
use iced_core::widget::{Operation, Tree};
|
||||||
use iced_core::{
|
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,
|
overlay as iced_overlay, renderer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,8 @@ impl<Message: Clone + 'static> ContextMenu<'_, Message> {
|
||||||
my_state: &mut LocalState,
|
my_state: &mut LocalState,
|
||||||
) {
|
) {
|
||||||
if self.window_id != window::Id::NONE && self.on_surface_action.is_some() {
|
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::{
|
use iced_runtime::platform_specific::wayland::popup::{
|
||||||
SctkPopupSettings, SctkPositioner,
|
SctkPopupSettings, SctkPositioner,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
use crate::{
|
use crate::iced::{Length, Pixels};
|
||||||
Element,
|
use crate::{Element, style, theme, widget};
|
||||||
iced::{Length, Pixels},
|
|
||||||
style, theme, widget,
|
|
||||||
};
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub fn dialog<'a, Message>() -> Dialog<'a, Message> {
|
pub fn dialog<'a, Message>() -> Dialog<'a, Message> {
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,17 @@
|
||||||
use std::{
|
use std::borrow::Cow;
|
||||||
borrow::Cow,
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
sync::atomic::{AtomicU64, Ordering},
|
|
||||||
};
|
|
||||||
|
|
||||||
use iced::Vector;
|
use iced::Vector;
|
||||||
|
|
||||||
use crate::{
|
use crate::Element;
|
||||||
Element,
|
use crate::widget::{Id, Widget};
|
||||||
widget::{Id, Widget},
|
|
||||||
};
|
|
||||||
|
|
||||||
use iced::{
|
use iced::clipboard::dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent};
|
||||||
Event, Length, Rectangle,
|
use iced::clipboard::mime::AllowedMimeTypes;
|
||||||
clipboard::{
|
use iced::id::Internal;
|
||||||
dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent},
|
use iced::{Event, Length, Rectangle, event, mouse, overlay};
|
||||||
mime::AllowedMimeTypes,
|
use iced_core::widget::{Tree, tree};
|
||||||
},
|
use iced_core::{self, Clipboard, Shell, layout};
|
||||||
event,
|
|
||||||
id::Internal,
|
|
||||||
mouse, overlay,
|
|
||||||
};
|
|
||||||
use iced_core::{
|
|
||||||
self, Clipboard, Shell, layout,
|
|
||||||
widget::{Tree, tree},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn dnd_destination<'a, Message: 'static>(
|
pub fn dnd_destination<'a, Message: 'static>(
|
||||||
child: impl Into<Element<'a, Message>>,
|
child: impl Into<Element<'a, Message>>,
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,14 @@
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
use iced_core::{widget::Operation, window};
|
use iced_core::widget::Operation;
|
||||||
|
use iced_core::window;
|
||||||
|
|
||||||
use crate::{
|
use crate::Element;
|
||||||
Element,
|
use crate::widget::{Id, Widget, container};
|
||||||
widget::{Id, Widget, container},
|
use iced::clipboard::dnd::{DndAction, DndEvent, SourceEvent};
|
||||||
};
|
use iced::{Event, Length, Point, Rectangle, Vector, event, mouse, overlay};
|
||||||
use iced::{
|
use iced_core::widget::{Tree, tree};
|
||||||
Event, Length, Point, Rectangle, Vector,
|
use iced_core::{self, Clipboard, Shell, layout, renderer};
|
||||||
clipboard::dnd::{DndAction, DndEvent, SourceEvent}, mouse, overlay,
|
|
||||||
};
|
|
||||||
use iced_core::{
|
|
||||||
self, Clipboard, Shell, layout, renderer,
|
|
||||||
widget::{Tree, tree},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn dnd_source<
|
pub fn dnd_source<
|
||||||
'a,
|
'a,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0 AND MIT
|
// SPDX-License-Identifier: MPL-2.0 AND MIT
|
||||||
|
|
||||||
//! Change the appearance of menus.
|
//! 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.
|
/// The appearance of a menu.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@ use iced_core::event::Event;
|
||||||
use iced_core::text::{self, Text};
|
use iced_core::text::{self, Text};
|
||||||
use iced_core::widget::tree::{self, Tree};
|
use iced_core::widget::tree::{self, Tree};
|
||||||
use iced_core::{
|
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;
|
use iced_widget::pick_list;
|
||||||
|
|
||||||
pub use iced_widget::pick_list::{Catalog, Style};
|
pub use iced_widget::pick_list::{Catalog, Style};
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ use iced_core::event::Event;
|
||||||
use iced_core::text::{self, Paragraph, Text};
|
use iced_core::text::{self, Paragraph, Text};
|
||||||
use iced_core::widget::tree::{self, Tree};
|
use iced_core::widget::tree::{self, Tree};
|
||||||
use iced_core::{
|
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 iced_widget::pick_list::{self, Catalog};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,12 @@
|
||||||
//! Based on <https://github.com/tarkah/iced_gif/>
|
//! Based on <https://github.com/tarkah/iced_gif/>
|
||||||
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fmt;
|
|
||||||
use std::io;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
use std::{fmt, io};
|
||||||
|
|
||||||
use ::image as image_rs;
|
use ::image as image_rs;
|
||||||
use iced::Task;
|
use iced::{Task, mouse};
|
||||||
use iced::mouse;
|
|
||||||
use iced_core::image::Renderer as ImageRenderer;
|
use iced_core::image::Renderer as ImageRenderer;
|
||||||
use iced_core::mouse::Cursor;
|
use iced_core::mouse::Cursor;
|
||||||
use iced_core::widget::{Tree, tree};
|
use iced_core::widget::{Tree, tree};
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ use crate::cosmic_theme::{Density, Spacing};
|
||||||
use crate::{Element, theme, widget};
|
use crate::{Element, theme, widget};
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
use derive_setters::Setters;
|
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;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
@ -400,6 +401,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
space_xxs,
|
space_xxs,
|
||||||
..
|
..
|
||||||
} = theme::spacing();
|
} = theme::spacing();
|
||||||
|
let is_ssd = self.is_ssd;
|
||||||
|
|
||||||
// Take ownership of the regions to be packed.
|
// Take ownership of the regions to be packed.
|
||||||
let mut start = std::mem::take(&mut self.start);
|
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),
|
WindowControlsPosition::Start => start.insert(0, controls),
|
||||||
}
|
}
|
||||||
|
|
||||||
let padding = if self.is_ssd {
|
let padding = if is_ssd {
|
||||||
[2, 8, 2, 8]
|
[2, 8, 2, 8]
|
||||||
} else {
|
} else {
|
||||||
match (
|
match (
|
||||||
|
|
@ -462,7 +464,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
.class(theme::Container::HeaderBar {
|
.class(theme::Container::HeaderBar {
|
||||||
focused: self.focused,
|
focused: self.focused,
|
||||||
sharp_corners: self.sharp_corners,
|
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))
|
.height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32))
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use super::{Handle, Icon};
|
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)]
|
#[derive(Debug, Clone, Default, Hash)]
|
||||||
/// Fallback icon to use if the icon was not found.
|
/// Fallback icon to use if the icon was not found.
|
||||||
|
|
@ -36,6 +39,10 @@ pub struct Named {
|
||||||
|
|
||||||
/// Prioritizes SVG over PNG
|
/// Prioritizes SVG over PNG
|
||||||
pub prefer_svg: bool,
|
pub prefer_svg: bool,
|
||||||
|
|
||||||
|
/// Extra directories to search as flat paths before the icon theme chain.
|
||||||
|
#[setters(skip)]
|
||||||
|
pub extra_paths: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Named {
|
impl Named {
|
||||||
|
|
@ -49,19 +56,30 @@ impl Named {
|
||||||
size: None,
|
size: None,
|
||||||
scale: None,
|
scale: None,
|
||||||
prefer_svg: symbolic,
|
prefer_svg: symbolic,
|
||||||
|
extra_paths: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_extra_paths(mut self, paths: Vec<PathBuf>) -> Self {
|
||||||
|
self.extra_paths = paths;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(all(unix, not(target_os = "macos")))]
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn path(self) -> Option<PathBuf> {
|
pub fn path(self) -> Option<PathBuf> {
|
||||||
let name = &*self.name;
|
let name = &*self.name;
|
||||||
let fallback = &self.fallback;
|
let fallback = &self.fallback;
|
||||||
|
let extra_paths = &self.extra_paths;
|
||||||
let locate = |theme: &str, name| {
|
let locate = |theme: &str, name| {
|
||||||
let mut lookup = freedesktop_icons::lookup(name)
|
let mut lookup = freedesktop_icons::lookup(name)
|
||||||
.with_theme(theme.as_ref())
|
.with_theme(theme.as_ref())
|
||||||
.with_cache();
|
.with_cache();
|
||||||
|
|
||||||
|
if !extra_paths.is_empty() {
|
||||||
|
lookup = lookup.with_extra_paths(extra_paths);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(scale) = self.scale {
|
if let Some(scale) = self.scale {
|
||||||
lookup = lookup.with_scale(scale);
|
lookup = lookup.with_scale(scale);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
use iced_core::event::Event;
|
use iced_core::event::{self, Event};
|
||||||
use iced_core::layout;
|
|
||||||
use iced_core::mouse;
|
|
||||||
use iced_core::overlay;
|
|
||||||
use iced_core::renderer;
|
|
||||||
use iced_core::widget::{Id, Operation, Tree};
|
use iced_core::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 use iced_widget::container::{Catalog, Style};
|
||||||
|
|
||||||
pub fn id_container<'a, Message: 'static, Theme, E>(
|
pub fn id_container<'a, Message: 'static, Theme, E>(
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
use crate::Theme;
|
use crate::Theme;
|
||||||
use cosmic_theme::LayeredTheme;
|
use cosmic_theme::LayeredTheme;
|
||||||
use iced::widget::Container;
|
use iced::widget::Container;
|
||||||
use iced_core::event::Event;
|
use iced_core::event::{self, Event};
|
||||||
use iced_core::layout;
|
|
||||||
use iced_core::mouse;
|
|
||||||
use iced_core::overlay;
|
|
||||||
use iced_core::renderer;
|
|
||||||
use iced_core::widget::Tree;
|
use iced_core::widget::Tree;
|
||||||
use iced_core::{
|
use iced_core::{
|
||||||
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget,
|
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget,
|
||||||
|
layout, mouse, overlay, renderer,
|
||||||
};
|
};
|
||||||
pub use iced_widget::container::{Catalog, Style};
|
pub use iced_widget::container::{Catalog, Style};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use crate::widget::container::Catalog;
|
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 crate::{Apply, Element, theme};
|
||||||
use iced::{Length, Padding};
|
use iced::{Length, Padding};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
// From iced_aw, license MIT
|
// From iced_aw, license MIT
|
||||||
|
|
||||||
use iced_core::{Widget, widget::Tree};
|
use iced_core::Widget;
|
||||||
use iced_widget::core::{
|
use iced_core::widget::Tree;
|
||||||
Alignment, Element, Padding, Point, Size,
|
use iced_widget::core::layout::{Limits, Node};
|
||||||
layout::{Limits, Node},
|
use iced_widget::core::{Alignment, Element, Padding, Point, Size, renderer};
|
||||||
renderer,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::widget::RcElementWrapper;
|
use crate::widget::RcElementWrapper;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use iced_core::keyboard::key::{Code, Physical};
|
||||||
use iced_core::keyboard::{Key, Modifiers};
|
use iced_core::keyboard::{Key, Modifiers};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
|
@ -27,28 +28,102 @@ pub struct KeyBind {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `modifiers` - A `Modifiers` instance representing the current active modifiers.
|
/// * `modifiers` - A `Modifiers` instance representing the current active modifiers.
|
||||||
/// * `key` - A reference to the `Key` that is being checked.
|
/// * `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
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `bool` - `true` if the key and modifiers match the `KeyBind`, `false` otherwise.
|
/// * `bool` - `true` if the key and modifiers match the `KeyBind`, `false` otherwise.
|
||||||
pub fn matches(&self, modifiers: Modifiers, key: &Key) -> bool {
|
pub fn matches(
|
||||||
let key_eq = match (key, &self.key) {
|
&self,
|
||||||
// CapsLock and Shift change the case of Key::Character, so we compare these in a case insensitive way
|
modifiers: Modifiers,
|
||||||
(Key::Character(a), Key::Character(b)) => a.eq_ignore_ascii_case(b),
|
key: &Key,
|
||||||
(a, b) => a.eq(b),
|
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
|
key_eq
|
||||||
&& modifiers.logo() == self.modifiers.contains(&Modifier::Super)
|
&& modifiers.logo() == self.modifiers.contains(&Modifier::Super)
|
||||||
&& modifiers.control() == self.modifiers.contains(&Modifier::Ctrl)
|
&& modifiers.control() == self.modifiers.contains(&Modifier::Ctrl)
|
||||||
&& modifiers.alt() == self.modifiers.contains(&Modifier::Alt)
|
&& modifiers.alt() == self.modifiers.contains(&Modifier::Alt)
|
||||||
&& modifiers.shift() == self.modifiers.contains(&Modifier::Shift)
|
&& 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<Key> {
|
||||||
|
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 {
|
impl fmt::Display for KeyBind {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
// From iced_aw, license MIT
|
// From iced_aw, license MIT
|
||||||
|
|
||||||
//! A widget that handles menu trees
|
//! A widget that handles menu trees
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{
|
use super::menu_inner::{
|
||||||
menu_inner::{
|
|
||||||
CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight,
|
CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight,
|
||||||
},
|
|
||||||
menu_tree::MenuTree,
|
|
||||||
};
|
};
|
||||||
|
use super::menu_tree::MenuTree;
|
||||||
|
use crate::Renderer;
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "multi-window",
|
feature = "multi-window",
|
||||||
feature = "wayland",
|
feature = "wayland",
|
||||||
|
|
@ -16,25 +16,21 @@ use super::{
|
||||||
feature = "surface-message"
|
feature = "surface-message"
|
||||||
))]
|
))]
|
||||||
use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem};
|
use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem};
|
||||||
use crate::{
|
use crate::style::menu_bar::StyleSheet;
|
||||||
Renderer,
|
use crate::widget::RcWrapper;
|
||||||
style::menu_bar::StyleSheet,
|
use crate::widget::dropdown::menu::{self, State};
|
||||||
widget::{
|
use crate::widget::menu::menu_inner::init_root_menu;
|
||||||
RcWrapper,
|
|
||||||
menu::menu_inner::init_root_menu,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
use iced::event::Status;
|
||||||
use iced::{Point, Shadow, Vector, window};
|
use iced::{Point, Shadow, Vector, window};
|
||||||
use iced_core::Border;
|
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::{
|
use iced_widget::core::{
|
||||||
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, event,
|
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, event,
|
||||||
layout::{Limits, Node},
|
overlay, touch,
|
||||||
mouse::{self, Cursor},
|
|
||||||
overlay,
|
|
||||||
renderer::{self, Renderer as IcedRenderer},
|
|
||||||
touch,
|
|
||||||
widget::{Tree, tree},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing.
|
/// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing.
|
||||||
|
|
@ -574,7 +570,8 @@ where
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
use event::Event::{Mouse, Touch};
|
use event::Event::{Mouse, Touch};
|
||||||
use mouse::{Button::Left, Event::ButtonReleased};
|
use mouse::Button::Left;
|
||||||
|
use mouse::Event::ButtonReleased;
|
||||||
use touch::Event::{FingerLifted, FingerLost};
|
use touch::Event::{FingerLifted, FingerLost};
|
||||||
|
|
||||||
process_root_events(
|
process_root_events(
|
||||||
|
|
@ -741,7 +738,7 @@ where
|
||||||
tree: &'b mut Tree,
|
tree: &'b mut Tree,
|
||||||
layout: Layout<'b>,
|
layout: Layout<'b>,
|
||||||
_renderer: &Renderer,
|
_renderer: &Renderer,
|
||||||
_viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
// From iced_aw, license MIT
|
// From iced_aw, license MIT
|
||||||
|
|
||||||
//! Menu tree overlay
|
//! 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(
|
#[cfg(all(
|
||||||
feature = "multi-window",
|
feature = "multi-window",
|
||||||
feature = "wayland",
|
feature = "wayland",
|
||||||
|
|
@ -15,12 +17,12 @@ use crate::style::menu_bar::StyleSheet;
|
||||||
|
|
||||||
use iced::window;
|
use iced::window;
|
||||||
use iced_core::{Border, Renderer as IcedRenderer, Shadow, Widget};
|
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::{
|
use iced_widget::core::{
|
||||||
Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, event,
|
Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, event, overlay,
|
||||||
layout::{Limits, Node},
|
renderer, touch,
|
||||||
mouse::{self, Cursor},
|
|
||||||
overlay, renderer, touch,
|
|
||||||
widget::Tree,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The condition of when to close a menu
|
/// The condition of when to close a menu
|
||||||
|
|
@ -559,10 +561,9 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
) -> Option<(usize, MenuState)> {
|
) -> Option<(usize, MenuState)> {
|
||||||
use event::Event::{Mouse, Touch};
|
use event::Event::{Mouse, Touch};
|
||||||
use mouse::{
|
use event::Status::{Captured, Ignored};
|
||||||
Button::Left,
|
use mouse::Button::Left;
|
||||||
Event::{ButtonPressed, ButtonReleased, CursorMoved, WheelScrolled},
|
use mouse::Event::{ButtonPressed, ButtonReleased, CursorMoved, WheelScrolled};
|
||||||
};
|
|
||||||
use touch::Event::{FingerLifted, FingerMoved, FingerPressed};
|
use touch::Event::{FingerLifted, FingerMoved, FingerPressed};
|
||||||
|
|
||||||
if !self
|
if !self
|
||||||
|
|
@ -578,7 +579,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
||||||
let viewport_size = viewport.size();
|
let viewport_size = viewport.size();
|
||||||
let overlay_offset = Point::ORIGIN - viewport.position();
|
let overlay_offset = Point::ORIGIN - viewport.position();
|
||||||
let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset;
|
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::Borrowed(_) => panic!(),
|
||||||
Cow::Owned(o) => o.as_mut_slice(),
|
Cow::Owned(o) => o.as_mut_slice(),
|
||||||
};
|
};
|
||||||
|
|
@ -948,7 +949,7 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn update(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
_tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: &iced::Event,
|
event: &iced::Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
|
|
@ -966,21 +967,21 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
|
||||||
target_os = "linux"
|
target_os = "linux"
|
||||||
))]
|
))]
|
||||||
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
|
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
|
||||||
&& let Some((new_root, _new_ms)) = new_root
|
&& let Some((new_root, new_ms)) = new_root
|
||||||
{
|
{
|
||||||
use iced_runtime::platform_specific::wayland::popup::{
|
use iced_runtime::platform_specific::wayland::popup::{
|
||||||
SctkPopupSettings, SctkPositioner,
|
SctkPopupSettings, SctkPositioner,
|
||||||
};
|
};
|
||||||
let overlay_offset = Point::ORIGIN - viewport.position();
|
let overlay_offset = Point::ORIGIN - viewport.position();
|
||||||
|
|
||||||
let _overlay_cursor = cursor.position().unwrap_or_default() - overlay_offset;
|
let overlay_cursor = cursor.position().unwrap_or_default() - overlay_offset;
|
||||||
|
|
||||||
let Some((mut menu, popup_id)) = self.tree.inner.with_data_mut(|state| {
|
let Some((mut menu, popup_id)) = self.tree.inner.with_data_mut(|state| {
|
||||||
let popup_id = *state
|
let popup_id = *state
|
||||||
.popup_id
|
.popup_id
|
||||||
.entry(self.window_id)
|
.entry(self.window_id)
|
||||||
.or_insert_with(window::Id::unique);
|
.or_insert_with(window::Id::unique);
|
||||||
let _active_roots = state
|
let active_roots = state
|
||||||
.active_root
|
.active_root
|
||||||
.get(self.depth)
|
.get(self.depth)
|
||||||
.cloned()
|
.cloned()
|
||||||
|
|
@ -994,7 +995,7 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
|
||||||
.map(|lo| lo.bounds())
|
.map(|lo| lo.bounds())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let popup_menu = Menu {
|
let mut popup_menu = Menu {
|
||||||
tree: self.tree.clone(),
|
tree: self.tree.clone(),
|
||||||
menu_roots: Cow::Owned(Cow::into_owned(self.menu_roots.clone())),
|
menu_roots: Cow::Owned(Cow::into_owned(self.menu_roots.clone())),
|
||||||
bounds_expand: self.bounds_expand,
|
bounds_expand: self.bounds_expand,
|
||||||
|
|
@ -1599,7 +1600,7 @@ fn process_scroll_events<Message>(
|
||||||
) where
|
) where
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
{
|
{
|
||||||
|
use event::Status::{Captured, Ignored};
|
||||||
use mouse::ScrollDelta;
|
use mouse::ScrollDelta;
|
||||||
|
|
||||||
menu.tree.inner.with_data_mut(|state| {
|
menu.tree.inner.with_data_mut(|state| {
|
||||||
|
|
|
||||||
|
|
@ -234,6 +234,7 @@ pub fn menu_items<
|
||||||
color.alpha *= 0.75;
|
color.alpha *= 0.75;
|
||||||
TextStyle {
|
TextStyle {
|
||||||
color: Some(color.into()),
|
color: Some(color.into()),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let key_class = theme::Text::Custom(key_style);
|
let key_class = theme::Text::Custom(key_style);
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ pub use dialog::{Dialog, dialog};
|
||||||
pub mod divider {
|
pub mod divider {
|
||||||
/// Horizontal variant of a divider.
|
/// Horizontal variant of a divider.
|
||||||
pub mod horizontal {
|
pub mod horizontal {
|
||||||
use iced::{widget::Rule, widget::rule};
|
use iced::widget::{Rule, rule};
|
||||||
|
|
||||||
/// Horizontal divider with default thickness
|
/// Horizontal divider with default thickness
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,9 @@
|
||||||
//! For details on the model, see the [`segmented_button`] module for more details.
|
//! For details on the model, see the [`segmented_button`] module for more details.
|
||||||
|
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
use iced::{
|
use iced::clipboard::dnd::DndAction;
|
||||||
Background, Length,
|
use iced::clipboard::mime::AllowedMimeTypes;
|
||||||
clipboard::{dnd::DndAction, mime::AllowedMimeTypes},
|
use iced::{Background, Length};
|
||||||
};
|
|
||||||
use iced_core::{Border, Color, Shadow};
|
use iced_core::{Border, Color, Shadow};
|
||||||
|
|
||||||
use crate::widget::{Container, Icon, container, menu, scrollable, segmented_button};
|
use crate::widget::{Container, Icon, container, menu, scrollable, segmented_button};
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,11 @@
|
||||||
//! A container which displays an overlay when a popup widget is attached.
|
//! A container which displays an overlay when a popup widget is attached.
|
||||||
|
|
||||||
use iced::widget;
|
use iced::widget;
|
||||||
use iced_core::event::Event;
|
use iced_core::event::{self, Event};
|
||||||
use iced_core::layout;
|
|
||||||
use iced_core::mouse;
|
|
||||||
use iced_core::overlay;
|
|
||||||
use iced_core::renderer;
|
|
||||||
use iced_core::touch;
|
|
||||||
use iced_core::widget::{Operation, Tree};
|
use iced_core::widget::{Operation, Tree};
|
||||||
use iced_core::{
|
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};
|
pub use iced_widget::container::{Catalog, Style};
|
||||||
|
|
|
||||||
115
src/widget/progress_bar/animation.rs
Normal file
115
src/widget/progress_bar/animation.rs
Normal file
|
|
@ -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<f32>,
|
||||||
|
last: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,10 @@
|
||||||
//! Show a circular progress indicator.
|
//! Show a circular progress indicator.
|
||||||
|
use super::animation::{Animation, Progress};
|
||||||
use super::style::StyleSheet;
|
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::widget::tree::{self, Tree};
|
||||||
use iced::advanced::{self, Clipboard, Layout, Shell, Widget};
|
use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer};
|
||||||
use iced::mouse;
|
|
||||||
use iced::time::Instant;
|
|
||||||
use iced::widget::canvas;
|
use iced::widget::canvas;
|
||||||
use iced::window;
|
use iced::{Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector, mouse, window};
|
||||||
use iced::{Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector};
|
|
||||||
|
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
@ -23,9 +18,9 @@ where
|
||||||
{
|
{
|
||||||
size: f32,
|
size: f32,
|
||||||
bar_height: f32,
|
bar_height: f32,
|
||||||
style: <Theme as StyleSheet>::Style,
|
style: Theme::Style,
|
||||||
cycle_duration: Duration,
|
cycle_duration: Duration,
|
||||||
rotation_duration: Duration,
|
period: Duration,
|
||||||
progress: Option<f32>,
|
progress: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,9 +33,9 @@ where
|
||||||
Circular {
|
Circular {
|
||||||
size: 40.0,
|
size: 40.0,
|
||||||
bar_height: 4.0,
|
bar_height: 4.0,
|
||||||
style: <Theme as StyleSheet>::Style::default(),
|
style: Theme::Style::default(),
|
||||||
cycle_duration: Duration::from_millis(1500),
|
cycle_duration: Duration::from_millis(1500),
|
||||||
rotation_duration: Duration::from_secs(2),
|
period: Duration::from_secs(2),
|
||||||
progress: None,
|
progress: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +53,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style variant of this [`Circular`].
|
/// Sets the style variant of this [`Circular`].
|
||||||
pub fn style(mut self, style: <Theme as StyleSheet>::Style) -> Self {
|
pub fn style(mut self, style: Theme::Style) -> Self {
|
||||||
self.style = style;
|
self.style = style;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -69,10 +64,10 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the base rotation duration of this [`Circular`]. This is the duration that a full
|
/// Sets the base period of this [`Circular`]. This is the duration that a full rotation
|
||||||
/// rotation would take if the cycle rotation were set to 0.0 (no expanding or contracting)
|
/// would take if the cycle duration were set to 0.0 (no expanding or contracting)
|
||||||
pub fn rotation_duration(mut self, duration: Duration) -> Self {
|
pub fn period(mut self, duration: Duration) -> Self {
|
||||||
self.rotation_duration = duration;
|
self.period = duration;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,10 +77,10 @@ where
|
||||||
self
|
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 cap_angle = self.bar_height / track_radius;
|
||||||
let gap = MIN_ANGLE.0.max(cap_angle);
|
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)]
|
#[derive(Default)]
|
||||||
struct State {
|
struct State {
|
||||||
animation: Animation,
|
animation: Animation,
|
||||||
cache: canvas::Cache,
|
cache: canvas::Cache,
|
||||||
progress: Option<f32>,
|
progress: Progress,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message, Theme> Widget<Message, Theme, Renderer> for Circular<Theme>
|
impl<Message, Theme> Widget<Message, Theme, Renderer> for Circular<Theme>
|
||||||
|
|
@ -260,29 +141,22 @@ where
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
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 {
|
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
||||||
let (_, wrap_angle) = self.min_wrap_angle(self.size / 2.0 - self.bar_height);
|
if let Some(target) = self.progress {
|
||||||
state.animation = state.animation.timed_transition(
|
if state.progress.update(target, *now) {
|
||||||
self.cycle_duration,
|
|
||||||
self.rotation_duration,
|
|
||||||
wrap_angle,
|
|
||||||
*now,
|
|
||||||
);
|
|
||||||
|
|
||||||
state.cache.clear();
|
state.cache.clear();
|
||||||
shell.request_redraw();
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
@ -299,11 +173,13 @@ where
|
||||||
|
|
||||||
let state = tree.state.downcast_ref::<State>();
|
let state = tree.state.downcast_ref::<State>();
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let custom_style =
|
let custom_style = Theme::appearance(theme, &self.style, self.progress.is_some(), true);
|
||||||
<Theme as StyleSheet>::appearance(theme, &self.style, self.progress.is_some(), true);
|
|
||||||
|
|
||||||
let geometry = state.cache.draw(renderer, bounds.size(), |frame| {
|
let geometry = state.cache.draw(renderer, bounds.size(), |frame| {
|
||||||
let track_radius = frame.width() / 2.0 - self.bar_height;
|
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);
|
let track_path = canvas::Path::circle(frame.center(), track_radius);
|
||||||
|
|
||||||
frame.stroke(
|
frame.stroke(
|
||||||
|
|
@ -313,133 +189,65 @@ where
|
||||||
.with_width(self.bar_height),
|
.with_width(self.bar_height),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(progress) = self.progress {
|
// Converts a track fraction to an angle in radians, with 0 being top of circle
|
||||||
// outer border
|
let to_angle = |t: f32| t * 2.0 * PI - PI / 2.0;
|
||||||
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(
|
let draw_cap = |frame: &mut canvas::Frame, t: f32, flip: bool| {
|
||||||
&border_path,
|
let angle = to_angle(t);
|
||||||
canvas::Stroke::default()
|
let center = frame.center() + Vector::new(angle.cos(), angle.sin()) * track_radius;
|
||||||
.with_color(border_color)
|
let (start_angle, end_angle) = if flip {
|
||||||
.with_width(1.0),
|
(angle - PI, angle)
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
} else {
|
||||||
let mut builder = canvas::path::Builder::new();
|
(angle, angle + PI)
|
||||||
|
|
||||||
let start = state.animation.rotation() * 2.0 * PI;
|
|
||||||
let (min_angle, wrap_angle) = self.min_wrap_angle(track_radius);
|
|
||||||
let (start_angle, end_angle) = match state.animation {
|
|
||||||
Animation::Expanding { progress, .. } => (
|
|
||||||
start,
|
|
||||||
start + min_angle + wrap_angle * smootherstep(progress),
|
|
||||||
),
|
|
||||||
Animation::Contracting { progress, .. } => (
|
|
||||||
start + wrap_angle * smootherstep(progress),
|
|
||||||
start + min_angle + wrap_angle,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
let mut builder = canvas::path::Builder::new();
|
||||||
builder.arc(canvas::path::Arc {
|
builder.arc(canvas::path::Arc {
|
||||||
center: frame.center(),
|
center,
|
||||||
radius: track_radius,
|
radius: self.bar_height / 2.0,
|
||||||
start_angle: Radians(start_angle),
|
start_angle: Radians(start_angle),
|
||||||
end_angle: Radians(end_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(
|
frame.stroke(
|
||||||
&bar_path,
|
&builder.build(),
|
||||||
canvas::Stroke::default()
|
canvas::Stroke::default()
|
||||||
.with_color(custom_style.bar_color)
|
.with_color(custom_style.bar_color)
|
||||||
.with_width(self.bar_height),
|
.with_width(self.bar_height),
|
||||||
);
|
);
|
||||||
|
draw_cap(frame, end, false);
|
||||||
|
draw_cap(frame, start, true);
|
||||||
|
};
|
||||||
|
|
||||||
let mut builder = canvas::path::Builder::new();
|
if self.progress.is_some() {
|
||||||
|
if let Some(border_color) = custom_style.border_color {
|
||||||
// get center of end of arc for rounded cap
|
for radius_offset in [self.bar_height / 2.0, -(self.bar_height / 2.0)] {
|
||||||
let end_center =
|
let border_path =
|
||||||
frame.center() + Vector::new(end_angle.cos(), end_angle.sin()) * track_radius;
|
canvas::Path::circle(frame.center(), track_radius + radius_offset);
|
||||||
builder.arc(canvas::path::Arc {
|
frame.stroke(
|
||||||
center: end_center,
|
&border_path,
|
||||||
radius: self.bar_height / 2.0,
|
canvas::Stroke::default()
|
||||||
start_angle: Radians(end_angle),
|
.with_color(border_color)
|
||||||
end_angle: Radians(end_angle + PI),
|
.with_width(1.0),
|
||||||
});
|
);
|
||||||
|
}
|
||||||
// get center of start of arc for rounded cap
|
}
|
||||||
let start_center = frame.center()
|
draw_bar(frame, 0.0, state.progress.current);
|
||||||
+ Vector::new(start_angle.cos(), start_angle.sin()) * track_radius;
|
} else {
|
||||||
builder.arc(canvas::path::Arc {
|
let (min, wrap) = self.min_wrap(track_radius);
|
||||||
center: start_center,
|
let (start, end) = state
|
||||||
radius: self.bar_height / 2.0,
|
.animation
|
||||||
start_angle: Radians(start_angle - PI),
|
.bar_positions(self.cycle_duration, min, wrap);
|
||||||
end_angle: Radians(start_angle),
|
draw_bar(frame, start, end);
|
||||||
});
|
|
||||||
|
|
||||||
let cap_path = builder.build();
|
|
||||||
frame.fill(&cap_path, custom_style.bar_color);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,15 @@
|
||||||
//! Show a linear progress indicator.
|
//! Show a linear progress indicator.
|
||||||
use iced::advanced::layout;
|
use super::animation::{Animation, Progress};
|
||||||
use iced::advanced::renderer::{self, Quad};
|
|
||||||
use iced::advanced::widget::tree::{self, Tree};
|
|
||||||
use iced::advanced::{self, Clipboard, Layout, Shell, Widget};
|
|
||||||
use iced::mouse;
|
|
||||||
use iced::time::Instant;
|
|
||||||
use iced::window;
|
|
||||||
use iced::{Background, Element, Event, Length, Rectangle, Size};
|
|
||||||
|
|
||||||
use crate::anim::smootherstep;
|
|
||||||
|
|
||||||
use super::style::StyleSheet;
|
use 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;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
const MIN_LENGTH: f32 = 0.15;
|
||||||
|
const WRAP_LENGTH: f32 = 0.618; // avoids animation repetition
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct Linear<Theme>
|
pub struct Linear<Theme>
|
||||||
where
|
where
|
||||||
|
|
@ -23,7 +19,10 @@ where
|
||||||
girth: Length,
|
girth: Length,
|
||||||
style: Theme::Style,
|
style: Theme::Style,
|
||||||
cycle_duration: Duration,
|
cycle_duration: Duration,
|
||||||
|
period: Duration,
|
||||||
progress: Option<f32>,
|
progress: Option<f32>,
|
||||||
|
markers: Vec<f32>,
|
||||||
|
segment_spacing: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Theme> Linear<Theme>
|
impl<Theme> Linear<Theme>
|
||||||
|
|
@ -37,7 +36,10 @@ where
|
||||||
girth: Length::Fixed(4.0),
|
girth: Length::Fixed(4.0),
|
||||||
style: Theme::Style::default(),
|
style: Theme::Style::default(),
|
||||||
cycle_duration: Duration::from_millis(1500),
|
cycle_duration: Duration::from_millis(1500),
|
||||||
|
period: Duration::from_secs(2),
|
||||||
progress: None,
|
progress: None,
|
||||||
|
markers: Vec::new(),
|
||||||
|
segment_spacing: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,11 +67,38 @@ where
|
||||||
self
|
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`.
|
/// Override the default behavior by providing a determinate progress value between `0.0` and `1.0`.
|
||||||
pub fn progress(mut self, progress: f32) -> Self {
|
pub fn progress(mut self, progress: f32) -> Self {
|
||||||
self.progress = Some(progress.clamp(0.0, 1.0));
|
self.progress = Some(progress.clamp(0.0, 1.0));
|
||||||
self
|
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<Vec<f32>>) -> 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<Pixels>) -> Self {
|
||||||
|
self.segment_spacing = spacing.into().0;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Theme> Default for Linear<Theme>
|
impl<Theme> Default for Linear<Theme>
|
||||||
|
|
@ -81,63 +110,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Default)]
|
||||||
enum State {
|
struct State {
|
||||||
Expanding { start: Instant, progress: f32 },
|
animation: Animation,
|
||||||
Contracting { start: Instant, progress: f32 },
|
progress: Progress,
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for State {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Expanding {
|
|
||||||
start: Instant::now(),
|
|
||||||
progress: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
fn next(&self, now: Instant) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Expanding { .. } => Self::Contracting {
|
|
||||||
start: now,
|
|
||||||
progress: 0.0,
|
|
||||||
},
|
|
||||||
Self::Contracting { .. } => Self::Expanding {
|
|
||||||
start: now,
|
|
||||||
progress: 0.0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start(&self) -> Instant {
|
|
||||||
match self {
|
|
||||||
Self::Expanding { start, .. } | Self::Contracting { start, .. } => *start,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timed_transition(&self, cycle_duration: Duration, now: Instant) -> Self {
|
|
||||||
let elapsed = now.duration_since(self.start());
|
|
||||||
|
|
||||||
match elapsed {
|
|
||||||
elapsed if elapsed > cycle_duration => self.next(now),
|
|
||||||
_ => self.with_elapsed(cycle_duration, elapsed),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_elapsed(&self, cycle_duration: Duration, elapsed: Duration) -> Self {
|
|
||||||
let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32();
|
|
||||||
match self {
|
|
||||||
Self::Expanding { start, .. } => Self::Expanding {
|
|
||||||
start: *start,
|
|
||||||
progress,
|
|
||||||
},
|
|
||||||
Self::Contracting { start, .. } => Self::Contracting {
|
|
||||||
start: *start,
|
|
||||||
progress,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Linear<Theme>
|
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Linear<Theme>
|
||||||
|
|
@ -181,17 +157,22 @@ where
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
if self.progress.is_some() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
||||||
*state = state.timed_transition(self.cycle_duration, *now);
|
if let Some(target) = self.progress {
|
||||||
|
if state.progress.update(target, *now) {
|
||||||
shell.request_redraw();
|
shell.request_redraw();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
state.animation = state.animation.timed_transition(
|
||||||
|
self.cycle_duration,
|
||||||
|
self.period,
|
||||||
|
WRAP_LENGTH,
|
||||||
|
*now,
|
||||||
|
);
|
||||||
|
shell.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
@ -208,88 +189,148 @@ where
|
||||||
let custom_style = theme.appearance(&self.style, self.progress.is_some(), false);
|
let custom_style = theme.appearance(&self.style, self.progress.is_some(), false);
|
||||||
let state = tree.state.downcast_ref::<State>();
|
let state = tree.state.downcast_ref::<State>();
|
||||||
|
|
||||||
renderer.fill_quad(
|
let border_width = if custom_style.border_color.is_some() {
|
||||||
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
|
1.0
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
},
|
};
|
||||||
color: custom_style.border_color.unwrap_or(custom_style.bar_color),
|
let border_color = custom_style.border_color.unwrap_or(custom_style.bar_color);
|
||||||
radius: custom_style.border_radius.into(),
|
let radius = custom_style.border_radius;
|
||||||
},
|
|
||||||
snap: true,
|
|
||||||
..renderer::Quad::default()
|
|
||||||
},
|
|
||||||
Background::Color(custom_style.track_color),
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(progress) = self.progress {
|
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.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: Rectangle {
|
bounds: Rectangle {
|
||||||
x: bounds.x,
|
x: bounds.x + x * bounds.width,
|
||||||
y: bounds.y,
|
y: bounds.y,
|
||||||
width: progress * bounds.width,
|
width: width * bounds.width,
|
||||||
height: bounds.height,
|
height: bounds.height,
|
||||||
},
|
},
|
||||||
border: iced::Border {
|
border,
|
||||||
width: 0.,
|
|
||||||
color: iced::Color::TRANSPARENT,
|
|
||||||
radius: custom_style.border_radius.into(),
|
|
||||||
},
|
|
||||||
snap: true,
|
snap: true,
|
||||||
..renderer::Quad::default()
|
..renderer::Quad::default()
|
||||||
},
|
},
|
||||||
Background::Color(custom_style.bar_color),
|
Background::Color(color),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
match state {
|
|
||||||
State::Expanding { progress, .. } => renderer.fill_quad(
|
|
||||||
renderer::Quad {
|
|
||||||
bounds: Rectangle {
|
|
||||||
x: bounds.x,
|
|
||||||
y: bounds.y,
|
|
||||||
width: smootherstep(*progress) * bounds.width,
|
|
||||||
height: bounds.height,
|
|
||||||
},
|
|
||||||
border: iced::Border {
|
|
||||||
width: 0.,
|
|
||||||
color: iced::Color::TRANSPARENT,
|
|
||||||
radius: custom_style.border_radius.into(),
|
|
||||||
},
|
|
||||||
snap: true,
|
|
||||||
..renderer::Quad::default()
|
|
||||||
},
|
|
||||||
Background::Color(custom_style.bar_color),
|
|
||||||
),
|
|
||||||
|
|
||||||
State::Contracting { progress, .. } => renderer.fill_quad(
|
|
||||||
Quad {
|
|
||||||
bounds: Rectangle {
|
|
||||||
x: bounds.x + smootherstep(*progress) * bounds.width,
|
|
||||||
y: bounds.y,
|
|
||||||
width: (1.0 - smootherstep(*progress)) * bounds.width,
|
|
||||||
height: bounds.height,
|
|
||||||
},
|
|
||||||
border: iced::Border {
|
|
||||||
width: 0.,
|
|
||||||
color: iced::Color::TRANSPARENT,
|
|
||||||
radius: custom_style.border_radius.into(),
|
|
||||||
},
|
|
||||||
snap: true,
|
|
||||||
..renderer::Quad::default()
|
|
||||||
},
|
|
||||||
Background::Color(custom_style.bar_color),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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()
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod animation;
|
||||||
pub mod circular;
|
pub mod circular;
|
||||||
pub mod linear;
|
pub mod linear;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
//! Create choices using radio buttons.
|
//! Create choices using radio buttons.
|
||||||
use crate::{Theme, theme};
|
use crate::{Theme, theme};
|
||||||
use iced::border;
|
use iced::border;
|
||||||
use iced_core::event::Event;
|
use iced_core::event::{self, Event};
|
||||||
use iced_core::layout;
|
|
||||||
use iced_core::mouse;
|
|
||||||
use iced_core::overlay;
|
|
||||||
use iced_core::renderer;
|
|
||||||
use iced_core::touch;
|
|
||||||
use iced_core::widget::tree::Tree;
|
use iced_core::widget::tree::Tree;
|
||||||
use iced_core::{
|
use iced_core::{
|
||||||
Border, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Vector, Widget,
|
Border, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Vector, Widget,
|
||||||
|
layout, mouse, overlay, renderer, touch,
|
||||||
};
|
};
|
||||||
|
|
||||||
use iced_widget::radio as iced_radio;
|
use iced_widget::radio as iced_radio;
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@ use iced::futures::channel::mpsc::UnboundedSender;
|
||||||
use iced::widget::Container;
|
use iced::widget::Container;
|
||||||
pub use subscription::*;
|
pub use subscription::*;
|
||||||
|
|
||||||
use iced_core::event::Event;
|
use iced_core::event::{self, Event};
|
||||||
use iced_core::layout;
|
|
||||||
use iced_core::mouse;
|
|
||||||
use iced_core::overlay;
|
|
||||||
use iced_core::renderer;
|
|
||||||
use iced_core::widget::Tree;
|
use iced_core::widget::Tree;
|
||||||
use iced_core::{Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget};
|
use iced_core::{
|
||||||
use std::{fmt::Debug, hash::Hash};
|
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};
|
pub use iced_widget::container::{Catalog, Style};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
use iced::{
|
use iced::Rectangle;
|
||||||
Rectangle,
|
use iced::futures::channel::mpsc::{UnboundedReceiver, unbounded};
|
||||||
futures::{
|
use iced::futures::{StreamExt, stream};
|
||||||
StreamExt,
|
|
||||||
channel::mpsc::{UnboundedReceiver, unbounded},
|
|
||||||
stream,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use iced_futures::Subscription;
|
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;
|
use super::RectangleTracker;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
//! Responsive Container, which will notify of size changes.
|
//! Responsive Container, which will notify of size changes.
|
||||||
|
|
||||||
use iced::{Limits, Size};
|
use iced::{Limits, Size};
|
||||||
use iced_core::event::Event;
|
use iced_core::event::{self, Event};
|
||||||
use iced_core::layout;
|
|
||||||
use iced_core::mouse;
|
|
||||||
use iced_core::overlay;
|
|
||||||
use iced_core::renderer;
|
|
||||||
use iced_core::widget::{Id, Operation, Tree, tree};
|
use iced_core::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>(
|
pub(crate) fn responsive_container<'a, Message: 'static, Theme, E>(
|
||||||
content: E,
|
content: E,
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,8 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
|
|
||||||
use crate::{
|
use crate::widget::{button, icon, responsive_container};
|
||||||
Core, Element,
|
use crate::{Core, Element};
|
||||||
widget::{button, icon, responsive_container},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::menu::{self, ItemHeight, ItemWidth};
|
use super::menu::{self, ItemHeight, ItemWidth};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,11 @@ use iced_core::id::Internal;
|
||||||
use iced_core::mouse::ScrollDelta;
|
use iced_core::mouse::ScrollDelta;
|
||||||
use iced_core::text::{self, Ellipsize, LineHeight, Renderer as TextRenderer, Shaping, Wrapping};
|
use iced_core::text::{self, Ellipsize, LineHeight, Renderer as TextRenderer, Shaping, Wrapping};
|
||||||
use iced_core::widget::operation::Focusable;
|
use iced_core::widget::operation::Focusable;
|
||||||
use iced_core::widget::{self, operation, tree};
|
use iced_core::widget::{self, Tree, operation, tree};
|
||||||
use iced_core::{Border, Point, Renderer as IcedRenderer, Shadow, Text};
|
use iced_core::{
|
||||||
use iced_core::{Clipboard, Layout, Shell, Widget, layout, renderer, widget::Tree};
|
Border, Clipboard, Layout, Point, Renderer as IcedRenderer, Shadow, Shell, Text, Widget,
|
||||||
|
layout, renderer,
|
||||||
|
};
|
||||||
use iced_runtime::{Action, task};
|
use iced_runtime::{Action, task};
|
||||||
use slotmap::{Key, SecondaryMap};
|
use slotmap::{Key, SecondaryMap};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
@ -1446,6 +1448,8 @@ where
|
||||||
state.context_cursor = cursor_position.position().unwrap_or_default();
|
state.context_cursor = cursor_position.position().unwrap_or_default();
|
||||||
|
|
||||||
state.menu_state.inner.with_data_mut(|data| {
|
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.open = true;
|
||||||
data.view_cursor = cursor_position;
|
data.view_cursor = cursor_position;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue