Compare commits
83 commits
iced-updat
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95756b1a57 | ||
|
|
c423ad1bfc | ||
|
|
8d7bcab258 | ||
|
|
c162a1f24a | ||
|
|
3f9e93067b | ||
|
|
917af9fda2 | ||
|
|
9b465a8b5c | ||
|
|
9cac422c24 | ||
|
|
0fc4638af3 | ||
|
|
3d8d8915be | ||
|
|
46d9f0c344 | ||
|
|
0d69cd9183 | ||
|
|
52116d2f36 | ||
|
|
0e72508dcc | ||
|
|
1d7113a244 | ||
|
|
e287a789c1 | ||
|
|
6caccaba33 | ||
|
|
a44cff8011 | ||
|
|
47ab72be50 | ||
|
|
c7093beca3 | ||
|
|
77b37f2246 | ||
|
|
6df3f76a33 | ||
|
|
12d2233c6b | ||
|
|
e5955b568d | ||
|
|
5d1dfc4c54 | ||
|
|
d9121d6f0d | ||
|
|
b963fbfea9 | ||
|
|
724351727a | ||
|
|
1f87cbc883 | ||
|
|
9aa87cd66b | ||
|
|
ab3eedd0f2 | ||
|
|
8e3672a7dd | ||
|
|
1d01054993 | ||
|
|
fdf3369cea | ||
|
|
a9e0671075 | ||
|
|
34219d1fd4 | ||
|
|
cdd825b953 | ||
|
|
b0f4e931f2 | ||
|
|
97a805e5a1 | ||
|
|
24464908f6 | ||
|
|
7a02c9a296 | ||
|
|
61e5d882ae | ||
|
|
12be83a8ef | ||
|
|
f6eb314606 | ||
|
|
0ba668eb52 | ||
|
|
aef328238f | ||
|
|
22661fd764 | ||
|
|
e1738d2ea7 | ||
|
|
2299fba69b | ||
|
|
c33455e9ad | ||
|
|
9a72fe6c2d | ||
|
|
39e8300d90 | ||
|
|
f734ccbbde | ||
|
|
e86304cf3f | ||
|
|
672f9047a2 | ||
|
|
8b52592f2d | ||
|
|
d631f9d6d7 | ||
|
|
4541c6a275 | ||
|
|
1433b89e40 | ||
|
|
f06d15ae35 | ||
|
|
413e63f62a | ||
|
|
380b341bdc | ||
|
|
254c13cfc4 | ||
|
|
e63f3196e2 | ||
|
|
a38a6f5d73 | ||
|
|
763f0da64c | ||
|
|
adb3e341fc | ||
|
|
8e439c842c | ||
|
|
d7fd880ac6 | ||
|
|
141261b9bf | ||
|
|
c804d3851d | ||
|
|
dc3ebaa38e | ||
|
|
7a56762422 | ||
|
|
36cba695d2 | ||
|
|
3da55e8074 | ||
|
|
54bcb9ec12 | ||
|
|
6c6d16d34a | ||
|
|
c7ac9cfd31 | ||
|
|
0bb006c5bb | ||
|
|
adb6e30405 | ||
|
|
9602dfd2f1 | ||
|
|
12cc536cd5 | ||
|
|
c52ef97650 |
101 changed files with 3747 additions and 1327 deletions
25
.github/workflows/ci.yml
vendored
25
.github/workflows/ci.yml
vendored
|
|
@ -33,16 +33,17 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
features:
|
test_args:
|
||||||
- "" # for cosmic-comp, don't remove!
|
- --no-default-features --features "" # for cosmic-comp, don't remove!
|
||||||
- 'winit_debug'
|
- --no-default-features --features "winit_debug"
|
||||||
- 'winit_tokio'
|
- --no-default-features --features "winit_tokio"
|
||||||
- winit
|
- --no-default-features --features "winit"
|
||||||
- winit_wgpu
|
- --no-default-features --features "winit_wgpu"
|
||||||
- wayland
|
- --no-default-features --features "wayland"
|
||||||
- applet
|
- --no-default-features --features "applet"
|
||||||
- desktop,smol
|
- --no-default-features --features "desktop,smol"
|
||||||
- desktop,tokio
|
- --no-default-features --features "desktop,tokio"
|
||||||
|
- -p cosmic-theme
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
|
|
@ -66,7 +67,7 @@ jobs:
|
||||||
- name: Rust toolchain
|
- name: Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Test features
|
- name: Test features
|
||||||
run: cargo test --no-default-features --features "${{ matrix.features }}" -- --test-threads=1
|
run: cargo test ${{ matrix.test_args }} -- --test-threads=1
|
||||||
env:
|
env:
|
||||||
RUST_BACKTRACE: full
|
RUST_BACKTRACE: full
|
||||||
|
|
||||||
|
|
@ -103,7 +104,7 @@ jobs:
|
||||||
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: Rust toolchain
|
- name: Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Test example
|
- name: Check example
|
||||||
run: cargo check -p "${{ matrix.examples }}"
|
run: cargo check -p "${{ matrix.examples }}"
|
||||||
env:
|
env:
|
||||||
RUST_BACKTRACE: full
|
RUST_BACKTRACE: full
|
||||||
|
|
|
||||||
37
.github/workflows/pages.yml
vendored
37
.github/workflows/pages.yml
vendored
|
|
@ -7,19 +7,30 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pages:
|
pages:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Build documentation
|
- name: Install Rust nightly
|
||||||
run: cargo doc --verbose --features tokio,winit
|
uses: dtolnay/rust-toolchain@master
|
||||||
- name: Deploy documentation
|
with:
|
||||||
uses: peaceiris/actions-gh-pages@v3
|
toolchain: nightly-2025-07-31
|
||||||
with:
|
- name: System dependencies
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
|
||||||
publish_dir: ./target/doc
|
- name: Build documentation
|
||||||
force_orphan: true
|
run: |
|
||||||
|
RUSTDOCFLAGS="--cfg docsrs" \
|
||||||
|
cargo +nightly-2025-07-31 doc --no-deps \
|
||||||
|
-p cosmic-client-toolkit \
|
||||||
|
-p cosmic-protocols \
|
||||||
|
-p libcosmic \
|
||||||
|
--verbose --features tokio,winit,wayland,desktop,single-instance,applet,xdg-portal,multi-window
|
||||||
|
- name: Deploy documentation
|
||||||
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
publish_dir: ./target/doc
|
||||||
|
force_orphan: true
|
||||||
|
|
|
||||||
46
Cargo.toml
46
Cargo.toml
|
|
@ -14,9 +14,10 @@ default = [
|
||||||
"a11y",
|
"a11y",
|
||||||
"dbus-config",
|
"dbus-config",
|
||||||
"x11",
|
"x11",
|
||||||
"wayland",
|
"iced-wayland",
|
||||||
"multi-window",
|
"multi-window",
|
||||||
] # default = ["dbus-config", "multi-window", "a11y"]
|
]
|
||||||
|
advanced-shaping = ["iced/advanced-shaping"]
|
||||||
# Accessibility support
|
# Accessibility support
|
||||||
a11y = ["iced/a11y", "iced_accessibility"]
|
a11y = ["iced/a11y", "iced_accessibility"]
|
||||||
# Enable about widget
|
# Enable about widget
|
||||||
|
|
@ -57,6 +58,7 @@ desktop = [
|
||||||
"process",
|
"process",
|
||||||
"dep:cosmic-settings-config",
|
"dep:cosmic-settings-config",
|
||||||
"dep:freedesktop-desktop-entry",
|
"dep:freedesktop-desktop-entry",
|
||||||
|
"dep:image-extras",
|
||||||
"dep:mime",
|
"dep:mime",
|
||||||
"dep:shlex",
|
"dep:shlex",
|
||||||
"tokio?/io-util",
|
"tokio?/io-util",
|
||||||
|
|
@ -80,15 +82,21 @@ tokio = [
|
||||||
]
|
]
|
||||||
# Tokio async runtime
|
# Tokio async runtime
|
||||||
# Wayland window support
|
# Wayland window support
|
||||||
wayland = [
|
iced-wayland = [
|
||||||
"ashpd?/wayland",
|
"ashpd?/wayland",
|
||||||
"autosize",
|
"autosize",
|
||||||
"iced_runtime/wayland",
|
|
||||||
"iced/wayland",
|
"iced/wayland",
|
||||||
"iced_winit/wayland",
|
"iced_winit/wayland",
|
||||||
"cctk",
|
|
||||||
"surface-message",
|
"surface-message",
|
||||||
]
|
]
|
||||||
|
wayland = [
|
||||||
|
"iced-wayland",
|
||||||
|
"iced_runtime/cctk",
|
||||||
|
"iced_winit/cctk",
|
||||||
|
"iced_wgpu/cctk",
|
||||||
|
"iced/cctk",
|
||||||
|
"dep:cctk",
|
||||||
|
]
|
||||||
surface-message = []
|
surface-message = []
|
||||||
# multi-window support
|
# multi-window support
|
||||||
multi-window = []
|
multi-window = []
|
||||||
|
|
@ -115,10 +123,10 @@ x11 = ["iced/x11", "iced_winit/x11"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
apply = "0.3.0"
|
apply = "0.3.0"
|
||||||
ashpd = { version = "0.12.1", 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 = { version = "1.13", optional = true }
|
||||||
auto_enums = "0.8.7"
|
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 = "160b086", optional = true }
|
||||||
jiff = "0.2"
|
jiff = "0.2"
|
||||||
cosmic-config = { path = "cosmic-config" }
|
cosmic-config = { path = "cosmic-config" }
|
||||||
|
|
@ -131,17 +139,21 @@ i18n-embed = { version = "0.16.0", features = [
|
||||||
i18n-embed-fl = "0.10"
|
i18n-embed-fl = "0.10"
|
||||||
rust-embed = "8.11.0"
|
rust-embed = "8.11.0"
|
||||||
css-color = "0.2.8"
|
css-color = "0.2.8"
|
||||||
derive_setters = "0.1.8"
|
derive_setters = "0.1.9"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
image = { version = "0.25.9", default-features = false, features = [
|
image = { version = "0.25.10", default-features = false, features = [
|
||||||
|
"ico",
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"png",
|
"png",
|
||||||
] }
|
] }
|
||||||
libc = { version = "0.2.180", optional = true }
|
image-extras = { version = "0.1.0", default-features = false, features = [
|
||||||
|
"xpm",
|
||||||
|
"xbm",
|
||||||
|
], optional = true }
|
||||||
|
libc = { version = "0.2.183", 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 = "0.7.6"
|
||||||
raw-window-handle = "0.6"
|
|
||||||
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 }
|
||||||
|
|
@ -151,25 +163,25 @@ slotmap = "1.1.1"
|
||||||
smol = { version = "2.0.2", optional = true }
|
smol = { version = "2.0.2", optional = true }
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
taffy = { version = "0.9.2", features = ["grid"] }
|
taffy = { version = "0.9.2", features = ["grid"] }
|
||||||
tokio = { version = "1.49.0", optional = true }
|
tokio = { version = "1.50.0", optional = true }
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.44"
|
||||||
unicode-segmentation = "1.12"
|
unicode-segmentation = "1.12"
|
||||||
url = "2.5.8"
|
url = "2.5.8"
|
||||||
zbus = { version = "5.13.2", default-features = false, optional = true }
|
zbus = { version = "5.14.0", default-features = false, optional = true }
|
||||||
float-cmp = "0.10.0"
|
float-cmp = "0.10.0"
|
||||||
|
|
||||||
# Enable DBus feature on Linux targets
|
# Enable DBus feature on Linux targets
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
cosmic-config = { path = "cosmic-config", features = ["dbus"] }
|
cosmic-config = { path = "cosmic-config", features = ["dbus"] }
|
||||||
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" }
|
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" }
|
||||||
zbus = { version = "5.13.2", default-features = false }
|
zbus = { version = "5.14.0", default-features = false }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
|
||||||
freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" }
|
freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" }
|
||||||
freedesktop-desktop-entry = { version = "0.8.1", optional = true }
|
freedesktop-desktop-entry = { version = "0.8.1", optional = true }
|
||||||
shlex = { version = "1.3.0", optional = true }
|
shlex = { version = "1.3.0", optional = true }
|
||||||
|
|
||||||
[target.'cfg(not(unix))'.dependencies]
|
[target.'cfg(any(not(unix), target_os = "macos"))'.dependencies]
|
||||||
# Used to embed bundled icons for non-unix platforms.
|
# Used to embed bundled icons for non-unix platforms.
|
||||||
phf = { version = "0.13.1", features = ["macros"] }
|
phf = { version = "0.13.1", features = ["macros"] }
|
||||||
|
|
||||||
|
|
@ -242,4 +254,4 @@ exclude = ["iced"]
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.24.0"
|
tempfile = "3.27.0"
|
||||||
|
|
|
||||||
4
build.rs
4
build.rs
|
|
@ -3,7 +3,9 @@ use std::env;
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("cargo::rerun-if-changed=build.rs");
|
println!("cargo::rerun-if-changed=build.rs");
|
||||||
|
|
||||||
if env::var_os("CARGO_CFG_UNIX").is_none() {
|
if env::var_os("CARGO_CFG_UNIX").is_none()
|
||||||
|
|| env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos")
|
||||||
|
{
|
||||||
generate_bundled_icons();
|
generate_bundled_icons();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ subscription = ["iced_futures"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
||||||
zbus = { version = "5.13.2", default-features = false, optional = true }
|
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.3", optional = true }
|
calloop = { version = "0.14.4", optional = true }
|
||||||
notify = "8.2.0"
|
notify = "8.2.0"
|
||||||
ron = "0.12.0"
|
ron = "0.12.0"
|
||||||
serde = "1.0.228"
|
serde = "1.0.228"
|
||||||
|
|
@ -22,7 +22,7 @@ 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.49", optional = true, features = ["time"] }
|
tokio = { version = "1.50", optional = true, features = ["time"] }
|
||||||
async-std = { version = "1.13", optional = true }
|
async-std = { version = "1.13", optional = true }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
|
||||||
|
|
@ -30,4 +30,4 @@ tracing = "0.1"
|
||||||
xdg = "3.0"
|
xdg = "3.0"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
known-folders = "1.4.0"
|
known-folders = "1.4.2"
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ serde_json = { version = "1.0.149", optional = true, features = [
|
||||||
"preserve_order",
|
"preserve_order",
|
||||||
] }
|
] }
|
||||||
ron = "0.12.0"
|
ron = "0.12.0"
|
||||||
csscolorparser = { version = "0.8.1", 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",
|
||||||
"macro",
|
"macro",
|
||||||
|
|
@ -30,3 +30,10 @@ 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 = "2.0.18"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
insta = "1.47.2"
|
||||||
|
|
||||||
|
[profile.dev.package]
|
||||||
|
insta.opt-level = 3
|
||||||
|
similar.opt-level = 3
|
||||||
|
|
|
||||||
|
|
@ -986,19 +986,19 @@ impl ThemeBuilder {
|
||||||
let success = if let Some(success) = success {
|
let success = if let Some(success) = success {
|
||||||
success.into_color()
|
success.into_color()
|
||||||
} else {
|
} else {
|
||||||
palette.as_ref().accent_green
|
palette.as_ref().bright_green
|
||||||
};
|
};
|
||||||
|
|
||||||
let warning = if let Some(warning) = warning {
|
let warning = if let Some(warning) = warning {
|
||||||
warning.into_color()
|
warning.into_color()
|
||||||
} else {
|
} else {
|
||||||
palette.as_ref().accent_yellow
|
palette.as_ref().bright_orange
|
||||||
};
|
};
|
||||||
|
|
||||||
let destructive = if let Some(destructive) = destructive {
|
let destructive = if let Some(destructive) = destructive {
|
||||||
destructive.into_color()
|
destructive.into_color()
|
||||||
} else {
|
} else {
|
||||||
palette.as_ref().accent_red
|
palette.as_ref().bright_red
|
||||||
};
|
};
|
||||||
|
|
||||||
let text_steps_array = text_tint.map(|c| steps(c, NonZeroUsize::new(100).unwrap()));
|
let text_steps_array = text_tint.map(|c| steps(c, NonZeroUsize::new(100).unwrap()));
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,10 @@ impl Theme {
|
||||||
pub fn write_exports(&self) -> Result<(), OutputError> {
|
pub fn write_exports(&self) -> Result<(), OutputError> {
|
||||||
let gtk_res = self.write_gtk4();
|
let gtk_res = self.write_gtk4();
|
||||||
let qt_res = self.write_qt();
|
let qt_res = self.write_qt();
|
||||||
|
let qt56ct_res = self.write_qt56ct();
|
||||||
gtk_res?;
|
gtk_res?;
|
||||||
qt_res?;
|
qt_res?;
|
||||||
|
qt56ct_res?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,8 +58,10 @@ impl Theme {
|
||||||
pub fn reset_exports() -> Result<(), OutputError> {
|
pub fn reset_exports() -> Result<(), OutputError> {
|
||||||
let gtk_res = Theme::reset_gtk();
|
let gtk_res = Theme::reset_gtk();
|
||||||
let qt_res = Theme::reset_qt();
|
let qt_res = Theme::reset_qt();
|
||||||
|
let qt56ct_res = Theme::reset_qt56ct();
|
||||||
gtk_res?;
|
gtk_res?;
|
||||||
qt_res?;
|
qt_res?;
|
||||||
|
qt56ct_res?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
use crate::Theme;
|
use crate::Theme;
|
||||||
use configparser::ini::Ini;
|
use configparser::ini::Ini;
|
||||||
|
use palette::{Mix, Srgba, WithAlpha, blend::Compose, rgb::Rgba};
|
||||||
use std::{
|
use std::{
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
|
io::Write,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{OutputError, qt_settings_ini_style};
|
use super::{OutputError, qt_settings_ini_style};
|
||||||
|
|
@ -15,7 +18,117 @@ impl Theme {
|
||||||
/// Increment this value when changes to qt{5,6}ct.conf are needed.
|
/// Increment this value when changes to qt{5,6}ct.conf are needed.
|
||||||
/// If the config's version is outdated, we update several sections.
|
/// If the config's version is outdated, we update several sections.
|
||||||
/// Otherwise, only the light/dark mode is updated.
|
/// Otherwise, only the light/dark mode is updated.
|
||||||
const COSMIC_QT_VERSION: u64 = 1;
|
const COSMIC_QT_VERSION: u64 = 2;
|
||||||
|
|
||||||
|
/// Produces a QPalette ini file for qt5ct and qt6ct.
|
||||||
|
///
|
||||||
|
/// Example file: https://github.com/trialuser02/qt6ct/blob/master/colors/airy.conf
|
||||||
|
#[must_use]
|
||||||
|
#[cold]
|
||||||
|
pub fn as_qpalette(&self) -> String {
|
||||||
|
let lightest = if self.is_dark {
|
||||||
|
self.background.on
|
||||||
|
} else {
|
||||||
|
self.background.base
|
||||||
|
};
|
||||||
|
let darkest = if self.is_dark {
|
||||||
|
self.background.base
|
||||||
|
} else {
|
||||||
|
self.background.on
|
||||||
|
};
|
||||||
|
let active = QPaletteGroup {
|
||||||
|
window_text: self.background.on,
|
||||||
|
button: self.button.base,
|
||||||
|
light: self.button.base.mix(lightest, 0.1),
|
||||||
|
midlight: self.button.base.mix(lightest, 0.05),
|
||||||
|
dark: self.button.base.mix(darkest, 0.1),
|
||||||
|
mid: self.button.base.mix(darkest, 0.05),
|
||||||
|
text: self.background.component.on,
|
||||||
|
bright_text: lightest,
|
||||||
|
button_text: self.button.on,
|
||||||
|
base: self.background.component.base,
|
||||||
|
window: self.background.base,
|
||||||
|
shadow: darkest,
|
||||||
|
// selection colors are swapped to fix menu bar contrast
|
||||||
|
highlight: self.background.component.selected_text,
|
||||||
|
highlighted_text: self.background.component.selected,
|
||||||
|
link: self.link_button.on,
|
||||||
|
link_visited: self.link_button.on.mix(self.secondary.component.base, 0.2),
|
||||||
|
alternate_base: self.background.base.mix(self.accent.base, 0.05),
|
||||||
|
no_role: self.background.component.disabled,
|
||||||
|
tool_tip_base: self.background.component.base,
|
||||||
|
tool_tip_text: self.background.component.on,
|
||||||
|
placeholder_text: self.background.component.on.with_alpha(0.5),
|
||||||
|
};
|
||||||
|
let inactive = QPaletteGroup {
|
||||||
|
window_text: active.window_text.with_alpha(0.8),
|
||||||
|
text: active.text.with_alpha(0.8),
|
||||||
|
highlighted_text: active.highlighted_text.with_alpha(0.8),
|
||||||
|
tool_tip_text: active.tool_tip_text.with_alpha(0.8),
|
||||||
|
..active
|
||||||
|
};
|
||||||
|
let disabled = QPaletteGroup {
|
||||||
|
button: self.button.disabled,
|
||||||
|
text: self.background.component.on_disabled,
|
||||||
|
button_text: self.button.on_disabled,
|
||||||
|
base: self.background.component.disabled,
|
||||||
|
highlighted_text: active.highlighted_text.with_alpha(0.5),
|
||||||
|
link: self.link_button.on_disabled,
|
||||||
|
link_visited: self
|
||||||
|
.link_button
|
||||||
|
.on_disabled
|
||||||
|
.mix(self.secondary.component.disabled, 0.2),
|
||||||
|
alternate_base: self.background.base.mix(self.accent.disabled, 0.05),
|
||||||
|
tool_tip_base: self.background.component.disabled,
|
||||||
|
tool_tip_text: self.background.component.on_disabled,
|
||||||
|
placeholder_text: self.background.component.on_disabled.with_alpha(0.5),
|
||||||
|
..inactive
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
r#"# GENERATED BY COSMIC
|
||||||
|
|
||||||
|
[ColorScheme]
|
||||||
|
active_colors={}
|
||||||
|
disabled_colors={}
|
||||||
|
inactive_colors={}
|
||||||
|
"#,
|
||||||
|
active.as_list(),
|
||||||
|
disabled.as_list(),
|
||||||
|
inactive.as_list(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the QPalette ini files to:
|
||||||
|
/// - `~/.config/qt6ct/colors/`
|
||||||
|
/// - `~/.config/qt5ct/colors/`
|
||||||
|
#[cold]
|
||||||
|
pub fn write_qt56ct(&self) -> Result<(), OutputError> {
|
||||||
|
let qpalette = self.as_qpalette();
|
||||||
|
let qt5ct_res = self.write_ct("qt5ct", &qpalette);
|
||||||
|
let qt6ct_res = self.write_ct("qt6ct", &qpalette);
|
||||||
|
qt5ct_res?;
|
||||||
|
qt6ct_res?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[must_use]
|
||||||
|
#[cold]
|
||||||
|
fn write_ct(&self, ct: &str, qpalette: &str) -> Result<(), OutputError> {
|
||||||
|
let file_path = Self::get_qpalette_path(ct, self.is_dark)?;
|
||||||
|
let tmp_file_path = file_path.with_extension("conf.new");
|
||||||
|
|
||||||
|
let mut tmp_file = File::create(&tmp_file_path).map_err(OutputError::Io)?;
|
||||||
|
let res = tmp_file
|
||||||
|
.write_all(qpalette.as_bytes())
|
||||||
|
.and_then(|_| tmp_file.flush())
|
||||||
|
.and_then(|_| std::fs::rename(&tmp_file_path, file_path));
|
||||||
|
if let Err(e) = res {
|
||||||
|
_ = std::fs::remove_file(&tmp_file_path);
|
||||||
|
return Err(OutputError::Io(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Edits qt{5,6}ct.conf to use COSMIC styles if needed.
|
/// Edits qt{5,6}ct.conf to use COSMIC styles if needed.
|
||||||
#[cold]
|
#[cold]
|
||||||
|
|
@ -39,7 +152,7 @@ impl Theme {
|
||||||
.map_err(OutputError::Ini)?
|
.map_err(OutputError::Ini)?
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let color_scheme_path = Self::get_qt_colors_path(is_dark)?;
|
let color_scheme_path = Self::get_qpalette_path(ct, is_dark)?;
|
||||||
let icon_theme = if is_dark { "breeze-dark" } else { "breeze" };
|
let icon_theme = if is_dark { "breeze-dark" } else { "breeze" };
|
||||||
|
|
||||||
ini.set(
|
ini.set(
|
||||||
|
|
@ -91,11 +204,48 @@ impl Theme {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reset the applied qt56ct config by removing COSMIC-specific entries from the config file.
|
||||||
|
#[cold]
|
||||||
|
pub fn reset_qt56ct() -> Result<(), OutputError> {
|
||||||
|
let qt5ct_res = Self::reset_ct("qt5ct");
|
||||||
|
let qt6ct_res = Self::reset_ct("qt6ct");
|
||||||
|
qt5ct_res?;
|
||||||
|
qt6ct_res?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[must_use]
|
||||||
|
#[cold]
|
||||||
|
fn reset_ct(ct: &str) -> Result<(), OutputError> {
|
||||||
|
let path = Self::get_conf_path(ct)?;
|
||||||
|
let file_content = fs::read_to_string(&path).map_err(OutputError::Io)?;
|
||||||
|
let mut ini = Ini::new_cs();
|
||||||
|
ini.read(file_content).map_err(OutputError::Ini)?;
|
||||||
|
|
||||||
|
let old_version = ini
|
||||||
|
.getuint("Appearance", "cosmic_qt_version")
|
||||||
|
.map_err(OutputError::Ini)?
|
||||||
|
.unwrap_or_default();
|
||||||
|
if old_version == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
ini.remove_key("Appearance", "cosmic_qt_version");
|
||||||
|
ini.remove_key("Appearance", "color_scheme_path");
|
||||||
|
ini.remove_key("Appearance", "icon_theme");
|
||||||
|
|
||||||
|
ini.pretty_write(path, &qt_settings_ini_style())
|
||||||
|
.map_err(OutputError::Io)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the file paths of the form `~/.config/ct/ct.conf`:
|
/// Returns the file paths of the form `~/.config/ct/ct.conf`:
|
||||||
/// e.g. `~/.config/qt6ct/qt6ct.conf`.
|
/// e.g. `~/.config/qt6ct/qt6ct.conf`.
|
||||||
///
|
///
|
||||||
/// The file and its parent directory are created if they don't exist.
|
/// The file and its parent directory are created if they don't exist.
|
||||||
|
#[cold]
|
||||||
fn get_conf_path(ct: &str) -> Result<PathBuf, OutputError> {
|
fn get_conf_path(ct: &str) -> Result<PathBuf, OutputError> {
|
||||||
|
assert!(ct == "qt5ct" || ct == "qt6ct");
|
||||||
|
|
||||||
let Some(mut config_dir) = dirs::config_dir() else {
|
let Some(mut config_dir) = dirs::config_dir() else {
|
||||||
return Err(OutputError::MissingConfigDir);
|
return Err(OutputError::MissingConfigDir);
|
||||||
};
|
};
|
||||||
|
|
@ -111,4 +261,155 @@ impl Theme {
|
||||||
|
|
||||||
Ok(file_path)
|
Ok(file_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a path like `~/.config/qt6ct/colors/CosmicDark.conf`
|
||||||
|
///
|
||||||
|
/// Its parent directory is created if it doesn't exist.
|
||||||
|
#[cold]
|
||||||
|
fn get_qpalette_path(ct: &str, is_dark: bool) -> Result<PathBuf, OutputError> {
|
||||||
|
assert!(ct == "qt5ct" || ct == "qt6ct");
|
||||||
|
|
||||||
|
let Some(mut config_dir) = dirs::config_dir() else {
|
||||||
|
return Err(OutputError::MissingConfigDir);
|
||||||
|
};
|
||||||
|
config_dir.push(&ct);
|
||||||
|
config_dir.push("colors");
|
||||||
|
if !config_dir.exists() {
|
||||||
|
fs::create_dir_all(&config_dir).map_err(OutputError::Io)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_name = if is_dark {
|
||||||
|
"CosmicDark.conf"
|
||||||
|
} else {
|
||||||
|
"CosmicLight.conf"
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(config_dir.join(file_name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines the different symbolic color roles used in current GUIs.
|
||||||
|
///
|
||||||
|
/// qt5ct and qt6ct consume this as a list of colors, ordered by ColorRole:
|
||||||
|
/// - https://doc.qt.io/qt-6/qpalette.html#ColorRole-enum
|
||||||
|
/// - https://doc.qt.io/archives/qt-5.15/qpalette.html#ColorRole-enum
|
||||||
|
struct QPaletteGroup {
|
||||||
|
/// A general foreground color.
|
||||||
|
window_text: Srgba,
|
||||||
|
/// The general button background color.
|
||||||
|
button: Srgba,
|
||||||
|
/// Lighter than [button] color, used mostly for 3D bevel and shadow effects.
|
||||||
|
light: Srgba,
|
||||||
|
/// Between [button] and [light], used mostly for 3D bevel and shadow effects.
|
||||||
|
midlight: Srgba,
|
||||||
|
/// Darker than [button], used mostly for 3D bevel and shadow effects.
|
||||||
|
dark: Srgba,
|
||||||
|
/// Between [button] and [dark], used mostly for 3D bevel and shadow effects.
|
||||||
|
mid: Srgba,
|
||||||
|
/// The foreground color used with [base].
|
||||||
|
text: Srgba,
|
||||||
|
/// A text color that is very different from [window_text], and contrasts well with e.g. [dark].
|
||||||
|
/// Typically used for text that needs to be drawn where [text] or [window_text] would give poor contrast, such as on pressed push buttons.
|
||||||
|
bright_text: Srgba,
|
||||||
|
/// A foreground color used with the [button] color.
|
||||||
|
button_text: Srgba,
|
||||||
|
/// Used mostly as the background color for text entry widgets, but can also be used for other painting -
|
||||||
|
/// such as the background of combobox drop down lists and toolbar handles.
|
||||||
|
base: Srgba,
|
||||||
|
/// A general background color.
|
||||||
|
window: Srgba,
|
||||||
|
/// A very dark color, used mostly for 3D bevel and shadow effects.
|
||||||
|
/// Opaque black by default.
|
||||||
|
shadow: Srgba,
|
||||||
|
/// A color to indicate a selected item or the current item.
|
||||||
|
highlight: Srgba,
|
||||||
|
/// A text color that contrasts with [highlight].
|
||||||
|
highlighted_text: Srgba,
|
||||||
|
/// A text color used for unvisited hyperlinks.
|
||||||
|
link: Srgba,
|
||||||
|
/// A text color used for already visited hyperlinks.
|
||||||
|
link_visited: Srgba,
|
||||||
|
/// Used as the alternate background color in views with alternating row colors.
|
||||||
|
alternate_base: Srgba,
|
||||||
|
/// No role; this special role is often used to indicate that a role has not been assigned.
|
||||||
|
no_role: Srgba,
|
||||||
|
/// Used as the background color for QToolTip and QWhatsThis.
|
||||||
|
/// Tool tips use the inactive color group of QPalette, because tool tips are not active windows.
|
||||||
|
tool_tip_base: Srgba,
|
||||||
|
/// Used as the foreground color for QToolTip and QWhatsThis.
|
||||||
|
/// Tool tips use the inactive color group of QPalette, because tool tips are not active windows.
|
||||||
|
tool_tip_text: Srgba,
|
||||||
|
/// Used as the placeholder color for various text input widgets.
|
||||||
|
placeholder_text: Srgba,
|
||||||
|
// /// [accent] only exists since Qt 6.6. Including it here breaks qt5ct.
|
||||||
|
// /// When omitted, it defaults to [highlight].
|
||||||
|
// accent: Srgba,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QPaletteGroup {
|
||||||
|
/// Returns a comma-separated list of the colors as hex codes.
|
||||||
|
/// E.g. `#ff000000, #ffdcdcdc, ...`
|
||||||
|
///
|
||||||
|
/// Any transparent colors are flattened with [base] to avoid issues with
|
||||||
|
/// the Fusion style.
|
||||||
|
fn as_list(&self) -> String {
|
||||||
|
let colors = vec![
|
||||||
|
to_argb_hex(self.window_text.over(self.base)),
|
||||||
|
to_argb_hex(self.button.over(self.base)),
|
||||||
|
to_argb_hex(self.light.over(self.base)),
|
||||||
|
to_argb_hex(self.midlight.over(self.base)),
|
||||||
|
to_argb_hex(self.dark.over(self.base)),
|
||||||
|
to_argb_hex(self.mid.over(self.base)),
|
||||||
|
to_argb_hex(self.text.over(self.base)),
|
||||||
|
to_argb_hex(self.bright_text.over(self.base)),
|
||||||
|
to_argb_hex(self.button_text.over(self.base)),
|
||||||
|
to_argb_hex(self.base.over(self.base)),
|
||||||
|
to_argb_hex(self.window.over(self.base)),
|
||||||
|
to_argb_hex(self.shadow.over(self.base)),
|
||||||
|
to_argb_hex(self.highlight.over(self.base)),
|
||||||
|
to_argb_hex(self.highlighted_text.over(self.base)),
|
||||||
|
to_argb_hex(self.link.over(self.base)),
|
||||||
|
to_argb_hex(self.link_visited.over(self.base)),
|
||||||
|
to_argb_hex(self.alternate_base.over(self.base)),
|
||||||
|
to_argb_hex(self.no_role.over(self.base)),
|
||||||
|
to_argb_hex(self.tool_tip_base.over(self.base)),
|
||||||
|
to_argb_hex(self.tool_tip_text.over(self.base)),
|
||||||
|
to_argb_hex(self.placeholder_text.over(self.base)),
|
||||||
|
];
|
||||||
|
colors.join(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a color to a hex string in the format `#AARRGGBB`.
|
||||||
|
/// Do not use [to_hex] since that uses the format `RRGGBBAA`.
|
||||||
|
fn to_argb_hex(c: Srgba) -> String {
|
||||||
|
let c_u8: Rgba<palette::encoding::Srgb, u8> = c.into_format();
|
||||||
|
format!(
|
||||||
|
"#{:02x}{:02x}{:02x}{:02x}",
|
||||||
|
c_u8.alpha, c_u8.red, c_u8.green, c_u8.blue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_color_to_argb_hex() {
|
||||||
|
let color = Srgba::new(0x33, 0x55, 0x77, 0xff);
|
||||||
|
let argb = to_argb_hex(color.into());
|
||||||
|
assert_eq!(argb, "#ff335577");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_light_default_qpalette() {
|
||||||
|
let light_default_qpalette = Theme::light_default().as_qpalette();
|
||||||
|
insta::assert_snapshot!(light_default_qpalette);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dark_default_qpalette() {
|
||||||
|
let dark_default_qpalette = Theme::dark_default().as_qpalette();
|
||||||
|
insta::assert_snapshot!(dark_default_qpalette);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,11 @@ impl Theme {
|
||||||
/// Produces a color scheme ini file for Qt.
|
/// Produces a color scheme ini file for Qt.
|
||||||
///
|
///
|
||||||
/// Some high-level documentation for this file can be found at:
|
/// Some high-level documentation for this file can be found at:
|
||||||
/// https://web.archive.org/web/20250402234329/https://docs.kde.org/stable5/en/plasma-workspace/kcontrol/colors/
|
/// - https://api.kde.org/kcolorscheme.html
|
||||||
|
/// - https://web.archive.org/web/20250402234329/https://docs.kde.org/stable5/en/plasma-workspace/kcontrol/colors/
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[cold]
|
#[cold]
|
||||||
pub fn as_qt(&self) -> String {
|
pub fn as_kcolorscheme(&self) -> String {
|
||||||
// Usually, disabled elements will have strongly reduced contrast and are often notably darker or lighter
|
// Usually, disabled elements will have strongly reduced contrast and are often notably darker or lighter
|
||||||
let disabled_color_effects = IniColorEffects {
|
let disabled_color_effects = IniColorEffects {
|
||||||
color: self.button.disabled,
|
color: self.button.disabled,
|
||||||
|
|
@ -41,7 +42,7 @@ impl Theme {
|
||||||
|
|
||||||
let bg = self.background.base;
|
let bg = self.background.base;
|
||||||
// the background container
|
// the background container
|
||||||
let view_colors = IniColors {
|
let window_colors = IniColors {
|
||||||
background_alternate: bg.mix(self.accent.base, 0.05),
|
background_alternate: bg.mix(self.accent.base, 0.05),
|
||||||
background_normal: bg,
|
background_normal: bg,
|
||||||
decoration_focus: self.accent_text_color(),
|
decoration_focus: self.accent_text_color(),
|
||||||
|
|
@ -56,16 +57,17 @@ impl Theme {
|
||||||
foreground_visited: self.accent_text_color(),
|
foreground_visited: self.accent_text_color(),
|
||||||
};
|
};
|
||||||
// components inside the background container
|
// components inside the background container
|
||||||
let window_colors = IniColors {
|
let view_colors = IniColors {
|
||||||
background_alternate: self.background.component.base.mix(self.accent.base, 0.05),
|
background_alternate: self.background.component.base.mix(self.accent.base, 0.05),
|
||||||
background_normal: self.background.component.base,
|
background_normal: self.background.component.base,
|
||||||
..view_colors
|
..window_colors
|
||||||
};
|
};
|
||||||
|
|
||||||
// selected text and items
|
// selected text and items
|
||||||
let selection_colors = {
|
let selection_colors = {
|
||||||
let selected = self.background.component.selected;
|
// selection colors are swapped to fix menu bar contrast
|
||||||
let selected_text = self.background.component.selected_text;
|
let selected = self.background.component.selected_text;
|
||||||
|
let selected_text = self.background.component.selected;
|
||||||
IniColors {
|
IniColors {
|
||||||
background_alternate: selected.mix(bg, 0.5),
|
background_alternate: selected.mix(bg, 0.5),
|
||||||
background_normal: selected,
|
background_normal: selected,
|
||||||
|
|
@ -92,8 +94,11 @@ impl Theme {
|
||||||
let complementary_colors = {
|
let complementary_colors = {
|
||||||
let dark = if self.is_dark {
|
let dark = if self.is_dark {
|
||||||
self.clone()
|
self.clone()
|
||||||
|
} else if cfg!(test) {
|
||||||
|
// For reproducible results in tests, use the default dark theme
|
||||||
|
Theme::dark_default()
|
||||||
} else {
|
} else {
|
||||||
Theme::light_config()
|
Theme::dark_config()
|
||||||
.ok()
|
.ok()
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|conf| Theme::get_entry(conf).ok())
|
.and_then(|conf| Theme::get_entry(conf).ok())
|
||||||
|
|
@ -116,10 +121,10 @@ impl Theme {
|
||||||
};
|
};
|
||||||
|
|
||||||
// headers in cosmic don't have a background
|
// headers in cosmic don't have a background
|
||||||
let header_colors = &view_colors;
|
let header_colors = &window_colors;
|
||||||
let header_colors_inactive = &view_colors;
|
let header_colors_inactive = &window_colors;
|
||||||
// tool tips, "What's This" tips, and similar elements
|
// tool tips, "What's This" tips, and similar elements
|
||||||
let tooltip_colors = &window_colors;
|
let tooltip_colors = &view_colors;
|
||||||
|
|
||||||
let general_color_scheme = if self.is_dark {
|
let general_color_scheme = if self.is_dark {
|
||||||
"CosmicDark"
|
"CosmicDark"
|
||||||
|
|
@ -198,7 +203,7 @@ widgetStyle=qt6ct-style
|
||||||
format_ini_colors(&tooltip_colors, bg),
|
format_ini_colors(&tooltip_colors, bg),
|
||||||
format_ini_colors(&view_colors, bg),
|
format_ini_colors(&view_colors, bg),
|
||||||
format_ini_colors(&window_colors, bg),
|
format_ini_colors(&window_colors, bg),
|
||||||
format_ini_wm_colors(&view_colors, self.is_dark),
|
format_ini_wm_colors(&window_colors, self.is_dark),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,14 +217,14 @@ widgetStyle=qt6ct-style
|
||||||
/// Returns an `OutputError` if there is an error writing the colors file.
|
/// Returns an `OutputError` if there is an error writing the colors file.
|
||||||
#[cold]
|
#[cold]
|
||||||
pub fn write_qt(&self) -> Result<(), OutputError> {
|
pub fn write_qt(&self) -> Result<(), OutputError> {
|
||||||
let colors = self.as_qt();
|
let kcolorscheme = self.as_kcolorscheme();
|
||||||
let file_path = Self::get_qt_colors_path(self.is_dark)?;
|
let file_path = Self::get_kcolorscheme_path(self.is_dark)?;
|
||||||
let tmp_file_path = file_path.with_extension("colors.new");
|
let tmp_file_path = file_path.with_extension("colors.new");
|
||||||
|
|
||||||
// Write to tmp_file_path first, then move it to file_path
|
// Write to tmp_file_path first, then move it to file_path
|
||||||
let mut tmp_file = File::create(&tmp_file_path).map_err(OutputError::Io)?;
|
let mut tmp_file = File::create(&tmp_file_path).map_err(OutputError::Io)?;
|
||||||
let res = tmp_file
|
let res = tmp_file
|
||||||
.write_all(colors.as_bytes())
|
.write_all(kcolorscheme.as_bytes())
|
||||||
.and_then(|_| tmp_file.flush())
|
.and_then(|_| tmp_file.flush())
|
||||||
.and_then(|_| std::fs::rename(&tmp_file_path, file_path));
|
.and_then(|_| std::fs::rename(&tmp_file_path, file_path));
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
|
|
@ -245,7 +250,7 @@ widgetStyle=qt6ct-style
|
||||||
let kdeglobals_file = config_dir.join("kdeglobals");
|
let kdeglobals_file = config_dir.join("kdeglobals");
|
||||||
let mut kdeglobals_ini = Self::read_ini(&kdeglobals_file)?;
|
let mut kdeglobals_ini = Self::read_ini(&kdeglobals_file)?;
|
||||||
|
|
||||||
let src_file = Self::get_qt_colors_path(is_dark)?;
|
let src_file = Self::get_kcolorscheme_path(is_dark)?;
|
||||||
let src_ini = Self::read_ini(&src_file)?;
|
let src_ini = Self::read_ini(&src_file)?;
|
||||||
|
|
||||||
Self::backup_non_cosmic_kdeglobals(&kdeglobals_ini, &kdeglobals_file)
|
Self::backup_non_cosmic_kdeglobals(&kdeglobals_ini, &kdeglobals_file)
|
||||||
|
|
@ -288,7 +293,7 @@ widgetStyle=qt6ct-style
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_dark = false; // doesn't matter since we're only reading keys
|
let is_dark = false; // doesn't matter since we're only reading keys
|
||||||
let src_file = Self::get_qt_colors_path(is_dark)?;
|
let src_file = Self::get_kcolorscheme_path(is_dark)?;
|
||||||
let src_ini = Self::read_ini(&src_file)?;
|
let src_ini = Self::read_ini(&src_file)?;
|
||||||
|
|
||||||
for (section, key_value) in src_ini.get_map_ref() {
|
for (section, key_value) in src_ini.get_map_ref() {
|
||||||
|
|
@ -303,8 +308,8 @@ widgetStyle=qt6ct-style
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a path like `~/.config/color-schemes/CosmicDark.colors`
|
/// Gets a path like `~/.local/share/color-schemes/CosmicDark.colors`
|
||||||
pub fn get_qt_colors_path(is_dark: bool) -> Result<PathBuf, OutputError> {
|
fn get_kcolorscheme_path(is_dark: bool) -> Result<PathBuf, OutputError> {
|
||||||
let Some(mut data_dir) = dirs::data_dir() else {
|
let Some(mut data_dir) = dirs::data_dir() else {
|
||||||
return Err(OutputError::MissingDataDir);
|
return Err(OutputError::MissingDataDir);
|
||||||
};
|
};
|
||||||
|
|
@ -520,3 +525,44 @@ impl ColorEffect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_opaque_color_to_rgb() {
|
||||||
|
let color = Srgba::new(30.0 / 255.0, 50.0 / 255.0, 70.0 / 255.0, 1.0);
|
||||||
|
let bg = Srgba::new(1.0, 1.0, 1.0, 1.0);
|
||||||
|
let result = to_rgb(color, bg);
|
||||||
|
assert_eq!(result, "30,50,70");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transparent_color_to_rgb() {
|
||||||
|
let color = Srgba::new(0.0, 0.0, 0.0, 0.0);
|
||||||
|
let bg = Srgba::new(1.0, 1.0, 1.0, 1.0);
|
||||||
|
let result = to_rgb(color, bg);
|
||||||
|
assert_eq!(result, "255,255,255");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_translucent_color_to_rgb() {
|
||||||
|
let color = Srgba::new(0.0, 0.0, 0.0, 0.9);
|
||||||
|
let bg = Srgba::new(1.0, 1.0, 1.0, 1.0);
|
||||||
|
let result = to_rgb(color, bg);
|
||||||
|
assert_eq!(result, "26,26,26");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_light_default_kcolorscheme() {
|
||||||
|
let light_default_kcolorscheme = Theme::light_default().as_kcolorscheme();
|
||||||
|
insta::assert_snapshot!(light_default_kcolorscheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dark_default_kcolorscheme() {
|
||||||
|
let dark_default_kcolorscheme = Theme::dark_default().as_kcolorscheme();
|
||||||
|
insta::assert_snapshot!(dark_default_kcolorscheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
source: cosmic-theme/src/output/qt56ct_output.rs
|
||||||
|
expression: dark_default_qpalette
|
||||||
|
---
|
||||||
|
# GENERATED BY COSMIC
|
||||||
|
|
||||||
|
[ColorScheme]
|
||||||
|
active_colors=#ffe7e7e7, #ff4a4a4a, #ff555555, #ff505050, #ff4f4f4f, #ff4d4d4d, #ffc0c0c0, #ffe7e7e7, #ffc0c0c0, #ff2e2e2e, #ff1b1b1b, #ff1b1b1b, #ff63d0df, #ff434343, #ff63d0df, #ff5bb2be, #ff1f2425, #ff2e2e2e, #ff2e2e2e, #ffc0c0c0, #ff777777
|
||||||
|
disabled_colors=#e6d3d3d3, #8f474747, #a9696969, #a4626262, #a95f5f5f, #a45d5d5d, #d2a1a1a1, #ffe7e7e7, #d2a1a1a1, #bf2e2e2e, #ff1b1b1b, #ff1b1b1b, #ff63d0df, #bf3c3c3c, #bf30555a, #bf324f53, #ff1f2425, #bf2e2e2e, #bf2e2e2e, #d2a1a1a1, #bf909090
|
||||||
|
inactive_colors=#ffc2c2c2, #ff4a4a4a, #ff555555, #ff505050, #ff4f4f4f, #ff4d4d4d, #ffa3a3a3, #ffe7e7e7, #ffc0c0c0, #ff2e2e2e, #ff1b1b1b, #ff1b1b1b, #ff63d0df, #ff3f3f3f, #ff63d0df, #ff5bb2be, #ff1f2425, #ff2e2e2e, #ff2e2e2e, #ffa3a3a3, #ff777777
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
source: cosmic-theme/src/output/qt56ct_output.rs
|
||||||
|
expression: light_default_qpalette
|
||||||
|
---
|
||||||
|
# GENERATED BY COSMIC
|
||||||
|
|
||||||
|
[ColorScheme]
|
||||||
|
active_colors=#ff121212, #ffc3c3c3, #ffbababa, #ffbebebe, #ffb3b3b3, #ffbbbbbb, #ff272727, #ffd7d7d7, #ff272727, #fff5f5f5, #ffd7d7d7, #ff121212, #ff00525a, #fff6f6f6, #ff00525a, #ff317379, #ffccd0d1, #fff5f5f5, #fff5f5f5, #ff272727, #ff8e8e8e
|
||||||
|
disabled_colors=#e62b2b2b, #8fc9c9c9, #a99b9b9b, #a4a0a0a0, #a9929292, #a49b9b9b, #d2535353, #ffd7d7d7, #d2535353, #bff5f5f5, #ffd7d7d7, #ff121212, #ff00525a, #bff6f6f6, #bf526d70, #bf72888a, #ffccd0d1, #bff5f5f5, #bff5f5f5, #d2535353, #bf6c6c6c
|
||||||
|
inactive_colors=#ff3f3f3f, #ffc3c3c3, #ffbababa, #ffbebebe, #ffb3b3b3, #ffbbbbbb, #ff505050, #ffd7d7d7, #ff272727, #fff5f5f5, #ffd7d7d7, #ff121212, #ff00525a, #fff6f6f6, #ff00525a, #ff317379, #ffccd0d1, #fff5f5f5, #fff5f5f5, #ff505050, #ff8e8e8e
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
---
|
||||||
|
source: cosmic-theme/src/output/qt_output.rs
|
||||||
|
expression: dark_default_kcolorscheme
|
||||||
|
---
|
||||||
|
# GENERATED BY COSMIC
|
||||||
|
|
||||||
|
[ColorEffects:Disabled]
|
||||||
|
Color=43,43,43
|
||||||
|
ColorAmount=0
|
||||||
|
ColorEffect=0
|
||||||
|
ContrastAmount=0.65
|
||||||
|
ContrastEffect=1
|
||||||
|
IntensityAmount=0.1
|
||||||
|
IntensityEffect=2
|
||||||
|
|
||||||
|
[ColorEffects:Inactive]
|
||||||
|
ChangeSelectionColor=false
|
||||||
|
Enable=false
|
||||||
|
Color=27,27,27
|
||||||
|
ColorAmount=0.025
|
||||||
|
ColorEffect=2
|
||||||
|
ContrastAmount=0.1
|
||||||
|
ContrastEffect=2
|
||||||
|
IntensityAmount=0
|
||||||
|
IntensityEffect=0
|
||||||
|
|
||||||
|
[Colors:Button]
|
||||||
|
BackgroundAlternate=99,208,223
|
||||||
|
BackgroundNormal=60,60,60
|
||||||
|
DecorationFocus=99,208,223
|
||||||
|
DecorationHover=99,208,223
|
||||||
|
ForegroundActive=99,208,223
|
||||||
|
ForegroundInactive=211,211,211
|
||||||
|
ForegroundLink=99,208,223
|
||||||
|
ForegroundNegative=255,160,154
|
||||||
|
ForegroundNeutral=255,163,125
|
||||||
|
ForegroundNormal=231,231,231
|
||||||
|
ForegroundPositive=94,219,140
|
||||||
|
ForegroundVisited=99,208,223
|
||||||
|
|
||||||
|
[Colors:Complementary]
|
||||||
|
BackgroundAlternate=99,208,223
|
||||||
|
BackgroundNormal=27,27,27
|
||||||
|
DecorationFocus=99,208,223
|
||||||
|
DecorationHover=99,208,223
|
||||||
|
ForegroundActive=99,208,223
|
||||||
|
ForegroundInactive=211,211,211
|
||||||
|
ForegroundLink=99,208,223
|
||||||
|
ForegroundNegative=255,160,154
|
||||||
|
ForegroundNeutral=255,163,125
|
||||||
|
ForegroundNormal=231,231,231
|
||||||
|
ForegroundPositive=94,219,140
|
||||||
|
ForegroundVisited=99,208,223
|
||||||
|
|
||||||
|
[Colors:Header]
|
||||||
|
BackgroundAlternate=31,36,37
|
||||||
|
BackgroundNormal=27,27,27
|
||||||
|
DecorationFocus=99,208,223
|
||||||
|
DecorationHover=99,208,223
|
||||||
|
ForegroundActive=99,208,223
|
||||||
|
ForegroundInactive=211,211,211
|
||||||
|
ForegroundLink=99,208,223
|
||||||
|
ForegroundNegative=255,160,154
|
||||||
|
ForegroundNeutral=255,163,125
|
||||||
|
ForegroundNormal=231,231,231
|
||||||
|
ForegroundPositive=94,219,140
|
||||||
|
ForegroundVisited=99,208,223
|
||||||
|
|
||||||
|
[Colors:Header][Inactive]
|
||||||
|
BackgroundAlternate=31,36,37
|
||||||
|
BackgroundNormal=27,27,27
|
||||||
|
DecorationFocus=99,208,223
|
||||||
|
DecorationHover=99,208,223
|
||||||
|
ForegroundActive=99,208,223
|
||||||
|
ForegroundInactive=211,211,211
|
||||||
|
ForegroundLink=99,208,223
|
||||||
|
ForegroundNegative=255,160,154
|
||||||
|
ForegroundNeutral=255,163,125
|
||||||
|
ForegroundNormal=231,231,231
|
||||||
|
ForegroundPositive=94,219,140
|
||||||
|
ForegroundVisited=99,208,223
|
||||||
|
|
||||||
|
[Colors:Selection]
|
||||||
|
BackgroundAlternate=63,118,125
|
||||||
|
BackgroundNormal=99,208,223
|
||||||
|
DecorationFocus=99,208,223
|
||||||
|
DecorationHover=99,208,223
|
||||||
|
ForegroundActive=67,67,67
|
||||||
|
ForegroundInactive=83,138,145
|
||||||
|
ForegroundLink=27,27,27
|
||||||
|
ForegroundNegative=255,160,154
|
||||||
|
ForegroundNeutral=255,163,125
|
||||||
|
ForegroundNormal=67,67,67
|
||||||
|
ForegroundPositive=94,219,140
|
||||||
|
ForegroundVisited=99,208,223
|
||||||
|
|
||||||
|
[Colors:Tooltip]
|
||||||
|
BackgroundAlternate=49,55,55
|
||||||
|
BackgroundNormal=46,46,46
|
||||||
|
DecorationFocus=99,208,223
|
||||||
|
DecorationHover=99,208,223
|
||||||
|
ForegroundActive=99,208,223
|
||||||
|
ForegroundInactive=211,211,211
|
||||||
|
ForegroundLink=99,208,223
|
||||||
|
ForegroundNegative=255,160,154
|
||||||
|
ForegroundNeutral=255,163,125
|
||||||
|
ForegroundNormal=231,231,231
|
||||||
|
ForegroundPositive=94,219,140
|
||||||
|
ForegroundVisited=99,208,223
|
||||||
|
|
||||||
|
[Colors:View]
|
||||||
|
BackgroundAlternate=49,55,55
|
||||||
|
BackgroundNormal=46,46,46
|
||||||
|
DecorationFocus=99,208,223
|
||||||
|
DecorationHover=99,208,223
|
||||||
|
ForegroundActive=99,208,223
|
||||||
|
ForegroundInactive=211,211,211
|
||||||
|
ForegroundLink=99,208,223
|
||||||
|
ForegroundNegative=255,160,154
|
||||||
|
ForegroundNeutral=255,163,125
|
||||||
|
ForegroundNormal=231,231,231
|
||||||
|
ForegroundPositive=94,219,140
|
||||||
|
ForegroundVisited=99,208,223
|
||||||
|
|
||||||
|
[Colors:Window]
|
||||||
|
BackgroundAlternate=31,36,37
|
||||||
|
BackgroundNormal=27,27,27
|
||||||
|
DecorationFocus=99,208,223
|
||||||
|
DecorationHover=99,208,223
|
||||||
|
ForegroundActive=99,208,223
|
||||||
|
ForegroundInactive=211,211,211
|
||||||
|
ForegroundLink=99,208,223
|
||||||
|
ForegroundNegative=255,160,154
|
||||||
|
ForegroundNeutral=255,163,125
|
||||||
|
ForegroundNormal=231,231,231
|
||||||
|
ForegroundPositive=94,219,140
|
||||||
|
ForegroundVisited=99,208,223
|
||||||
|
|
||||||
|
[General]
|
||||||
|
ColorScheme=CosmicDark
|
||||||
|
Name=COSMIC Dark
|
||||||
|
shadeSortColumn=true
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Theme=breeze-dark
|
||||||
|
|
||||||
|
[KDE]
|
||||||
|
contrast=4
|
||||||
|
widgetStyle=qt6ct-style
|
||||||
|
|
||||||
|
[WM]
|
||||||
|
activeBackground=27,27,27
|
||||||
|
activeBlend=99,208,223
|
||||||
|
activeForeground=99,208,223
|
||||||
|
inactiveBackground=27,27,27
|
||||||
|
inactiveBlend=99,208,223
|
||||||
|
inactiveForeground=99,208,223
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
---
|
||||||
|
source: cosmic-theme/src/output/qt_output.rs
|
||||||
|
expression: light_default_kcolorscheme
|
||||||
|
---
|
||||||
|
# GENERATED BY COSMIC
|
||||||
|
|
||||||
|
[ColorEffects:Disabled]
|
||||||
|
Color=194,194,194
|
||||||
|
ColorAmount=0
|
||||||
|
ColorEffect=0
|
||||||
|
ContrastAmount=0.65
|
||||||
|
ContrastEffect=1
|
||||||
|
IntensityAmount=0.1
|
||||||
|
IntensityEffect=2
|
||||||
|
|
||||||
|
[ColorEffects:Inactive]
|
||||||
|
ChangeSelectionColor=false
|
||||||
|
Enable=false
|
||||||
|
Color=215,215,215
|
||||||
|
ColorAmount=0.025
|
||||||
|
ColorEffect=2
|
||||||
|
ContrastAmount=0.1
|
||||||
|
ContrastEffect=2
|
||||||
|
IntensityAmount=0
|
||||||
|
IntensityEffect=0
|
||||||
|
|
||||||
|
[Colors:Button]
|
||||||
|
BackgroundAlternate=0,82,90
|
||||||
|
BackgroundNormal=173,173,173
|
||||||
|
DecorationFocus=0,82,90
|
||||||
|
DecorationHover=0,82,90
|
||||||
|
ForegroundActive=0,82,90
|
||||||
|
ForegroundInactive=38,38,38
|
||||||
|
ForegroundLink=0,82,90
|
||||||
|
ForegroundNegative=137,4,24
|
||||||
|
ForegroundNeutral=121,44,0
|
||||||
|
ForegroundNormal=18,18,18
|
||||||
|
ForegroundPositive=0,87,44
|
||||||
|
ForegroundVisited=0,82,90
|
||||||
|
|
||||||
|
[Colors:Complementary]
|
||||||
|
BackgroundAlternate=99,208,223
|
||||||
|
BackgroundNormal=27,27,27
|
||||||
|
DecorationFocus=99,208,223
|
||||||
|
DecorationHover=99,208,223
|
||||||
|
ForegroundActive=99,208,223
|
||||||
|
ForegroundInactive=211,211,211
|
||||||
|
ForegroundLink=99,208,223
|
||||||
|
ForegroundNegative=255,160,154
|
||||||
|
ForegroundNeutral=255,163,125
|
||||||
|
ForegroundNormal=231,231,231
|
||||||
|
ForegroundPositive=94,219,140
|
||||||
|
ForegroundVisited=99,208,223
|
||||||
|
|
||||||
|
[Colors:Header]
|
||||||
|
BackgroundAlternate=204,208,209
|
||||||
|
BackgroundNormal=215,215,215
|
||||||
|
DecorationFocus=0,82,90
|
||||||
|
DecorationHover=0,82,90
|
||||||
|
ForegroundActive=0,82,90
|
||||||
|
ForegroundInactive=38,38,38
|
||||||
|
ForegroundLink=0,82,90
|
||||||
|
ForegroundNegative=137,4,24
|
||||||
|
ForegroundNeutral=121,44,0
|
||||||
|
ForegroundNormal=18,18,18
|
||||||
|
ForegroundPositive=0,87,44
|
||||||
|
ForegroundVisited=0,82,90
|
||||||
|
|
||||||
|
[Colors:Header][Inactive]
|
||||||
|
BackgroundAlternate=204,208,209
|
||||||
|
BackgroundNormal=215,215,215
|
||||||
|
DecorationFocus=0,82,90
|
||||||
|
DecorationHover=0,82,90
|
||||||
|
ForegroundActive=0,82,90
|
||||||
|
ForegroundInactive=38,38,38
|
||||||
|
ForegroundLink=0,82,90
|
||||||
|
ForegroundNegative=137,4,24
|
||||||
|
ForegroundNeutral=121,44,0
|
||||||
|
ForegroundNormal=18,18,18
|
||||||
|
ForegroundPositive=0,87,44
|
||||||
|
ForegroundVisited=0,82,90
|
||||||
|
|
||||||
|
[Colors:Selection]
|
||||||
|
BackgroundAlternate=108,149,152
|
||||||
|
BackgroundNormal=0,82,90
|
||||||
|
DecorationFocus=0,82,90
|
||||||
|
DecorationHover=0,82,90
|
||||||
|
ForegroundActive=246,246,246
|
||||||
|
ForegroundInactive=123,164,168
|
||||||
|
ForegroundLink=215,215,215
|
||||||
|
ForegroundNegative=137,4,24
|
||||||
|
ForegroundNeutral=121,44,0
|
||||||
|
ForegroundNormal=246,246,246
|
||||||
|
ForegroundPositive=0,87,44
|
||||||
|
ForegroundVisited=0,82,90
|
||||||
|
|
||||||
|
[Colors:Tooltip]
|
||||||
|
BackgroundAlternate=233,237,237
|
||||||
|
BackgroundNormal=245,245,245
|
||||||
|
DecorationFocus=0,82,90
|
||||||
|
DecorationHover=0,82,90
|
||||||
|
ForegroundActive=0,82,90
|
||||||
|
ForegroundInactive=38,38,38
|
||||||
|
ForegroundLink=0,82,90
|
||||||
|
ForegroundNegative=137,4,24
|
||||||
|
ForegroundNeutral=121,44,0
|
||||||
|
ForegroundNormal=18,18,18
|
||||||
|
ForegroundPositive=0,87,44
|
||||||
|
ForegroundVisited=0,82,90
|
||||||
|
|
||||||
|
[Colors:View]
|
||||||
|
BackgroundAlternate=233,237,237
|
||||||
|
BackgroundNormal=245,245,245
|
||||||
|
DecorationFocus=0,82,90
|
||||||
|
DecorationHover=0,82,90
|
||||||
|
ForegroundActive=0,82,90
|
||||||
|
ForegroundInactive=38,38,38
|
||||||
|
ForegroundLink=0,82,90
|
||||||
|
ForegroundNegative=137,4,24
|
||||||
|
ForegroundNeutral=121,44,0
|
||||||
|
ForegroundNormal=18,18,18
|
||||||
|
ForegroundPositive=0,87,44
|
||||||
|
ForegroundVisited=0,82,90
|
||||||
|
|
||||||
|
[Colors:Window]
|
||||||
|
BackgroundAlternate=204,208,209
|
||||||
|
BackgroundNormal=215,215,215
|
||||||
|
DecorationFocus=0,82,90
|
||||||
|
DecorationHover=0,82,90
|
||||||
|
ForegroundActive=0,82,90
|
||||||
|
ForegroundInactive=38,38,38
|
||||||
|
ForegroundLink=0,82,90
|
||||||
|
ForegroundNegative=137,4,24
|
||||||
|
ForegroundNeutral=121,44,0
|
||||||
|
ForegroundNormal=18,18,18
|
||||||
|
ForegroundPositive=0,87,44
|
||||||
|
ForegroundVisited=0,82,90
|
||||||
|
|
||||||
|
[General]
|
||||||
|
ColorScheme=CosmicLight
|
||||||
|
Name=COSMIC Light
|
||||||
|
shadeSortColumn=true
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Theme=breeze
|
||||||
|
|
||||||
|
[KDE]
|
||||||
|
contrast=4
|
||||||
|
widgetStyle=qt6ct-style
|
||||||
|
|
||||||
|
[WM]
|
||||||
|
activeBackground=215,215,215
|
||||||
|
activeBlend=215,215,215
|
||||||
|
activeForeground=0,82,90
|
||||||
|
inactiveBackground=215,215,215
|
||||||
|
inactiveBlend=215,215,215
|
||||||
|
inactiveForeground=0,82,90
|
||||||
|
|
@ -145,7 +145,6 @@ pub fn is_valid_srgb(c: Srgba) -> bool {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use almost::equal;
|
|
||||||
use palette::{OklabHue, Srgba};
|
use palette::{OklabHue, Srgba};
|
||||||
|
|
||||||
use super::{is_valid_srgb, oklch_to_srgba_nearest_chroma};
|
use super::{is_valid_srgb, oklch_to_srgba_nearest_chroma};
|
||||||
|
|
@ -173,57 +172,57 @@ mod tests {
|
||||||
fn test_conversion_boundaries() {
|
fn test_conversion_boundaries() {
|
||||||
let c1 = palette::Oklcha::new(0.0, 0.288, OklabHue::from_degrees(0.0), 1.0);
|
let c1 = palette::Oklcha::new(0.0, 0.288, OklabHue::from_degrees(0.0), 1.0);
|
||||||
let srgb = oklch_to_srgba_nearest_chroma(c1);
|
let srgb = oklch_to_srgba_nearest_chroma(c1);
|
||||||
equal(srgb.red, 0.0);
|
almost::zero(srgb.red);
|
||||||
equal(srgb.blue, 0.0);
|
almost::zero(srgb.blue);
|
||||||
equal(srgb.green, 0.0);
|
almost::zero(srgb.green);
|
||||||
|
|
||||||
let c1 = palette::Oklcha::new(1.0, 0.288, OklabHue::from_degrees(0.0), 1.0);
|
let c1 = palette::Oklcha::new(1.0, 0.288, OklabHue::from_degrees(0.0), 1.0);
|
||||||
let srgb = oklch_to_srgba_nearest_chroma(c1);
|
let srgb = oklch_to_srgba_nearest_chroma(c1);
|
||||||
|
|
||||||
equal(srgb.red, 1.0);
|
almost::equal(srgb.red, 1.0);
|
||||||
equal(srgb.blue, 1.0);
|
almost::equal(srgb.blue, 1.0);
|
||||||
equal(srgb.green, 1.0);
|
almost::equal(srgb.green, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_conversion_colors() {
|
fn test_conversion_colors() {
|
||||||
let c1 = palette::Oklcha::new(0.4608, 0.11111, OklabHue::new(57.31), 1.0);
|
let c1 = palette::Oklcha::new(0.4608, 0.11111, OklabHue::new(57.31), 1.0);
|
||||||
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
||||||
assert!(srgb.red == 133);
|
assert_eq!(srgb.red, 133);
|
||||||
assert!(srgb.green == 69);
|
assert_eq!(srgb.green, 69);
|
||||||
assert!(srgb.blue == 0);
|
assert_eq!(srgb.blue, 0);
|
||||||
|
|
||||||
let c1 = palette::Oklcha::new(0.30, 0.08, OklabHue::new(35.0), 1.0);
|
let c1 = palette::Oklcha::new(0.30, 0.08, OklabHue::new(35.0), 1.0);
|
||||||
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
||||||
assert!(srgb.red == 78);
|
assert_eq!(srgb.red, 78);
|
||||||
assert!(srgb.green == 27);
|
assert_eq!(srgb.green, 27);
|
||||||
assert!(srgb.blue == 15);
|
assert_eq!(srgb.blue, 15);
|
||||||
|
|
||||||
let c1 = palette::Oklcha::new(0.757, 0.146, OklabHue::new(301.2), 1.0);
|
let c1 = palette::Oklcha::new(0.757, 0.146, OklabHue::new(301.2), 1.0);
|
||||||
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
||||||
assert!(srgb.red == 192);
|
assert_eq!(srgb.red, 192);
|
||||||
assert!(srgb.green == 153);
|
assert_eq!(srgb.green, 153);
|
||||||
assert!(srgb.blue == 253);
|
assert_eq!(srgb.blue, 253);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_conversion_fallback_colors() {
|
fn test_conversion_fallback_colors() {
|
||||||
let c1 = palette::Oklcha::new(0.70, 0.284, OklabHue::new(35.0), 1.0);
|
let c1 = palette::Oklcha::new(0.70, 0.284, OklabHue::new(35.0), 1.0);
|
||||||
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
||||||
assert!(srgb.red == 255);
|
assert_eq!(srgb.red, 255);
|
||||||
assert!(srgb.green == 103);
|
assert_eq!(srgb.green, 102);
|
||||||
assert!(srgb.blue == 65);
|
assert_eq!(srgb.blue, 65);
|
||||||
|
|
||||||
let c1 = palette::Oklcha::new(0.757, 0.239, OklabHue::new(301.2), 1.0);
|
let c1 = palette::Oklcha::new(0.757, 0.239, OklabHue::new(301.2), 1.0);
|
||||||
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
||||||
assert!(srgb.red == 193);
|
assert_eq!(srgb.red, 193);
|
||||||
assert!(srgb.green == 152);
|
assert_eq!(srgb.green, 152);
|
||||||
assert!(srgb.blue == 255);
|
assert_eq!(srgb.blue, 255);
|
||||||
|
|
||||||
let c1 = palette::Oklcha::new(0.163, 0.333, OklabHue::new(141.0), 1.0);
|
let c1 = palette::Oklcha::new(0.163, 0.333, OklabHue::new(141.0), 1.0);
|
||||||
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
||||||
assert!(srgb.red == 1);
|
assert_eq!(srgb.red, 1);
|
||||||
assert!(srgb.green == 19);
|
assert_eq!(srgb.green, 19);
|
||||||
assert!(srgb.blue == 0);
|
assert_eq!(srgb.blue, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ impl cosmic::Application for App {
|
||||||
fn view(&self) -> Element<'_, Self::Message> {
|
fn view(&self) -> Element<'_, Self::Message> {
|
||||||
let show_about_button = widget::button::text("Show about").on_press(Message::ToggleAbout);
|
let show_about_button = widget::button::text("Show about").on_press(Message::ToggleAbout);
|
||||||
let centered = cosmic::widget::container(
|
let centered = cosmic::widget::container(
|
||||||
widget::column()
|
widget::column::with_capacity(1)
|
||||||
.push(show_about_button)
|
.push(show_about_button)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Shrink)
|
.height(Length::Shrink)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,6 @@ env_logger = "0.10.2"
|
||||||
log = "0.4.29"
|
log = "0.4.29"
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
git = "https://github.com/pop-os/libcosmic"
|
path = "../../"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["applet-token"]
|
features = ["applet-token"]
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use cosmic::app::{Core, Task};
|
use cosmic::app::{Core, Task};
|
||||||
|
|
||||||
|
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::iced_runtime::core::window;
|
|
||||||
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, list_column, settings, toggler};
|
||||||
use cosmic::Element;
|
use cosmic::Element;
|
||||||
|
|
@ -159,7 +159,7 @@ impl cosmic::Application for Window {
|
||||||
"oops".into()
|
"oops".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&self) -> Option<cosmic::iced_core::theme::Style> {
|
fn style(&self) -> Option<cosmic::iced::theme::Style> {
|
||||||
Some(cosmic::applet::style())
|
Some(cosmic::applet::style())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,15 @@ wayland = ["libcosmic/wayland"]
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
git = "https://github.com/pop-os/libcosmic"
|
path = "../../"
|
||||||
features = [
|
features = [
|
||||||
"debug",
|
"debug",
|
||||||
"winit",
|
"winit",
|
||||||
"tokio",
|
"tokio",
|
||||||
"xdg-portal",
|
"xdg-portal",
|
||||||
"a11y",
|
"a11y",
|
||||||
"wgpu",
|
|
||||||
"single-instance",
|
"single-instance",
|
||||||
"surface-message",
|
"surface-message",
|
||||||
"multi-window",
|
"multi-window",
|
||||||
|
"wgpu",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ impl widget::menu::Action for Action {
|
||||||
/// Runs application with these settings
|
/// Runs application with these settings
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -82,6 +82,7 @@ pub enum Message {
|
||||||
Hi,
|
Hi,
|
||||||
Hi2,
|
Hi2,
|
||||||
Hi3,
|
Hi3,
|
||||||
|
Tick,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The [`App`] stores application-specific state.
|
/// The [`App`] stores application-specific state.
|
||||||
|
|
@ -92,6 +93,7 @@ pub struct App {
|
||||||
input_2: String,
|
input_2: String,
|
||||||
hidden: bool,
|
hidden: bool,
|
||||||
keybinds: HashMap<KeyBind, Action>,
|
keybinds: HashMap<KeyBind, Action>,
|
||||||
|
progress: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implement [`cosmic::Application`] to integrate with COSMIC.
|
/// Implement [`cosmic::Application`] to integrate with COSMIC.
|
||||||
|
|
@ -133,6 +135,7 @@ impl cosmic::Application for App {
|
||||||
input_2: String::new(),
|
input_2: String::new(),
|
||||||
hidden: true,
|
hidden: true,
|
||||||
keybinds: HashMap::new(),
|
keybinds: HashMap::new(),
|
||||||
|
progress: 0.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let command = app.update_title();
|
let command = app.update_title();
|
||||||
|
|
@ -178,10 +181,17 @@ impl cosmic::Application for App {
|
||||||
Message::Hi3 => {
|
Message::Hi3 => {
|
||||||
dbg!("hi 3");
|
dbg!("hi 3");
|
||||||
}
|
}
|
||||||
|
Message::Tick => {
|
||||||
|
self.progress = (self.progress + 0.01) % 1.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn subscription(&self) -> iced::Subscription<Self::Message> {
|
||||||
|
iced::time::every(std::time::Duration::from_millis(64)).map(|_| Message::Tick)
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a view after each update.
|
/// Creates a view after each update.
|
||||||
fn view(&self) -> Element<'_, Self::Message> {
|
fn view(&self) -> Element<'_, Self::Message> {
|
||||||
let page_content = self
|
let page_content = self
|
||||||
|
|
@ -190,7 +200,7 @@ impl cosmic::Application for App {
|
||||||
.map_or("No page selected", String::as_str);
|
.map_or("No page selected", String::as_str);
|
||||||
|
|
||||||
let centered = widget::container(
|
let centered = widget::container(
|
||||||
widget::column()
|
widget::column::with_capacity(14)
|
||||||
.push(widget::text::body(page_content))
|
.push(widget::text::body(page_content))
|
||||||
.push(
|
.push(
|
||||||
widget::text_input::text_input("", &self.input_1)
|
widget::text_input::text_input("", &self.input_1)
|
||||||
|
|
@ -212,6 +222,47 @@ impl cosmic::Application for App {
|
||||||
.on_input(Message::Input2)
|
.on_input(Message::Input2)
|
||||||
.on_clear(Message::Ignore),
|
.on_clear(Message::Ignore),
|
||||||
)
|
)
|
||||||
|
.push(widget::progress_bar::circular::Circular::new().size(50.0))
|
||||||
|
.push(widget::progress_bar::circular::Circular::new().size(20.0))
|
||||||
|
.push(
|
||||||
|
widget::progress_bar::linear::Linear::new()
|
||||||
|
.girth(10.0)
|
||||||
|
.width(Length::Fill),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
widget::progress_bar::circular::Circular::new()
|
||||||
|
.bar_height(10.0)
|
||||||
|
.size(50.0)
|
||||||
|
.progress(self.progress),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
widget::progress_bar::linear::Linear::new()
|
||||||
|
.girth(10.0)
|
||||||
|
.progress(self.progress)
|
||||||
|
.width(Length::Fill),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
widget::progress_bar::circular::Circular::new()
|
||||||
|
.size(50.0)
|
||||||
|
.progress(0.0),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
widget::progress_bar::linear::Linear::new()
|
||||||
|
.girth(10.0)
|
||||||
|
.progress(0.0)
|
||||||
|
.width(Length::Fill),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
widget::progress_bar::circular::Circular::new()
|
||||||
|
.size(50.0)
|
||||||
|
.progress(1.0),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
widget::progress_bar::linear::Linear::new()
|
||||||
|
.girth(10.0)
|
||||||
|
.progress(1.0)
|
||||||
|
.width(Length::Fill),
|
||||||
|
)
|
||||||
.spacing(cosmic::theme::spacing().space_s)
|
.spacing(cosmic::theme::spacing().space_s)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Shrink)
|
.height(Length::Shrink)
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,6 @@ impl cosmic::Application for App {
|
||||||
|
|
||||||
/// Creates a view after each update.
|
/// Creates a view after each update.
|
||||||
fn view(&self) -> Element<'_, Self::Message> {
|
fn view(&self) -> Element<'_, Self::Message> {
|
||||||
let mut content = cosmic::widget::column().spacing(12);
|
|
||||||
|
|
||||||
let calendar = cosmic::widget::calendar(
|
let calendar = cosmic::widget::calendar(
|
||||||
&self.calendar_model,
|
&self.calendar_model,
|
||||||
|date| Message::DateSelected(date),
|
|date| Message::DateSelected(date),
|
||||||
|
|
@ -95,9 +93,7 @@ impl cosmic::Application for App {
|
||||||
Weekday::Sunday,
|
Weekday::Sunday,
|
||||||
);
|
);
|
||||||
|
|
||||||
content = content.push(calendar);
|
let centered = cosmic::widget::container(calendar)
|
||||||
|
|
||||||
let centered = cosmic::widget::container(content)
|
|
||||||
.width(iced::Length::Fill)
|
.width(iced::Length::Fill)
|
||||||
.height(iced::Length::Shrink)
|
.height(iced::Length::Shrink)
|
||||||
.align_x(iced::Alignment::Center)
|
.align_x(iced::Alignment::Center)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
//! Application API example
|
//! Application API example
|
||||||
|
|
||||||
use cosmic::app::{Core, Settings, Task};
|
use cosmic::app::{Core, Settings, Task};
|
||||||
use cosmic::iced_core::Size;
|
use cosmic::iced::Size;
|
||||||
use cosmic::widget::menu;
|
use cosmic::widget::menu;
|
||||||
use cosmic::{executor, iced, ApplicationExt, Element};
|
use cosmic::{executor, iced, ApplicationExt, Element};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
|
||||||
|
|
@ -28,13 +28,14 @@ impl State {
|
||||||
column!(
|
column!(
|
||||||
list_column().add(settings::item(
|
list_column().add(settings::item(
|
||||||
"Bluetooth",
|
"Bluetooth",
|
||||||
toggler(None, self.enabled, Message::Enable)
|
toggler(self.enabled).on_toggle(Message::Enable)
|
||||||
)),
|
)),
|
||||||
text("Now visible as \"TODO\", just kidding")
|
text("Now visible as \"TODO\", just kidding")
|
||||||
)
|
)
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Devices")
|
settings::section()
|
||||||
|
.title("Devices")
|
||||||
.add(settings::item("No devices found", text("")))
|
.add(settings::item("No devices found", text("")))
|
||||||
.into(),
|
.into(),
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -258,12 +258,13 @@ impl State {
|
||||||
match self.tab_bar.active_data() {
|
match self.tab_bar.active_data() {
|
||||||
None => panic!("no tab is active"),
|
None => panic!("no tab is active"),
|
||||||
Some(DemoView::TabA) => settings::view_column(vec![
|
Some(DemoView::TabA) => settings::view_column(vec![
|
||||||
settings::view_section("Debug")
|
settings::section()
|
||||||
|
.title("Debug")
|
||||||
.add(settings::item("Debug theme", choose_theme))
|
.add(settings::item("Debug theme", choose_theme))
|
||||||
.add(settings::item("Debug icon theme", choose_icon_theme))
|
.add(settings::item("Debug icon theme", choose_icon_theme))
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Debug layout",
|
"Debug layout",
|
||||||
toggler(None, window.debug, Message::Debug),
|
toggler(window.debug).on_toggle(Message::Debug),
|
||||||
))
|
))
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Scaling Factor",
|
"Scaling Factor",
|
||||||
|
|
@ -276,10 +277,11 @@ impl State {
|
||||||
.into(),
|
.into(),
|
||||||
]))
|
]))
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Controls")
|
settings::section()
|
||||||
|
.title("Controls")
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Toggler",
|
"Toggler",
|
||||||
toggler(None, self.toggler_value, Message::TogglerToggled),
|
toggler(self.toggler_value).on_toggle(Message::TogglerToggled),
|
||||||
))
|
))
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Pick List (TODO)",
|
"Pick List (TODO)",
|
||||||
|
|
@ -299,15 +301,13 @@ impl State {
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Progress",
|
"Progress",
|
||||||
progress_bar(0.0..=100.0, self.slider_value)
|
progress_bar(0.0..=100.0, self.slider_value)
|
||||||
.width(Length::Fixed(250.0))
|
.length(Length::Fixed(250.0))
|
||||||
.height(Length::Fixed(4.0)),
|
.girth(Length::Fixed(4.0)),
|
||||||
))
|
))
|
||||||
.add(settings::item_row(vec![checkbox(
|
.add(settings::item_row(vec![checkbox(self.checkbox_value)
|
||||||
"Checkbox",
|
.label("Checkbox")
|
||||||
self.checkbox_value,
|
.on_toggle(Message::CheckboxToggled)
|
||||||
Message::CheckboxToggled,
|
.into()]))
|
||||||
)
|
|
||||||
.into()]))
|
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
format!(
|
format!(
|
||||||
"Spin Button (Range {}:{})",
|
"Spin Button (Range {}:{})",
|
||||||
|
|
@ -354,8 +354,7 @@ impl State {
|
||||||
.width(Length::Shrink)
|
.width(Length::Shrink)
|
||||||
.on_activate(Message::MultiSelection)
|
.on_activate(Message::MultiSelection)
|
||||||
.apply(container)
|
.apply(container)
|
||||||
.center_x()
|
.center_x(Length::Fill)
|
||||||
.width(Length::Fill)
|
|
||||||
.into(),
|
.into(),
|
||||||
text("Vertical With Spacing").into(),
|
text("Vertical With Spacing").into(),
|
||||||
cosmic::iced::widget::row(vec![
|
cosmic::iced::widget::row(vec![
|
||||||
|
|
@ -424,13 +423,12 @@ impl State {
|
||||||
])
|
])
|
||||||
.padding(0)
|
.padding(0)
|
||||||
.into(),
|
.into(),
|
||||||
Some(DemoView::TabC) => {
|
Some(DemoView::TabC) => settings::view_column(vec![settings::section()
|
||||||
settings::view_column(vec![settings::view_section("Tab C")
|
.title("Tab C")
|
||||||
.add(text("Nothing here yet").width(Length::Fill))
|
.add(text("Nothing here yet").width(Length::Fill))
|
||||||
.into()])
|
.into()])
|
||||||
.padding(0)
|
.padding(0)
|
||||||
.into()
|
.into(),
|
||||||
}
|
|
||||||
},
|
},
|
||||||
container(text("Background container with some text").size(24))
|
container(text("Background container with some text").size(24))
|
||||||
.layer(cosmic_theme::Layer::Background)
|
.layer(cosmic_theme::Layer::Background)
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,8 @@ impl State {
|
||||||
fn view_desktop_options<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
fn view_desktop_options<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
||||||
settings::view_column(vec![
|
settings::view_column(vec![
|
||||||
window.parent_page_button(DesktopPage::DesktopOptions),
|
window.parent_page_button(DesktopPage::DesktopOptions),
|
||||||
settings::view_section("Super Key Action")
|
settings::section()
|
||||||
|
.title("Super Key Action")
|
||||||
.add(settings::item("Launcher", horizontal_space(Length::Fill)))
|
.add(settings::item("Launcher", horizontal_space(Length::Fill)))
|
||||||
.add(settings::item("Workspaces", horizontal_space(Length::Fill)))
|
.add(settings::item("Workspaces", horizontal_space(Length::Fill)))
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
|
|
@ -155,38 +156,34 @@ impl State {
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
))
|
))
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Hot Corner")
|
settings::section()
|
||||||
|
.title("Hot Corner")
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Enable top-left hot corner for Workspaces",
|
"Enable top-left hot corner for Workspaces",
|
||||||
toggler(None, self.top_left_hot_corner, Message::TopLeftHotCorner),
|
toggler(self.top_left_hot_corner).on_toggle(Message::TopLeftHotCorner),
|
||||||
))
|
))
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Top Panel")
|
settings::section()
|
||||||
|
.title("Top Panel")
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Show Workspaces Button",
|
"Show Workspaces Button",
|
||||||
toggler(
|
toggler(self.show_workspaces_button).on_toggle(Message::ShowWorkspacesButton),
|
||||||
None,
|
|
||||||
self.show_workspaces_button,
|
|
||||||
Message::ShowWorkspacesButton,
|
|
||||||
),
|
|
||||||
))
|
))
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Show Applications Button",
|
"Show Applications Button",
|
||||||
toggler(
|
toggler(self.show_applications_button)
|
||||||
None,
|
.on_toggle(Message::ShowApplicationsButton),
|
||||||
self.show_applications_button,
|
|
||||||
Message::ShowApplicationsButton,
|
|
||||||
),
|
|
||||||
))
|
))
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Window Controls")
|
settings::section()
|
||||||
|
.title("Window Controls")
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Show Minimize Button",
|
"Show Minimize Button",
|
||||||
toggler(None, self.show_minimize_button, Message::ShowMinimizeButton),
|
toggler(self.show_minimize_button).on_toggle(Message::ShowMinimizeButton),
|
||||||
))
|
))
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Show Maximize Button",
|
"Show Maximize Button",
|
||||||
toggler(None, self.show_maximize_button, Message::ShowMaximizeButton),
|
toggler(self.show_maximize_button).on_toggle(Message::ShowMaximizeButton),
|
||||||
))
|
))
|
||||||
.into(),
|
.into(),
|
||||||
])
|
])
|
||||||
|
|
@ -245,12 +242,12 @@ impl State {
|
||||||
list_column()
|
list_column()
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Same background on all displays",
|
"Same background on all displays",
|
||||||
toggler(None, self.same_background, Message::SameBackground),
|
toggler(self.same_background).on_toggle(Message::SameBackground),
|
||||||
))
|
))
|
||||||
.add(settings::item("Background fit", text("TODO")))
|
.add(settings::item("Background fit", text("TODO")))
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Slideshow",
|
"Slideshow",
|
||||||
toggler(None, self.slideshow, Message::Slideshow),
|
toggler(self.slideshow).on_toggle(Message::Slideshow),
|
||||||
))
|
))
|
||||||
.into(),
|
.into(),
|
||||||
column(image_column).spacing(16).into(),
|
column(image_column).spacing(16).into(),
|
||||||
|
|
@ -261,7 +258,8 @@ impl State {
|
||||||
fn view_desktop_workspaces<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
fn view_desktop_workspaces<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
||||||
settings::view_column(vec![
|
settings::view_column(vec![
|
||||||
window.parent_page_button(DesktopPage::Wallpaper),
|
window.parent_page_button(DesktopPage::Wallpaper),
|
||||||
settings::view_section("Workspace Behavior")
|
settings::section()
|
||||||
|
.title("Workspace Behavior")
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Dynamic workspaces",
|
"Dynamic workspaces",
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
|
|
@ -271,7 +269,8 @@ impl State {
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
))
|
))
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Multi-monitor Behavior")
|
settings::section()
|
||||||
|
.title("Multi-monitor Behavior")
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Workspaces Span Displays",
|
"Workspaces Span Displays",
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
|
|
|
||||||
|
|
@ -69,14 +69,16 @@ impl State {
|
||||||
list_column()
|
list_column()
|
||||||
.add(settings::item("Device name", text("TODO")))
|
.add(settings::item("Device name", text("TODO")))
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Hardware")
|
settings::section()
|
||||||
|
.title("Hardware")
|
||||||
.add(settings::item("Hardware model", text("TODO")))
|
.add(settings::item("Hardware model", text("TODO")))
|
||||||
.add(settings::item("Memory", text("TODO")))
|
.add(settings::item("Memory", text("TODO")))
|
||||||
.add(settings::item("Processor", text("TODO")))
|
.add(settings::item("Processor", text("TODO")))
|
||||||
.add(settings::item("Graphics", text("TODO")))
|
.add(settings::item("Graphics", text("TODO")))
|
||||||
.add(settings::item("Disk Capacity", text("TODO")))
|
.add(settings::item("Disk Capacity", text("TODO")))
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Operating System")
|
settings::section()
|
||||||
|
.title("Operating System")
|
||||||
.add(settings::item("Operating system", text("TODO")))
|
.add(settings::item("Operating system", text("TODO")))
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Operating system architecture",
|
"Operating system architecture",
|
||||||
|
|
@ -85,7 +87,8 @@ impl State {
|
||||||
.add(settings::item("Desktop environment", text("TODO")))
|
.add(settings::item("Desktop environment", text("TODO")))
|
||||||
.add(settings::item("Windowing system", text("TODO")))
|
.add(settings::item("Windowing system", text("TODO")))
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Related settings")
|
settings::section()
|
||||||
|
.title("Related settings")
|
||||||
.add(settings::item("Get support", text("TODO")))
|
.add(settings::item("Get support", text("TODO")))
|
||||||
.into(),
|
.into(),
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ impl cosmic::Application for App {
|
||||||
|
|
||||||
/// Creates a view after each update.
|
/// Creates a view after each update.
|
||||||
fn view(&self) -> Element<'_, Self::Message> {
|
fn view(&self) -> Element<'_, Self::Message> {
|
||||||
let mut content = cosmic::widget::column().spacing(12);
|
let mut content = cosmic::widget::column::with_capacity(self.images.len()).spacing(12);
|
||||||
|
|
||||||
for (id, image) in self.images.iter().enumerate() {
|
for (id, image) in self.images.iter().enumerate() {
|
||||||
content = content.push(
|
content = content.push(
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ use std::collections::HashMap;
|
||||||
use std::{env, process};
|
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::keyboard::Key;
|
||||||
use cosmic::iced::window;
|
use cosmic::iced::window;
|
||||||
use cosmic::iced_core::alignment::{Horizontal, Vertical};
|
use cosmic::iced::{Length, Size};
|
||||||
use cosmic::iced_core::keyboard::Key;
|
|
||||||
use cosmic::iced_core::{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;
|
||||||
use cosmic::widget::menu::key_bind::Modifier;
|
use cosmic::widget::menu::key_bind::Modifier;
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
app::Core,
|
app::Core,
|
||||||
|
iced::core::{id, Alignment, Length, Point},
|
||||||
|
iced::widget::{column, container, scrollable, text},
|
||||||
iced::{self, event, window, Subscription},
|
iced::{self, event, window, Subscription},
|
||||||
iced_core::{id, Alignment, Length, Point},
|
|
||||||
iced_widget::{column, container, scrollable, text},
|
|
||||||
prelude::*,
|
prelude::*,
|
||||||
widget::{button, header_bar},
|
widget::{button, header_bar},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use cosmic::app::{Core, Settings, Task};
|
use cosmic::app::{Core, Settings, Task};
|
||||||
use cosmic::iced_core::Size;
|
use cosmic::iced::Size;
|
||||||
use cosmic::widget::{menu, nav_bar};
|
use cosmic::widget::{menu, nav_bar};
|
||||||
use cosmic::{executor, iced, ApplicationExt, Element};
|
use cosmic::{executor, iced, ApplicationExt, Element};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
use cosmic::app::{Core, Settings, Task};
|
use cosmic::app::{Core, Settings, Task};
|
||||||
use cosmic::dialog::file_chooser::{self, FileFilter};
|
use cosmic::dialog::file_chooser::{self, FileFilter};
|
||||||
use cosmic::iced_core::Length;
|
use cosmic::iced::Length;
|
||||||
use cosmic::widget::button;
|
use cosmic::widget::button;
|
||||||
use cosmic::{executor, iced, ApplicationExt, Element};
|
use cosmic::{executor, iced, ApplicationExt, Element};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ impl cosmic::Application for App {
|
||||||
|
|
||||||
/// Creates a view after each update.
|
/// Creates a view after each update.
|
||||||
fn view(&self) -> Element<'_, Self::Message> {
|
fn view(&self) -> Element<'_, Self::Message> {
|
||||||
widget::row().into()
|
widget::Row::new().into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use chrono::Datelike;
|
use chrono::Datelike;
|
||||||
use cosmic::app::{Core, Settings, Task};
|
use cosmic::app::{Core, Settings, Task};
|
||||||
use cosmic::iced_core::Size;
|
use cosmic::iced::Size;
|
||||||
use cosmic::prelude::*;
|
use cosmic::prelude::*;
|
||||||
use cosmic::widget::table;
|
use cosmic::widget::table;
|
||||||
use cosmic::widget::{self, nav_bar};
|
use cosmic::widget::{self, nav_bar};
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,9 @@ impl cosmic::Application for App {
|
||||||
|
|
||||||
let inline = cosmic::widget::inline_input("", &self.input).on_input(Message::Input);
|
let inline = cosmic::widget::inline_input("", &self.input).on_input(Message::Input);
|
||||||
|
|
||||||
let column = cosmic::widget::column().push(editable).push(inline);
|
let column = cosmic::widget::column::with_capacity(2)
|
||||||
|
.push(editable)
|
||||||
|
.push(inline);
|
||||||
|
|
||||||
let centered = cosmic::widget::container(column.width(200))
|
let centered = cosmic::widget::container(column.width(200))
|
||||||
.width(iced::Length::Fill)
|
.width(iced::Length::Fill)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ links = Links
|
||||||
developers = Entwickler(innen)
|
developers = Entwickler(innen)
|
||||||
designers = Designer(innen)
|
designers = Designer(innen)
|
||||||
artists = Künstler(innen)
|
artists = Künstler(innen)
|
||||||
translators = Übersetzer*innen
|
translators = Übersetzer(innen)
|
||||||
documenters = Dokumentierer(innen)
|
documenters = Dokumentierer(innen)
|
||||||
# Calendar
|
# Calendar
|
||||||
january = Januar { $year }
|
january = Januar { $year }
|
||||||
|
|
|
||||||
0
i18n/eu/libcosmic.ftl
Normal file
0
i18n/eu/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
close = Mdel
|
||||||
|
license = Turagt
|
||||||
|
links = Iseɣwan
|
||||||
|
developers = Ineflayen
|
||||||
|
artists = Inaẓuren
|
||||||
|
translators = Imsuqlen
|
||||||
|
january = Yennayer { $year }
|
||||||
|
february = Fuṛar { $year }
|
||||||
|
march = Meɣres { $year }
|
||||||
|
april = Yebrir { $year }
|
||||||
|
may = Mayyu { $year }
|
||||||
|
june = Yunyu { $year }
|
||||||
|
july = Yulyu { $year }
|
||||||
|
august = Ɣuct { $year }
|
||||||
|
september = Ctembeṛ { $year }
|
||||||
|
october = Tubeṛ { $year }
|
||||||
|
november = Wambeṛ { $year }
|
||||||
|
december = Dujembeṛ { $year }
|
||||||
|
documenters = Imeskaren
|
||||||
|
monday = Arim
|
||||||
|
mon = Ari
|
||||||
|
tuesday = Aram
|
||||||
|
tue = Ara
|
||||||
|
wednesday = Ahad
|
||||||
|
wed = Aha
|
||||||
|
thursday = Amhad
|
||||||
|
thu = Amh
|
||||||
|
friday = Sem
|
||||||
|
fri = Sm
|
||||||
|
saturday = Sed
|
||||||
|
sat = Sd
|
||||||
|
sunday = Acer
|
||||||
|
sun = Ace
|
||||||
|
|
@ -2,26 +2,33 @@ february = { $year }년 2월
|
||||||
close = 닫기
|
close = 닫기
|
||||||
documenters = 문서 작성자
|
documenters = 문서 작성자
|
||||||
november = { $year }년 11월
|
november = { $year }년 11월
|
||||||
friday = 금
|
friday = 금요일
|
||||||
tuesday = 화
|
tuesday = 화요일
|
||||||
may = { $year }년 5월
|
may = { $year }년 5월
|
||||||
wednesday = 수
|
wednesday = 수요일
|
||||||
april = { $year }년 4월
|
april = { $year }년 4월
|
||||||
monday = 월
|
monday = 월요일
|
||||||
translators = 번역가
|
translators = 번역가
|
||||||
artists = 아티스트
|
artists = 아티스트
|
||||||
license = 라이선스
|
license = 라이선스
|
||||||
december = { $year }년 12월
|
december = { $year }년 12월
|
||||||
sunday = 일
|
sunday = 일요일
|
||||||
links = 링크
|
links = 링크
|
||||||
march = { $year }년 3월
|
march = { $year }년 3월
|
||||||
june = { $year }년 6월
|
june = { $year }년 6월
|
||||||
saturday = 토
|
saturday = 토요일
|
||||||
august = { $year }년 8월
|
august = { $year }년 8월
|
||||||
developers = 개발자
|
developers = 개발자
|
||||||
july = { $year }년 7월
|
july = { $year }년 7월
|
||||||
thursday = 목
|
thursday = 목요일
|
||||||
september = { $year }년 9월
|
september = { $year }년 9월
|
||||||
designers = 디자이너
|
designers = 디자이너
|
||||||
october = { $year }년 10월
|
october = { $year }년 10월
|
||||||
january = { $year }년 1월
|
january = { $year }년 1월
|
||||||
|
mon = 월
|
||||||
|
tue = 화
|
||||||
|
wed = 수
|
||||||
|
thu = 목
|
||||||
|
fri = 금
|
||||||
|
sat = 토
|
||||||
|
sun = 일
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
close = 關閉
|
||||||
|
developers = 開發人員
|
||||||
|
designers = 設計人員
|
||||||
|
artists = 美編設計
|
||||||
|
translators = 翻譯人員
|
||||||
|
documenters = 文件編輯人員
|
||||||
|
january = { $year } 年 1 月
|
||||||
|
monday = 星期一
|
||||||
|
tuesday = 星期二
|
||||||
|
wednesday = 星期三
|
||||||
|
thursday = 星期四
|
||||||
|
friday = 星期五
|
||||||
|
saturday = 星期六
|
||||||
|
sunday = 星期日
|
||||||
|
mon = 週一
|
||||||
|
tue = 週二
|
||||||
|
wed = 週三
|
||||||
|
thu = 週四
|
||||||
|
fri = 週五
|
||||||
|
sat = 週六
|
||||||
|
sun = 週日
|
||||||
|
license = 授權
|
||||||
|
links = 連結
|
||||||
|
february = { $year } 年 2 月
|
||||||
|
march = { $year } 年 3 月
|
||||||
|
april = { $year } 年 4 月
|
||||||
|
may = { $year } 年 5 月
|
||||||
|
june = { $year } 年 6 月
|
||||||
|
july = { $year } 年 7 月
|
||||||
|
august = { $year } 年 8 月
|
||||||
|
september = { $year } 年 9 月
|
||||||
|
october = { $year } 年 10 月
|
||||||
|
november = { $year } 年 11 月
|
||||||
|
december = { $year } 年 12 月
|
||||||
2
iced
2
iced
|
|
@ -1 +1 @@
|
||||||
Subproject commit 88f3b00d9625a3dd08ebaadb328fd119957fcd85
|
Subproject commit 78caabba7ef91cd1030da6f70b41d266704ffece
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::surface;
|
||||||
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::{config::CosmicTk, keyboard_nav};
|
||||||
#[cfg(feature = "wayland")]
|
#[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;
|
||||||
|
|
||||||
|
|
@ -69,10 +69,10 @@ pub enum Action {
|
||||||
/// Updates the tracked window geometry.
|
/// Updates the tracked window geometry.
|
||||||
WindowResize(iced::window::Id, f32, f32),
|
WindowResize(iced::window::Id, f32, f32),
|
||||||
/// Tracks updates to window state.
|
/// Tracks updates to window state.
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
WindowState(iced::window::Id, WindowState),
|
WindowState(iced::window::Id, WindowState),
|
||||||
/// Capabilities the window manager supports
|
/// Capabilities the window manager supports
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
WmCapabilities(iced::window::Id, WindowManagerCapabilities),
|
WmCapabilities(iced::window::Id, WindowManagerCapabilities),
|
||||||
#[cfg(feature = "xdg-portal")]
|
#[cfg(feature = "xdg-portal")]
|
||||||
DesktopSettings(crate::theme::portal::Desktop),
|
DesktopSettings(crate::theme::portal::Desktop),
|
||||||
|
|
|
||||||
|
|
@ -8,16 +8,16 @@ use std::sync::Arc;
|
||||||
use super::{Action, Application, ApplicationExt, Subscription};
|
use super::{Action, Application, ApplicationExt, Subscription};
|
||||||
use crate::theme::{THEME, Theme, ThemeType};
|
use crate::theme::{THEME, Theme, ThemeType};
|
||||||
use crate::{Core, Element, keyboard_nav};
|
use crate::{Core, Element, keyboard_nav};
|
||||||
#[cfg(feature = "wayland")]
|
#[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;
|
||||||
#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
|
#[cfg(not(any(feature = "multi-window", feature = "wayland", target_os = "linux")))]
|
||||||
use iced::Application as IcedApplication;
|
use iced::Application as IcedApplication;
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
use iced::event::wayland;
|
use iced::event::wayland;
|
||||||
use iced::{Task, theme, window};
|
use iced::{Task, theme, window};
|
||||||
use iced_futures::event::listen_with;
|
use iced_futures::event::listen_with;
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
use iced_winit::SurfaceIdWrapper;
|
use iced_winit::SurfaceIdWrapper;
|
||||||
use palette::color_difference::EuclideanDistance;
|
use palette::color_difference::EuclideanDistance;
|
||||||
|
|
||||||
|
|
@ -49,8 +49,8 @@ pub fn windowing_system() -> Option<WindowingSystem> {
|
||||||
WINDOWING_SYSTEM.get().copied()
|
WINDOWING_SYSTEM.get().copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_windowing_system<M>(handle: raw_window_handle::WindowHandle) -> crate::Action<M> {
|
fn init_windowing_system<M>(handle: window::raw_window_handle::WindowHandle) -> crate::Action<M> {
|
||||||
let raw: &raw_window_handle::RawWindowHandle = handle.as_ref();
|
let raw = handle.as_ref();
|
||||||
let system = match raw {
|
let system = match raw {
|
||||||
window::raw_window_handle::RawWindowHandle::UiKit(_) => WindowingSystem::UiKit,
|
window::raw_window_handle::RawWindowHandle::UiKit(_) => WindowingSystem::UiKit,
|
||||||
window::raw_window_handle::RawWindowHandle::AppKit(_) => WindowingSystem::AppKit,
|
window::raw_window_handle::RawWindowHandle::AppKit(_) => WindowingSystem::AppKit,
|
||||||
|
|
@ -83,7 +83,7 @@ fn init_windowing_system<M>(handle: raw_window_handle::WindowHandle) -> crate::A
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Cosmic<App: Application> {
|
pub struct Cosmic<App: Application> {
|
||||||
pub app: App,
|
pub app: App,
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
pub surface_views: HashMap<
|
pub surface_views: HashMap<
|
||||||
window::Id,
|
window::Id,
|
||||||
(
|
(
|
||||||
|
|
@ -138,7 +138,7 @@ where
|
||||||
) -> iced::Task<crate::Action<T::Message>> {
|
) -> iced::Task<crate::Action<T::Message>> {
|
||||||
#[cfg(feature = "surface-message")]
|
#[cfg(feature = "surface-message")]
|
||||||
match _surface_message {
|
match _surface_message {
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
crate::surface::Action::AppSubsurface(settings, view) => {
|
crate::surface::Action::AppSubsurface(settings, view) => {
|
||||||
let Some(settings) = std::sync::Arc::try_unwrap(settings)
|
let Some(settings) = std::sync::Arc::try_unwrap(settings)
|
||||||
.ok()
|
.ok()
|
||||||
|
|
@ -168,7 +168,7 @@ where
|
||||||
iced_winit::commands::subsurface::get_subsurface(settings(&mut self.app))
|
iced_winit::commands::subsurface::get_subsurface(settings(&mut self.app))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
crate::surface::Action::Subsurface(settings, view) => {
|
crate::surface::Action::Subsurface(settings, view) => {
|
||||||
let Some(settings) = std::sync::Arc::try_unwrap(settings)
|
let Some(settings) = std::sync::Arc::try_unwrap(settings)
|
||||||
.ok()
|
.ok()
|
||||||
|
|
@ -196,7 +196,7 @@ where
|
||||||
iced_winit::commands::subsurface::get_subsurface(settings())
|
iced_winit::commands::subsurface::get_subsurface(settings())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
crate::surface::Action::AppPopup(settings, view) => {
|
crate::surface::Action::AppPopup(settings, view) => {
|
||||||
let Some(settings) = std::sync::Arc::try_unwrap(settings)
|
let Some(settings) = std::sync::Arc::try_unwrap(settings)
|
||||||
.ok()
|
.ok()
|
||||||
|
|
@ -225,15 +225,26 @@ where
|
||||||
iced_winit::commands::popup::get_popup(settings(&mut self.app))
|
iced_winit::commands::popup::get_popup(settings(&mut self.app))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
crate::surface::Action::DestroyPopup(id) => {
|
crate::surface::Action::DestroyPopup(id) => {
|
||||||
iced_winit::commands::popup::destroy_popup(id)
|
iced_winit::commands::popup::destroy_popup(id)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
|
crate::surface::Action::DestroyTooltipPopup => {
|
||||||
|
#[cfg(feature = "applet")]
|
||||||
|
{
|
||||||
|
iced_winit::commands::popup::destroy_popup(*crate::applet::TOOLTIP_WINDOW_ID)
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "applet"))]
|
||||||
|
{
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
crate::surface::Action::DestroySubsurface(id) => {
|
crate::surface::Action::DestroySubsurface(id) => {
|
||||||
iced_winit::commands::subsurface::destroy_subsurface(id)
|
iced_winit::commands::subsurface::destroy_subsurface(id)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
crate::surface::Action::DestroyWindow(id) => iced::window::close(id),
|
crate::surface::Action::DestroyWindow(id) => iced::window::close(id),
|
||||||
crate::surface::Action::ResponsiveMenuBar {
|
crate::surface::Action::ResponsiveMenuBar {
|
||||||
menu_bar,
|
menu_bar,
|
||||||
|
|
@ -244,7 +255,7 @@ where
|
||||||
core.menu_bars.insert(menu_bar, (limits, size));
|
core.menu_bars.insert(menu_bar, (limits, size));
|
||||||
iced::Task::none()
|
iced::Task::none()
|
||||||
}
|
}
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
crate::surface::Action::Popup(settings, view) => {
|
crate::surface::Action::Popup(settings, view) => {
|
||||||
let Some(settings) = std::sync::Arc::try_unwrap(settings)
|
let Some(settings) = std::sync::Arc::try_unwrap(settings)
|
||||||
.ok()
|
.ok()
|
||||||
|
|
@ -271,7 +282,7 @@ where
|
||||||
iced_winit::commands::popup::get_popup(settings())
|
iced_winit::commands::popup::get_popup(settings())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
crate::surface::Action::AppWindow(id, settings, view) => {
|
crate::surface::Action::AppWindow(id, settings, view) => {
|
||||||
let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| {
|
let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| {
|
||||||
s.downcast::<Box<dyn Fn(&mut T) -> iced::window::Settings + Send + Sync>>()
|
s.downcast::<Box<dyn Fn(&mut T) -> iced::window::Settings + Send + Sync>>()
|
||||||
|
|
@ -310,7 +321,7 @@ where
|
||||||
.discard()
|
.discard()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
crate::surface::Action::Window(id, settings, view) => {
|
crate::surface::Action::Window(id, settings, view) => {
|
||||||
let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| {
|
let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| {
|
||||||
s.downcast::<Box<dyn Fn() -> iced::window::Settings + Send + Sync>>()
|
s.downcast::<Box<dyn Fn() -> iced::window::Settings + Send + Sync>>()
|
||||||
|
|
@ -430,7 +441,7 @@ where
|
||||||
}
|
}
|
||||||
iced::Event::Window(window::Event::Focused) => return Some(Action::Focus(id)),
|
iced::Event::Window(window::Event::Focused) => return Some(Action::Focus(id)),
|
||||||
iced::Event::Window(window::Event::Unfocused) => return Some(Action::Unfocus(id)),
|
iced::Event::Window(window::Event::Unfocused) => return Some(Action::Unfocus(id)),
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => {
|
iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => {
|
||||||
match event {
|
match event {
|
||||||
wayland::Event::Popup(wayland::PopupEvent::Done, _, id)
|
wayland::Event::Popup(wayland::PopupEvent::Done, _, id)
|
||||||
|
|
@ -443,7 +454,7 @@ where
|
||||||
) => {
|
) => {
|
||||||
return Some(Action::SuggestedBounds(b));
|
return Some(Action::SuggestedBounds(b));
|
||||||
}
|
}
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
wayland::Event::Window(iced::event::wayland::WindowEvent::WindowState(
|
wayland::Event::Window(iced::event::wayland::WindowEvent::WindowState(
|
||||||
s,
|
s,
|
||||||
)) => {
|
)) => {
|
||||||
|
|
@ -560,7 +571,7 @@ where
|
||||||
|
|
||||||
#[cfg(feature = "multi-window")]
|
#[cfg(feature = "multi-window")]
|
||||||
pub fn view(&self, id: window::Id) -> Element<'_, crate::Action<T::Message>> {
|
pub fn view(&self, id: window::Id) -> Element<'_, crate::Action<T::Message>> {
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
if let Some((_, _, v)) = self.surface_views.get(&id) {
|
if let Some((_, _, v)) = self.surface_views.get(&id) {
|
||||||
return v(&self.app);
|
return v(&self.app);
|
||||||
}
|
}
|
||||||
|
|
@ -611,7 +622,7 @@ impl<T: Application> Cosmic<T> {
|
||||||
fn cosmic_update(&mut self, message: Action) -> iced::Task<crate::Action<T::Message>> {
|
fn cosmic_update(&mut self, message: Action) -> iced::Task<crate::Action<T::Message>> {
|
||||||
match message {
|
match message {
|
||||||
Action::WindowMaximized(id, maximized) => {
|
Action::WindowMaximized(id, maximized) => {
|
||||||
#[cfg(not(feature = "wayland"))]
|
#[cfg(not(all(feature = "wayland", target_os = "linux")))]
|
||||||
if self
|
if self
|
||||||
.app
|
.app
|
||||||
.core()
|
.core()
|
||||||
|
|
@ -641,7 +652,7 @@ impl<T: Application> Cosmic<T> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
Action::WindowState(id, state) => {
|
Action::WindowState(id, state) => {
|
||||||
if self
|
if self
|
||||||
.app
|
.app
|
||||||
|
|
@ -693,7 +704,7 @@ impl<T: Application> Cosmic<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
Action::WmCapabilities(id, capabilities) => {
|
Action::WmCapabilities(id, capabilities) => {
|
||||||
if self
|
if self
|
||||||
.app
|
.app
|
||||||
|
|
@ -800,7 +811,7 @@ impl<T: Application> Cosmic<T> {
|
||||||
new_theme.theme_type.prefer_dark(prefer_dark);
|
new_theme.theme_type.prefer_dark(prefer_dark);
|
||||||
|
|
||||||
cosmic_theme.set_theme(new_theme.theme_type);
|
cosmic_theme.set_theme(new_theme.theme_type);
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
if self.app.core().sync_window_border_radii_to_theme() {
|
if self.app.core().sync_window_border_radii_to_theme() {
|
||||||
use iced_runtime::platform_specific::wayland::CornerRadius;
|
use iced_runtime::platform_specific::wayland::CornerRadius;
|
||||||
use iced_winit::platform_specific::commands::corner_radius::corner_radius;
|
use iced_winit::platform_specific::commands::corner_radius::corner_radius;
|
||||||
|
|
@ -946,7 +957,7 @@ impl<T: Application> Cosmic<T> {
|
||||||
// Only apply update if the theme is set to load a system theme
|
// Only apply update if the theme is set to load a system theme
|
||||||
if let ThemeType::System { .. } = cosmic_theme.theme_type {
|
if let ThemeType::System { .. } = cosmic_theme.theme_type {
|
||||||
cosmic_theme.set_theme(new_theme.theme_type);
|
cosmic_theme.set_theme(new_theme.theme_type);
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
if self.app.core().sync_window_border_radii_to_theme() {
|
if self.app.core().sync_window_border_radii_to_theme() {
|
||||||
use iced_runtime::platform_specific::wayland::CornerRadius;
|
use iced_runtime::platform_specific::wayland::CornerRadius;
|
||||||
use iced_winit::platform_specific::commands::corner_radius::corner_radius;
|
use iced_winit::platform_specific::commands::corner_radius::corner_radius;
|
||||||
|
|
@ -1040,7 +1051,7 @@ impl<T: Application> Cosmic<T> {
|
||||||
// Unminimize window before requesting to activate it.
|
// Unminimize window before requesting to activate it.
|
||||||
let mut task = iced_runtime::window::minimize(id, false);
|
let mut task = iced_runtime::window::minimize(id, false);
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
{
|
{
|
||||||
task = task.chain(
|
task = task.chain(
|
||||||
iced_winit::platform_specific::commands::activation::activate(
|
iced_winit::platform_specific::commands::activation::activate(
|
||||||
|
|
@ -1051,7 +1062,7 @@ impl<T: Application> Cosmic<T> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "wayland"))]
|
#[cfg(not(all(feature = "wayland", target_os = "linux")))]
|
||||||
{
|
{
|
||||||
task = task.chain(iced_runtime::window::gain_focus(id));
|
task = task.chain(iced_runtime::window::gain_focus(id));
|
||||||
}
|
}
|
||||||
|
|
@ -1068,7 +1079,7 @@ impl<T: Application> Cosmic<T> {
|
||||||
*v == 0
|
*v == 0
|
||||||
}) {
|
}) {
|
||||||
self.opened_surfaces.remove(&id);
|
self.opened_surfaces.remove(&id);
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
self.surface_views.remove(&id);
|
self.surface_views.remove(&id);
|
||||||
self.tracked_windows.remove(&id);
|
self.tracked_windows.remove(&id);
|
||||||
}
|
}
|
||||||
|
|
@ -1190,7 +1201,8 @@ impl<T: Application> Cosmic<T> {
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "wayland",
|
feature = "wayland",
|
||||||
feature = "multi-window",
|
feature = "multi-window",
|
||||||
feature = "surface-message"
|
feature = "surface-message",
|
||||||
|
target_os = "linux"
|
||||||
))]
|
))]
|
||||||
if let Some((
|
if let Some((
|
||||||
parent,
|
parent,
|
||||||
|
|
@ -1235,7 +1247,7 @@ impl<T: Application> Cosmic<T> {
|
||||||
core.applet.suggested_bounds = b;
|
core.applet.suggested_bounds = b;
|
||||||
}
|
}
|
||||||
Action::Opened(id) => {
|
Action::Opened(id) => {
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
if self.app.core().sync_window_border_radii_to_theme() {
|
if self.app.core().sync_window_border_radii_to_theme() {
|
||||||
use iced_runtime::platform_specific::wayland::CornerRadius;
|
use iced_runtime::platform_specific::wayland::CornerRadius;
|
||||||
use iced_winit::platform_specific::commands::corner_radius::corner_radius;
|
use iced_winit::platform_specific::commands::corner_radius::corner_radius;
|
||||||
|
|
@ -1284,14 +1296,14 @@ impl<App: Application> Cosmic<App> {
|
||||||
pub fn new(app: App) -> Self {
|
pub fn new(app: App) -> Self {
|
||||||
Self {
|
Self {
|
||||||
app,
|
app,
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
surface_views: HashMap::new(),
|
surface_views: HashMap::new(),
|
||||||
tracked_windows: HashSet::new(),
|
tracked_windows: HashSet::new(),
|
||||||
opened_surfaces: HashMap::new(),
|
opened_surfaces: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
/// Create a subsurface
|
/// Create a subsurface
|
||||||
pub fn get_subsurface(
|
pub fn get_subsurface(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
@ -1314,7 +1326,7 @@ impl<App: Application> Cosmic<App> {
|
||||||
get_subsurface(settings)
|
get_subsurface(settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
/// Create a subsurface
|
/// Create a subsurface
|
||||||
pub fn get_popup(
|
pub fn get_popup(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
@ -1336,7 +1348,7 @@ impl<App: Application> Cosmic<App> {
|
||||||
get_popup(settings)
|
get_popup(settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
/// Create a window surface
|
/// Create a window surface
|
||||||
pub fn get_window(
|
pub fn get_window(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,9 @@ impl<A: crate::app::Application> BootFn<cosmic::Cosmic<A>, crate::Action<A::Mess
|
||||||
///
|
///
|
||||||
/// Returns error on application failure.
|
/// Returns error on application failure.
|
||||||
pub fn run<App: Application>(settings: Settings, flags: App::Flags) -> iced::Result {
|
pub fn run<App: Application>(settings: Settings, flags: App::Flags) -> iced::Result {
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
|
image_extras::register();
|
||||||
|
|
||||||
#[cfg(all(target_env = "gnu", not(target_os = "windows")))]
|
#[cfg(all(target_env = "gnu", not(target_os = "windows")))]
|
||||||
if let Some(threshold) = settings.default_mmap_threshold {
|
if let Some(threshold) = settings.default_mmap_threshold {
|
||||||
crate::malloc::limit_mmap_threshold(threshold);
|
crate::malloc::limit_mmap_threshold(threshold);
|
||||||
|
|
@ -194,6 +197,9 @@ where
|
||||||
App::Flags: CosmicFlags,
|
App::Flags: CosmicFlags,
|
||||||
App::Message: Clone + std::fmt::Debug + Send + 'static,
|
App::Message: Clone + std::fmt::Debug + Send + 'static,
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
|
image_extras::register();
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
let activation_token = std::env::var("XDG_ACTIVATION_TOKEN").ok();
|
let activation_token = std::env::var("XDG_ACTIVATION_TOKEN").ok();
|
||||||
|
|
@ -742,7 +748,6 @@ impl<App: Application> ApplicationExt for App {
|
||||||
}));
|
}));
|
||||||
let content: Element<_> = if content_container {
|
let content: Element<_> = if content_container {
|
||||||
content_col
|
content_col
|
||||||
.apply(container)
|
|
||||||
.width(iced::Length::Fill)
|
.width(iced::Length::Fill)
|
||||||
.height(iced::Length::Fill)
|
.height(iced::Length::Fill)
|
||||||
.apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_content_container")))
|
.apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_content_container")))
|
||||||
|
|
@ -772,8 +777,7 @@ impl<App: Application> ApplicationExt for App {
|
||||||
.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))
|
||||||
.on_double_click(crate::Action::Cosmic(Action::Maximize))
|
.on_double_click(crate::Action::Cosmic(Action::Maximize));
|
||||||
.is_condensed(is_condensed);
|
|
||||||
|
|
||||||
if self.nav_model().is_some() {
|
if self.nav_model().is_some() {
|
||||||
let toggle = crate::widget::nav_bar_toggle()
|
let toggle = crate::widget::nav_bar_toggle()
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ pub struct Settings {
|
||||||
pub(crate) antialiasing: bool,
|
pub(crate) antialiasing: bool,
|
||||||
|
|
||||||
/// Autosize the window to fit its contents
|
/// Autosize the window to fit its contents
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
pub(crate) autosize: bool,
|
pub(crate) autosize: bool,
|
||||||
|
|
||||||
/// Set the application to not create a main window
|
/// Set the application to not create a main window
|
||||||
|
|
@ -80,7 +80,7 @@ impl Default for Settings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
antialiasing: true,
|
antialiasing: true,
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
autosize: false,
|
autosize: false,
|
||||||
no_main_window: false,
|
no_main_window: false,
|
||||||
client_decorations: true,
|
client_decorations: true,
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,6 @@ use crate::{
|
||||||
Application, Element, Renderer,
|
Application, Element, Renderer,
|
||||||
app::iced_settings,
|
app::iced_settings,
|
||||||
cctk::sctk,
|
cctk::sctk,
|
||||||
iced::{
|
|
||||||
self, Color, Length, Limits, Rectangle,
|
|
||||||
alignment::{Alignment, Horizontal, Vertical},
|
|
||||||
widget::Container,
|
|
||||||
window,
|
|
||||||
},
|
|
||||||
iced_widget,
|
|
||||||
theme::{self, Button, THEME, system_dark, system_light},
|
theme::{self, Button, THEME, system_dark, system_light},
|
||||||
widget::{
|
widget::{
|
||||||
self,
|
self,
|
||||||
|
|
@ -24,8 +17,15 @@ use crate::{
|
||||||
space::vertical,
|
space::vertical,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
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::{
|
||||||
|
self, Color, Length, Limits, Rectangle,
|
||||||
|
alignment::{Alignment, Horizontal, Vertical},
|
||||||
|
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;
|
||||||
|
|
@ -42,7 +42,7 @@ static AUTOSIZE_ID: LazyLock<iced::id::Id> =
|
||||||
static AUTOSIZE_MAIN_ID: LazyLock<iced::id::Id> =
|
static AUTOSIZE_MAIN_ID: LazyLock<iced::id::Id> =
|
||||||
LazyLock::new(|| iced::id::Id::new("cosmic-applet-autosize-main"));
|
LazyLock::new(|| iced::id::Id::new("cosmic-applet-autosize-main"));
|
||||||
static TOOLTIP_ID: LazyLock<crate::widget::Id> = LazyLock::new(|| iced::id::Id::new("subsurface"));
|
static TOOLTIP_ID: LazyLock<crate::widget::Id> = LazyLock::new(|| iced::id::Id::new("subsurface"));
|
||||||
static TOOLTIP_WINDOW_ID: LazyLock<window::Id> = LazyLock::new(window::Id::unique);
|
pub(crate) static TOOLTIP_WINDOW_ID: LazyLock<window::Id> = LazyLock::new(window::Id::unique);
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
|
|
@ -226,7 +226,7 @@ impl Context {
|
||||||
let symbolic = icon.symbolic;
|
let symbolic = icon.symbolic;
|
||||||
let icon = widget::icon(icon)
|
let icon = widget::icon(icon)
|
||||||
.class(if symbolic {
|
.class(if symbolic {
|
||||||
theme::Svg::Custom(Rc::new(|theme| crate::iced_widget::svg::Style {
|
theme::Svg::Custom(Rc::new(|theme| iced_widget::svg::Style {
|
||||||
color: Some(theme.cosmic().background.on.into()),
|
color: Some(theme.cosmic().background.on.into()),
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use crate::iced;
|
use crate::iced;
|
||||||
use crate::iced_futures::futures;
|
|
||||||
use cctk::sctk::reexports::calloop;
|
use cctk::sctk::reexports::calloop;
|
||||||
use futures::{
|
use futures::{
|
||||||
SinkExt, StreamExt,
|
SinkExt, StreamExt,
|
||||||
channel::mpsc::{UnboundedReceiver, unbounded},
|
channel::mpsc::{UnboundedReceiver, unbounded},
|
||||||
};
|
};
|
||||||
use iced::Subscription;
|
use iced::Subscription;
|
||||||
|
use iced_futures::futures;
|
||||||
use iced_futures::stream;
|
use iced_futures::stream;
|
||||||
use std::{fmt::Debug, hash::Hash, thread::JoinHandle};
|
use std::{fmt::Debug, hash::Hash, thread::JoinHandle};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ pub struct Core {
|
||||||
|
|
||||||
pub(crate) menu_bars: HashMap<crate::widget::Id, (Limits, Size)>,
|
pub(crate) menu_bars: HashMap<crate::widget::Id, (Limits, Size)>,
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
pub(crate) sync_window_border_radii_to_theme: bool,
|
pub(crate) sync_window_border_radii_to_theme: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,7 +159,7 @@ impl Default for Core {
|
||||||
main_window: None,
|
main_window: None,
|
||||||
exit_on_main_window_closed: true,
|
exit_on_main_window_closed: true,
|
||||||
menu_bars: HashMap::new(),
|
menu_bars: HashMap::new(),
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
sync_window_border_radii_to_theme: true,
|
sync_window_border_radii_to_theme: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -493,12 +493,12 @@ impl Core {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO should we emit tasks setting the corner radius or unsetting it if this is changed?
|
// TODO should we emit tasks setting the corner radius or unsetting it if this is changed?
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
pub fn set_sync_window_border_radii_to_theme(&mut self, sync: bool) {
|
pub fn set_sync_window_border_radii_to_theme(&mut self, sync: bool) {
|
||||||
self.sync_window_border_radii_to_theme = sync;
|
self.sync_window_border_radii_to_theme = sync;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
pub fn sync_window_border_radii_to_theme(&self) -> bool {
|
pub fn sync_window_border_radii_to_theme(&self) -> bool {
|
||||||
self.sync_window_border_radii_to_theme
|
self.sync_window_border_radii_to_theme
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -789,7 +789,7 @@ pub async fn spawn_desktop_exec<S, I, K, V>(
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| String::from("cosmic-term"));
|
.unwrap_or_else(|| String::from("cosmic-term"));
|
||||||
|
|
||||||
term_exec = format!("{term} -- {}", exec.as_ref());
|
term_exec = format!("{term} -e {}", exec.as_ref());
|
||||||
&term_exec
|
&term_exec
|
||||||
} else {
|
} else {
|
||||||
exec.as_ref()
|
exec.as_ref()
|
||||||
|
|
|
||||||
66
src/ext.rs
66
src/ext.rs
|
|
@ -19,72 +19,6 @@ impl<Message: 'static> ElementExt for crate::Element<'_, Message> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Additional methods for the [`Column`] and [`Row`] widgets.
|
|
||||||
pub trait CollectionWidget<'a, Message: 'a>:
|
|
||||||
Widget<Message, crate::Theme, crate::Renderer>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
/// Moves all the elements of `other` into `self`, leaving `other` empty.
|
|
||||||
#[must_use]
|
|
||||||
fn append<E>(self, other: &mut Vec<E>) -> Self
|
|
||||||
where
|
|
||||||
E: Into<crate::Element<'a, Message>>;
|
|
||||||
|
|
||||||
/// Appends all elements in an iterator to the widget.
|
|
||||||
#[must_use]
|
|
||||||
fn extend<E>(mut self, iterator: impl Iterator<Item = E>) -> Self
|
|
||||||
where
|
|
||||||
E: Into<crate::Element<'a, Message>>,
|
|
||||||
{
|
|
||||||
for item in iterator {
|
|
||||||
self = self.push(item.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pushes an element into the widget.
|
|
||||||
#[must_use]
|
|
||||||
fn push(self, element: impl Into<crate::Element<'a, Message>>) -> Self;
|
|
||||||
|
|
||||||
/// Conditionally pushes an element to the widget.
|
|
||||||
#[must_use]
|
|
||||||
fn push_maybe(self, element: Option<impl Into<crate::Element<'a, Message>>>) -> Self {
|
|
||||||
if let Some(element) = element {
|
|
||||||
self.push(element.into())
|
|
||||||
} else {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message: 'a> CollectionWidget<'a, Message> for crate::widget::Column<'a, Message> {
|
|
||||||
fn append<E>(self, other: &mut Vec<E>) -> Self
|
|
||||||
where
|
|
||||||
E: Into<crate::Element<'a, Message>>,
|
|
||||||
{
|
|
||||||
self.extend(other.drain(..).map(Into::into))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push(self, element: impl Into<crate::Element<'a, Message>>) -> Self {
|
|
||||||
self.push(element)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message: 'a> CollectionWidget<'a, Message> for crate::widget::Row<'a, Message> {
|
|
||||||
fn append<E>(self, other: &mut Vec<E>) -> Self
|
|
||||||
where
|
|
||||||
E: Into<crate::Element<'a, Message>>,
|
|
||||||
{
|
|
||||||
self.extend(other.drain(..).map(Into::into))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push(self, element: impl Into<crate::Element<'a, Message>>) -> Self {
|
|
||||||
self.push(element)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ColorExt {
|
pub trait ColorExt {
|
||||||
/// Combines color with background to create appearance of transparency.
|
/// Combines color with background to create appearance of transparency.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
|
||||||
27
src/lib.rs
27
src/lib.rs
|
|
@ -3,6 +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))]
|
||||||
|
|
||||||
/// Recommended default imports.
|
/// Recommended default imports.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
|
|
@ -66,29 +67,6 @@ pub mod font;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use iced;
|
pub use iced;
|
||||||
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use iced_core;
|
|
||||||
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use iced_futures;
|
|
||||||
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use iced_renderer;
|
|
||||||
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use iced_runtime;
|
|
||||||
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use iced_widget;
|
|
||||||
|
|
||||||
#[doc(inline)]
|
|
||||||
#[cfg(feature = "winit")]
|
|
||||||
pub use iced_winit;
|
|
||||||
|
|
||||||
#[doc(inline)]
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
pub use iced_wgpu;
|
|
||||||
|
|
||||||
pub mod icon_theme;
|
pub mod icon_theme;
|
||||||
pub mod keyboard_nav;
|
pub mod keyboard_nav;
|
||||||
|
|
||||||
|
|
@ -100,7 +78,8 @@ pub(crate) mod malloc;
|
||||||
#[cfg(all(feature = "process", not(windows)))]
|
#[cfg(all(feature = "process", not(windows)))]
|
||||||
pub mod process;
|
pub mod process;
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[doc(inline)]
|
||||||
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
pub use cctk;
|
pub use cctk;
|
||||||
|
|
||||||
pub mod surface;
|
pub mod surface;
|
||||||
|
|
|
||||||
|
|
@ -9,25 +9,25 @@ use iced::window;
|
||||||
use std::{any::Any, sync::Arc};
|
use std::{any::Any, 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(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn destroy_popup(id: iced_core::window::Id) -> Action {
|
pub fn destroy_popup(id: iced_core::window::Id) -> Action {
|
||||||
Action::DestroyPopup(id)
|
Action::DestroyPopup(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn destroy_subsurface(id: iced_core::window::Id) -> Action {
|
pub fn destroy_subsurface(id: iced_core::window::Id) -> Action {
|
||||||
Action::DestroySubsurface(id)
|
Action::DestroySubsurface(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn destroy_window(id: iced_core::window::Id) -> Action {
|
pub fn destroy_window(id: iced_core::window::Id) -> Action {
|
||||||
Action::DestroyWindow(id)
|
Action::DestroyWindow(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "wayland", feature = "winit"))]
|
#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn app_window<App: Application>(
|
pub fn app_window<App: Application>(
|
||||||
settings: impl Fn(&mut App) -> window::Settings + Send + Sync + 'static,
|
settings: impl Fn(&mut App) -> window::Settings + Send + Sync + 'static,
|
||||||
|
|
@ -60,7 +60,7 @@ pub fn app_window<App: Application>(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used to create a window message from within a widget.
|
/// Used to create a window message from within a widget.
|
||||||
#[cfg(all(feature = "wayland", feature = "winit"))]
|
#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn simple_window<Message: 'static>(
|
pub fn simple_window<Message: 'static>(
|
||||||
settings: impl Fn() -> window::Settings + Send + Sync + 'static,
|
settings: impl Fn() -> window::Settings + Send + Sync + 'static,
|
||||||
|
|
@ -92,7 +92,7 @@ pub fn simple_window<Message: 'static>(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "wayland", feature = "winit"))]
|
#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn app_popup<App: Application>(
|
pub fn app_popup<App: Application>(
|
||||||
settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
|
settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
|
||||||
|
|
@ -126,7 +126,7 @@ pub fn app_popup<App: Application>(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used to create a subsurface message from within a widget.
|
/// Used to create a subsurface message from within a widget.
|
||||||
#[cfg(all(feature = "wayland", feature = "winit"))]
|
#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn simple_subsurface<Message: 'static, V>(
|
pub fn simple_subsurface<Message: 'static, V>(
|
||||||
settings: impl Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings
|
settings: impl Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings
|
||||||
|
|
@ -155,7 +155,7 @@ pub fn simple_subsurface<Message: 'static, V>(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used to create a popup message from within a widget.
|
/// Used to create a popup message from within a widget.
|
||||||
#[cfg(all(feature = "wayland", feature = "winit"))]
|
#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn simple_popup<Message: 'static>(
|
pub fn simple_popup<Message: 'static>(
|
||||||
settings: impl Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
|
settings: impl Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
|
||||||
|
|
@ -186,7 +186,7 @@ pub fn simple_popup<Message: 'static>(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "wayland", feature = "winit"))]
|
#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn subsurface<App: Application>(
|
pub fn subsurface<App: Application>(
|
||||||
settings: impl Fn(
|
settings: impl Fn(
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ pub enum Action {
|
||||||
),
|
),
|
||||||
/// Destroy a subsurface with a view function
|
/// Destroy a subsurface with a view function
|
||||||
DestroyPopup(iced::window::Id),
|
DestroyPopup(iced::window::Id),
|
||||||
|
/// Destroys the global tooltip popup subsurface
|
||||||
|
DestroyTooltipPopup,
|
||||||
|
|
||||||
/// Create a window with a view function accepting the App as a parameter
|
/// Create a window with a view function accepting the App as a parameter
|
||||||
AppWindow(
|
AppWindow(
|
||||||
|
|
@ -85,6 +87,7 @@ impl std::fmt::Debug for Action {
|
||||||
}
|
}
|
||||||
Self::Popup(arg0, arg1) => f.debug_tuple("Popup").field(arg0).field(arg1).finish(),
|
Self::Popup(arg0, arg1) => f.debug_tuple("Popup").field(arg0).field(arg1).finish(),
|
||||||
Self::DestroyPopup(arg0) => f.debug_tuple("DestroyPopup").field(arg0).finish(),
|
Self::DestroyPopup(arg0) => f.debug_tuple("DestroyPopup").field(arg0).finish(),
|
||||||
|
Self::DestroyTooltipPopup => f.debug_tuple("DestroyTooltipPopup").finish(),
|
||||||
Self::ResponsiveMenuBar {
|
Self::ResponsiveMenuBar {
|
||||||
menu_bar,
|
menu_bar,
|
||||||
limits,
|
limits,
|
||||||
|
|
|
||||||
|
|
@ -307,7 +307,7 @@ impl DefaultStyle for Theme {
|
||||||
fn default_style(&self) -> Appearance {
|
fn default_style(&self) -> Appearance {
|
||||||
let cosmic = self.cosmic();
|
let cosmic = self.cosmic();
|
||||||
Appearance {
|
Appearance {
|
||||||
icon_color: cosmic.bg_color().into(),
|
icon_color: cosmic.on_bg_color().into(),
|
||||||
background_color: cosmic.bg_color().into(),
|
background_color: cosmic.bg_color().into(),
|
||||||
text_color: cosmic.on_bg_color().into(),
|
text_color: cosmic.on_bg_color().into(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ pub enum Button {
|
||||||
IconVertical,
|
IconVertical,
|
||||||
Image,
|
Image,
|
||||||
Link,
|
Link,
|
||||||
ListItem,
|
ListItem([f32; 4]),
|
||||||
MenuFolder,
|
MenuFolder,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
MenuRoot,
|
MenuRoot,
|
||||||
|
|
@ -148,8 +148,8 @@ pub fn appearance(
|
||||||
appearance.text_color = Some(component.on.into());
|
appearance.text_color = Some(component.on.into());
|
||||||
corner_radii = &cosmic.corner_radii.radius_s;
|
corner_radii = &cosmic.corner_radii.radius_s;
|
||||||
}
|
}
|
||||||
Button::ListItem => {
|
Button::ListItem(radii) => {
|
||||||
corner_radii = &[0.0; 4];
|
corner_radii = radii;
|
||||||
let (background, text, icon) = color(&cosmic.background.component);
|
let (background, text, icon) = color(&cosmic.background.component);
|
||||||
|
|
||||||
if selected {
|
if selected {
|
||||||
|
|
@ -197,7 +197,7 @@ impl Catalog for crate::Theme {
|
||||||
return active(focused, self);
|
return active(focused, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
appearance(self, focused, selected, false, style, move |component| {
|
let mut s = appearance(self, focused, selected, false, style, move |component| {
|
||||||
let text_color = if matches!(
|
let text_color = if matches!(
|
||||||
style,
|
style,
|
||||||
Button::Icon | Button::IconVertical | Button::HeaderBar
|
Button::Icon | Button::IconVertical | Button::HeaderBar
|
||||||
|
|
@ -209,7 +209,15 @@ impl Catalog for crate::Theme {
|
||||||
};
|
};
|
||||||
|
|
||||||
(component.base.into(), text_color, text_color)
|
(component.base.into(), text_color, text_color)
|
||||||
})
|
});
|
||||||
|
|
||||||
|
if let Button::ListItem(_) = style {
|
||||||
|
if !selected {
|
||||||
|
s.background = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
fn disabled(&self, style: &Self::Class) -> Style {
|
fn disabled(&self, style: &Self::Class) -> Style {
|
||||||
|
|
@ -237,7 +245,7 @@ impl Catalog for crate::Theme {
|
||||||
return hovered(focused, self);
|
return hovered(focused, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
appearance(
|
let mut s = appearance(
|
||||||
self,
|
self,
|
||||||
focused || matches!(style, Button::Image),
|
focused || matches!(style, Button::Image),
|
||||||
selected,
|
selected,
|
||||||
|
|
@ -256,7 +264,15 @@ impl Catalog for crate::Theme {
|
||||||
|
|
||||||
(component.hover.into(), text_color, text_color)
|
(component.hover.into(), text_color, text_color)
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
|
if let Button::ListItem(_) = style {
|
||||||
|
if !selected {
|
||||||
|
s.background = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
|
fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ pub mod application {
|
||||||
iced::theme::Style {
|
iced::theme::Style {
|
||||||
background_color: cosmic.bg_color().into(),
|
background_color: cosmic.bg_color().into(),
|
||||||
text_color: cosmic.on_bg_color().into(),
|
text_color: cosmic.on_bg_color().into(),
|
||||||
icon_color: cosmic.bg_color().into(),
|
icon_color: cosmic.on_bg_color().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ mod text_input;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use self::text_input::TextInput;
|
pub use self::text_input::TextInput;
|
||||||
|
|
||||||
#[cfg(all(feature = "wayland", feature = "winit"))]
|
#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))]
|
||||||
pub mod tooltip;
|
pub mod tooltip;
|
||||||
#[cfg(all(feature = "wayland", feature = "winit"))]
|
#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))]
|
||||||
pub use tooltip::Tooltip;
|
pub use tooltip::Tooltip;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
Apply, Element, fl,
|
Apply, Element, fl,
|
||||||
iced::{Alignment, Length},
|
iced::{Alignment, Length},
|
||||||
widget::{self, space},
|
widget::{self, list},
|
||||||
};
|
};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, derive_setters::Setters)]
|
#[derive(Debug, Default, Clone, derive_setters::Setters)]
|
||||||
#[setters(into, strip_option)]
|
#[setters(into, strip_option)]
|
||||||
|
|
@ -47,32 +48,40 @@ pub struct About {
|
||||||
fn add_contributors(contributors: Vec<(&str, &str)>) -> Vec<(String, String)> {
|
fn add_contributors(contributors: Vec<(&str, &str)>) -> Vec<(String, String)> {
|
||||||
contributors
|
contributors
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(name, email)| (name.to_string(), format!("mailto:{email}")))
|
.map(|(name, email)| (name.into(), format!("mailto:{email}")))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! set_contributors {
|
|
||||||
($field:ident, $doc:expr) => {
|
|
||||||
#[doc = $doc]
|
|
||||||
pub fn $field(mut self, contributors: impl Into<Vec<(&'a str, &'a str)>>) -> Self {
|
|
||||||
self.$field = add_contributors(contributors.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> About {
|
impl<'a> About {
|
||||||
set_contributors!(artists, "Artists who contributed to the application.");
|
/// Artists who contributed to the application.
|
||||||
set_contributors!(designers, "Designers who contributed to the application.");
|
pub fn artists(mut self, contributors: impl Into<Vec<(&'a str, &'a str)>>) -> Self {
|
||||||
set_contributors!(developers, "Developers who contributed to the application.");
|
self.artists = add_contributors(contributors.into());
|
||||||
set_contributors!(
|
self
|
||||||
documenters,
|
}
|
||||||
"Documenters who contributed to the application."
|
|
||||||
);
|
/// Designers who contributed to the application.
|
||||||
set_contributors!(
|
pub fn designers(mut self, contributors: impl Into<Vec<(&'a str, &'a str)>>) -> Self {
|
||||||
translators,
|
self.designers = add_contributors(contributors.into());
|
||||||
"Translators who contributed to the application."
|
self
|
||||||
);
|
}
|
||||||
|
|
||||||
|
/// Developers who contributed to the application.
|
||||||
|
pub fn developers(mut self, contributors: impl Into<Vec<(&'a str, &'a str)>>) -> Self {
|
||||||
|
self.developers = add_contributors(contributors.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Documenters who contributed to the application.
|
||||||
|
pub fn documenters(mut self, contributors: impl Into<Vec<(&'a str, &'a str)>>) -> Self {
|
||||||
|
self.documenters = add_contributors(contributors.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Translators who contributed to the application.
|
||||||
|
pub fn translators(mut self, contributors: impl Into<Vec<(&'a str, &'a str)>>) -> Self {
|
||||||
|
self.translators = add_contributors(contributors.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Links associated with the application.
|
/// Links associated with the application.
|
||||||
pub fn links<K: Into<String>, V: Into<String>>(
|
pub fn links<K: Into<String>, V: Into<String>>(
|
||||||
|
|
@ -96,19 +105,23 @@ pub fn about<'a, Message: Clone + 'static>(
|
||||||
space_xxs, space_m, ..
|
space_xxs, space_m, ..
|
||||||
} = crate::theme::spacing();
|
} = crate::theme::spacing();
|
||||||
|
|
||||||
let section_button = |name: &'a str, url: &'a str| -> Element<'a, Message> {
|
let svg_accent = Rc::new(|theme: &crate::Theme| widget::svg::Style {
|
||||||
widget::row()
|
color: Some(theme.cosmic().accent_text_color().into()),
|
||||||
.push(widget::text(name))
|
});
|
||||||
.push(space::horizontal())
|
|
||||||
|
let section_button = |name: &'a str, url: &'a str| -> list::ListButton<'a, Message> {
|
||||||
|
widget::row::with_capacity(2)
|
||||||
|
.push(widget::text::body(name).width(Length::Fill))
|
||||||
.push_maybe(
|
.push_maybe(
|
||||||
(!url.is_empty()).then_some(crate::widget::icon::from_name("link-symbolic").icon()),
|
(!url.is_empty()).then_some(
|
||||||
|
widget::icon::from_name("link-symbolic")
|
||||||
|
.icon()
|
||||||
|
.class(crate::theme::Svg::Custom(svg_accent.clone())),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.align_y(Alignment::Center)
|
.align_y(Alignment::Center)
|
||||||
.apply(widget::button::custom)
|
.apply(list::button)
|
||||||
.class(crate::theme::Button::Link)
|
|
||||||
.on_press(on_url_press(url))
|
.on_press(on_url_press(url))
|
||||||
.width(Length::Fill)
|
|
||||||
.into()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let section = |list: &'a Vec<(String, String)>, title: String| {
|
let section = |list: &'a Vec<(String, String)>, title: String| {
|
||||||
|
|
@ -158,7 +171,7 @@ pub fn about<'a, Message: Clone + 'static>(
|
||||||
let copyright = about.copyright.as_ref().map(widget::text::body);
|
let copyright = about.copyright.as_ref().map(widget::text::body);
|
||||||
let comments = about.comments.as_ref().map(widget::text::body);
|
let comments = about.comments.as_ref().map(widget::text::body);
|
||||||
|
|
||||||
widget::column()
|
widget::column::with_capacity(10)
|
||||||
.push_maybe(header)
|
.push_maybe(header)
|
||||||
.push_maybe(links_section)
|
.push_maybe(links_section)
|
||||||
.push_maybe(developers_section)
|
.push_maybe(developers_section)
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ where
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||||
if matches!(
|
if matches!(
|
||||||
event,
|
event,
|
||||||
Event::PlatformSpecific(event::PlatformSpecific::Wayland(
|
Event::PlatformSpecific(event::PlatformSpecific::Wayland(
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,7 @@
|
||||||
|
|
||||||
use super::{Builder, ButtonClass};
|
use super::{Builder, ButtonClass};
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use crate::widget::{
|
use crate::widget::{icon::Handle, tooltip};
|
||||||
icon::{self, Handle},
|
|
||||||
tooltip,
|
|
||||||
};
|
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
use iced_core::{Alignment, Length, Padding, font::Weight, text::LineHeight, widget::Id};
|
use iced_core::{Alignment, Length, Padding, font::Weight, text::LineHeight, widget::Id};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
@ -133,7 +130,7 @@ impl<Message> Button<'_, Message> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Message> {
|
impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Message> {
|
||||||
fn from(mut builder: Button<'a, Message>) -> Element<'a, Message> {
|
fn from(builder: Button<'a, Message>) -> Element<'a, Message> {
|
||||||
let mut content = Vec::with_capacity(2);
|
let mut content = Vec::with_capacity(2);
|
||||||
|
|
||||||
content.push(
|
content.push(
|
||||||
|
|
|
||||||
|
|
@ -357,6 +357,8 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
operation,
|
operation,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
operation.focusable(Some(&self.id), layout.bounds(), state);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@
|
||||||
//! A widget that displays an interactive calendar.
|
//! A widget that displays an interactive calendar.
|
||||||
|
|
||||||
use crate::fl;
|
use crate::fl;
|
||||||
use crate::iced_core::{Alignment, Length};
|
|
||||||
use crate::widget::{button, column, grid, icon, row, text};
|
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 jiff::{
|
use jiff::{
|
||||||
ToSpan,
|
ToSpan,
|
||||||
civil::{Date, Weekday},
|
civil::{Date, Weekday},
|
||||||
|
|
@ -212,7 +212,7 @@ where
|
||||||
|
|
||||||
let content_list = column::with_children([
|
let content_list = column::with_children([
|
||||||
row::with_children([
|
row::with_children([
|
||||||
column().push(date).push(day).into(),
|
column([date.into(), day.into()]).into(),
|
||||||
crate::widget::space::horizontal()
|
crate::widget::space::horizontal()
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.into(),
|
.into(),
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,8 @@
|
||||||
//! An expandable stack of cards
|
//! An expandable stack of cards
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use self::iced_core::{
|
|
||||||
Element, Event, Length, Size, Vector, Widget, border::Radius, id::Id, layout::Node,
|
|
||||||
renderer::Quad, widget::Tree,
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
anim,
|
anim,
|
||||||
iced_core::{self, Border, Shadow},
|
|
||||||
widget::{
|
widget::{
|
||||||
button,
|
button,
|
||||||
card::style::Style,
|
card::style::Style,
|
||||||
|
|
@ -18,6 +13,10 @@ use crate::{
|
||||||
};
|
};
|
||||||
use float_cmp::approx_eq;
|
use float_cmp::approx_eq;
|
||||||
use iced::widget;
|
use iced::widget;
|
||||||
|
use iced_core::{
|
||||||
|
Border, Element, Event, Length, Shadow, Size, Vector, Widget, border::Radius, id::Id,
|
||||||
|
layout::Node, renderer::Quad, widget::Tree,
|
||||||
|
};
|
||||||
use iced_core::{widget::tree, window};
|
use iced_core::{widget::tree, window};
|
||||||
|
|
||||||
const ICON_SIZE: u16 = 16;
|
const ICON_SIZE: u16 = 16;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
//! Widgets for selecting colors with a color picker.
|
//! Widgets for selecting colors with a color picker.
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::iter;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
@ -93,8 +92,6 @@ pub struct ColorPickerModel {
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
active_color: palette::Hsv,
|
active_color: palette::Hsv,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
save_next: Option<Color>,
|
|
||||||
#[setters(skip)]
|
|
||||||
input_color: String,
|
input_color: String,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
applied_color: Option<Color>,
|
applied_color: Option<Color>,
|
||||||
|
|
@ -128,7 +125,6 @@ impl ColorPickerModel {
|
||||||
.insert(move |b| b.text(rgb.clone()))
|
.insert(move |b| b.text(rgb.clone()))
|
||||||
.build(),
|
.build(),
|
||||||
active_color: hsv,
|
active_color: hsv,
|
||||||
save_next: None,
|
|
||||||
input_color: color_to_string(hsv, true),
|
input_color: color_to_string(hsv, true),
|
||||||
applied_color: initial,
|
applied_color: initial,
|
||||||
fallback_color,
|
fallback_color,
|
||||||
|
|
@ -159,22 +155,26 @@ impl ColorPickerModel {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_recent_colors(&mut self, new_color: Color) {
|
||||||
|
if let Some(pos) = self.recent_colors.iter().position(|c| *c == new_color) {
|
||||||
|
self.recent_colors.remove(pos);
|
||||||
|
}
|
||||||
|
self.recent_colors.insert(0, new_color);
|
||||||
|
self.recent_colors.truncate(MAX_RECENT);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update<Message>(&mut self, update: ColorPickerUpdate) -> Task<Message> {
|
pub fn update<Message>(&mut self, update: ColorPickerUpdate) -> Task<Message> {
|
||||||
match update {
|
match update {
|
||||||
ColorPickerUpdate::ActiveColor(c) => {
|
ColorPickerUpdate::ActiveColor(c) => {
|
||||||
self.must_clear_cache.store(true, Ordering::SeqCst);
|
self.must_clear_cache.store(true, Ordering::SeqCst);
|
||||||
self.input_color = color_to_string(c, self.is_hex());
|
self.input_color = color_to_string(c, self.is_hex());
|
||||||
if let Some(to_save) = self.save_next.take() {
|
|
||||||
self.recent_colors.insert(0, to_save);
|
|
||||||
self.recent_colors.truncate(MAX_RECENT);
|
|
||||||
}
|
|
||||||
self.active_color = c;
|
self.active_color = c;
|
||||||
self.copied_at = None;
|
self.copied_at = None;
|
||||||
}
|
}
|
||||||
ColorPickerUpdate::AppliedColor => {
|
ColorPickerUpdate::AppliedColor | ColorPickerUpdate::ActionFinished => {
|
||||||
let srgb = palette::Srgb::from_color(self.active_color);
|
let srgb = palette::Srgb::from_color(self.active_color);
|
||||||
if let Some(applied_color) = self.applied_color.take() {
|
if let Some(applied_color) = self.applied_color.take() {
|
||||||
self.recent_colors.push(applied_color);
|
self.update_recent_colors(applied_color);
|
||||||
}
|
}
|
||||||
self.applied_color = Some(Color::from(srgb));
|
self.applied_color = Some(Color::from(srgb));
|
||||||
self.active = false;
|
self.active = false;
|
||||||
|
|
@ -215,21 +215,12 @@ impl ColorPickerModel {
|
||||||
palette::Hsv::from_color(palette::Srgb::new(c.red, c.green, c.blue));
|
palette::Hsv::from_color(palette::Srgb::new(c.red, c.green, c.blue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ColorPickerUpdate::ActionFinished => {
|
|
||||||
let srgb = palette::Srgb::from_color(self.active_color);
|
|
||||||
if let Some(applied_color) = self.applied_color.take() {
|
|
||||||
self.recent_colors.push(applied_color);
|
|
||||||
}
|
|
||||||
self.applied_color = Some(Color::from(srgb));
|
|
||||||
self.active = false;
|
|
||||||
self.save_next = Some(Color::from(srgb));
|
|
||||||
}
|
|
||||||
ColorPickerUpdate::ToggleColorPicker => {
|
ColorPickerUpdate::ToggleColorPicker => {
|
||||||
self.must_clear_cache.store(true, Ordering::SeqCst);
|
self.must_clear_cache.store(true, Ordering::SeqCst);
|
||||||
self.active = !self.active;
|
self.active = !self.active;
|
||||||
self.copied_at = None;
|
self.copied_at = None;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -395,7 +386,8 @@ where
|
||||||
text_input("", self.input_color)
|
text_input("", self.input_color)
|
||||||
.on_input(move |s| on_update(ColorPickerUpdate::Input(s)))
|
.on_input(move |s| on_update(ColorPickerUpdate::Input(s)))
|
||||||
.on_paste(move |s| on_update(ColorPickerUpdate::Input(s)))
|
.on_paste(move |s| on_update(ColorPickerUpdate::Input(s)))
|
||||||
.on_submit(move |_| on_update(ColorPickerUpdate::AppliedColor))
|
.on_submit(move |_| on_update(ColorPickerUpdate::ActionFinished))
|
||||||
|
// .on_unfocus(on_update(ColorPickerUpdate::ActionFinished)) Somehow this is called even when the field wasn't previously focused
|
||||||
.leading_icon(
|
.leading_icon(
|
||||||
color_button(
|
color_button(
|
||||||
None,
|
None,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,12 @@
|
||||||
|
|
||||||
//! A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation.
|
//! A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation.
|
||||||
|
|
||||||
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
|
#[cfg(all(
|
||||||
|
feature = "wayland",
|
||||||
|
target_os = "linux",
|
||||||
|
feature = "winit",
|
||||||
|
feature = "surface-message"
|
||||||
|
))]
|
||||||
use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem};
|
use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem};
|
||||||
use crate::widget::menu::{
|
use crate::widget::menu::{
|
||||||
self, CloseCondition, Direction, ItemHeight, ItemWidth, MenuBarState, PathHighlight,
|
self, CloseCondition, Direction, ItemHeight, ItemWidth, MenuBarState, PathHighlight,
|
||||||
|
|
@ -27,7 +32,7 @@ pub fn context_menu<'a, Message: 'static + Clone>(
|
||||||
content: content.into(),
|
content: content.into(),
|
||||||
context_menu: context_menu.map(|menus| {
|
context_menu: context_menu.map(|menus| {
|
||||||
vec![menu::Tree::with_children(
|
vec![menu::Tree::with_children(
|
||||||
crate::Element::from(crate::widget::row::<'static, Message>()),
|
crate::Element::from(crate::widget::Row::new()),
|
||||||
menus,
|
menus,
|
||||||
)]
|
)]
|
||||||
}),
|
}),
|
||||||
|
|
@ -59,7 +64,12 @@ pub struct ContextMenu<'a, Message> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message: Clone + 'static> ContextMenu<'_, Message> {
|
impl<Message: Clone + 'static> ContextMenu<'_, Message> {
|
||||||
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
|
#[cfg(all(
|
||||||
|
feature = "wayland",
|
||||||
|
target_os = "linux",
|
||||||
|
feature = "winit",
|
||||||
|
feature = "surface-message"
|
||||||
|
))]
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn create_popup(
|
fn create_popup(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
@ -364,7 +374,12 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
state.active_root.clear();
|
state.active_root.clear();
|
||||||
state.open = false;
|
state.open = false;
|
||||||
|
|
||||||
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
|
#[cfg(all(
|
||||||
|
feature = "wayland",
|
||||||
|
target_os = "linux",
|
||||||
|
feature = "winit",
|
||||||
|
feature = "surface-message"
|
||||||
|
))]
|
||||||
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
|
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
|
||||||
&& let Some(id) = state.popup_id.remove(&self.window_id)
|
&& let Some(id) = state.popup_id.remove(&self.window_id)
|
||||||
{
|
{
|
||||||
|
|
@ -403,7 +418,12 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
state.open = true;
|
state.open = true;
|
||||||
state.view_cursor = cursor;
|
state.view_cursor = cursor;
|
||||||
});
|
});
|
||||||
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
|
#[cfg(all(
|
||||||
|
feature = "wayland",
|
||||||
|
target_os = "linux",
|
||||||
|
feature = "winit",
|
||||||
|
feature = "surface-message"
|
||||||
|
))]
|
||||||
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
|
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
|
||||||
self.create_popup(layout, cursor, renderer, shell, viewport, state);
|
self.create_popup(layout, cursor, renderer, shell, viewport, state);
|
||||||
}
|
}
|
||||||
|
|
@ -422,6 +442,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "wayland",
|
feature = "wayland",
|
||||||
|
target_os = "linux",
|
||||||
feature = "winit",
|
feature = "winit",
|
||||||
feature = "surface-message"
|
feature = "surface-message"
|
||||||
))]
|
))]
|
||||||
|
|
@ -458,7 +479,12 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
_viewport: &iced::Rectangle,
|
_viewport: &iced::Rectangle,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||||
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
|
#[cfg(all(
|
||||||
|
feature = "wayland",
|
||||||
|
target_os = "linux",
|
||||||
|
feature = "winit",
|
||||||
|
feature = "surface-message"
|
||||||
|
))]
|
||||||
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
|
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
|
||||||
&& self.window_id != window::Id::NONE
|
&& self.window_id != window::Id::NONE
|
||||||
&& self.on_surface_action.is_some()
|
&& self.on_surface_action.is_some()
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,24 @@ use iced::Vector;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Element,
|
Element,
|
||||||
iced::{
|
|
||||||
Event, Length, Rectangle,
|
|
||||||
clipboard::{
|
|
||||||
dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent},
|
|
||||||
mime::AllowedMimeTypes,
|
|
||||||
},
|
|
||||||
event,
|
|
||||||
id::Internal,
|
|
||||||
mouse, overlay,
|
|
||||||
},
|
|
||||||
iced_core::{
|
|
||||||
self, Clipboard, Shell, layout,
|
|
||||||
widget::{Tree, tree},
|
|
||||||
},
|
|
||||||
widget::{Id, Widget},
|
widget::{Id, Widget},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use iced::{
|
||||||
|
Event, Length, Rectangle,
|
||||||
|
clipboard::{
|
||||||
|
dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent},
|
||||||
|
mime::AllowedMimeTypes,
|
||||||
|
},
|
||||||
|
event,
|
||||||
|
id::Internal,
|
||||||
|
mouse, overlay,
|
||||||
|
};
|
||||||
|
use iced_core::{
|
||||||
|
self, Clipboard, Shell, layout,
|
||||||
|
widget::{Tree, tree},
|
||||||
|
};
|
||||||
|
|
||||||
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>>,
|
||||||
mimes: Vec<Cow<'static, str>>,
|
mimes: Vec<Cow<'static, str>>,
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,17 @@ use iced_core::{widget::Operation, window};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Element,
|
Element,
|
||||||
iced::{
|
|
||||||
Event, Length, Point, Rectangle, Vector,
|
|
||||||
clipboard::dnd::{DndAction, DndEvent, SourceEvent},
|
|
||||||
event, mouse, overlay,
|
|
||||||
},
|
|
||||||
iced_core::{
|
|
||||||
self, Clipboard, Shell, layout, renderer,
|
|
||||||
widget::{Tree, tree},
|
|
||||||
},
|
|
||||||
widget::{Id, Widget, container},
|
widget::{Id, Widget, container},
|
||||||
};
|
};
|
||||||
|
use iced::{
|
||||||
|
Event, Length, Point, Rectangle, Vector,
|
||||||
|
clipboard::dnd::{DndAction, DndEvent, SourceEvent},
|
||||||
|
event, mouse, overlay,
|
||||||
|
};
|
||||||
|
use iced_core::{
|
||||||
|
self, Clipboard, Shell, layout, renderer,
|
||||||
|
widget::{Tree, tree},
|
||||||
|
};
|
||||||
|
|
||||||
pub fn dnd_source<
|
pub fn dnd_source<
|
||||||
'a,
|
'a,
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ pub fn popup_dropdown<
|
||||||
let dropdown: Dropdown<'_, S, Message, AppMessage> =
|
let dropdown: Dropdown<'_, S, Message, AppMessage> =
|
||||||
Dropdown::new(selections.into(), selected, on_selected);
|
Dropdown::new(selections.into(), selected, on_selected);
|
||||||
|
|
||||||
#[cfg(all(feature = "winit", feature = "wayland"))]
|
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))]
|
||||||
let dropdown = dropdown.with_popup(_parent_id, _on_surface_action, _map_action);
|
let dropdown = dropdown.with_popup(_parent_id, _on_surface_action, _map_action);
|
||||||
|
|
||||||
dropdown
|
dropdown
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ where
|
||||||
action_map: Option<Arc<dyn Fn(Message) -> AppMessage + 'static + Send + Sync>>,
|
action_map: Option<Arc<dyn Fn(Message) -> AppMessage + 'static + Send + Sync>>,
|
||||||
#[setters(strip_option)]
|
#[setters(strip_option)]
|
||||||
window_id: Option<window::Id>,
|
window_id: Option<window::Id>,
|
||||||
#[cfg(all(feature = "winit", feature = "wayland"))]
|
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))]
|
||||||
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,14 +96,14 @@ where
|
||||||
text_line_height: text::LineHeight::Relative(1.2),
|
text_line_height: text::LineHeight::Relative(1.2),
|
||||||
font: None,
|
font: None,
|
||||||
window_id: None,
|
window_id: None,
|
||||||
#[cfg(all(feature = "winit", feature = "wayland"))]
|
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))]
|
||||||
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(),
|
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(),
|
||||||
on_surface_action: None,
|
on_surface_action: None,
|
||||||
action_map: None,
|
action_map: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "winit", feature = "wayland"))]
|
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))]
|
||||||
/// Handle dropdown requests for popup creation.
|
/// Handle dropdown requests for popup creation.
|
||||||
/// Intended to be used with [`crate::app::message::get_popup`]
|
/// Intended to be used with [`crate::app::message::get_popup`]
|
||||||
pub fn with_popup<NewAppMessage>(
|
pub fn with_popup<NewAppMessage>(
|
||||||
|
|
@ -154,7 +154,7 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "winit", feature = "wayland"))]
|
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))]
|
||||||
pub fn with_positioner(
|
pub fn with_positioner(
|
||||||
mut self,
|
mut self,
|
||||||
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
||||||
|
|
@ -268,7 +268,7 @@ where
|
||||||
layout,
|
layout,
|
||||||
cursor,
|
cursor,
|
||||||
shell,
|
shell,
|
||||||
#[cfg(all(feature = "winit", feature = "wayland"))]
|
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))]
|
||||||
self.positioner.clone(),
|
self.positioner.clone(),
|
||||||
self.on_selected.clone(),
|
self.on_selected.clone(),
|
||||||
self.selected,
|
self.selected,
|
||||||
|
|
@ -346,7 +346,7 @@ where
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||||
#[cfg(all(feature = "winit", feature = "wayland"))]
|
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))]
|
||||||
if self.window_id.is_some() || self.on_surface_action.is_some() {
|
if self.window_id.is_some() || self.on_surface_action.is_some() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
@ -545,7 +545,7 @@ pub fn update<
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
#[cfg(all(feature = "winit", feature = "wayland"))]
|
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))]
|
||||||
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
||||||
on_selected: Arc<dyn Fn(usize) -> Message + Send + Sync + 'static>,
|
on_selected: Arc<dyn Fn(usize) -> Message + Send + Sync + 'static>,
|
||||||
selected: Option<usize>,
|
selected: Option<usize>,
|
||||||
|
|
@ -571,7 +571,7 @@ pub fn update<
|
||||||
*hovered_guard = selected;
|
*hovered_guard = selected;
|
||||||
let id = window::Id::unique();
|
let id = window::Id::unique();
|
||||||
state.popup_id = id;
|
state.popup_id = id;
|
||||||
#[cfg(all(feature = "winit", feature = "wayland"))]
|
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))]
|
||||||
if let Some(((on_surface_action, parent), action_map)) = on_surface_action
|
if let Some(((on_surface_action, parent), action_map)) = on_surface_action
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.zip(_window_id)
|
.zip(_window_id)
|
||||||
|
|
@ -658,7 +658,7 @@ pub fn update<
|
||||||
state.close_operation = false;
|
state.close_operation = false;
|
||||||
state.is_open.store(false, Ordering::SeqCst);
|
state.is_open.store(false, Ordering::SeqCst);
|
||||||
if is_open {
|
if is_open {
|
||||||
#[cfg(all(feature = "winit", feature = "wayland"))]
|
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))]
|
||||||
if let Some(ref on_close) = on_surface_action {
|
if let Some(ref on_close) = on_surface_action {
|
||||||
shell.publish(on_close(surface::action::destroy_popup(state.popup_id)));
|
shell.publish(on_close(surface::action::destroy_popup(state.popup_id)));
|
||||||
}
|
}
|
||||||
|
|
@ -681,7 +681,7 @@ pub fn update<
|
||||||
// Event wasn't processed by overlay, so cursor was clicked either outside it's
|
// Event wasn't processed by overlay, so cursor was clicked either outside it's
|
||||||
// bounds or on the drop-down, either way we close the overlay.
|
// bounds or on the drop-down, either way we close the overlay.
|
||||||
state.is_open.store(false, Ordering::Relaxed);
|
state.is_open.store(false, Ordering::Relaxed);
|
||||||
#[cfg(all(feature = "winit", feature = "wayland"))]
|
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))]
|
||||||
if let Some(on_close) = on_surface_action {
|
if let Some(on_close) = on_surface_action {
|
||||||
shell.publish(on_close(surface::action::destroy_popup(state.popup_id)));
|
shell.publish(on_close(surface::action::destroy_popup(state.popup_id)));
|
||||||
}
|
}
|
||||||
|
|
@ -726,7 +726,7 @@ pub fn mouse_interaction(layout: Layout<'_>, cursor: mouse::Cursor) -> mouse::In
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "winit", feature = "wayland"))]
|
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))]
|
||||||
/// Returns the current menu widget of a [`Dropdown`].
|
/// Returns the current menu widget of a [`Dropdown`].
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn menu_widget<
|
pub fn menu_widget<
|
||||||
|
|
|
||||||
|
|
@ -162,9 +162,14 @@ pub fn resolve<Message>(
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let actual_height = nodes
|
||||||
|
.iter()
|
||||||
|
.map(|node| node.bounds().y + node.bounds().height)
|
||||||
|
.fold(0.0f32, f32::max);
|
||||||
|
|
||||||
let size = Size {
|
let size = Size {
|
||||||
width: flex_layout.content_size.width,
|
width: flex_layout.content_size.width,
|
||||||
height: flex_layout.content_size.height,
|
height: actual_height.max(flex_layout.content_size.height),
|
||||||
};
|
};
|
||||||
|
|
||||||
Node::with_children(size, nodes)
|
Node::with_children(size, nodes)
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@ 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};
|
||||||
use iced_core::{
|
use iced_core::{
|
||||||
Clipboard, ContentFit, Element, Event, Layout, Length, Rectangle, Shell, Size, Vector, Widget,
|
Clipboard, ContentFit, Element, Event, Layout, Length, Rectangle, Rotation, Shell, Size,
|
||||||
event, layout, renderer, window,
|
Widget, event, layout, renderer, window,
|
||||||
};
|
};
|
||||||
use iced_widget::image::{self, Handle};
|
use iced_widget::image::{self, FilterMethod, Handle};
|
||||||
use image_rs::AnimationDecoder;
|
use image_rs::AnimationDecoder;
|
||||||
use image_rs::codecs::gif::GifDecoder;
|
use image_rs::codecs::gif::GifDecoder;
|
||||||
use image_rs::codecs::png::PngDecoder;
|
use image_rs::codecs::png::PngDecoder;
|
||||||
|
|
@ -146,7 +146,7 @@ impl Frames {
|
||||||
|
|
||||||
match image_type {
|
match image_type {
|
||||||
ImageType::Gif => Self::from_decoder(GifDecoder::new(io::Cursor::new(bytes))?),
|
ImageType::Gif => Self::from_decoder(GifDecoder::new(io::Cursor::new(bytes))?),
|
||||||
ImageType::Apng => Self::from_decoder(PngDecoder::new(io::Cursor::new(bytes))?.apng()),
|
ImageType::Apng => Self::from_decoder(PngDecoder::new(io::Cursor::new(bytes))?.apng()?),
|
||||||
ImageType::WebP => Self::from_decoder(WebPDecoder::new(io::Cursor::new(bytes))?),
|
ImageType::WebP => Self::from_decoder(WebPDecoder::new(io::Cursor::new(bytes))?),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -168,10 +168,10 @@ impl Frames {
|
||||||
let first = frames.first().cloned().unwrap();
|
let first = frames.first().cloned().unwrap();
|
||||||
let total_bytes = frames
|
let total_bytes = frames
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f| match f.handle.data() {
|
.map(|f| match &f.handle {
|
||||||
iced_core::image::Handle::Path(..) => 0,
|
Handle::Path(..) => 0,
|
||||||
iced_core::image::Handle::Bytes(_, b) => b.len(),
|
Handle::Bytes(_, b) => b.len(),
|
||||||
iced_core::image::Handle::Rgba { pixels, .. } => pixels.len(),
|
Handle::Rgba { pixels, .. } => pixels.len(),
|
||||||
})
|
})
|
||||||
.sum::<usize>()
|
.sum::<usize>()
|
||||||
.try_into()
|
.try_into()
|
||||||
|
|
@ -324,7 +324,11 @@ where
|
||||||
&self.frames.first.handle,
|
&self.frames.first.handle,
|
||||||
self.width,
|
self.width,
|
||||||
self.height,
|
self.height,
|
||||||
|
None,
|
||||||
self.content_fit,
|
self.content_fit,
|
||||||
|
Rotation::default(),
|
||||||
|
false,
|
||||||
|
[0.0; 4],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -371,37 +375,18 @@ where
|
||||||
) {
|
) {
|
||||||
let state = tree.state.downcast_ref::<State>();
|
let state = tree.state.downcast_ref::<State>();
|
||||||
|
|
||||||
// Pulled from iced_native::widget::<Image as Widget>::draw
|
iced_widget::image::draw(
|
||||||
//
|
renderer,
|
||||||
// TODO: export iced_native::widget::image::draw as standalone function
|
layout,
|
||||||
{
|
&state.current.frame.handle,
|
||||||
let Size { width, height } = renderer.dimensions(&state.current.frame.handle);
|
None,
|
||||||
let image_size = Size::new(width as f32, height as f32);
|
iced_core::border::Radius::default(),
|
||||||
|
self.content_fit,
|
||||||
let bounds = layout.bounds();
|
FilterMethod::default(),
|
||||||
let adjusted_fit = self.content_fit.fit(image_size, bounds.size());
|
Rotation::default(),
|
||||||
|
1.0,
|
||||||
let render = |renderer: &mut Renderer| {
|
1.0,
|
||||||
let offset = Vector::new(
|
);
|
||||||
(bounds.width - adjusted_fit.width).max(0.0) / 2.0,
|
|
||||||
(bounds.height - adjusted_fit.height).max(0.0) / 2.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
let drawing_bounds = Rectangle {
|
|
||||||
width: adjusted_fit.width,
|
|
||||||
height: adjusted_fit.height,
|
|
||||||
..bounds
|
|
||||||
};
|
|
||||||
|
|
||||||
renderer.draw(state.current.frame.handle.clone(), drawing_bounds + offset);
|
|
||||||
};
|
|
||||||
|
|
||||||
if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height {
|
|
||||||
renderer.with_layer(bounds, render);
|
|
||||||
} else {
|
|
||||||
render(renderer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,9 +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::{Length, mouse};
|
use iced_core::{Length, Size, Vector, Widget, layout, text, widget::tree};
|
||||||
use iced_core::{Vector, Widget, widget::tree};
|
use std::borrow::Cow;
|
||||||
use std::{borrow::Cow, cmp};
|
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> {
|
pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> {
|
||||||
|
|
@ -27,7 +26,6 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> {
|
||||||
sharp_corners: false,
|
sharp_corners: false,
|
||||||
is_ssd: false,
|
is_ssd: false,
|
||||||
on_double_click: None,
|
on_double_click: None,
|
||||||
is_condensed: false,
|
|
||||||
transparent: false,
|
transparent: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,9 +89,6 @@ pub struct HeaderBar<'a, Message> {
|
||||||
/// HeaderBar used for server-side decorations
|
/// HeaderBar used for server-side decorations
|
||||||
is_ssd: bool,
|
is_ssd: bool,
|
||||||
|
|
||||||
/// Whether the headerbar should be compact
|
|
||||||
is_condensed: bool,
|
|
||||||
|
|
||||||
/// Whether the headerbar should be transparent
|
/// Whether the headerbar should be transparent
|
||||||
transparent: bool,
|
transparent: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -126,48 +121,116 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
self.end.push(widget.into());
|
self.end.push(widget.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the widget
|
|
||||||
#[must_use]
|
|
||||||
#[inline]
|
|
||||||
pub fn build(self) -> HeaderBarWidget<'a, Message> {
|
|
||||||
HeaderBarWidget {
|
|
||||||
header_bar_inner: self.view(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct HeaderBarWidget<'a, Message> {
|
pub struct HeaderBarWidget<'a, Message> {
|
||||||
header_bar_inner: Element<'a, Message>,
|
start: Element<'a, Message>,
|
||||||
|
center: Option<Element<'a, Message>>,
|
||||||
|
end: Element<'a, Message>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
impl<'a, Message> HeaderBarWidget<'a, Message> {
|
||||||
for HeaderBarWidget<'_, Message>
|
pub fn new(
|
||||||
|
start: Element<'a, Message>,
|
||||||
|
center: Option<Element<'a, Message>>,
|
||||||
|
end: Element<'a, Message>,
|
||||||
|
) -> Self {
|
||||||
|
Self { start, center, end }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn elems(&self) -> impl Iterator<Item = &Element<'a, Message>> {
|
||||||
|
std::iter::once(&self.start)
|
||||||
|
.chain(std::iter::once(&self.end))
|
||||||
|
.chain(self.center.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn elems_mut(&mut self) -> impl Iterator<Item = &mut Element<'a, Message>> {
|
||||||
|
std::iter::once(&mut self.start)
|
||||||
|
.chain(std::iter::once(&mut self.end))
|
||||||
|
.chain(self.center.as_mut())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
|
for HeaderBarWidget<'a, Message>
|
||||||
{
|
{
|
||||||
fn diff(&mut self, tree: &mut tree::Tree) {
|
fn diff(&mut self, tree: &mut tree::Tree) {
|
||||||
tree.diff_children(&mut [&mut self.header_bar_inner]);
|
if let Some(center) = &mut self.center {
|
||||||
|
tree.diff_children(&mut [&mut self.start, &mut self.end, center]);
|
||||||
|
} else {
|
||||||
|
tree.diff_children(&mut [&mut self.start, &mut self.end]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn children(&self) -> Vec<tree::Tree> {
|
fn children(&self) -> Vec<tree::Tree> {
|
||||||
vec![tree::Tree::new(&self.header_bar_inner)]
|
self.elems().map(tree::Tree::new).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size(&self) -> iced_core::Size<Length> {
|
fn size(&self) -> Size<Length> {
|
||||||
self.header_bar_inner.as_widget().size()
|
Size {
|
||||||
|
width: Length::Fill,
|
||||||
|
height: Length::Shrink,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut tree::Tree,
|
tree: &mut tree::Tree,
|
||||||
renderer: &crate::Renderer,
|
renderer: &crate::Renderer,
|
||||||
limits: &iced_core::layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> iced_core::layout::Node {
|
) -> layout::Node {
|
||||||
let child_tree = &mut tree.children[0];
|
let width = limits.max().width;
|
||||||
let child = self
|
let height = limits.max().height;
|
||||||
.header_bar_inner
|
let gap = 8.0;
|
||||||
.as_widget_mut()
|
|
||||||
.layout(child_tree, renderer, limits);
|
let end_node =
|
||||||
iced_core::layout::Node::with_children(child.size(), vec![child])
|
self.end
|
||||||
|
.as_widget_mut()
|
||||||
|
.layout(&mut tree.children[1], renderer, &limits.loose());
|
||||||
|
let end_width = end_node.size().width;
|
||||||
|
|
||||||
|
let start_available = (width - end_width - gap).max(0.0);
|
||||||
|
let start_node = self.start.as_widget_mut().layout(
|
||||||
|
&mut tree.children[0],
|
||||||
|
renderer,
|
||||||
|
&layout::Limits::new(Size::ZERO, Size::new(start_available, height)),
|
||||||
|
);
|
||||||
|
let start_width = start_node.size().width;
|
||||||
|
|
||||||
|
let vcenter = |node: layout::Node, x: f32| -> layout::Node {
|
||||||
|
let dy = ((height - node.size().height) / 2.0).max(0.0);
|
||||||
|
node.translate(Vector::new(x, dy))
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut child_nodes = Vec::with_capacity(3);
|
||||||
|
child_nodes.push(vcenter(start_node, 0.0));
|
||||||
|
child_nodes.push(vcenter(end_node, width - end_width));
|
||||||
|
|
||||||
|
if let Some(center) = &mut self.center {
|
||||||
|
let slot_start = start_width + gap;
|
||||||
|
let slot_end = (width - end_width - gap).max(slot_start);
|
||||||
|
let slot_width = slot_end - slot_start;
|
||||||
|
// this instead of `node.size().width` prevents center jitter as text ellipsizes
|
||||||
|
let natural_width = center
|
||||||
|
.as_widget_mut()
|
||||||
|
.layout(&mut tree.children[2], renderer, &limits.loose())
|
||||||
|
.size()
|
||||||
|
.width;
|
||||||
|
|
||||||
|
let node = center.as_widget_mut().layout(
|
||||||
|
&mut tree.children[2],
|
||||||
|
renderer,
|
||||||
|
&layout::Limits::new(Size::ZERO, Size::new(slot_width, height)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let ideal_x = (width - natural_width) / 2.0;
|
||||||
|
let max_x = (width - end_width - gap - natural_width).max(slot_start);
|
||||||
|
let center_x = ideal_x.clamp(slot_start, max_x);
|
||||||
|
|
||||||
|
child_nodes.push(vcenter(node, center_x))
|
||||||
|
}
|
||||||
|
|
||||||
|
layout::Node::with_children(Size::new(width, height), child_nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
@ -180,17 +243,13 @@ impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
cursor: iced_core::mouse::Cursor,
|
cursor: iced_core::mouse::Cursor,
|
||||||
viewport: &iced_core::Rectangle,
|
viewport: &iced_core::Rectangle,
|
||||||
) {
|
) {
|
||||||
let layout_children = layout.children().next().unwrap();
|
self.elems()
|
||||||
let state_children = &tree.children[0];
|
.zip(&tree.children)
|
||||||
self.header_bar_inner.as_widget().draw(
|
.zip(layout.children())
|
||||||
state_children,
|
.for_each(|((e, s), l)| {
|
||||||
renderer,
|
e.as_widget()
|
||||||
theme,
|
.draw(s, renderer, theme, style, l, cursor, viewport);
|
||||||
style,
|
});
|
||||||
layout_children,
|
|
||||||
cursor,
|
|
||||||
viewport,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
|
|
@ -204,19 +263,13 @@ impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
shell: &mut iced_core::Shell<'_, Message>,
|
shell: &mut iced_core::Shell<'_, Message>,
|
||||||
viewport: &iced_core::Rectangle,
|
viewport: &iced_core::Rectangle,
|
||||||
) {
|
) {
|
||||||
let child_state = &mut state.children[0];
|
self.elems_mut()
|
||||||
let child_layout = layout.children().next().unwrap();
|
.zip(&mut state.children)
|
||||||
|
.zip(layout.children())
|
||||||
self.header_bar_inner.as_widget_mut().update(
|
.for_each(|((e, s), l)| {
|
||||||
child_state,
|
e.as_widget_mut()
|
||||||
event,
|
.update(s, event, l, cursor, renderer, clipboard, shell, viewport);
|
||||||
child_layout,
|
});
|
||||||
cursor,
|
|
||||||
renderer,
|
|
||||||
clipboard,
|
|
||||||
shell,
|
|
||||||
viewport,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
@ -227,15 +280,15 @@ impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
viewport: &iced_core::Rectangle,
|
viewport: &iced_core::Rectangle,
|
||||||
renderer: &crate::Renderer,
|
renderer: &crate::Renderer,
|
||||||
) -> iced_core::mouse::Interaction {
|
) -> iced_core::mouse::Interaction {
|
||||||
let child_tree = &state.children[0];
|
self.elems()
|
||||||
let child_layout = layout.children().next().unwrap();
|
.zip(&state.children)
|
||||||
self.header_bar_inner.as_widget().mouse_interaction(
|
.zip(layout.children())
|
||||||
child_tree,
|
.map(|((e, s), l)| {
|
||||||
child_layout,
|
e.as_widget()
|
||||||
cursor,
|
.mouse_interaction(s, l, cursor, viewport, renderer)
|
||||||
viewport,
|
})
|
||||||
renderer,
|
.max()
|
||||||
)
|
.unwrap_or(iced_core::mouse::Interaction::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(
|
fn operate(
|
||||||
|
|
@ -245,14 +298,12 @@ impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
renderer: &crate::Renderer,
|
renderer: &crate::Renderer,
|
||||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||||
) {
|
) {
|
||||||
let child_tree = &mut state.children[0];
|
self.elems_mut()
|
||||||
let child_layout = layout.children().next().unwrap();
|
.zip(&mut state.children)
|
||||||
self.header_bar_inner.as_widget_mut().operate(
|
.zip(layout.children())
|
||||||
child_tree,
|
.for_each(|((e, s), l)| {
|
||||||
child_layout,
|
e.as_widget_mut().operate(s, l, renderer, operation);
|
||||||
renderer,
|
});
|
||||||
operation,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overlay<'b>(
|
fn overlay<'b>(
|
||||||
|
|
@ -263,15 +314,13 @@ impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
viewport: &iced_core::Rectangle,
|
viewport: &iced_core::Rectangle,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||||
let child_tree = &mut state.children[0];
|
self.elems_mut()
|
||||||
let child_layout = layout.children().next().unwrap();
|
.zip(&mut state.children)
|
||||||
self.header_bar_inner.as_widget_mut().overlay(
|
.zip(layout.children())
|
||||||
child_tree,
|
.find_map(|((e, s), l)| {
|
||||||
child_layout,
|
e.as_widget_mut()
|
||||||
renderer,
|
.overlay(s, l, renderer, viewport, translation)
|
||||||
viewport,
|
})
|
||||||
translation,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drag_destinations(
|
fn drag_destinations(
|
||||||
|
|
@ -281,16 +330,13 @@ impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
renderer: &crate::Renderer,
|
renderer: &crate::Renderer,
|
||||||
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||||
) {
|
) {
|
||||||
if let Some((child_tree, child_layout)) =
|
self.elems()
|
||||||
state.children.iter().zip(layout.children()).next()
|
.zip(&state.children)
|
||||||
{
|
.zip(layout.children())
|
||||||
self.header_bar_inner.as_widget().drag_destinations(
|
.for_each(|((e, s), l)| {
|
||||||
child_tree,
|
e.as_widget()
|
||||||
child_layout,
|
.drag_destinations(s, l, renderer, dnd_rectangles);
|
||||||
renderer,
|
});
|
||||||
dnd_rectangles,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "a11y")]
|
#[cfg(feature = "a11y")]
|
||||||
|
|
@ -301,16 +347,22 @@ impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
state: &tree::Tree,
|
state: &tree::Tree,
|
||||||
p: iced::mouse::Cursor,
|
p: iced::mouse::Cursor,
|
||||||
) -> iced_accessibility::A11yTree {
|
) -> iced_accessibility::A11yTree {
|
||||||
let c_layout = layout.children().next().unwrap();
|
iced_accessibility::A11yTree::join(
|
||||||
let c_state = &state.children[0];
|
self.elems()
|
||||||
self.header_bar_inner
|
.zip(&state.children)
|
||||||
.as_widget()
|
.zip(layout.children())
|
||||||
.a11y_nodes(c_layout, c_state, p)
|
.map(|((e, s), l)| e.as_widget().a11y_nodes(l, s, p)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: Clone + 'static> From<HeaderBarWidget<'a, Message>> for Element<'a, Message> {
|
||||||
|
fn from(w: HeaderBarWidget<'a, Message>) -> Self {
|
||||||
|
Element::new(w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
#[allow(clippy::too_many_lines)]
|
|
||||||
/// Converts the headerbar builder into an Iced element.
|
/// Converts the headerbar builder into an Iced element.
|
||||||
pub fn view(mut self) -> Element<'a, Message> {
|
pub fn view(mut self) -> Element<'a, Message> {
|
||||||
let Spacing {
|
let Spacing {
|
||||||
|
|
@ -324,154 +376,84 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
let center = std::mem::take(&mut self.center);
|
let center = std::mem::take(&mut self.center);
|
||||||
let mut end = std::mem::take(&mut self.end);
|
let mut end = std::mem::take(&mut self.end);
|
||||||
|
|
||||||
let window_control_cnt = self.on_close.is_some() as usize
|
|
||||||
+ self.on_maximize.is_some() as usize
|
|
||||||
+ self.on_minimize.is_some() as usize;
|
|
||||||
// Also packs the window controls at the very end.
|
// Also packs the window controls at the very end.
|
||||||
end.push(self.window_controls());
|
end.push(self.window_controls(space_xxs));
|
||||||
|
|
||||||
// Center content depending on window border
|
let padding = if self.is_ssd {
|
||||||
let padding = match self.density.unwrap_or_else(crate::config::header_size) {
|
[2, 8, 2, 8]
|
||||||
Density::Compact => {
|
} else {
|
||||||
if self.maximized {
|
match (
|
||||||
[4, 8, 4, 8]
|
self.density.unwrap_or_else(crate::config::header_size),
|
||||||
} else {
|
self.maximized, // window border handling
|
||||||
[3, 7, 4, 7]
|
) {
|
||||||
}
|
(Density::Compact, true) => [4, 8, 4, 8],
|
||||||
}
|
(Density::Compact, false) => [3, 7, 4, 7],
|
||||||
_ => {
|
(_, true) => [8, 8, 8, 8],
|
||||||
if self.maximized {
|
(_, false) => [7, 7, 8, 7],
|
||||||
[8, 8, 8, 8]
|
|
||||||
} else {
|
|
||||||
[7, 7, 8, 7]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let acc_count = |v: &[Element<'a, Message>]| {
|
let start = widget::row::with_children(start)
|
||||||
v.iter().fold(0, |acc, e| {
|
.spacing(space_xxxs)
|
||||||
acc + match e.as_widget().size().width {
|
.align_y(iced::Alignment::Center)
|
||||||
Length::Fixed(w) if w > 30. => (w / 30.0).ceil() as usize,
|
.into();
|
||||||
_ => 1,
|
let center = if !center.is_empty() {
|
||||||
}
|
Some(
|
||||||
})
|
widget::row::with_children(center)
|
||||||
};
|
|
||||||
|
|
||||||
let left_len = acc_count(&start);
|
|
||||||
let right_len = acc_count(&end);
|
|
||||||
|
|
||||||
let portion = ((left_len.max(right_len + window_control_cnt) as f32
|
|
||||||
/ center.len().max(1) as f32)
|
|
||||||
.round() as u16)
|
|
||||||
.max(1);
|
|
||||||
let (left_portion, right_portion) =
|
|
||||||
if center.is_empty() && (self.title.is_empty() || self.is_condensed) {
|
|
||||||
let left_to_right_ratio = left_len as f32 / right_len.max(1) as f32;
|
|
||||||
let right_to_left_ratio = right_len as f32 / left_len.max(1) as f32;
|
|
||||||
if right_to_left_ratio > 2. || left_len < 1 {
|
|
||||||
(1, 2)
|
|
||||||
} else if left_to_right_ratio > 2. || right_len < 1 {
|
|
||||||
(2, 1)
|
|
||||||
} else {
|
|
||||||
(left_len as u16, (right_len + window_control_cnt) as u16)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(portion, portion)
|
|
||||||
};
|
|
||||||
let title_portion = cmp::max(left_portion, right_portion) * 2;
|
|
||||||
// Creates the headerbar widget.
|
|
||||||
let mut widget = widget::row::with_capacity(3)
|
|
||||||
// If elements exist in the start region, append them here.
|
|
||||||
.push(
|
|
||||||
widget::row::with_children(start)
|
|
||||||
.spacing(space_xxxs)
|
.spacing(space_xxxs)
|
||||||
.align_y(iced::Alignment::Center)
|
.align_y(iced::Alignment::Center)
|
||||||
.apply(widget::container)
|
.into(),
|
||||||
.align_x(iced::Alignment::Start)
|
|
||||||
.width(Length::FillPortion(left_portion)),
|
|
||||||
)
|
)
|
||||||
// If elements exist in the center region, use them here.
|
} else if !self.title.is_empty() {
|
||||||
// This will otherwise use the title as a widget if a title was defined.
|
Some(
|
||||||
.push_maybe(if !center.is_empty() {
|
widget::text::heading(self.title)
|
||||||
Some(
|
.wrapping(text::Wrapping::None)
|
||||||
widget::row::with_children(center)
|
.ellipsize(text::Ellipsize::End(text::EllipsizeHeightLimit::Lines(1)))
|
||||||
.spacing(space_xxxs)
|
.into(),
|
||||||
.align_y(iced::Alignment::Center)
|
|
||||||
.apply(widget::container)
|
|
||||||
.center_x(Length::Fill)
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
} else if !self.title.is_empty() && !self.is_condensed {
|
|
||||||
Some(self.title_widget(title_portion))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.push(
|
|
||||||
widget::row::with_children(end)
|
|
||||||
.spacing(space_xxs)
|
|
||||||
.align_y(iced::Alignment::Center)
|
|
||||||
.apply(widget::container)
|
|
||||||
.align_x(iced::Alignment::End)
|
|
||||||
.width(Length::FillPortion(right_portion)),
|
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let end = widget::row::with_children(end)
|
||||||
|
.spacing(space_xxs)
|
||||||
.align_y(iced::Alignment::Center)
|
.align_y(iced::Alignment::Center)
|
||||||
.height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32))
|
.into();
|
||||||
.padding(if self.is_ssd { [0, 8, 0, 8] } else { padding })
|
|
||||||
.spacing(8)
|
let mut widget = HeaderBarWidget::new(start, center, end)
|
||||||
.apply(widget::container)
|
.apply(widget::container)
|
||||||
.class(crate::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: self.transparent,
|
||||||
})
|
})
|
||||||
.center_y(Length::Shrink)
|
.height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32))
|
||||||
|
.padding(padding)
|
||||||
.apply(widget::mouse_area);
|
.apply(widget::mouse_area);
|
||||||
|
|
||||||
// Assigns a message to emit when the headerbar is dragged.
|
if let Some(message) = self.on_drag {
|
||||||
if let Some(message) = self.on_drag.clone() {
|
|
||||||
widget = widget.on_drag(message);
|
widget = widget.on_drag(message);
|
||||||
}
|
}
|
||||||
|
if let Some(message) = self.on_maximize {
|
||||||
// Assigns a message to emit when the headerbar is double-clicked.
|
|
||||||
if let Some(message) = self.on_maximize.clone() {
|
|
||||||
widget = widget.on_release(message);
|
widget = widget.on_release(message);
|
||||||
}
|
}
|
||||||
|
if let Some(message) = self.on_double_click {
|
||||||
if let Some(message) = self.on_double_click.clone() {
|
|
||||||
widget = widget.on_double_press(message);
|
widget = widget.on_double_press(message);
|
||||||
}
|
}
|
||||||
if let Some(message) = self.on_right_click.clone() {
|
if let Some(message) = self.on_right_click {
|
||||||
widget = widget.on_right_press(message);
|
widget = widget.on_right_press(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
widget.into()
|
widget.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title_widget(&mut self, title_portion: u16) -> Element<'a, Message> {
|
|
||||||
let mut title = Cow::default();
|
|
||||||
std::mem::swap(&mut title, &mut self.title);
|
|
||||||
|
|
||||||
widget::text::heading(title)
|
|
||||||
.wrapping(iced_core::text::Wrapping::None)
|
|
||||||
.ellipsize(iced_core::text::Ellipsize::End(
|
|
||||||
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
|
||||||
))
|
|
||||||
.apply(widget::container)
|
|
||||||
.center(Length::FillPortion(title_portion))
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates the widget for window controls.
|
/// Creates the widget for window controls.
|
||||||
fn window_controls(&mut self) -> Element<'a, Message> {
|
fn window_controls(&mut self, spacing: u16) -> Element<'a, Message> {
|
||||||
macro_rules! icon {
|
macro_rules! icon {
|
||||||
($name:expr, $size:expr, $on_press:expr) => {{
|
($name:expr, $size:expr, $on_press:expr) => {{
|
||||||
let icon = {
|
widget::icon::from_name($name)
|
||||||
widget::icon::from_name($name)
|
.apply(widget::button::icon)
|
||||||
.apply(widget::button::icon)
|
.padding(8)
|
||||||
.padding(8)
|
.class(theme::Button::HeaderBar)
|
||||||
};
|
|
||||||
|
|
||||||
icon.class(crate::theme::Button::HeaderBar)
|
|
||||||
.selected(self.focused)
|
.selected(self.focused)
|
||||||
.icon_size($size)
|
.icon_size($size)
|
||||||
.on_press($on_press)
|
.on_press($on_press)
|
||||||
|
|
@ -482,7 +464,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
.push_maybe(
|
.push_maybe(
|
||||||
self.on_minimize
|
self.on_minimize
|
||||||
.take()
|
.take()
|
||||||
.map(|m: Message| icon!("window-minimize-symbolic", 16, m)),
|
.map(|m| icon!("window-minimize-symbolic", 16, m)),
|
||||||
)
|
)
|
||||||
.push_maybe(self.on_maximize.take().map(|m| {
|
.push_maybe(self.on_maximize.take().map(|m| {
|
||||||
if self.maximized {
|
if self.maximized {
|
||||||
|
|
@ -496,21 +478,14 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
.take()
|
.take()
|
||||||
.map(|m| icon!("window-close-symbolic", 16, m)),
|
.map(|m| icon!("window-close-symbolic", 16, m)),
|
||||||
)
|
)
|
||||||
.spacing(theme::spacing().space_xxs)
|
.spacing(spacing)
|
||||||
.apply(widget::container)
|
.align_y(iced::Alignment::Center)
|
||||||
.center_y(Length::Fill)
|
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message: Clone + 'static> From<HeaderBar<'a, Message>> for Element<'a, Message> {
|
impl<'a, Message: Clone + 'static> From<HeaderBar<'a, Message>> for Element<'a, Message> {
|
||||||
fn from(headerbar: HeaderBar<'a, Message>) -> Self {
|
fn from(headerbar: HeaderBar<'a, Message>) -> Self {
|
||||||
Element::new(headerbar.build())
|
headerbar.view()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message: Clone + 'static> From<HeaderBarWidget<'a, Message>> for Element<'a, Message> {
|
|
||||||
fn from(headerbar: HeaderBarWidget<'a, Message>) -> Self {
|
|
||||||
Element::new(headerbar)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@
|
||||||
//! Embedded icons for platforms which do not support icon themes yet.
|
//! Embedded icons for platforms which do not support icon themes yet.
|
||||||
|
|
||||||
/// Icon bundling is not enabled on unix platforms.
|
/// Icon bundling is not enabled on unix platforms.
|
||||||
#[cfg(unix)]
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
pub fn get(icon_name: &str) -> Option<super::Data> {
|
pub fn get(icon_name: &str) -> Option<super::Data> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(any(not(unix), target_os = "macos"))]
|
||||||
/// Get a bundled icon on non-unix platforms.
|
/// Get a bundled icon on non-unix platforms.
|
||||||
pub fn get(icon_name: &str) -> Option<super::Data> {
|
pub fn get(icon_name: &str) -> Option<super::Data> {
|
||||||
ICONS
|
ICONS
|
||||||
|
|
@ -17,5 +17,5 @@ pub fn get(icon_name: &str) -> Option<super::Data> {
|
||||||
.map(|bytes| super::Data::Svg(crate::iced::widget::svg::Handle::from_memory(*bytes)))
|
.map(|bytes| super::Data::Svg(crate::iced::widget::svg::Handle::from_memory(*bytes)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(any(not(unix), target_os = "macos"))]
|
||||||
include!(concat!(env!("OUT_DIR"), "/bundled_icons.rs"));
|
include!(concat!(env!("OUT_DIR"), "/bundled_icons.rs"));
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ impl Named {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[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;
|
||||||
|
|
@ -107,7 +107,7 @@ impl Named {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(any(not(unix), target_os = "macos"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn path(self) -> Option<PathBuf> {
|
pub fn path(self) -> Option<PathBuf> {
|
||||||
//TODO: implement icon lookup for Windows
|
//TODO: implement icon lookup for Windows
|
||||||
|
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
// Copyright 2022 System76 <info@system76.com>
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
use iced_core::Padding;
|
|
||||||
use iced_widget::container::Catalog;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
Apply, Element, theme,
|
|
||||||
widget::{container, divider, space::vertical},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> {
|
|
||||||
ListColumn::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub struct ListColumn<'a, Message> {
|
|
||||||
spacing: u16,
|
|
||||||
padding: Padding,
|
|
||||||
list_item_padding: Padding,
|
|
||||||
divider_padding: u16,
|
|
||||||
style: theme::Container<'a>,
|
|
||||||
children: Vec<Element<'a, Message>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Message: 'static> Default for ListColumn<'_, Message> {
|
|
||||||
fn default() -> Self {
|
|
||||||
let cosmic_theme::Spacing {
|
|
||||||
space_xxs, space_m, ..
|
|
||||||
} = theme::spacing();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
spacing: 0,
|
|
||||||
padding: Padding::from(0),
|
|
||||||
divider_padding: 16,
|
|
||||||
list_item_padding: [space_xxs, space_m].into(),
|
|
||||||
style: theme::Container::List,
|
|
||||||
children: Vec::with_capacity(4),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message: 'static> ListColumn<'a, Message> {
|
|
||||||
#[inline]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::should_implement_trait)]
|
|
||||||
pub fn add(self, item: impl Into<Element<'a, Message>>) -> Self {
|
|
||||||
#[inline(never)]
|
|
||||||
fn inner<'a, Message: 'static>(
|
|
||||||
mut this: ListColumn<'a, Message>,
|
|
||||||
item: Element<'a, Message>,
|
|
||||||
) -> ListColumn<'a, Message> {
|
|
||||||
if !this.children.is_empty() {
|
|
||||||
this.children.push(
|
|
||||||
container(divider::horizontal::default())
|
|
||||||
.padding([0, this.divider_padding])
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure a minimum height of 32.
|
|
||||||
let list_item = iced::widget::row![
|
|
||||||
container(item).align_y(iced::Alignment::Center),
|
|
||||||
vertical().height(iced::Length::Fixed(32.))
|
|
||||||
]
|
|
||||||
.padding(this.list_item_padding)
|
|
||||||
.align_y(iced::Alignment::Center);
|
|
||||||
|
|
||||||
this.children.push(list_item.into());
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
inner(self, item.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn spacing(mut self, spacing: u16) -> Self {
|
|
||||||
self.spacing = spacing;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the style variant of this [`Circular`].
|
|
||||||
#[inline]
|
|
||||||
pub fn style(mut self, style: <crate::Theme as Catalog>::Class<'a>) -> Self {
|
|
||||||
self.style = style;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
|
|
||||||
self.padding = padding.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn divider_padding(mut self, padding: u16) -> Self {
|
|
||||||
self.divider_padding = padding;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_item_padding(mut self, padding: impl Into<Padding>) -> Self {
|
|
||||||
self.list_item_padding = padding.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn into_element(self) -> Element<'a, Message> {
|
|
||||||
crate::widget::column::with_children(self.children)
|
|
||||||
.spacing(self.spacing)
|
|
||||||
.padding(self.padding)
|
|
||||||
.width(iced::Length::Fill)
|
|
||||||
.apply(container)
|
|
||||||
.padding([self.spacing, 0])
|
|
||||||
.class(self.style)
|
|
||||||
.width(iced::Length::Fill)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message: 'static> From<ListColumn<'a, Message>> for Element<'a, Message> {
|
|
||||||
fn from(column: ListColumn<'a, Message>) -> Self {
|
|
||||||
column.into_element()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
213
src/widget/list/list_column.rs
Normal file
213
src/widget/list/list_column.rs
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use crate::widget::container::Catalog;
|
||||||
|
use crate::widget::{button, column, container, divider, row, space::vertical};
|
||||||
|
use crate::{Apply, Element, theme};
|
||||||
|
use iced::{Length, Padding};
|
||||||
|
|
||||||
|
/// A button list item for use in a [`ListColumn`].
|
||||||
|
pub struct ListButton<'a, Message> {
|
||||||
|
content: Element<'a, Message>,
|
||||||
|
on_press: Option<Message>,
|
||||||
|
selected: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a [`ListButton`] with the given content.
|
||||||
|
pub fn button<'a, Message>(content: impl Into<Element<'a, Message>>) -> ListButton<'a, Message> {
|
||||||
|
ListButton {
|
||||||
|
content: content.into(),
|
||||||
|
on_press: None,
|
||||||
|
selected: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: 'static> ListButton<'a, Message> {
|
||||||
|
pub fn on_press(mut self, on_press: Message) -> Self {
|
||||||
|
self.on_press = Some(on_press);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_press_maybe(mut self, on_press: Option<Message>) -> Self {
|
||||||
|
self.on_press = on_press;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selected(mut self, selected: bool) -> Self {
|
||||||
|
self.selected = selected;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ListItem<'a, Message> {
|
||||||
|
Element(Element<'a, Message>),
|
||||||
|
Button(ListButton<'a, Message>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait for types that can be added to a [`ListColumn`].
|
||||||
|
pub trait IntoListItem<'a, Message> {
|
||||||
|
fn into_list_item(self) -> ListItem<'a, Message>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, T> IntoListItem<'a, Message> for T
|
||||||
|
where
|
||||||
|
T: Into<Element<'a, Message>>,
|
||||||
|
{
|
||||||
|
fn into_list_item(self) -> ListItem<'a, Message> {
|
||||||
|
ListItem::Element(self.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message> IntoListItem<'a, Message> for ListButton<'a, Message> {
|
||||||
|
fn into_list_item(self) -> ListItem<'a, Message> {
|
||||||
|
ListItem::Button(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snapshots the padding values at the moment an item is added
|
||||||
|
struct ListEntry<'a, Message> {
|
||||||
|
item: ListItem<'a, Message>,
|
||||||
|
item_padding: Padding,
|
||||||
|
divider_padding: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub struct ListColumn<'a, Message> {
|
||||||
|
list_item_padding: Padding,
|
||||||
|
divider_padding: u16,
|
||||||
|
style: theme::Container<'a>,
|
||||||
|
children: Vec<ListEntry<'a, Message>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> {
|
||||||
|
ListColumn::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_capacity<'a, Message: 'static>(capacity: usize) -> ListColumn<'a, Message> {
|
||||||
|
let cosmic_theme::Spacing {
|
||||||
|
space_xxs, space_m, ..
|
||||||
|
} = theme::spacing();
|
||||||
|
|
||||||
|
ListColumn {
|
||||||
|
list_item_padding: [space_xxs, space_m].into(),
|
||||||
|
divider_padding: 0,
|
||||||
|
style: theme::Container::List,
|
||||||
|
children: Vec::with_capacity(capacity),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message: 'static> Default for ListColumn<'_, Message> {
|
||||||
|
fn default() -> Self {
|
||||||
|
with_capacity(4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: Clone + 'static> ListColumn<'a, Message> {
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a [`ListItem`] to the [`ListColumn`].
|
||||||
|
#[allow(clippy::should_implement_trait)]
|
||||||
|
pub fn add(mut self, item: impl IntoListItem<'a, Message>) -> Self {
|
||||||
|
self.children.push(ListEntry {
|
||||||
|
item: item.into_list_item(),
|
||||||
|
item_padding: self.list_item_padding,
|
||||||
|
divider_padding: self.divider_padding,
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the style variant of this [`ListColumn`].
|
||||||
|
#[inline]
|
||||||
|
pub fn style(mut self, style: <crate::Theme as Catalog>::Class<'a>) -> Self {
|
||||||
|
self.style = style;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_item_padding(mut self, padding: impl Into<Padding>) -> Self {
|
||||||
|
self.list_item_padding = padding.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn divider_padding(mut self, padding: u16) -> Self {
|
||||||
|
self.divider_padding = padding;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn into_element(self) -> Element<'a, Message> {
|
||||||
|
let count = self.children.len();
|
||||||
|
let last_index = count.saturating_sub(1);
|
||||||
|
let radius_s = theme::active().cosmic().radius_s();
|
||||||
|
let mut col = column::with_capacity((2 * count).saturating_sub(1));
|
||||||
|
|
||||||
|
// Ensure minimum height of 32
|
||||||
|
let content_row = |content| {
|
||||||
|
row![container(content), vertical().height(32)].align_y(iced::Alignment::Center)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (
|
||||||
|
i,
|
||||||
|
ListEntry {
|
||||||
|
item,
|
||||||
|
item_padding,
|
||||||
|
divider_padding,
|
||||||
|
},
|
||||||
|
) in self.children.into_iter().enumerate()
|
||||||
|
{
|
||||||
|
if i > 0 {
|
||||||
|
col = col
|
||||||
|
.push(container(divider::horizontal::default()).padding([0, divider_padding]));
|
||||||
|
}
|
||||||
|
|
||||||
|
col = match item {
|
||||||
|
ListItem::Element(content) => col.push(
|
||||||
|
content_row(content)
|
||||||
|
.padding(item_padding)
|
||||||
|
.width(Length::Fill),
|
||||||
|
),
|
||||||
|
ListItem::Button(ListButton {
|
||||||
|
content,
|
||||||
|
on_press,
|
||||||
|
selected,
|
||||||
|
}) => col.push(
|
||||||
|
content_row(content)
|
||||||
|
.apply(button::custom)
|
||||||
|
.padding(item_padding)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.on_press_maybe(on_press)
|
||||||
|
.selected(selected)
|
||||||
|
.class(theme::Button::ListItem(get_radius(
|
||||||
|
radius_s,
|
||||||
|
i == 0,
|
||||||
|
i == last_index,
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
col.width(Length::Fill)
|
||||||
|
.apply(container)
|
||||||
|
.class(self.style)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: Clone + 'static> From<ListColumn<'a, Message>> for Element<'a, Message> {
|
||||||
|
fn from(column: ListColumn<'a, Message>) -> Self {
|
||||||
|
column.into_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_radius(radius: [f32; 4], first: bool, last: bool) -> [f32; 4] {
|
||||||
|
match (first, last) {
|
||||||
|
(true, true) => radius,
|
||||||
|
(true, false) => [radius[0], radius[1], 0.0, 0.0],
|
||||||
|
(false, true) => [0.0, 0.0, radius[2], radius[3]],
|
||||||
|
(false, false) => [0.0, 0.0, 0.0, 0.0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright 2022 System76 <info@system76.com>
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
pub mod column;
|
pub mod list_column;
|
||||||
|
|
||||||
pub use self::column::{ListColumn, list_column};
|
pub use self::list_column::{ListButton, ListColumn, button, list_column};
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use super::{
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "multi-window",
|
feature = "multi-window",
|
||||||
feature = "wayland",
|
feature = "wayland",
|
||||||
|
target_os = "linux",
|
||||||
feature = "winit",
|
feature = "winit",
|
||||||
feature = "surface-message"
|
feature = "surface-message"
|
||||||
))]
|
))]
|
||||||
|
|
@ -195,7 +196,12 @@ pub struct MenuBar<Message> {
|
||||||
menu_roots: Vec<MenuTree<Message>>,
|
menu_roots: Vec<MenuTree<Message>>,
|
||||||
style: <crate::Theme as StyleSheet>::Style,
|
style: <crate::Theme as StyleSheet>::Style,
|
||||||
window_id: window::Id,
|
window_id: window::Id,
|
||||||
#[cfg(all(feature = "multi-window", feature = "wayland", feature = "winit"))]
|
#[cfg(all(
|
||||||
|
feature = "multi-window",
|
||||||
|
feature = "wayland",
|
||||||
|
feature = "winit",
|
||||||
|
target_os = "linux"
|
||||||
|
))]
|
||||||
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
||||||
pub(crate) on_surface_action:
|
pub(crate) on_surface_action:
|
||||||
Option<Arc<dyn Fn(crate::surface::Action) -> Message + Send + Sync + 'static>>,
|
Option<Arc<dyn Fn(crate::surface::Action) -> Message + Send + Sync + 'static>>,
|
||||||
|
|
@ -230,7 +236,12 @@ where
|
||||||
menu_roots,
|
menu_roots,
|
||||||
style: <crate::Theme as StyleSheet>::Style::default(),
|
style: <crate::Theme as StyleSheet>::Style::default(),
|
||||||
window_id: window::Id::NONE,
|
window_id: window::Id::NONE,
|
||||||
#[cfg(all(feature = "multi-window", feature = "wayland", feature = "winit"))]
|
#[cfg(all(
|
||||||
|
feature = "multi-window",
|
||||||
|
feature = "wayland",
|
||||||
|
feature = "winit",
|
||||||
|
target_os = "linux"
|
||||||
|
))]
|
||||||
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(),
|
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(),
|
||||||
on_surface_action: None,
|
on_surface_action: None,
|
||||||
}
|
}
|
||||||
|
|
@ -324,7 +335,12 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "multi-window", feature = "wayland", feature = "winit"))]
|
#[cfg(all(
|
||||||
|
feature = "multi-window",
|
||||||
|
feature = "wayland",
|
||||||
|
feature = "winit",
|
||||||
|
target_os = "linux"
|
||||||
|
))]
|
||||||
pub fn with_positioner(
|
pub fn with_positioner(
|
||||||
mut self,
|
mut self,
|
||||||
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
||||||
|
|
@ -359,6 +375,7 @@ where
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "multi-window",
|
feature = "multi-window",
|
||||||
feature = "wayland",
|
feature = "wayland",
|
||||||
|
target_os = "linux",
|
||||||
feature = "winit",
|
feature = "winit",
|
||||||
feature = "surface-message"
|
feature = "surface-message"
|
||||||
))]
|
))]
|
||||||
|
|
@ -629,6 +646,7 @@ where
|
||||||
state.open = false;
|
state.open = false;
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "wayland",
|
feature = "wayland",
|
||||||
|
target_os = "linux",
|
||||||
feature = "winit",
|
feature = "winit",
|
||||||
feature = "surface-message"
|
feature = "surface-message"
|
||||||
))]
|
))]
|
||||||
|
|
@ -652,6 +670,7 @@ where
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "multi-window",
|
feature = "multi-window",
|
||||||
feature = "wayland",
|
feature = "wayland",
|
||||||
|
target_os = "linux",
|
||||||
feature = "winit",
|
feature = "winit",
|
||||||
feature = "surface-message"
|
feature = "surface-message"
|
||||||
))]
|
))]
|
||||||
|
|
@ -666,6 +685,7 @@ where
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "multi-window",
|
feature = "multi-window",
|
||||||
feature = "wayland",
|
feature = "wayland",
|
||||||
|
target_os = "linux",
|
||||||
feature = "winit",
|
feature = "winit",
|
||||||
feature = "surface-message"
|
feature = "surface-message"
|
||||||
))]
|
))]
|
||||||
|
|
@ -748,6 +768,7 @@ where
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "multi-window",
|
feature = "multi-window",
|
||||||
feature = "wayland",
|
feature = "wayland",
|
||||||
|
target_os = "linux",
|
||||||
feature = "winit",
|
feature = "winit",
|
||||||
feature = "surface-message"
|
feature = "surface-message"
|
||||||
))]
|
))]
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use super::{menu_bar::MenuBarState, menu_tree::MenuTree};
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "multi-window",
|
feature = "multi-window",
|
||||||
feature = "wayland",
|
feature = "wayland",
|
||||||
|
target_os = "linux",
|
||||||
feature = "winit",
|
feature = "winit",
|
||||||
feature = "surface-message"
|
feature = "surface-message"
|
||||||
))]
|
))]
|
||||||
|
|
@ -680,6 +681,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "multi-window",
|
feature = "multi-window",
|
||||||
feature = "wayland",
|
feature = "wayland",
|
||||||
|
target_os = "linux",
|
||||||
feature = "winit",
|
feature = "winit",
|
||||||
feature = "surface-message"
|
feature = "surface-message"
|
||||||
))]
|
))]
|
||||||
|
|
@ -765,7 +767,13 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
||||||
PathHighlight::OmitActive => {
|
PathHighlight::OmitActive => {
|
||||||
!indices.is_empty() && i < indices.len() - 1
|
!indices.is_empty() && i < indices.len() - 1
|
||||||
}
|
}
|
||||||
PathHighlight::MenuActive => self.depth == state.active_root.len() - 1,
|
PathHighlight::MenuActive => {
|
||||||
|
!indices.is_empty()
|
||||||
|
&& i < indices.len()
|
||||||
|
&& menu_roots.len() > indices[i]
|
||||||
|
&& (i < indices.len() - 1
|
||||||
|
|| !menu_roots[indices[i]].children.is_empty())
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// react only to the last menu
|
// react only to the last menu
|
||||||
|
|
@ -960,7 +968,8 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
|
||||||
feature = "multi-window",
|
feature = "multi-window",
|
||||||
feature = "wayland",
|
feature = "wayland",
|
||||||
feature = "winit",
|
feature = "winit",
|
||||||
feature = "surface-message"
|
feature = "surface-message",
|
||||||
|
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
|
||||||
|
|
@ -1220,6 +1229,7 @@ pub(crate) fn init_root_menu<Message: Clone>(
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "multi-window",
|
feature = "multi-window",
|
||||||
feature = "wayland",
|
feature = "wayland",
|
||||||
|
target_os = "linux",
|
||||||
feature = "winit",
|
feature = "winit",
|
||||||
feature = "surface-message"
|
feature = "surface-message"
|
||||||
))]
|
))]
|
||||||
|
|
@ -1517,7 +1527,7 @@ where
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty());
|
.is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty());
|
||||||
|
|
||||||
#[cfg(all(feature = "multi-window", feature = "wayland", feature = "winit", feature = "surface-message"))]
|
#[cfg(all(feature = "multi-window", feature = "wayland",target_os = "linux", feature = "winit", feature = "surface-message"))]
|
||||||
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove {
|
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove {
|
||||||
if let Some(id) = state.popup_id.remove(&menu.window_id) {
|
if let Some(id) = state.popup_id.remove(&menu.window_id) {
|
||||||
state.active_root.truncate(menu.depth + 1);
|
state.active_root.truncate(menu.depth + 1);
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,11 @@ use std::rc::Rc;
|
||||||
use iced::advanced::widget::text::Style as TextStyle;
|
use iced::advanced::widget::text::Style as TextStyle;
|
||||||
use iced_widget::core::{Element, renderer};
|
use iced_widget::core::{Element, renderer};
|
||||||
|
|
||||||
use crate::iced_core::{Alignment, Length};
|
|
||||||
use crate::widget::menu::action::MenuAction;
|
use crate::widget::menu::action::MenuAction;
|
||||||
use crate::widget::menu::key_bind::KeyBind;
|
use crate::widget::menu::key_bind::KeyBind;
|
||||||
use crate::widget::{Button, RcElementWrapper, icon};
|
use crate::widget::{Button, RcElementWrapper, icon};
|
||||||
use crate::{theme, widget};
|
use crate::{theme, widget};
|
||||||
|
use iced_core::{Alignment, Length};
|
||||||
|
|
||||||
/// Nested menu is essentially a tree of items, a menu is a collection of items
|
/// Nested menu is essentially a tree of items, a menu is a collection of items
|
||||||
/// a menu itself can also be an item of another menu.
|
/// a menu itself can also be an item of another menu.
|
||||||
|
|
@ -252,9 +252,18 @@ pub fn menu_items<
|
||||||
let l: Cow<'static, str> = label.into();
|
let l: Cow<'static, str> = label.into();
|
||||||
let key = find_key(&action, key_binds);
|
let key = find_key(&action, key_binds);
|
||||||
let mut items = vec![
|
let mut items = vec![
|
||||||
widget::text(l).into(),
|
widget::text(l)
|
||||||
|
.ellipsize(iced_core::text::Ellipsize::Middle(
|
||||||
|
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
||||||
|
))
|
||||||
|
.into(),
|
||||||
widget::space::horizontal().into(),
|
widget::space::horizontal().into(),
|
||||||
widget::text(key).class(key_class).into(),
|
widget::text(key)
|
||||||
|
.class(key_class)
|
||||||
|
.ellipsize(iced_core::text::Ellipsize::Middle(
|
||||||
|
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
||||||
|
))
|
||||||
|
.into(),
|
||||||
];
|
];
|
||||||
|
|
||||||
if let Some(icon) = icon {
|
if let Some(icon) = icon {
|
||||||
|
|
@ -275,9 +284,18 @@ pub fn menu_items<
|
||||||
let key = find_key(&action, key_binds);
|
let key = find_key(&action, key_binds);
|
||||||
|
|
||||||
let mut items = vec![
|
let mut items = vec![
|
||||||
widget::text(l).into(),
|
widget::text(l)
|
||||||
|
.ellipsize(iced_core::text::Ellipsize::Middle(
|
||||||
|
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
||||||
|
))
|
||||||
|
.into(),
|
||||||
widget::space::horizontal().into(),
|
widget::space::horizontal().into(),
|
||||||
widget::text(key).class(key_class).into(),
|
widget::text(key)
|
||||||
|
.ellipsize(iced_core::text::Ellipsize::Middle(
|
||||||
|
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
||||||
|
))
|
||||||
|
.class(key_class)
|
||||||
|
.into(),
|
||||||
];
|
];
|
||||||
|
|
||||||
if let Some(icon) = icon {
|
if let Some(icon) = icon {
|
||||||
|
|
@ -312,9 +330,19 @@ pub fn menu_items<
|
||||||
.into()
|
.into()
|
||||||
},
|
},
|
||||||
widget::space::horizontal().width(spacing.space_xxs).into(),
|
widget::space::horizontal().width(spacing.space_xxs).into(),
|
||||||
widget::text(label).align_x(iced::Alignment::Start).into(),
|
widget::text(label)
|
||||||
|
.ellipsize(iced_core::text::Ellipsize::Middle(
|
||||||
|
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
||||||
|
))
|
||||||
|
.align_x(iced::Alignment::Start)
|
||||||
|
.into(),
|
||||||
widget::space::horizontal().into(),
|
widget::space::horizontal().into(),
|
||||||
widget::text(key).class(key_class).into(),
|
widget::text(key)
|
||||||
|
.class(key_class)
|
||||||
|
.ellipsize(iced_core::text::Ellipsize::Middle(
|
||||||
|
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
||||||
|
))
|
||||||
|
.into(),
|
||||||
];
|
];
|
||||||
|
|
||||||
if let Some(icon) = icon {
|
if let Some(icon) = icon {
|
||||||
|
|
@ -335,7 +363,11 @@ pub fn menu_items<
|
||||||
trees.push(MenuTree::<Message>::with_children(
|
trees.push(MenuTree::<Message>::with_children(
|
||||||
RcElementWrapper::new(crate::Element::from(
|
RcElementWrapper::new(crate::Element::from(
|
||||||
menu_button::<'static, _>(vec![
|
menu_button::<'static, _>(vec![
|
||||||
widget::text(l.clone()).into(),
|
widget::text(l.clone())
|
||||||
|
.ellipsize(iced_core::text::Ellipsize::Middle(
|
||||||
|
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
||||||
|
))
|
||||||
|
.into(),
|
||||||
widget::space::horizontal().into(),
|
widget::space::horizontal().into(),
|
||||||
widget::icon::from_name("pan-end-symbolic")
|
widget::icon::from_name("pan-end-symbolic")
|
||||||
.size(16)
|
.size(16)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
//! .on_press(Message::LaunchUrl(REPOSITORY))
|
//! .on_press(Message::LaunchUrl(REPOSITORY))
|
||||||
//! .padding(0);
|
//! .padding(0);
|
||||||
//!
|
//!
|
||||||
//! let content = widget::column()
|
//! let content = widget::column::with_capacity(3)
|
||||||
//! .push(widget::icon::from_name("my-app-icon"))
|
//! .push(widget::icon::from_name("my-app-icon"))
|
||||||
//! .push(widget::text::title3("My App Name"))
|
//! .push(widget::text::title3("My App Name"))
|
||||||
//! .push(link)
|
//! .push(link)
|
||||||
|
|
@ -53,6 +53,9 @@ pub use iced::widget::{Canvas, canvas};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use iced::widget::{Checkbox, checkbox};
|
pub use iced::widget::{Checkbox, checkbox};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use iced::widget::{Column, column};
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use iced::widget::{ComboBox, combo_box};
|
pub use iced::widget::{ComboBox, combo_box};
|
||||||
|
|
||||||
|
|
@ -75,10 +78,10 @@ pub use iced::widget::{MouseArea, mouse_area};
|
||||||
pub use iced::widget::{PaneGrid, pane_grid};
|
pub use iced::widget::{PaneGrid, pane_grid};
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use iced::widget::{ProgressBar, progress_bar};
|
pub use iced::widget::{Responsive, responsive};
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use iced::widget::{Responsive, responsive};
|
pub use iced::widget::{Row, row};
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use iced::widget::{Slider, VerticalSlider, slider, vertical_slider};
|
pub use iced::widget::{Slider, VerticalSlider, slider, vertical_slider};
|
||||||
|
|
@ -135,34 +138,6 @@ pub mod context_drawer;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use context_drawer::{ContextDrawer, context_drawer};
|
pub use context_drawer::{ContextDrawer, context_drawer};
|
||||||
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use column::{Column, column};
|
|
||||||
pub mod column {
|
|
||||||
//! A container which aligns its children in a column.
|
|
||||||
|
|
||||||
pub type Column<'a, Message> = iced::widget::Column<'a, Message, crate::Theme, crate::Renderer>;
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
/// A container which aligns its children in a column.
|
|
||||||
pub fn column<'a, Message>() -> Column<'a, Message> {
|
|
||||||
Column::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
/// A pre-allocated [`column`].
|
|
||||||
pub fn with_capacity<'a, Message>(capacity: usize) -> Column<'a, Message> {
|
|
||||||
Column::with_capacity(capacity)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
/// A [`column`] that will be assigned an [`Iterator`] of children.
|
|
||||||
pub fn with_children<'a, Message>(
|
|
||||||
children: impl IntoIterator<Item = crate::Element<'a, Message>>,
|
|
||||||
) -> Column<'a, Message> {
|
|
||||||
Column::with_children(children)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod layer_container;
|
pub mod layer_container;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use layer_container::{LayerContainer, layer_container};
|
pub use layer_container::{LayerContainer, layer_container};
|
||||||
|
|
@ -279,6 +254,13 @@ pub mod popover;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use popover::{Popover, popover};
|
pub use popover::{Popover, popover};
|
||||||
|
|
||||||
|
pub mod progress_bar;
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use progress_bar::{
|
||||||
|
circular, circular::Circular, determinate_circular, determinate_linear, indeterminate_circular,
|
||||||
|
indeterminate_linear, linear, linear::Linear, style,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod radio;
|
pub mod radio;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use radio::{Radio, radio};
|
pub use radio::{Radio, radio};
|
||||||
|
|
@ -287,35 +269,6 @@ pub mod rectangle_tracker;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use rectangle_tracker::{RectangleTracker, rectangle_tracking_container};
|
pub use rectangle_tracker::{RectangleTracker, rectangle_tracking_container};
|
||||||
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use row::{Row, row};
|
|
||||||
|
|
||||||
pub mod row {
|
|
||||||
//! A container which aligns its children in a row.
|
|
||||||
|
|
||||||
pub type Row<'a, Message> = iced::widget::Row<'a, Message, crate::Theme, crate::Renderer>;
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
/// A container which aligns its children in a row.
|
|
||||||
pub fn row<'a, Message>() -> Row<'a, Message> {
|
|
||||||
Row::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
/// A pre-allocated [`row`].
|
|
||||||
pub fn with_capacity<'a, Message>(capacity: usize) -> Row<'a, Message> {
|
|
||||||
Row::with_capacity(capacity)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
/// A [`row`] that will be assigned an [`Iterator`] of children.
|
|
||||||
pub fn with_children<'a, Message>(
|
|
||||||
children: impl IntoIterator<Item = crate::Element<'a, Message>>,
|
|
||||||
) -> Row<'a, Message> {
|
|
||||||
Row::with_children(children)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod scrollable;
|
pub mod scrollable;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use scrollable::scrollable;
|
pub use scrollable::scrollable;
|
||||||
|
|
@ -355,7 +308,7 @@ pub use toggler::{Toggler, toggler};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use tooltip::{Tooltip, tooltip};
|
pub use tooltip::{Tooltip, tooltip};
|
||||||
|
|
||||||
#[cfg(all(feature = "wayland", feature = "winit"))]
|
#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))]
|
||||||
pub mod wayland;
|
pub mod wayland;
|
||||||
|
|
||||||
pub mod tooltip {
|
pub mod tooltip {
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,10 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
operation: &mut dyn Operation,
|
operation: &mut dyn Operation,
|
||||||
) {
|
) {
|
||||||
|
// Skip operating on background content, prevents Tab from escaping
|
||||||
|
if self.modal && self.popup.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.content
|
self.content
|
||||||
.as_widget_mut()
|
.as_widget_mut()
|
||||||
.operate(content_tree_mut(tree), layout, renderer, operation);
|
.operate(content_tree_mut(tree), layout, renderer, operation);
|
||||||
|
|
@ -172,11 +176,17 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide cursor from background content when modal popup is active
|
||||||
|
let cursor = if self.modal && self.popup.is_some() {
|
||||||
|
mouse::Cursor::Unavailable
|
||||||
|
} else {
|
||||||
|
cursor_position
|
||||||
|
};
|
||||||
self.content.as_widget_mut().update(
|
self.content.as_widget_mut().update(
|
||||||
&mut tree.children[0],
|
&mut tree.children[0],
|
||||||
event,
|
event,
|
||||||
layout,
|
layout,
|
||||||
cursor_position,
|
cursor,
|
||||||
renderer,
|
renderer,
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
|
|
@ -214,13 +224,19 @@ where
|
||||||
cursor_position: mouse::Cursor,
|
cursor_position: mouse::Cursor,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
|
// Hide cursor from background content when a modal popup is active
|
||||||
|
let cursor = if self.modal && self.popup.is_some() {
|
||||||
|
mouse::Cursor::Unavailable
|
||||||
|
} else {
|
||||||
|
cursor_position
|
||||||
|
};
|
||||||
self.content.as_widget().draw(
|
self.content.as_widget().draw(
|
||||||
content_tree(tree),
|
content_tree(tree),
|
||||||
renderer,
|
renderer,
|
||||||
theme,
|
theme,
|
||||||
renderer_style,
|
renderer_style,
|
||||||
layout,
|
layout,
|
||||||
cursor_position,
|
cursor,
|
||||||
viewport,
|
viewport,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
462
src/widget/progress_bar/circular.rs
Normal file
462
src/widget/progress_bar/circular.rs
Normal file
|
|
@ -0,0 +1,462 @@
|
||||||
|
//! Show a circular progress indicator.
|
||||||
|
use super::style::StyleSheet;
|
||||||
|
use crate::anim::smootherstep;
|
||||||
|
use iced::advanced::layout;
|
||||||
|
use iced::advanced::renderer;
|
||||||
|
use iced::advanced::widget::tree::{self, Tree};
|
||||||
|
use iced::advanced::{self, Clipboard, Layout, Shell, Widget};
|
||||||
|
use iced::mouse;
|
||||||
|
use iced::time::Instant;
|
||||||
|
use iced::widget::canvas;
|
||||||
|
use iced::window;
|
||||||
|
use iced::{Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector};
|
||||||
|
|
||||||
|
use std::f32::consts::PI;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
const MIN_ANGLE: Radians = Radians(PI / 8.0);
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub struct Circular<Theme>
|
||||||
|
where
|
||||||
|
Theme: StyleSheet,
|
||||||
|
{
|
||||||
|
size: f32,
|
||||||
|
bar_height: f32,
|
||||||
|
style: <Theme as StyleSheet>::Style,
|
||||||
|
cycle_duration: Duration,
|
||||||
|
rotation_duration: Duration,
|
||||||
|
progress: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Theme> Circular<Theme>
|
||||||
|
where
|
||||||
|
Theme: StyleSheet,
|
||||||
|
{
|
||||||
|
/// Creates a new [`Circular`] with the given content.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Circular {
|
||||||
|
size: 40.0,
|
||||||
|
bar_height: 4.0,
|
||||||
|
style: <Theme as StyleSheet>::Style::default(),
|
||||||
|
cycle_duration: Duration::from_millis(1500),
|
||||||
|
rotation_duration: Duration::from_secs(2),
|
||||||
|
progress: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the size of the [`Circular`].
|
||||||
|
pub fn size(mut self, size: f32) -> Self {
|
||||||
|
self.size = size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the bar height of the [`Circular`].
|
||||||
|
pub fn bar_height(mut self, bar_height: f32) -> Self {
|
||||||
|
self.bar_height = bar_height;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the style variant of this [`Circular`].
|
||||||
|
pub fn style(mut self, style: <Theme as StyleSheet>::Style) -> Self {
|
||||||
|
self.style = style;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the cycle duration of this [`Circular`].
|
||||||
|
pub fn cycle_duration(mut self, duration: Duration) -> Self {
|
||||||
|
self.cycle_duration = duration / 2;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the base rotation duration of this [`Circular`]. This is the duration that a full
|
||||||
|
/// rotation would take if the cycle rotation were set to 0.0 (no expanding or contracting)
|
||||||
|
pub fn rotation_duration(mut self, duration: Duration) -> Self {
|
||||||
|
self.rotation_duration = duration;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override the default behavior by providing a determinate progress value between `0.0` and `1.0`.
|
||||||
|
pub fn progress(mut self, progress: f32) -> Self {
|
||||||
|
self.progress = Some(progress.clamp(0.0, 1.0));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn min_wrap_angle(&self, track_radius: f32) -> (f32, f32) {
|
||||||
|
let cap_angle = self.bar_height / track_radius;
|
||||||
|
let gap = MIN_ANGLE.0.max(cap_angle);
|
||||||
|
(gap - cap_angle, 2.0 * PI - gap * 2.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Theme> Default for Circular<Theme>
|
||||||
|
where
|
||||||
|
Theme: StyleSheet,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum Animation {
|
||||||
|
Expanding {
|
||||||
|
start: Instant,
|
||||||
|
progress: f32,
|
||||||
|
rotation: u32,
|
||||||
|
last: Instant,
|
||||||
|
},
|
||||||
|
Contracting {
|
||||||
|
start: Instant,
|
||||||
|
progress: f32,
|
||||||
|
rotation: u32,
|
||||||
|
last: Instant,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Animation {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Expanding {
|
||||||
|
start: Instant::now(),
|
||||||
|
progress: 0.0,
|
||||||
|
rotation: 0,
|
||||||
|
last: Instant::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Animation {
|
||||||
|
fn next(&self, additional_rotation: u32, wrap_angle: f32, now: Instant) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Expanding { rotation, .. } => Self::Contracting {
|
||||||
|
start: now,
|
||||||
|
progress: 0.0,
|
||||||
|
rotation: rotation.wrapping_add(additional_rotation),
|
||||||
|
last: now,
|
||||||
|
},
|
||||||
|
Self::Contracting { rotation, .. } => Self::Expanding {
|
||||||
|
start: now,
|
||||||
|
progress: 0.0,
|
||||||
|
rotation: rotation.wrapping_add(
|
||||||
|
(f64::from((wrap_angle) / (2.0 * PI)) * f64::from(u32::MAX)) as u32,
|
||||||
|
),
|
||||||
|
last: now,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(&self) -> Instant {
|
||||||
|
match self {
|
||||||
|
Self::Expanding { start, .. } | Self::Contracting { start, .. } => *start,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last(&self) -> Instant {
|
||||||
|
match self {
|
||||||
|
Self::Expanding { last, .. } | Self::Contracting { last, .. } => *last,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timed_transition(
|
||||||
|
&self,
|
||||||
|
cycle_duration: Duration,
|
||||||
|
rotation_duration: Duration,
|
||||||
|
wrap_angle: f32,
|
||||||
|
now: Instant,
|
||||||
|
) -> Self {
|
||||||
|
let elapsed = now.duration_since(self.start());
|
||||||
|
let additional_rotation = ((now - self.last()).as_secs_f32()
|
||||||
|
/ rotation_duration.as_secs_f32()
|
||||||
|
* (u32::MAX) as f32) as u32;
|
||||||
|
|
||||||
|
match elapsed {
|
||||||
|
elapsed if elapsed > cycle_duration => self.next(additional_rotation, wrap_angle, now),
|
||||||
|
_ => self.with_elapsed(cycle_duration, additional_rotation, elapsed, now),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_elapsed(
|
||||||
|
&self,
|
||||||
|
cycle_duration: Duration,
|
||||||
|
additional_rotation: u32,
|
||||||
|
elapsed: Duration,
|
||||||
|
now: Instant,
|
||||||
|
) -> Self {
|
||||||
|
let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32();
|
||||||
|
match self {
|
||||||
|
Self::Expanding {
|
||||||
|
start, rotation, ..
|
||||||
|
} => Self::Expanding {
|
||||||
|
start: *start,
|
||||||
|
progress,
|
||||||
|
rotation: rotation.wrapping_add(additional_rotation),
|
||||||
|
last: now,
|
||||||
|
},
|
||||||
|
Self::Contracting {
|
||||||
|
start, rotation, ..
|
||||||
|
} => Self::Contracting {
|
||||||
|
start: *start,
|
||||||
|
progress,
|
||||||
|
rotation: rotation.wrapping_add(additional_rotation),
|
||||||
|
last: now,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotation(&self) -> f32 {
|
||||||
|
match self {
|
||||||
|
Self::Expanding { rotation, .. } | Self::Contracting { rotation, .. } => {
|
||||||
|
*rotation as f32 / u32::MAX as f32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct State {
|
||||||
|
animation: Animation,
|
||||||
|
cache: canvas::Cache,
|
||||||
|
progress: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message, Theme> Widget<Message, Theme, Renderer> for Circular<Theme>
|
||||||
|
where
|
||||||
|
Message: Clone,
|
||||||
|
Theme: StyleSheet,
|
||||||
|
{
|
||||||
|
fn tag(&self) -> tree::Tag {
|
||||||
|
tree::Tag::of::<State>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> tree::State {
|
||||||
|
tree::State::new(State::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> Size<Length> {
|
||||||
|
Size {
|
||||||
|
width: Length::Fixed(self.size),
|
||||||
|
height: Length::Fixed(self.size),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&mut self,
|
||||||
|
_tree: &mut Tree,
|
||||||
|
_renderer: &Renderer,
|
||||||
|
limits: &layout::Limits,
|
||||||
|
) -> layout::Node {
|
||||||
|
layout::atomic(limits, self.size, self.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
event: &Event,
|
||||||
|
_layout: Layout<'_>,
|
||||||
|
_cursor: mouse::Cursor,
|
||||||
|
_renderer: &Renderer,
|
||||||
|
_clipboard: &mut dyn Clipboard,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
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 {
|
||||||
|
let (_, wrap_angle) = self.min_wrap_angle(self.size / 2.0 - self.bar_height);
|
||||||
|
state.animation = state.animation.timed_transition(
|
||||||
|
self.cycle_duration,
|
||||||
|
self.rotation_duration,
|
||||||
|
wrap_angle,
|
||||||
|
*now,
|
||||||
|
);
|
||||||
|
|
||||||
|
state.cache.clear();
|
||||||
|
shell.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
theme: &Theme,
|
||||||
|
_style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
_cursor: mouse::Cursor,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
use advanced::Renderer as _;
|
||||||
|
|
||||||
|
let state = tree.state.downcast_ref::<State>();
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let custom_style =
|
||||||
|
<Theme as StyleSheet>::appearance(theme, &self.style, self.progress.is_some(), true);
|
||||||
|
|
||||||
|
let geometry = state.cache.draw(renderer, bounds.size(), |frame| {
|
||||||
|
let track_radius = frame.width() / 2.0 - self.bar_height;
|
||||||
|
let track_path = canvas::Path::circle(frame.center(), track_radius);
|
||||||
|
|
||||||
|
frame.stroke(
|
||||||
|
&track_path,
|
||||||
|
canvas::Stroke::default()
|
||||||
|
.with_color(custom_style.track_color)
|
||||||
|
.with_width(self.bar_height),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(progress) = self.progress {
|
||||||
|
// outer border
|
||||||
|
if let Some(border_color) = custom_style.border_color {
|
||||||
|
let border_path =
|
||||||
|
canvas::Path::circle(frame.center(), track_radius + self.bar_height / 2.0);
|
||||||
|
|
||||||
|
frame.stroke(
|
||||||
|
&border_path,
|
||||||
|
canvas::Stroke::default()
|
||||||
|
.with_color(border_color)
|
||||||
|
.with_width(1.0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// inner border
|
||||||
|
if let Some(border_color) = custom_style.border_color {
|
||||||
|
let border_path =
|
||||||
|
canvas::Path::circle(frame.center(), track_radius - self.bar_height / 2.0);
|
||||||
|
|
||||||
|
frame.stroke(
|
||||||
|
&border_path,
|
||||||
|
canvas::Stroke::default()
|
||||||
|
.with_color(border_color)
|
||||||
|
.with_width(1.0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// bar
|
||||||
|
let mut builder = canvas::path::Builder::new();
|
||||||
|
|
||||||
|
builder.arc(canvas::path::Arc {
|
||||||
|
center: frame.center(),
|
||||||
|
radius: track_radius,
|
||||||
|
start_angle: Radians(-PI / 2.0),
|
||||||
|
end_angle: Radians(-PI / 2.0 + progress * 2.0 * PI),
|
||||||
|
});
|
||||||
|
|
||||||
|
let bar_path = builder.build();
|
||||||
|
|
||||||
|
frame.stroke(
|
||||||
|
&bar_path,
|
||||||
|
canvas::Stroke::default()
|
||||||
|
.with_color(custom_style.bar_color)
|
||||||
|
.with_width(self.bar_height),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut builder = canvas::path::Builder::new();
|
||||||
|
|
||||||
|
// get center of end of arc for rounded cap
|
||||||
|
let end_angle = -PI / 2.0 + progress * 2.0 * PI;
|
||||||
|
let end_center =
|
||||||
|
frame.center() + Vector::new(end_angle.cos(), end_angle.sin()) * track_radius;
|
||||||
|
builder.arc(canvas::path::Arc {
|
||||||
|
center: end_center,
|
||||||
|
radius: self.bar_height / 2.0,
|
||||||
|
start_angle: Radians(end_angle),
|
||||||
|
end_angle: Radians(end_angle + PI),
|
||||||
|
});
|
||||||
|
|
||||||
|
// get center of start of arc for rounded cap
|
||||||
|
let start_angle = -PI / 2.0;
|
||||||
|
let start_center = frame.center()
|
||||||
|
+ Vector::new(start_angle.cos(), start_angle.sin()) * track_radius;
|
||||||
|
builder.arc(canvas::path::Arc {
|
||||||
|
center: start_center,
|
||||||
|
radius: self.bar_height / 2.0,
|
||||||
|
start_angle: Radians(start_angle - PI),
|
||||||
|
end_angle: Radians(start_angle),
|
||||||
|
});
|
||||||
|
|
||||||
|
let cap_path = builder.build();
|
||||||
|
frame.fill(&cap_path, custom_style.bar_color);
|
||||||
|
} else {
|
||||||
|
let mut builder = canvas::path::Builder::new();
|
||||||
|
|
||||||
|
let start = state.animation.rotation() * 2.0 * PI;
|
||||||
|
let (min_angle, wrap_angle) = self.min_wrap_angle(track_radius);
|
||||||
|
let (start_angle, end_angle) = match state.animation {
|
||||||
|
Animation::Expanding { progress, .. } => (
|
||||||
|
start,
|
||||||
|
start + min_angle + wrap_angle * smootherstep(progress),
|
||||||
|
),
|
||||||
|
Animation::Contracting { progress, .. } => (
|
||||||
|
start + wrap_angle * smootherstep(progress),
|
||||||
|
start + min_angle + wrap_angle,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
builder.arc(canvas::path::Arc {
|
||||||
|
center: frame.center(),
|
||||||
|
radius: track_radius,
|
||||||
|
start_angle: Radians(start_angle),
|
||||||
|
end_angle: Radians(end_angle),
|
||||||
|
});
|
||||||
|
|
||||||
|
let bar_path = builder.build();
|
||||||
|
|
||||||
|
frame.stroke(
|
||||||
|
&bar_path,
|
||||||
|
canvas::Stroke::default()
|
||||||
|
.with_color(custom_style.bar_color)
|
||||||
|
.with_width(self.bar_height),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut builder = canvas::path::Builder::new();
|
||||||
|
|
||||||
|
// get center of end of arc for rounded cap
|
||||||
|
let end_center =
|
||||||
|
frame.center() + Vector::new(end_angle.cos(), end_angle.sin()) * track_radius;
|
||||||
|
builder.arc(canvas::path::Arc {
|
||||||
|
center: end_center,
|
||||||
|
radius: self.bar_height / 2.0,
|
||||||
|
start_angle: Radians(end_angle),
|
||||||
|
end_angle: Radians(end_angle + PI),
|
||||||
|
});
|
||||||
|
|
||||||
|
// get center of start of arc for rounded cap
|
||||||
|
let start_center = frame.center()
|
||||||
|
+ Vector::new(start_angle.cos(), start_angle.sin()) * track_radius;
|
||||||
|
builder.arc(canvas::path::Arc {
|
||||||
|
center: start_center,
|
||||||
|
radius: self.bar_height / 2.0,
|
||||||
|
start_angle: Radians(start_angle - PI),
|
||||||
|
end_angle: Radians(start_angle),
|
||||||
|
});
|
||||||
|
|
||||||
|
let cap_path = builder.build();
|
||||||
|
frame.fill(&cap_path, custom_style.bar_color);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
renderer.with_translation(Vector::new(bounds.x, bounds.y), |renderer| {
|
||||||
|
use iced::advanced::graphics::geometry::Renderer as _;
|
||||||
|
|
||||||
|
renderer.draw_geometry(geometry);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Theme> From<Circular<Theme>> for Element<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
Message: Clone + 'a,
|
||||||
|
Theme: StyleSheet + 'a,
|
||||||
|
{
|
||||||
|
fn from(circular: Circular<Theme>) -> Self {
|
||||||
|
Self::new(circular)
|
||||||
|
}
|
||||||
|
}
|
||||||
306
src/widget/progress_bar/linear.rs
Normal file
306
src/widget/progress_bar/linear.rs
Normal file
|
|
@ -0,0 +1,306 @@
|
||||||
|
//! Show a linear progress indicator.
|
||||||
|
use iced::advanced::layout;
|
||||||
|
use iced::advanced::renderer::{self, Quad};
|
||||||
|
use iced::advanced::widget::tree::{self, Tree};
|
||||||
|
use iced::advanced::{self, Clipboard, Layout, Shell, Widget};
|
||||||
|
use iced::mouse;
|
||||||
|
use iced::time::Instant;
|
||||||
|
use iced::window;
|
||||||
|
use iced::{Background, Element, Event, Length, Rectangle, Size};
|
||||||
|
|
||||||
|
use crate::anim::smootherstep;
|
||||||
|
|
||||||
|
use super::style::StyleSheet;
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub struct Linear<Theme>
|
||||||
|
where
|
||||||
|
Theme: StyleSheet,
|
||||||
|
{
|
||||||
|
width: Length,
|
||||||
|
girth: Length,
|
||||||
|
style: Theme::Style,
|
||||||
|
cycle_duration: Duration,
|
||||||
|
progress: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Theme> Linear<Theme>
|
||||||
|
where
|
||||||
|
Theme: StyleSheet,
|
||||||
|
{
|
||||||
|
/// Creates a new [`Linear`] with the given content.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Linear {
|
||||||
|
width: Length::Fixed(100.0),
|
||||||
|
girth: Length::Fixed(4.0),
|
||||||
|
style: Theme::Style::default(),
|
||||||
|
cycle_duration: Duration::from_millis(1500),
|
||||||
|
progress: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the width of the [`Linear`].
|
||||||
|
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||||
|
self.width = width.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the girth of the [`Linear`].
|
||||||
|
pub fn girth(mut self, girth: impl Into<Length>) -> Self {
|
||||||
|
self.girth = girth.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the style variant of this [`Linear`].
|
||||||
|
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
|
||||||
|
self.style = style.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the cycle duration of this [`Linear`].
|
||||||
|
pub fn cycle_duration(mut self, duration: Duration) -> Self {
|
||||||
|
self.cycle_duration = duration / 2;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override the default behavior by providing a determinate progress value between `0.0` and `1.0`.
|
||||||
|
pub fn progress(mut self, progress: f32) -> Self {
|
||||||
|
self.progress = Some(progress.clamp(0.0, 1.0));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Theme> Default for Linear<Theme>
|
||||||
|
where
|
||||||
|
Theme: StyleSheet,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum State {
|
||||||
|
Expanding { start: Instant, progress: f32 },
|
||||||
|
Contracting { start: Instant, progress: f32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for State {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Expanding {
|
||||||
|
start: Instant::now(),
|
||||||
|
progress: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
fn next(&self, now: Instant) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Expanding { .. } => Self::Contracting {
|
||||||
|
start: now,
|
||||||
|
progress: 0.0,
|
||||||
|
},
|
||||||
|
Self::Contracting { .. } => Self::Expanding {
|
||||||
|
start: now,
|
||||||
|
progress: 0.0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(&self) -> Instant {
|
||||||
|
match self {
|
||||||
|
Self::Expanding { start, .. } | Self::Contracting { start, .. } => *start,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timed_transition(&self, cycle_duration: Duration, now: Instant) -> Self {
|
||||||
|
let elapsed = now.duration_since(self.start());
|
||||||
|
|
||||||
|
match elapsed {
|
||||||
|
elapsed if elapsed > cycle_duration => self.next(now),
|
||||||
|
_ => self.with_elapsed(cycle_duration, elapsed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_elapsed(&self, cycle_duration: Duration, elapsed: Duration) -> Self {
|
||||||
|
let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32();
|
||||||
|
match self {
|
||||||
|
Self::Expanding { start, .. } => Self::Expanding {
|
||||||
|
start: *start,
|
||||||
|
progress,
|
||||||
|
},
|
||||||
|
Self::Contracting { start, .. } => Self::Contracting {
|
||||||
|
start: *start,
|
||||||
|
progress,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Linear<Theme>
|
||||||
|
where
|
||||||
|
Message: Clone,
|
||||||
|
Theme: StyleSheet,
|
||||||
|
Renderer: advanced::Renderer,
|
||||||
|
{
|
||||||
|
fn tag(&self) -> tree::Tag {
|
||||||
|
tree::Tag::of::<State>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> tree::State {
|
||||||
|
tree::State::new(State::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> Size<Length> {
|
||||||
|
Size {
|
||||||
|
width: self.width,
|
||||||
|
height: self.girth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&mut self,
|
||||||
|
_tree: &mut Tree,
|
||||||
|
_renderer: &Renderer,
|
||||||
|
limits: &layout::Limits,
|
||||||
|
) -> layout::Node {
|
||||||
|
layout::atomic(limits, self.width, self.girth)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
event: &Event,
|
||||||
|
_layout: Layout<'_>,
|
||||||
|
_cursor: mouse::Cursor,
|
||||||
|
_renderer: &Renderer,
|
||||||
|
_clipboard: &mut dyn Clipboard,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
if self.progress.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
|
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
||||||
|
*state = state.timed_transition(self.cycle_duration, *now);
|
||||||
|
|
||||||
|
shell.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
theme: &Theme,
|
||||||
|
_style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
_cursor: mouse::Cursor,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let custom_style = theme.appearance(&self.style, self.progress.is_some(), false);
|
||||||
|
let state = tree.state.downcast_ref::<State>();
|
||||||
|
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: Rectangle {
|
||||||
|
x: bounds.x,
|
||||||
|
y: bounds.y,
|
||||||
|
width: bounds.width,
|
||||||
|
height: bounds.height,
|
||||||
|
},
|
||||||
|
border: iced::Border {
|
||||||
|
width: if custom_style.border_color.is_some() {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
},
|
||||||
|
color: custom_style.border_color.unwrap_or(custom_style.bar_color),
|
||||||
|
radius: custom_style.border_radius.into(),
|
||||||
|
},
|
||||||
|
snap: true,
|
||||||
|
..renderer::Quad::default()
|
||||||
|
},
|
||||||
|
Background::Color(custom_style.track_color),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(progress) = self.progress {
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: Rectangle {
|
||||||
|
x: bounds.x,
|
||||||
|
y: bounds.y,
|
||||||
|
width: progress * bounds.width,
|
||||||
|
height: bounds.height,
|
||||||
|
},
|
||||||
|
border: iced::Border {
|
||||||
|
width: 0.,
|
||||||
|
color: iced::Color::TRANSPARENT,
|
||||||
|
radius: custom_style.border_radius.into(),
|
||||||
|
},
|
||||||
|
snap: true,
|
||||||
|
..renderer::Quad::default()
|
||||||
|
},
|
||||||
|
Background::Color(custom_style.bar_color),
|
||||||
|
);
|
||||||
|
} 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),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Theme, Renderer> From<Linear<Theme>> for Element<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
Message: Clone + 'a,
|
||||||
|
Theme: StyleSheet + 'a,
|
||||||
|
Renderer: iced::advanced::Renderer + 'a,
|
||||||
|
{
|
||||||
|
fn from(linear: Linear<Theme>) -> Self {
|
||||||
|
Self::new(linear)
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/widget/progress_bar/mod.rs
Normal file
23
src/widget/progress_bar/mod.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
pub mod circular;
|
||||||
|
pub mod linear;
|
||||||
|
pub mod style;
|
||||||
|
|
||||||
|
/// A spinner / throbber widget that can be used to indicate that some operation is in progress.
|
||||||
|
pub fn indeterminate_circular() -> circular::Circular<crate::Theme> {
|
||||||
|
circular::Circular::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A linear throbber widget that can be used to indicate that some operation is in progress.
|
||||||
|
pub fn indeterminate_linear() -> linear::Linear<crate::Theme> {
|
||||||
|
linear::Linear::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A circular progress spinner widget that can be used to indicate the progress of some operation.
|
||||||
|
pub fn determinate_circular(progress: f32) -> circular::Circular<crate::Theme> {
|
||||||
|
circular::Circular::new().progress(progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A linear progress bar widget that can be used to indicate the progress of some operation.
|
||||||
|
pub fn determinate_linear(progress: f32) -> linear::Linear<crate::Theme> {
|
||||||
|
linear::Linear::new().progress(progress)
|
||||||
|
}
|
||||||
105
src/widget/progress_bar/style.rs
Normal file
105
src/widget/progress_bar/style.rs
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
use iced::Color;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Appearance {
|
||||||
|
/// The track [`Color`] of the progress indicator.
|
||||||
|
pub track_color: Color,
|
||||||
|
/// The bar [`Color`] of the progress indicator.
|
||||||
|
pub bar_color: Color,
|
||||||
|
/// The border [`Color`] of the progress indicator.
|
||||||
|
pub border_color: Option<Color>,
|
||||||
|
/// The border radius of the progress indicator.
|
||||||
|
pub border_radius: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::default::Default for Appearance {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
track_color: Color::TRANSPARENT,
|
||||||
|
bar_color: Color::BLACK,
|
||||||
|
border_color: None,
|
||||||
|
border_radius: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A set of rules that dictate the style of an indicator.
|
||||||
|
pub trait StyleSheet {
|
||||||
|
/// The supported style of the [`StyleSheet`].
|
||||||
|
type Style: Default;
|
||||||
|
|
||||||
|
/// Produces the active [`Appearance`] of a indicator.
|
||||||
|
fn appearance(
|
||||||
|
&self,
|
||||||
|
style: &Self::Style,
|
||||||
|
is_determinate: bool,
|
||||||
|
is_circular: bool,
|
||||||
|
) -> Appearance;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StyleSheet for iced::Theme {
|
||||||
|
type Style = ();
|
||||||
|
|
||||||
|
fn appearance(
|
||||||
|
&self,
|
||||||
|
_style: &Self::Style,
|
||||||
|
_is_determinate: bool,
|
||||||
|
_is_circular: bool,
|
||||||
|
) -> Appearance {
|
||||||
|
let palette = self.extended_palette();
|
||||||
|
|
||||||
|
Appearance {
|
||||||
|
track_color: palette.background.weak.color,
|
||||||
|
bar_color: palette.primary.base.color,
|
||||||
|
border_color: None,
|
||||||
|
border_radius: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StyleSheet for crate::Theme {
|
||||||
|
type Style = ();
|
||||||
|
|
||||||
|
fn appearance(
|
||||||
|
&self,
|
||||||
|
_style: &Self::Style,
|
||||||
|
is_determinate: bool,
|
||||||
|
is_circular: bool,
|
||||||
|
) -> Appearance {
|
||||||
|
let cur = self.current_container();
|
||||||
|
let mut cur_divider = cur.divider;
|
||||||
|
cur_divider.alpha = 0.5;
|
||||||
|
let theme = self.cosmic();
|
||||||
|
|
||||||
|
let (mut track_color, bar_color) = if theme.is_dark && theme.is_high_contrast {
|
||||||
|
(
|
||||||
|
theme.palette.neutral_6.into(),
|
||||||
|
theme.accent_text_color().into(),
|
||||||
|
)
|
||||||
|
} else if theme.is_dark {
|
||||||
|
(theme.palette.neutral_5.into(), theme.accent_color().into())
|
||||||
|
} else if theme.is_high_contrast {
|
||||||
|
(
|
||||||
|
theme.palette.neutral_4.into(),
|
||||||
|
theme.accent_text_color().into(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(theme.palette.neutral_3.into(), theme.accent_color().into())
|
||||||
|
};
|
||||||
|
|
||||||
|
if !is_determinate && is_circular {
|
||||||
|
track_color = Color::TRANSPARENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
Appearance {
|
||||||
|
track_color,
|
||||||
|
bar_color,
|
||||||
|
border_color: if is_determinate && theme.is_high_contrast {
|
||||||
|
Some(cur_divider.into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
border_radius: theme.corner_radii.radius_xl[0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
//! Create choices using radio buttons.
|
//! Create choices using radio buttons.
|
||||||
use crate::Theme;
|
use crate::{Theme, theme};
|
||||||
use iced::border;
|
use iced::border;
|
||||||
use iced_core::event::{self, Event};
|
use iced_core::event::{self, Event};
|
||||||
use iced_core::layout;
|
use iced_core::layout;
|
||||||
|
|
@ -92,7 +92,7 @@ where
|
||||||
{
|
{
|
||||||
is_selected: bool,
|
is_selected: bool,
|
||||||
on_click: Message,
|
on_click: Message,
|
||||||
label: Element<'a, Message, Theme, Renderer>,
|
label: Option<Element<'a, Message, Theme, Renderer>>,
|
||||||
width: Length,
|
width: Length,
|
||||||
size: f32,
|
size: f32,
|
||||||
spacing: f32,
|
spacing: f32,
|
||||||
|
|
@ -106,9 +106,6 @@ where
|
||||||
/// The default size of a [`Radio`] button.
|
/// The default size of a [`Radio`] button.
|
||||||
pub const DEFAULT_SIZE: f32 = 16.0;
|
pub const DEFAULT_SIZE: f32 = 16.0;
|
||||||
|
|
||||||
/// The default spacing of a [`Radio`] button.
|
|
||||||
pub const DEFAULT_SPACING: f32 = 8.0;
|
|
||||||
|
|
||||||
/// Creates a new [`Radio`] button.
|
/// Creates a new [`Radio`] button.
|
||||||
///
|
///
|
||||||
/// It expects:
|
/// It expects:
|
||||||
|
|
@ -126,10 +123,29 @@ where
|
||||||
Radio {
|
Radio {
|
||||||
is_selected: Some(value) == selected,
|
is_selected: Some(value) == selected,
|
||||||
on_click: f(value),
|
on_click: f(value),
|
||||||
label: label.into(),
|
label: Some(label.into()),
|
||||||
width: Length::Shrink,
|
width: Length::Shrink,
|
||||||
size: Self::DEFAULT_SIZE,
|
size: Self::DEFAULT_SIZE,
|
||||||
spacing: Self::DEFAULT_SPACING,
|
spacing: theme::spacing().space_xs as f32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`Radio`] button without a label.
|
||||||
|
///
|
||||||
|
/// This is intended for internal use with the settings item builder,
|
||||||
|
/// where the label comes from the settings item title instead.
|
||||||
|
pub(crate) fn new_no_label<V, F>(value: V, selected: Option<V>, f: F) -> Self
|
||||||
|
where
|
||||||
|
V: Eq + Copy,
|
||||||
|
F: FnOnce(V) -> Message,
|
||||||
|
{
|
||||||
|
Radio {
|
||||||
|
is_selected: Some(value) == selected,
|
||||||
|
on_click: f(value),
|
||||||
|
label: None,
|
||||||
|
width: Length::Shrink,
|
||||||
|
size: Self::DEFAULT_SIZE,
|
||||||
|
spacing: theme::spacing().space_xs as f32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,11 +177,17 @@ where
|
||||||
Renderer: iced_core::Renderer,
|
Renderer: iced_core::Renderer,
|
||||||
{
|
{
|
||||||
fn children(&self) -> Vec<Tree> {
|
fn children(&self) -> Vec<Tree> {
|
||||||
vec![Tree::new(&self.label)]
|
if let Some(label) = &self.label {
|
||||||
|
vec![Tree::new(label)]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff(&mut self, tree: &mut Tree) {
|
fn diff(&mut self, tree: &mut Tree) {
|
||||||
tree.diff_children(std::slice::from_mut(&mut self.label));
|
if let Some(label) = &mut self.label {
|
||||||
|
tree.diff_children(std::slice::from_mut(label));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn size(&self) -> Size<Length> {
|
fn size(&self) -> Size<Length> {
|
||||||
Size {
|
Size {
|
||||||
|
|
@ -180,16 +202,20 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
layout::next_to_each_other(
|
if let Some(label) = &mut self.label {
|
||||||
&limits.width(self.width),
|
layout::next_to_each_other(
|
||||||
self.spacing,
|
&limits.width(self.width),
|
||||||
|_| layout::Node::new(Size::new(self.size, self.size)),
|
self.spacing,
|
||||||
|limits| {
|
|_| layout::Node::new(Size::new(self.size, self.size)),
|
||||||
self.label
|
|limits| {
|
||||||
.as_widget_mut()
|
label
|
||||||
.layout(&mut tree.children[0], renderer, limits)
|
.as_widget_mut()
|
||||||
},
|
.layout(&mut tree.children[0], renderer, limits)
|
||||||
)
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
layout::Node::new(Size::new(self.size, self.size))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(
|
fn operate(
|
||||||
|
|
@ -199,12 +225,14 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||||
) {
|
) {
|
||||||
self.label.as_widget_mut().operate(
|
if let Some(label) = &mut self.label {
|
||||||
&mut tree.children[0],
|
label.as_widget_mut().operate(
|
||||||
layout.children().nth(1).unwrap(),
|
&mut tree.children[0],
|
||||||
renderer,
|
layout.children().nth(1).unwrap(),
|
||||||
operation,
|
renderer,
|
||||||
);
|
operation,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
|
|
@ -218,24 +246,25 @@ where
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
self.label.as_widget_mut().update(
|
if let Some(label) = &mut self.label {
|
||||||
&mut tree.children[0],
|
label.as_widget_mut().update(
|
||||||
event,
|
&mut tree.children[0],
|
||||||
layout.children().nth(1).unwrap(),
|
event,
|
||||||
cursor,
|
layout.children().nth(1).unwrap(),
|
||||||
renderer,
|
cursor,
|
||||||
clipboard,
|
renderer,
|
||||||
shell,
|
clipboard,
|
||||||
viewport,
|
shell,
|
||||||
);
|
viewport,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if !shell.is_event_captured() {
|
if !shell.is_event_captured() {
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
| Event::Touch(touch::Event::FingerLifted { .. }) => {
|
||||||
if cursor.is_over(layout.bounds()) {
|
if cursor.is_over(layout.bounds()) {
|
||||||
shell.publish(self.on_click.clone());
|
shell.publish(self.on_click.clone());
|
||||||
|
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -253,13 +282,17 @@ where
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
) -> mouse::Interaction {
|
) -> mouse::Interaction {
|
||||||
let interaction = self.label.as_widget().mouse_interaction(
|
let interaction = if let Some(label) = &self.label {
|
||||||
&tree.children[0],
|
label.as_widget().mouse_interaction(
|
||||||
layout.children().nth(1).unwrap(),
|
&tree.children[0],
|
||||||
cursor,
|
layout.children().nth(1).unwrap(),
|
||||||
viewport,
|
cursor,
|
||||||
renderer,
|
viewport,
|
||||||
);
|
renderer,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
mouse::Interaction::default()
|
||||||
|
};
|
||||||
|
|
||||||
if interaction == mouse::Interaction::default() {
|
if interaction == mouse::Interaction::default() {
|
||||||
if cursor.is_over(layout.bounds()) {
|
if cursor.is_over(layout.bounds()) {
|
||||||
|
|
@ -284,8 +317,6 @@ where
|
||||||
) {
|
) {
|
||||||
let is_mouse_over = cursor.is_over(layout.bounds());
|
let is_mouse_over = cursor.is_over(layout.bounds());
|
||||||
|
|
||||||
let mut children = layout.children();
|
|
||||||
|
|
||||||
let custom_style = if is_mouse_over {
|
let custom_style = if is_mouse_over {
|
||||||
theme.style(
|
theme.style(
|
||||||
&(),
|
&(),
|
||||||
|
|
@ -302,16 +333,21 @@ where
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
let (dot_bounds, label_layout) = if self.label.is_some() {
|
||||||
let layout = children.next().unwrap();
|
let mut children = layout.children();
|
||||||
let bounds = layout.bounds();
|
let dot_bounds = children.next().unwrap().bounds();
|
||||||
|
(dot_bounds, children.next())
|
||||||
|
} else {
|
||||||
|
(layout.bounds(), None)
|
||||||
|
};
|
||||||
|
|
||||||
let size = bounds.width;
|
{
|
||||||
|
let size = dot_bounds.width;
|
||||||
let dot_size = 6.0;
|
let dot_size = 6.0;
|
||||||
|
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds,
|
bounds: dot_bounds,
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: (size / 2.0).into(),
|
radius: (size / 2.0).into(),
|
||||||
width: custom_style.border_width,
|
width: custom_style.border_width,
|
||||||
|
|
@ -326,8 +362,8 @@ where
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: Rectangle {
|
bounds: Rectangle {
|
||||||
x: bounds.x + (size - dot_size) / 2.0,
|
x: dot_bounds.x + (size - dot_size) / 2.0,
|
||||||
y: bounds.y + (size - dot_size) / 2.0,
|
y: dot_bounds.y + (size - dot_size) / 2.0,
|
||||||
width: dot_size,
|
width: dot_size,
|
||||||
height: dot_size,
|
height: dot_size,
|
||||||
},
|
},
|
||||||
|
|
@ -339,9 +375,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
if let (Some(label), Some(label_layout)) = (&self.label, label_layout) {
|
||||||
let label_layout = children.next().unwrap();
|
label.as_widget().draw(
|
||||||
self.label.as_widget().draw(
|
|
||||||
&tree.children[0],
|
&tree.children[0],
|
||||||
renderer,
|
renderer,
|
||||||
theme,
|
theme,
|
||||||
|
|
@ -361,7 +396,7 @@ where
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||||
self.label.as_widget_mut().overlay(
|
self.label.as_mut()?.as_widget_mut().overlay(
|
||||||
&mut tree.children[0],
|
&mut tree.children[0],
|
||||||
layout.children().nth(1).unwrap(),
|
layout.children().nth(1).unwrap(),
|
||||||
renderer,
|
renderer,
|
||||||
|
|
@ -377,12 +412,14 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||||
) {
|
) {
|
||||||
self.label.as_widget().drag_destinations(
|
if let Some(label) = &self.label {
|
||||||
&state.children[0],
|
label.as_widget().drag_destinations(
|
||||||
layout.children().nth(1).unwrap(),
|
&state.children[0],
|
||||||
renderer,
|
layout.children().nth(1).unwrap(),
|
||||||
dnd_rectangles,
|
renderer,
|
||||||
);
|
dnd_rectangles,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ impl Default for ResponsiveMenuBar {
|
||||||
fn default() -> ResponsiveMenuBar {
|
fn default() -> ResponsiveMenuBar {
|
||||||
ResponsiveMenuBar {
|
ResponsiveMenuBar {
|
||||||
collapsed_item_width: {
|
collapsed_item_width: {
|
||||||
#[cfg(all(feature = "winit", feature = "wayland"))]
|
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))]
|
||||||
if matches!(
|
if matches!(
|
||||||
crate::app::cosmic::WINDOWING_SYSTEM.get(),
|
crate::app::cosmic::WINDOWING_SYSTEM.get(),
|
||||||
Some(crate::app::cosmic::WindowingSystem::Wayland)
|
Some(crate::app::cosmic::WindowingSystem::Wayland)
|
||||||
|
|
@ -34,7 +34,7 @@ impl Default for ResponsiveMenuBar {
|
||||||
} else {
|
} else {
|
||||||
ItemWidth::Static(84)
|
ItemWidth::Static(84)
|
||||||
}
|
}
|
||||||
#[cfg(not(all(feature = "winit", feature = "wayland")))]
|
#[cfg(not(all(feature = "winit", feature = "wayland", target_os = "linux")))]
|
||||||
{
|
{
|
||||||
ItemWidth::Static(84)
|
ItemWidth::Static(84)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -213,6 +213,18 @@ where
|
||||||
state.buttons_offset = num - state.buttons_visible;
|
state.buttons_offset = num - state.buttons_visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resize paragraph bounds so that text ellipsis can take effect.
|
||||||
|
if !matches!(self.width, Length::Shrink) || state.collapsed {
|
||||||
|
let num = state.buttons_visible.max(1) as f32;
|
||||||
|
let spacing = f32::from(self.spacing);
|
||||||
|
let mut width_offset = 0.0;
|
||||||
|
if state.collapsed {
|
||||||
|
width_offset = f32::from(self.button_height) * 2.0;
|
||||||
|
}
|
||||||
|
let button_width = ((num).mul_add(-spacing, size.width - width_offset) + spacing) / num;
|
||||||
|
self.resize_paragraphs(state, button_width);
|
||||||
|
}
|
||||||
|
|
||||||
size
|
size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,10 +117,15 @@ where
|
||||||
height += item_height;
|
height += item_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
limits.height(Length::Fixed(height)).resolve(
|
let size = limits.height(Length::Fixed(height)).resolve(
|
||||||
self.width,
|
self.width,
|
||||||
self.height,
|
self.height,
|
||||||
Size::new(width, height),
|
Size::new(width, height),
|
||||||
)
|
);
|
||||||
|
|
||||||
|
// Resize paragraph bounds so that text ellipsis can take effect.
|
||||||
|
self.resize_paragraphs(state, size.width);
|
||||||
|
|
||||||
|
size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
use super::model::{Entity, Model, Selectable};
|
use super::model::{Entity, Model, Selectable};
|
||||||
use super::{InsertPosition, ReorderEvent};
|
use super::{InsertPosition, ReorderEvent};
|
||||||
use crate::iced_core::id::Internal;
|
|
||||||
use crate::theme::{SegmentedButton as Style, THEME};
|
use crate::theme::{SegmentedButton as Style, THEME};
|
||||||
use crate::widget::dnd_destination::DragId;
|
use crate::widget::dnd_destination::DragId;
|
||||||
use crate::widget::menu::{
|
use crate::widget::menu::{
|
||||||
|
|
@ -22,6 +21,7 @@ use iced::{
|
||||||
Alignment, Background, Color, Event, Length, Padding, Rectangle, Size, Task, Vector, alignment,
|
Alignment, Background, Color, Event, Length, Padding, Rectangle, Size, Task, Vector, alignment,
|
||||||
keyboard, mouse, touch, window,
|
keyboard, mouse, touch, window,
|
||||||
};
|
};
|
||||||
|
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;
|
||||||
|
|
@ -156,6 +156,8 @@ where
|
||||||
pub(super) spacing: u16,
|
pub(super) spacing: u16,
|
||||||
/// LineHeight of the font.
|
/// LineHeight of the font.
|
||||||
pub(super) line_height: LineHeight,
|
pub(super) line_height: LineHeight,
|
||||||
|
/// Ellipsize strategy for button text.
|
||||||
|
pub(super) ellipsize: Ellipsize,
|
||||||
/// Style to draw the widget in.
|
/// Style to draw the widget in.
|
||||||
#[setters(into)]
|
#[setters(into)]
|
||||||
pub(super) style: Style,
|
pub(super) style: Style,
|
||||||
|
|
@ -216,13 +218,14 @@ where
|
||||||
maximum_button_width: u16::MAX,
|
maximum_button_width: u16::MAX,
|
||||||
indent_spacing: 16,
|
indent_spacing: 16,
|
||||||
font_active: crate::font::semibold(),
|
font_active: crate::font::semibold(),
|
||||||
font_hovered: crate::font::semibold(),
|
font_hovered: crate::font::default(),
|
||||||
font_inactive: crate::font::default(),
|
font_inactive: crate::font::default(),
|
||||||
font_size: 14.0,
|
font_size: 14.0,
|
||||||
height: Length::Shrink,
|
height: Length::Shrink,
|
||||||
width: Length::Fill,
|
width: Length::Fill,
|
||||||
spacing: 0,
|
spacing: 0,
|
||||||
line_height: LineHeight::default(),
|
line_height: LineHeight::default(),
|
||||||
|
ellipsize: Ellipsize::default(),
|
||||||
style: Style::default(),
|
style: Style::default(),
|
||||||
context_menu: None,
|
context_menu: None,
|
||||||
on_activate: None,
|
on_activate: None,
|
||||||
|
|
@ -243,12 +246,13 @@ where
|
||||||
|
|
||||||
fn update_entity_paragraph(&mut self, state: &mut LocalState, key: Entity) {
|
fn update_entity_paragraph(&mut self, state: &mut LocalState, key: Entity) {
|
||||||
if let Some(text) = self.model.text.get(key) {
|
if let Some(text) = self.model.text.get(key) {
|
||||||
let font = if self.button_is_focused(state, key) {
|
let font = if self.button_is_focused(state, key)
|
||||||
|
|| state.show_context == Some(key)
|
||||||
|
|| self.model.is_active(key)
|
||||||
|
{
|
||||||
self.font_active
|
self.font_active
|
||||||
} else if state.show_context == Some(key) || self.button_is_hovered(state, key) {
|
} else if self.button_is_hovered(state, key) {
|
||||||
self.font_hovered
|
self.font_hovered
|
||||||
} else if self.model.is_active(key) {
|
|
||||||
self.font_active
|
|
||||||
} else {
|
} else {
|
||||||
self.font_inactive
|
self.font_inactive
|
||||||
};
|
};
|
||||||
|
|
@ -258,10 +262,10 @@ where
|
||||||
font.hash(&mut hasher);
|
font.hash(&mut hasher);
|
||||||
let text_hash = hasher.finish();
|
let text_hash = hasher.finish();
|
||||||
|
|
||||||
if let Some(prev_hash) = state.text_hashes.insert(key, text_hash) {
|
if let Some(prev_hash) = state.text_hashes.insert(key, text_hash)
|
||||||
if prev_hash == text_hash {
|
&& prev_hash == text_hash
|
||||||
return;
|
{
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(paragraph) = state.paragraphs.get_mut(key) {
|
if let Some(paragraph) = state.paragraphs.get_mut(key) {
|
||||||
|
|
@ -275,7 +279,7 @@ where
|
||||||
shaping: Shaping::Advanced,
|
shaping: Shaping::Advanced,
|
||||||
wrapping: Wrapping::None,
|
wrapping: Wrapping::None,
|
||||||
line_height: self.line_height,
|
line_height: self.line_height,
|
||||||
ellipsize: Ellipsize::default(),
|
ellipsize: self.ellipsize,
|
||||||
};
|
};
|
||||||
paragraph.update(text);
|
paragraph.update(text);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -289,7 +293,7 @@ where
|
||||||
shaping: Shaping::Advanced,
|
shaping: Shaping::Advanced,
|
||||||
wrapping: Wrapping::None,
|
wrapping: Wrapping::None,
|
||||||
line_height: self.line_height,
|
line_height: self.line_height,
|
||||||
ellipsize: Ellipsize::default(),
|
ellipsize: self.ellipsize,
|
||||||
};
|
};
|
||||||
state.paragraphs.insert(key, crate::Plain::new(text));
|
state.paragraphs.insert(key, crate::Plain::new(text));
|
||||||
}
|
}
|
||||||
|
|
@ -302,7 +306,7 @@ where
|
||||||
{
|
{
|
||||||
self.context_menu = context_menu.map(|menus| {
|
self.context_menu = context_menu.map(|menus| {
|
||||||
vec![menu::Tree::with_children(
|
vec![menu::Tree::with_children(
|
||||||
crate::Element::from(crate::widget::row::<'static, Message>()),
|
crate::Element::from(crate::widget::Row::new()),
|
||||||
menus,
|
menus,
|
||||||
)]
|
)]
|
||||||
});
|
});
|
||||||
|
|
@ -621,7 +625,7 @@ where
|
||||||
align_y: alignment::Vertical::Center,
|
align_y: alignment::Vertical::Center,
|
||||||
shaping: Shaping::Advanced,
|
shaping: Shaping::Advanced,
|
||||||
wrapping: Wrapping::default(),
|
wrapping: Wrapping::default(),
|
||||||
ellipsize: Ellipsize::default(),
|
ellipsize: self.ellipsize,
|
||||||
line_height: self.line_height,
|
line_height: self.line_height,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
@ -657,6 +661,50 @@ where
|
||||||
(width, f32::from(self.button_height))
|
(width, f32::from(self.button_height))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resizes paragraph bounds based on the actual available button width so that
|
||||||
|
/// text ellipsis can take effect. Call this after `variant_layout` has populated
|
||||||
|
/// `state.internal_layout` with final button sizes.
|
||||||
|
pub(super) fn resize_paragraphs(&self, state: &mut LocalState, available_width: f32) {
|
||||||
|
if matches!(self.ellipsize, Ellipsize::None) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (nth, key) in self.model.order.iter().copied().enumerate() {
|
||||||
|
if self.model.text(key).is_some_and(|text| !text.is_empty()) {
|
||||||
|
let mut non_text_width =
|
||||||
|
f32::from(self.button_padding[0]) + f32::from(self.button_padding[2]);
|
||||||
|
|
||||||
|
if let Some(icon) = self.model.icon(key) {
|
||||||
|
non_text_width += f32::from(icon.size) + f32::from(self.button_spacing);
|
||||||
|
} else if self.model.is_active(key) {
|
||||||
|
if let crate::theme::SegmentedButton::Control = self.style {
|
||||||
|
non_text_width += 16.0 + f32::from(self.button_spacing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.model.is_closable(key) {
|
||||||
|
non_text_width +=
|
||||||
|
f32::from(self.close_icon.size) + f32::from(self.button_spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
let text_width = (available_width - non_text_width).max(0.0);
|
||||||
|
|
||||||
|
if let Some(paragraph) = state.paragraphs.get_mut(key) {
|
||||||
|
paragraph.resize(Size::new(text_width, f32::INFINITY));
|
||||||
|
|
||||||
|
// Update internal_layout actual content width so that
|
||||||
|
// button_alignment centering uses the ellipsized size.
|
||||||
|
let content_width = paragraph.min_bounds().width + non_text_width
|
||||||
|
- f32::from(self.button_padding[0])
|
||||||
|
- f32::from(self.button_padding[2]);
|
||||||
|
if let Some(entry) = state.internal_layout.get_mut(nth) {
|
||||||
|
entry.1.width = content_width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn max_button_dimensions(
|
pub(super) fn max_button_dimensions(
|
||||||
&self,
|
&self,
|
||||||
state: &mut LocalState,
|
state: &mut LocalState,
|
||||||
|
|
@ -880,7 +928,6 @@ where
|
||||||
|
|
||||||
fn diff(&mut self, tree: &mut Tree) {
|
fn diff(&mut self, tree: &mut Tree) {
|
||||||
let state = tree.state.downcast_mut::<LocalState>();
|
let state = tree.state.downcast_mut::<LocalState>();
|
||||||
|
|
||||||
for key in self.model.order.iter().copied() {
|
for key in self.model.order.iter().copied() {
|
||||||
self.update_entity_paragraph(state, key);
|
self.update_entity_paragraph(state, key);
|
||||||
}
|
}
|
||||||
|
|
@ -1434,7 +1481,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let Item::Tab(key) = std::mem::replace(&mut state.hovered, Item::None) {
|
if let Item::Tab(_key) = std::mem::replace(&mut state.hovered, Item::None) {
|
||||||
for key in self.model.order.iter().copied() {
|
for key in self.model.order.iter().copied() {
|
||||||
self.update_entity_paragraph(state, key);
|
self.update_entity_paragraph(state, key);
|
||||||
}
|
}
|
||||||
|
|
@ -1596,7 +1643,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
iced_core::mouse::Interaction::Idle
|
iced_core::mouse::Interaction::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
|
|
@ -1938,7 +1985,9 @@ where
|
||||||
|
|
||||||
// Align contents of the button to the requested `button_alignment`.
|
// Align contents of the button to the requested `button_alignment`.
|
||||||
{
|
{
|
||||||
let actual_width = state.internal_layout[nth].1.width;
|
// Avoid shifting content outside the left edge when the measured content is
|
||||||
|
// wider than the available button bounds (for example, non-ellipsized text).
|
||||||
|
let actual_width = state.internal_layout[nth].1.width.min(bounds.width);
|
||||||
|
|
||||||
let offset = match self.button_alignment {
|
let offset = match self.button_alignment {
|
||||||
Alignment::Start => None,
|
Alignment::Start => None,
|
||||||
|
|
@ -1994,10 +2043,10 @@ where
|
||||||
..image_bounds
|
..image_bounds
|
||||||
},
|
},
|
||||||
crate::widget::icon(match crate::widget::common::object_select().data() {
|
crate::widget::icon(match crate::widget::common::object_select().data() {
|
||||||
crate::iced_core::svg::Data::Bytes(bytes) => {
|
iced_core::svg::Data::Bytes(bytes) => {
|
||||||
crate::widget::icon::from_svg_bytes(bytes.as_ref()).symbolic(true)
|
crate::widget::icon::from_svg_bytes(bytes.as_ref()).symbolic(true)
|
||||||
}
|
}
|
||||||
crate::iced_core::svg::Data::Path(path) => {
|
iced_core::svg::Data::Path(path) => {
|
||||||
crate::widget::icon::from_path(path.clone())
|
crate::widget::icon::from_path(path.clone())
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
@ -2090,7 +2139,7 @@ where
|
||||||
tree: &'b mut Tree,
|
tree: &'b mut Tree,
|
||||||
layout: iced_core::Layout<'b>,
|
layout: iced_core::Layout<'b>,
|
||||||
_renderer: &Renderer,
|
_renderer: &Renderer,
|
||||||
viewport: &iced_core::Rectangle,
|
_viewport: &iced_core::Rectangle,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||||
let state = tree.state.downcast_mut::<LocalState>();
|
let state = tree.state.downcast_mut::<LocalState>();
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Element, theme,
|
Element, Theme, theme,
|
||||||
widget::{FlexRow, Row, column, container, flex_row, row, text},
|
widget::{FlexRow, Row, column, container, flex_row, list, row, text},
|
||||||
};
|
};
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
use iced_core::{Length, text::Wrapping};
|
use iced_core::{Length, text::Wrapping};
|
||||||
|
|
@ -18,12 +18,12 @@ use taffy::AlignContent;
|
||||||
pub fn item<'a, Message: 'static>(
|
pub fn item<'a, Message: 'static>(
|
||||||
title: impl Into<Cow<'a, str>> + 'a,
|
title: impl Into<Cow<'a, str>> + 'a,
|
||||||
widget: impl Into<Element<'a, Message>> + 'a,
|
widget: impl Into<Element<'a, Message>> + 'a,
|
||||||
) -> Row<'a, Message> {
|
) -> Row<'a, Message, Theme> {
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
fn inner<'a, Message: 'static>(
|
fn inner<'a, Message: 'static>(
|
||||||
title: Cow<'a, str>,
|
title: Cow<'a, str>,
|
||||||
widget: Element<'a, Message>,
|
widget: Element<'a, Message>,
|
||||||
) -> Row<'a, Message> {
|
) -> Row<'a, Message, Theme> {
|
||||||
item_row(vec![
|
item_row(vec![
|
||||||
text(title).wrapping(Wrapping::Word).into(),
|
text(title).wrapping(Wrapping::Word).into(),
|
||||||
space::horizontal().into(),
|
space::horizontal().into(),
|
||||||
|
|
@ -37,7 +37,7 @@ pub fn item<'a, Message: 'static>(
|
||||||
/// A settings item aligned in a row
|
/// A settings item aligned in a row
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[allow(clippy::module_name_repetitions)]
|
#[allow(clippy::module_name_repetitions)]
|
||||||
pub fn item_row<Message>(children: Vec<Element<Message>>) -> Row<Message> {
|
pub fn item_row<Message>(children: Vec<Element<Message>>) -> Row<Message, Theme> {
|
||||||
row::with_children(children)
|
row::with_children(children)
|
||||||
.spacing(theme::spacing().space_xs)
|
.spacing(theme::spacing().space_xs)
|
||||||
.align_y(iced::Alignment::Center)
|
.align_y(iced::Alignment::Center)
|
||||||
|
|
@ -103,9 +103,9 @@ pub struct Item<'a, Message> {
|
||||||
icon: Option<Element<'a, Message>>,
|
icon: Option<Element<'a, Message>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message: 'static> Item<'a, Message> {
|
impl<'a, Message: Clone + 'static> Item<'a, Message> {
|
||||||
/// Assigns a control to the item.
|
/// Assigns a control to the item.
|
||||||
pub fn control(self, widget: impl Into<Element<'a, Message>>) -> Row<'a, Message> {
|
pub fn control(self, widget: impl Into<Element<'a, Message>>) -> Row<'a, Message, Theme> {
|
||||||
item_row(self.control_(widget.into()))
|
item_row(self.control_(widget.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,39 +114,109 @@ impl<'a, Message: 'static> Item<'a, Message> {
|
||||||
flex_item_row(self.control_(widget.into()))
|
flex_item_row(self.control_(widget.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(never)]
|
fn label(self) -> Element<'a, Message> {
|
||||||
fn control_(self, widget: Element<'a, Message>) -> Vec<Element<'a, Message>> {
|
|
||||||
let mut contents = Vec::with_capacity(4);
|
|
||||||
|
|
||||||
if let Some(icon) = self.icon {
|
|
||||||
contents.push(icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(description) = self.description {
|
if let Some(description) = self.description {
|
||||||
let column = column::with_capacity(2)
|
column::with_capacity(2)
|
||||||
.spacing(2)
|
.spacing(2)
|
||||||
.push(text::body(self.title).wrapping(Wrapping::Word))
|
.push(text::body(self.title).wrapping(Wrapping::Word))
|
||||||
.push(text::caption(description).wrapping(Wrapping::Word))
|
.push(text::caption(description).wrapping(Wrapping::Word))
|
||||||
.width(Length::Fill);
|
.width(Length::Fill)
|
||||||
|
.into()
|
||||||
contents.push(column.into());
|
|
||||||
} else {
|
} else {
|
||||||
contents.push(text(self.title).width(Length::Fill).into());
|
text(self.title).width(Length::Fill).into()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
fn control_(mut self, widget: Element<'a, Message>) -> Vec<Element<'a, Message>> {
|
||||||
|
let mut contents = Vec::with_capacity(3);
|
||||||
|
if let Some(icon) = self.icon.take() {
|
||||||
|
contents.push(icon);
|
||||||
|
}
|
||||||
|
contents.push(self.label());
|
||||||
contents.push(widget);
|
contents.push(widget);
|
||||||
contents
|
contents
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn control_start(self, widget: impl Into<Element<'a, Message>>) -> Row<'a, Message, Theme> {
|
||||||
|
item_row(vec![widget.into(), self.label()])
|
||||||
|
}
|
||||||
|
|
||||||
pub fn toggler(
|
pub fn toggler(
|
||||||
self,
|
self,
|
||||||
is_checked: bool,
|
is_checked: bool,
|
||||||
message: impl Fn(bool) -> Message + 'static,
|
message: impl Fn(bool) -> Message + 'static,
|
||||||
) -> Row<'a, Message> {
|
) -> list::ListButton<'a, Message> {
|
||||||
self.control(
|
let on_press = message(!is_checked);
|
||||||
crate::widget::toggler(is_checked)
|
list::button(
|
||||||
.width(Length::Shrink)
|
self.control(
|
||||||
.on_toggle(message),
|
crate::widget::toggler(is_checked)
|
||||||
|
.width(Length::Shrink)
|
||||||
|
.on_toggle(message),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
.on_press(on_press)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggler_maybe(
|
||||||
|
self,
|
||||||
|
is_checked: bool,
|
||||||
|
message: Option<impl Fn(bool) -> Message + 'static>,
|
||||||
|
) -> list::ListButton<'a, Message> {
|
||||||
|
let on_press = message.as_ref().map(|f| f(!is_checked));
|
||||||
|
list::button(
|
||||||
|
self.control(
|
||||||
|
crate::widget::toggler(is_checked)
|
||||||
|
.width(Length::Shrink)
|
||||||
|
.on_toggle_maybe(message),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.on_press_maybe(on_press)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checkbox(
|
||||||
|
self,
|
||||||
|
is_checked: bool,
|
||||||
|
message: impl Fn(bool) -> Message + 'static,
|
||||||
|
) -> list::ListButton<'a, Message> {
|
||||||
|
let on_press = message(!is_checked);
|
||||||
|
list::button(
|
||||||
|
self.control_start(
|
||||||
|
crate::widget::checkbox(is_checked)
|
||||||
|
.width(Length::Shrink)
|
||||||
|
.on_toggle(message),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.on_press(on_press)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checkbox_maybe(
|
||||||
|
self,
|
||||||
|
is_checked: bool,
|
||||||
|
message: Option<impl Fn(bool) -> Message + 'static>,
|
||||||
|
) -> list::ListButton<'a, Message> {
|
||||||
|
let on_press = message.as_ref().map(|f| f(!is_checked));
|
||||||
|
list::button(
|
||||||
|
self.control_start(
|
||||||
|
crate::widget::checkbox(is_checked)
|
||||||
|
.width(Length::Shrink)
|
||||||
|
.on_toggle_maybe(message),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.on_press_maybe(on_press)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn radio<V, F>(self, value: V, selected: Option<V>, f: F) -> list::ListButton<'a, Message>
|
||||||
|
where
|
||||||
|
V: Eq + Copy,
|
||||||
|
F: Fn(V) -> Message,
|
||||||
|
{
|
||||||
|
let on_press = f(value);
|
||||||
|
list::button(
|
||||||
|
self.control_start(crate::widget::radio::Radio::new_no_label(
|
||||||
|
value, selected, f,
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.on_press(on_press)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ pub use self::item::{flex_item, flex_item_row, item, item_row};
|
||||||
pub use self::section::{Section, section};
|
pub use self::section::{Section, section};
|
||||||
|
|
||||||
use crate::widget::{Column, column};
|
use crate::widget::{Column, column};
|
||||||
use crate::{Element, theme};
|
use crate::{Element, Theme, theme};
|
||||||
|
|
||||||
/// A column with a predefined style for creating a settings panel
|
/// A column with a predefined style for creating a settings panel
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn view_column<Message: 'static>(children: Vec<Element<Message>>) -> Column<Message> {
|
pub fn view_column<Message: 'static>(children: Vec<Element<Message>>) -> Column<Message, Theme> {
|
||||||
column::with_children(children).spacing(theme::spacing().space_m)
|
column::with_children(children).spacing(theme::spacing().space_m)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,24 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use crate::widget::{ListColumn, column, text};
|
use crate::widget::list_column::IntoListItem;
|
||||||
|
use crate::widget::{ListColumn, column, list_column, text};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// A section within a settings view column.
|
/// A section within a settings view column.
|
||||||
#[deprecated(note = "use `settings::section().title()` instead")]
|
pub fn section<'a, Message: Clone + 'static>() -> Section<'a, Message> {
|
||||||
pub fn view_section<'a, Message: 'static>(title: impl Into<Cow<'a, str>>) -> Section<'a, Message> {
|
|
||||||
section().title(title)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A section within a settings view column.
|
|
||||||
pub fn section<'a, Message: 'static>() -> Section<'a, Message> {
|
|
||||||
with_column(ListColumn::default())
|
with_column(ListColumn::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A section with a pre-defined list column of a given capacity.
|
||||||
|
pub fn with_capacity<'a, Message: Clone + 'static>(capacity: usize) -> Section<'a, Message> {
|
||||||
|
with_column(list_column::with_capacity(capacity))
|
||||||
|
}
|
||||||
|
|
||||||
/// A section with a pre-defined list column.
|
/// A section with a pre-defined list column.
|
||||||
pub fn with_column<Message: 'static>(children: ListColumn<'_, Message>) -> Section<'_, Message> {
|
pub fn with_column<Message: Clone + 'static>(
|
||||||
|
children: ListColumn<'_, Message>,
|
||||||
|
) -> Section<'_, Message> {
|
||||||
Section {
|
Section {
|
||||||
header: None,
|
header: None,
|
||||||
children,
|
children,
|
||||||
|
|
@ -30,9 +32,9 @@ pub struct Section<'a, Message> {
|
||||||
children: ListColumn<'a, Message>,
|
children: ListColumn<'a, Message>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message: 'static> Section<'a, Message> {
|
impl<'a, Message: Clone + 'static> Section<'a, Message> {
|
||||||
/// Define an optional title for the section.
|
/// Define an optional title for the section.
|
||||||
pub fn title(mut self, title: impl Into<Cow<'a, str>>) -> Self {
|
pub fn title(self, title: impl Into<Cow<'a, str>>) -> Self {
|
||||||
self.header(text::heading(title.into()))
|
self.header(text::heading(title.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,13 +46,13 @@ impl<'a, Message: 'static> Section<'a, Message> {
|
||||||
|
|
||||||
/// Add a child element to the section's list column.
|
/// Add a child element to the section's list column.
|
||||||
#[allow(clippy::should_implement_trait)]
|
#[allow(clippy::should_implement_trait)]
|
||||||
pub fn add(mut self, item: impl Into<Element<'a, Message>>) -> Self {
|
pub fn add(mut self, item: impl IntoListItem<'a, Message>) -> Self {
|
||||||
self.children = self.children.add(item.into());
|
self.children = self.children.add(item);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a child element to the section's list column, if `Some`.
|
/// Add a child element to the section's list column, if `Some`.
|
||||||
pub fn add_maybe(self, item: Option<impl Into<Element<'a, Message>>>) -> Self {
|
pub fn add_maybe(self, item: Option<impl IntoListItem<'a, Message>>) -> Self {
|
||||||
if let Some(item) = item {
|
if let Some(item) = item {
|
||||||
self.add(item)
|
self.add(item)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -61,13 +63,13 @@ impl<'a, Message: 'static> Section<'a, Message> {
|
||||||
/// Extends the [`Section`] with the given children.
|
/// Extends the [`Section`] with the given children.
|
||||||
pub fn extend(
|
pub fn extend(
|
||||||
self,
|
self,
|
||||||
children: impl IntoIterator<Item = impl Into<Element<'a, Message>>>,
|
children: impl IntoIterator<Item = impl IntoListItem<'a, Message>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
children.into_iter().fold(self, Self::add)
|
children.into_iter().fold(self, Self::add)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message: 'static> From<Section<'a, Message>> for Element<'a, Message> {
|
impl<'a, Message: Clone + 'static> From<Section<'a, Message>> for Element<'a, Message> {
|
||||||
fn from(data: Section<'a, Message>) -> Self {
|
fn from(data: Section<'a, Message>) -> Self {
|
||||||
column::with_capacity(2)
|
column::with_capacity(2)
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ where
|
||||||
let selected = val.model.is_active(entity);
|
let selected = val.model.is_active(entity);
|
||||||
let context_menu = (val.item_context_builder)(item);
|
let context_menu = (val.item_context_builder)(item);
|
||||||
|
|
||||||
widget::column()
|
widget::column::with_capacity(2)
|
||||||
.spacing(val.item_spacing)
|
.spacing(val.item_spacing)
|
||||||
.push(
|
.push(
|
||||||
widget::divider::horizontal::default()
|
widget::divider::horizontal::default()
|
||||||
|
|
@ -73,7 +73,7 @@ where
|
||||||
.padding(val.divider_padding),
|
.padding(val.divider_padding),
|
||||||
)
|
)
|
||||||
.push(
|
.push(
|
||||||
widget::row()
|
widget::row::with_capacity(2)
|
||||||
.spacing(space_xxxs)
|
.spacing(space_xxxs)
|
||||||
.align_y(Alignment::Center)
|
.align_y(Alignment::Center)
|
||||||
.push_maybe(
|
.push_maybe(
|
||||||
|
|
@ -81,7 +81,7 @@ where
|
||||||
.map(|icon| icon.size(val.icon_size)),
|
.map(|icon| icon.size(val.icon_size)),
|
||||||
)
|
)
|
||||||
.push(
|
.push(
|
||||||
widget::column()
|
widget::column::with_capacity(2)
|
||||||
.push(widget::text::body(item.get_text(Category::default())))
|
.push(widget::text::body(item.get_text(Category::default())))
|
||||||
.push({
|
.push({
|
||||||
let mut elements = val
|
let mut elements = val
|
||||||
|
|
@ -145,7 +145,7 @@ where
|
||||||
})
|
})
|
||||||
// Double click
|
// Double click
|
||||||
.apply(|mouse_area| {
|
.apply(|mouse_area| {
|
||||||
if let Some(ref on_item_mb) = val.on_item_mb_left {
|
if let Some(ref on_item_mb) = val.on_item_mb_double {
|
||||||
mouse_area.on_double_click((on_item_mb)(entity))
|
mouse_area.on_double_click((on_item_mb)(entity))
|
||||||
} else {
|
} else {
|
||||||
mouse_area
|
mouse_area
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build the category header
|
// Build the category header
|
||||||
widget::row()
|
widget::row::with_capacity(2)
|
||||||
.spacing(val.icon_spacing)
|
.spacing(val.icon_spacing)
|
||||||
.push(widget::text::heading(category.to_string()))
|
.push(widget::text::heading(category.to_string()))
|
||||||
.push_maybe(match sort_state {
|
.push_maybe(match sort_state {
|
||||||
|
|
@ -152,7 +152,7 @@ where
|
||||||
categories
|
categories
|
||||||
.iter()
|
.iter()
|
||||||
.map(|category| {
|
.map(|category| {
|
||||||
widget::row()
|
widget::row::with_capacity(2)
|
||||||
.spacing(val.icon_spacing)
|
.spacing(val.icon_spacing)
|
||||||
.push_maybe(
|
.push_maybe(
|
||||||
item.get_icon(*category)
|
item.get_icon(*category)
|
||||||
|
|
@ -206,7 +206,7 @@ where
|
||||||
})
|
})
|
||||||
// Double click
|
// Double click
|
||||||
.apply(|mouse_area| {
|
.apply(|mouse_area| {
|
||||||
if let Some(ref on_item_mb) = val.on_item_mb_left {
|
if let Some(ref on_item_mb) = val.on_item_mb_double {
|
||||||
mouse_area.on_double_click((on_item_mb)(entity))
|
mouse_area.on_double_click((on_item_mb)(entity))
|
||||||
} else {
|
} else {
|
||||||
mouse_area
|
mouse_area
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,19 @@
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//! Track the cursor of a text input.
|
//! Track the cursor of a text input.
|
||||||
|
use iced_core::text::Affinity;
|
||||||
|
|
||||||
use super::value::Value;
|
use super::value::Value;
|
||||||
|
|
||||||
/// The cursor of a text input.
|
/// The cursor of a text input.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct Cursor {
|
pub struct Cursor {
|
||||||
state: State,
|
state: State,
|
||||||
|
affinity: Affinity,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The state of a [`Cursor`].
|
/// The state of a [`Cursor`].
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum State {
|
pub enum State {
|
||||||
/// Cursor without a selection
|
/// Cursor without a selection
|
||||||
Index(usize),
|
Index(usize),
|
||||||
|
|
@ -31,6 +34,7 @@ impl Default for Cursor {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: State::Index(0),
|
state: State::Index(0),
|
||||||
|
affinity: Affinity::Before,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -193,4 +197,37 @@ impl Cursor {
|
||||||
State::Selection { start, end } => start.max(end),
|
State::Selection { start, end } => start.max(end),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the current cursor [`Affinity`].
|
||||||
|
#[must_use]
|
||||||
|
pub fn affinity(&self) -> Affinity {
|
||||||
|
self.affinity
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the cursor [`Affinity`].
|
||||||
|
pub fn set_affinity(&mut self, affinity: Affinity) {
|
||||||
|
self.affinity = affinity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves the cursor in a visual direction, accounting for RTL text.
|
||||||
|
///
|
||||||
|
/// `forward` = `true` is visually rightward.
|
||||||
|
pub fn move_visual(&mut self, forward: bool, by_words: bool, rtl: bool, value: &Value) {
|
||||||
|
match (forward ^ rtl, by_words) {
|
||||||
|
(true, false) => self.move_right(value),
|
||||||
|
(true, true) => self.move_right_by_words(value),
|
||||||
|
(false, false) => self.move_left(value),
|
||||||
|
(false, true) => self.move_left_by_words(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extends the selection in a visual direction, accounting for RTL text.
|
||||||
|
pub fn select_visual(&mut self, forward: bool, by_words: bool, rtl: bool, value: &Value) {
|
||||||
|
match (forward ^ rtl, by_words) {
|
||||||
|
(true, false) => self.select_right(value),
|
||||||
|
(true, true) => self.select_right_by_words(value),
|
||||||
|
(false, false) => self.select_left(value),
|
||||||
|
(false, true) => self.select_left_by_words(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -132,11 +132,42 @@ impl Value {
|
||||||
graphemes: std::iter::repeat_n(String::from("•"), self.graphemes.len()).collect(),
|
graphemes: std::iter::repeat_n(String::from("•"), self.graphemes.len()).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl ToString for Value {
|
/// Converts a grapheme index to a byte index in the underlying string.
|
||||||
#[inline]
|
#[must_use]
|
||||||
fn to_string(&self) -> String {
|
pub fn byte_index_at_grapheme(&self, grapheme_index: usize) -> usize {
|
||||||
self.graphemes.concat()
|
self.graphemes[..grapheme_index.min(self.graphemes.len())]
|
||||||
|
.iter()
|
||||||
|
.map(|g| g.len())
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the grapheme index of the last occurrence of the given character,
|
||||||
|
/// searching from the end.
|
||||||
|
#[must_use]
|
||||||
|
pub fn rfind_char(&self, ch: char) -> Option<usize> {
|
||||||
|
let needle = ch.to_string();
|
||||||
|
self.graphemes.iter().rposition(|g| g == &needle)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a byte index to a grapheme index.
|
||||||
|
#[must_use]
|
||||||
|
pub fn grapheme_index_at_byte(&self, byte_index: usize) -> usize {
|
||||||
|
let mut bytes = 0;
|
||||||
|
for (i, g) in self.graphemes.iter().enumerate() {
|
||||||
|
if bytes >= byte_index {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
bytes += g.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.graphemes.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Value {
|
||||||
|
#[inline]
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(&self.graphemes.concat())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,10 @@ pub fn toaster<'a, Message: Clone + 'static>(
|
||||||
} = theme.cosmic().spacing;
|
} = theme.cosmic().spacing;
|
||||||
|
|
||||||
let make_toast = move |(id, toast): (ToastId, &'a Toast<Message>)| {
|
let make_toast = move |(id, toast): (ToastId, &'a Toast<Message>)| {
|
||||||
let row = row()
|
let row = row::with_capacity(2)
|
||||||
.push(text(&toast.message))
|
.push(text(&toast.message))
|
||||||
.push(
|
.push(
|
||||||
row()
|
row::with_capacity(2)
|
||||||
.push_maybe(toast.action.as_ref().map(|action| {
|
.push_maybe(toast.action.as_ref().map(|action| {
|
||||||
button::text(&action.description).on_press((action.message)(id))
|
button::text(&action.description).on_press((action.message)(id))
|
||||||
}))
|
}))
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,18 @@
|
||||||
|
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use crate::{Element, anim, iced_core::Border, iced_widget::toggler::Status};
|
use crate::{Element, anim};
|
||||||
use iced_core::{
|
use iced_core::{
|
||||||
Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment, event,
|
Border, Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment,
|
||||||
layout, mouse,
|
event, layout, mouse,
|
||||||
renderer::{self, Renderer},
|
renderer::{self, Renderer},
|
||||||
text,
|
text, touch,
|
||||||
widget::{self, Tree, tree},
|
widget::{self, Tree, tree},
|
||||||
window,
|
window,
|
||||||
};
|
};
|
||||||
use iced_widget::Id;
|
use iced_widget::{Id, toggler::Status};
|
||||||
|
|
||||||
pub use crate::iced_widget::toggler::{Catalog, Style};
|
pub use iced_widget::toggler::{Catalog, Style};
|
||||||
|
|
||||||
pub fn toggler<'a, Message>(is_checked: bool) -> Toggler<'a, Message> {
|
pub fn toggler<'a, Message>(is_checked: bool) -> Toggler<'a, Message> {
|
||||||
Toggler::new(is_checked)
|
Toggler::new(is_checked)
|
||||||
|
|
@ -161,7 +161,10 @@ impl<'a, Message> Widget<Message, crate::Theme, crate::Renderer> for Toggler<'a,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state(&self) -> tree::State {
|
fn state(&self) -> tree::State {
|
||||||
tree::State::new(State::default())
|
tree::State::new(State {
|
||||||
|
prev_toggled: self.is_toggled,
|
||||||
|
..State::default()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> Option<Id> {
|
fn id(&self) -> Option<Id> {
|
||||||
|
|
@ -200,7 +203,7 @@ impl<'a, Message> Widget<Message, crate::Theme, crate::Renderer> for Toggler<'a,
|
||||||
align_x: self.text_alignment,
|
align_x: self.text_alignment,
|
||||||
align_y: alignment::Vertical::Top,
|
align_y: alignment::Vertical::Top,
|
||||||
shaping: self.text_shaping,
|
shaping: self.text_shaping,
|
||||||
wrapping: crate::iced_core::text::Wrapping::default(),
|
wrapping: iced_core::text::Wrapping::default(),
|
||||||
ellipsize: self.ellipsize,
|
ellipsize: self.ellipsize,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -238,13 +241,23 @@ impl<'a, Message> Widget<Message, crate::Theme, crate::Renderer> for Toggler<'a,
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
|
// animate external changes
|
||||||
|
if state.prev_toggled != self.is_toggled {
|
||||||
|
state.anim.changed(self.duration);
|
||||||
|
shell.request_redraw();
|
||||||
|
state.prev_toggled = self.is_toggled;
|
||||||
|
}
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||||
|
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||||
let mouse_over = cursor_position.is_over(layout.bounds());
|
let mouse_over = cursor_position.is_over(layout.bounds());
|
||||||
|
|
||||||
if mouse_over {
|
if mouse_over {
|
||||||
shell.publish((on_toggle)(!self.is_toggled));
|
shell.publish((on_toggle)(!self.is_toggled));
|
||||||
state.anim.changed(self.duration);
|
state.anim.changed(self.duration);
|
||||||
|
state.prev_toggled = !self.is_toggled;
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -429,4 +442,5 @@ pub fn next_to_each_other(
|
||||||
pub struct State {
|
pub struct State {
|
||||||
text: widget::text::State<<crate::Renderer as iced_core::text::Renderer>::Paragraph>,
|
text: widget::text::State<<crate::Renderer as iced_core::text::Renderer>::Paragraph>,
|
||||||
anim: anim::State,
|
anim: anim::State,
|
||||||
|
prev_toggled: bool,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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