Compare commits

..

13 commits

Author SHA1 Message Date
3daac73eff chore: use local graphics dependencies 2026-06-13 08:35:47 +02:00
f84e98536c chore: use local COSMIC support crates 2026-06-13 08:35:47 +02:00
ae8be3a1eb fix: drop iced_winit/single-instance feature ref
Upstream dc84488c "fix(iced): double IME commit on GNOME desktop" wires
the libcosmic single-instance feature through iced_winit/single-instance,
but our pinned iced fork (yoda-wayland-only @ 81639935) does not carry
that feature yet. Drop the ref so libcosmic-yoda compiles after rebase;
the GNOME IME fix needs to be ported separately into the iced submodule
before this can be reinstated.

Leyoda 2026 – GPLv3
2026-06-13 08:35:47 +02:00
74e1e1baaf yoda: use local dbus settings bindings 2026-06-13 08:35:47 +02:00
0d184816cc feat(flex_row): reorderable rows with shadow constant gated (squashed)
Squash of 2 yoda commits:
- 7dd0ee83 feat: reorderable flex row
- b1b8203a fix: gate reorderable flex row shadow constant
2026-06-13 08:35:47 +02:00
03f3769373 yoda: warning cleanup sweep (dead code + clippy --fix) (squashed)
Squash of 4 yoda commits:
- 84437e21 yoda: libcosmic-yoda dead-code purge (14->0 warnings)
- 999db0a4 yoda: cosmic-theme cleanup (4->0 warnings) — workspace at 0 warnings total
- 4743bb8e yoda: cargo clippy --fix on libcosmic-yoda (115->33 warnings)
- 675f3b59 chore: reduce local stack warnings
2026-06-13 08:35:47 +02:00
f9ff7496f3 yoda: bump iced submodule + cargo auto-fix sweeps (squashed)
Squash of 6 yoda commits:
- 282813c8 yoda: bump iced -> window_clipboard via public Forgejo fork
- cdf34938 yoda: cargo fix --lib (libcosmic-yoda) — drop 99 trivial warnings
- 38a988cb yoda: cargo fix on cosmic-config + bump iced auto-fix commit
- 301bbf6e yoda: bump iced submodule -> iced_winit warning cleanup (0 left)
- b94c03d9 yoda: bump iced submodule -> iced_widget cleanup (0 left)
- a9492d76 yoda: bump iced submodule -> all iced crates at 0 warnings
2026-06-13 08:35:47 +02:00
ba0b06da3c feat(cosmic-theme): add apply_gtk_decoration_layout helper
Writes the gtk-decoration-layout key to ~/.config/gtk-{3,4}.0/settings.ini
and best-effort updates GNOME's button-layout GSettings key for apps that
still consult it. Factors out write_gtk_settings_key for reuse.

Leyoda 2026 – GPLv3
2026-06-13 08:35:47 +02:00
2134eedc67 yoda: bump iced submodule (Wayland-only + softbuffer cuts) (squashed)
Squash of 3 yoda commits:
- c118f5a2 yoda: bump iced submodule ref -> yoda-wayland-only HEAD 8f6be798
- 388e0655 yoda: bump iced submodule -> softbuffer + window_clipboard cuts
- 9b2a3643 Update iced warning fixes
2026-06-13 08:25:37 +02:00
ac9caf8c2a yoda: fork pivot — Wayland-only + ungate winit + soft-fork libcosmic-yoda (squashed)
Squash of 7 yoda commits forming the fork pivot:
- 255cf7cc rename: libcosmic -> libcosmic-yoda (fork 0.1.0-yoda)
- 8701aa31 feat(yoda): Wayland-only cut — drop winit and x11 features
- 6736a596 yoda: soft-fork pivot — keep Cargo name 'libcosmic' for dep unification
- 3e23d087 yoda: re-apply hard rename — libcosmic -> libcosmic-yoda (0.1.0-yoda)
- aec3eb61 yoda: ungate remaining winit+wayland combined cfgs
- 8ab7b158 yoda-v2: color_picker Theme ref + context_menu/menu ungate winit
- 8d1d8739 yoda: drop x11 defaults on iced_winit + iced_tiny_skia
2026-06-13 08:24:24 +02:00
afff77bbfe header_bar: add WindowControlsPosition (macOS-style left controls)
Adds a new public enum `WindowControlsPosition { Start, End }` and a
matching field on `HeaderBar`, allowing window controls (close / minimize
/ maximize) to be packed on the start side of the headerbar (macOS
style, icon order close → minimize → maximize) instead of the default
end side (Linux / GNOME style, minimize → maximize → close).

Wiring:
- `crate::widget::WindowControlsPosition` re-exported alongside
  `HeaderBar`.
- `HeaderBar::controls_position(Option<WindowControlsPosition>)` setter;
  when left unset, falls back to `crate::config::window_controls_position()`
  (reads `CosmicTk.window_controls_position`), mirroring how `density`
  falls back to `header_size()`.
- New `CosmicTk.window_controls_position` field with default `End` for
  backwards compatibility; serde-friendly enum so existing configs keep
  working via `#[serde(default)]` semantics.

Tested with cosmic-yoterm, cosmic-settings, cosmic-edit, cosmic-files
rebuilt against this libcosmic via a local `[patch]` override. Config
changes picked up live through the existing cosmic-config subscription.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-13 08:24:24 +02:00
b4a8fd65e6 feat(segmented_button): on_double_click + internal tab reorder (squashed)
Squash of 2 yoda commits:
- 108441ef segmented_button: add on_double_click callback
- a322516f segmented_button: fix internal tab reorder end-to-end
2026-06-13 08:24:24 +02:00
0c91735d11 perf: throttle malloc_trim + avoid VecDeque clones (squashed)
Squash of 2 yoda commits:
- 77262dd0 perf(malloc): throttle malloc_trim to 1 Hz in hot paths
- 1d98eee6 perf(widget): avoid VecDeque clone in segmented_button/table Model::clear
2026-06-13 08:24:24 +02:00
70 changed files with 933 additions and 3743 deletions

View file

@ -6,20 +6,26 @@ on:
- master
pull_request:
jobs:
format:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout sources
uses: actions/checkout@v6
uses: actions/checkout@v3
- name: Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
toolchain: nightly
components: rustfmt
rustflags: ""
- name: Cargo cache
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-cargo-rust_stable-${{ hashFiles('**/Cargo.toml') }}
- name: Format
run: cargo +nightly fmt -- --check
run: cargo fmt -- --check
tests:
needs:
@ -28,28 +34,38 @@ jobs:
fail-fast: false
matrix:
test_args:
- --no-default-features --features "" # for cosmic-comp, don't remove!
- --no-default-features --features "winit_debug"
- --no-default-features --features "winit_tokio"
- --no-default-features --features "winit"
- --no-default-features --features "winit_wgpu"
- --no-default-features --features "wayland"
- --no-default-features --features "applet"
- --no-default-features --features "desktop,smol"
- --no-default-features --features "desktop,tokio"
- -p cosmic-theme
runs-on: ubuntu-latest
- --no-default-features --features "" # for cosmic-comp, don't remove!
- --no-default-features --features "winit_debug"
- --no-default-features --features "winit_tokio"
- --no-default-features --features "winit"
- --no-default-features --features "winit_wgpu"
- --no-default-features --features "wayland"
- --no-default-features --features "applet"
- --no-default-features --features "desktop,smol"
- --no-default-features --features "desktop,tokio"
- -p cosmic-theme
runs-on: ubuntu-22.04
steps:
- name: Checkout sources
uses: actions/checkout@v6
uses: actions/checkout@v3
with:
submodules: recursive
- name: Get date for registry cache
id: date
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
- name: Cargo registry cache
uses: actions/cache@v3
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git
key: ${{ runner.os }}-cargo-registry-${{ steps.date.outputs.date }}
restore-keys: ${{ runner.os }}-cargo-registry-
- name: System dependencies
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
- name: Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: ""
uses: dtolnay/rust-toolchain@stable
- name: Test features
run: cargo test ${{ matrix.test_args }} -- --test-threads=1
env:
@ -66,18 +82,28 @@ jobs:
- "open-dialog"
- "context-menu"
- "nav-context"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout sources
uses: actions/checkout@v6
uses: actions/checkout@v3
with:
submodules: recursive
- name: Get date for registry cache
id: date
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
- name: Cargo registry cache
uses: actions/cache@v3
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git
key: ${{ runner.os }}-cargo-registry-${{ steps.date.outputs.date }}
restore-keys: ${{ runner.os }}-cargo-registry-
- name: System dependencies
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
- name: Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: ""
uses: dtolnay/rust-toolchain@stable
- name: Check example
run: cargo check -p "${{ matrix.examples }}"
env:

View file

@ -11,14 +11,13 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v6
uses: actions/checkout@v3
with:
submodules: recursive
- name: Install Rust nightly
uses: actions-rust-lang/setup-rust-toolchain@v1
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-04-27
rustflags: ""
- name: System dependencies
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
- name: Build documentation
@ -30,7 +29,7 @@ jobs:
-p libcosmic \
--verbose --features tokio,winit,wayland,desktop,single-instance,applet,xdg-portal,multi-window
- name: Deploy documentation
uses: peaceiris/actions-gh-pages@v4
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./target/doc

View file

@ -173,7 +173,6 @@ url = "2.5.8"
zbus = { workspace = true, optional = true }
float-cmp = "0.10.0"
ron = { workspace = true, optional = true }
enumflags2 = "0.7.12"
# Enable DBus feature on Linux targets
[target.'cfg(target_os = "linux")'.dependencies]
@ -290,7 +289,7 @@ serde = "1.0"
thiserror = "2.0"
tracing = "0.1"
tokio = "1.52"
zbus = { version = "5.15", default-features = false }
zbus = {version = "5.15", default-features = false}
# Speed up snapshot diffing in cosmic-theme tests. Cargo silently ignores
# [profile.*] blocks in non-root manifests, so this lives at the

View file

@ -1,32 +1,17 @@
use proc_macro::TokenStream;
use quote::quote;
use syn;
use syn::{self};
#[proc_macro_derive(CosmicConfigEntry, attributes(version, id, cosmic_config_entry))]
#[proc_macro_derive(CosmicConfigEntry, attributes(version, id))]
pub fn cosmic_config_entry_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_cosmic_config_entry_macro(&ast)
}
fn get_cosmic_config_attrs(field: &syn::Field) -> Result<Option<syn::Type>, syn::Error> {
let mut with = None;
for attr in &field.attrs {
if !attr.path().is_ident("cosmic_config_entry") {
continue;
}
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("with") {
let value = meta.value()?;
with = Some(value.parse()?);
}
Ok(())
})?;
}
Ok(with)
}
fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
let attributes = &ast.attrs;
let version = attributes
@ -63,54 +48,19 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
let write_each_config_field = fields.iter().map(|field| {
let field_name = &field.ident;
let with = match get_cosmic_config_attrs(field) {
Ok(attrs) => attrs,
Err(e) => {
return e.to_compile_error();
}
};
if let Some(with) = with {
quote! {
{
let conv = self.#field_name.clone().into();
cosmic_config::ConfigSet::set::<#with>(&tx, stringify!(#field_name), conv)?;
}
}
} else {
quote! {
cosmic_config::ConfigSet::set(&tx, stringify!(#field_name), &self.#field_name)?;
}
quote! {
cosmic_config::ConfigSet::set(&tx, stringify!(#field_name), &self.#field_name)?;
}
});
let get_each_config_field = fields.iter().map(|field| {
let field_name = &field.ident;
let field_type = &field.ty;
let with = match get_cosmic_config_attrs(field) {
Ok(attrs) => attrs,
Err(e) => {
return e.to_compile_error();
}
};
if let Some(with) = with {
quote! {
match cosmic_config::ConfigGet::get::<#with>(config, stringify!(#field_name)) {
Ok(value) => {
default.#field_name = value.into();
}
Err(why) if matches!(why, cosmic_config::Error::NoConfigDirectory) => (),
Err(e) => errors.push(e),
}
}
} else {
quote! {
match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) {
Ok(#field_name) => default.#field_name = #field_name,
Err(why) if matches!(why, cosmic_config::Error::NoConfigDirectory) => (),
Err(e) => errors.push(e),
}
quote! {
match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) {
Ok(#field_name) => default.#field_name = #field_name,
Err(why) if matches!(why, cosmic_config::Error::NoConfigDirectory) => (),
Err(e) => errors.push(e),
}
}
});
@ -118,39 +68,17 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
let update_each_config_field = fields.iter().map(|field| {
let field_name = &field.ident;
let field_type = &field.ty;
let with = match get_cosmic_config_attrs(field) {
Ok(attrs) => attrs,
Err(e) => {
return e.to_compile_error();
}
};
if let Some(with) = with {
quote! {
stringify!(#field_name) => {
match cosmic_config::ConfigGet::get::<#with>(config, stringify!(#field_name)) {
Ok(value) => {
let value = value.into();
if self.#field_name != value {
keys.push(stringify!(#field_name));
}
self.#field_name = value;
},
Err(e) => errors.push(e),
}
}
}
} else {
quote! {
stringify!(#field_name) => {
match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) {
Ok(value) => {
if self.#field_name != value {
keys.push(stringify!(#field_name));
}
self.#field_name = value;
},
Err(e) => errors.push(e),
quote! {
stringify!(#field_name) => {
match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) {
Ok(value) => {
if self.#field_name != value {
keys.push(stringify!(#field_name));
}
self.#field_name = value;
},
Err(e) => {
errors.push(e);
}
}
}

View file

@ -175,7 +175,6 @@ pub trait ConfigSet {
pub struct Config {
system_path: Option<PathBuf>,
user_path: Option<PathBuf>,
previous: Option<Box<Config>>,
}
/// Check that the name is relative and doesn't contain . or ..
@ -194,13 +193,9 @@ fn sanitize_name(name: &str) -> Result<&Path, Error> {
impl Config {
/// Get a system config for the given name and config version
pub fn system(name: &str, version: u64) -> Result<Self, Error> {
Self::system_inner(name, version, true)
}
fn system_inner(name: &str, version: u64, look_for_previous: bool) -> Result<Self, Error> {
let path = sanitize_name(name)?.join(format!("v{version}"));
#[cfg(unix)]
let system_path = xdg::BaseDirectories::with_prefix("cosmic").find_data_file(&path);
let system_path = xdg::BaseDirectories::with_prefix("cosmic").find_data_file(path);
#[cfg(windows)]
let system_path =
@ -210,13 +205,6 @@ impl Config {
Ok(Self {
system_path,
user_path: None,
previous: if version > 1 && look_for_previous {
Self::system_inner(name, version - 1, false)
.ok()
.map(Box::new)
} else {
None
},
})
}
@ -224,10 +212,6 @@ impl Config {
// Use folder at XDG config/name for config storage, return Config if successful
//TODO: fallbacks for flatpak (HOST_XDG_CONFIG_HOME, xdg-desktop settings proxy)
pub fn new(name: &str, version: u64) -> Result<Self, Error> {
Self::new_inner(name, version, true)
}
fn new_inner(name: &str, version: u64, look_for_previous: bool) -> Result<Self, Error> {
// Look for [name]/v[version]
let path = sanitize_name(name)?.join(format!("v{}", version));
@ -252,29 +236,15 @@ impl Config {
Ok(Self {
system_path,
user_path: Some(user_path),
previous: if version > 1 && look_for_previous {
Self::new_inner(name, version - 1, false).ok().map(Box::new)
} else {
None
},
})
}
/// Get config for the given application name and config version and custom path.
pub fn with_custom_path(name: &str, version: u64, custom_path: PathBuf) -> Result<Self, Error> {
Self::with_custom_path_inner(name, version, custom_path, true)
}
fn with_custom_path_inner(
name: &str,
version: u64,
custom_path: PathBuf,
look_for_previous: bool,
) -> Result<Self, Error> {
// Look for [name]/v[version]
let path = sanitize_name(name)?.join(format!("v{version}"));
let mut user_path = custom_path.clone();
let mut user_path = custom_path;
user_path.push("cosmic");
user_path.push(path);
// Create new configuration directory if not found.
@ -284,13 +254,6 @@ impl Config {
Ok(Self {
system_path: None,
user_path: Some(user_path),
previous: if version > 1 && look_for_previous {
Self::with_custom_path_inner(name, version - 1, custom_path.clone(), false)
.ok()
.map(Box::new)
} else {
None
},
})
}
@ -300,10 +263,6 @@ impl Config {
// Use folder at XDG config/name for config storage, return Config if successful
//TODO: fallbacks for flatpak (HOST_XDG_CONFIG_HOME, xdg-desktop settings proxy)
pub fn new_state(name: &str, version: u64) -> Result<Self, Error> {
Self::new_state_inner(name, version, true)
}
fn new_state_inner(name: &str, version: u64, look_for_previous: bool) -> Result<Self, Error> {
// Look for [name]/v[version]
let path = sanitize_name(name)?.join(format!("v{}", version));
@ -317,27 +276,11 @@ impl Config {
Ok(Self {
system_path: None,
user_path: Some(user_path),
previous: if version > 1 && look_for_previous {
Self::new_state_inner(name, version - 1, false)
.ok()
.map(Box::new)
} else {
None
},
})
}
/// Get data for the given application name and config version.
/// pub fn new_state(name: &str, version: u64) -> Result<Self, Error> {
pub fn new_data(name: &str, version: u64) -> Result<Self, Error> {
Self::new_data_inner(name, version, true)
}
pub fn new_data_inner(
name: &str,
version: u64,
look_for_previous: bool,
) -> Result<Self, Error> {
// Look for [name]/v[version]
let path = sanitize_name(name)?.join(format!("v{}", version));
@ -351,13 +294,6 @@ impl Config {
Ok(Self {
system_path: None,
user_path: Some(user_path),
previous: if version > 1 && look_for_previous {
Self::new_data_inner(name, version - 1, false)
.ok()
.map(Box::new)
} else {
None
},
})
}
@ -468,13 +404,7 @@ impl ConfigGet for Config {
Ok(ron::from_str(&data)?)
}
_ => {
if let Some(previous) = self.previous.as_ref() {
previous.get_local(key)
} else {
Err(Error::NotFound)
}
}
_ => Err(Error::NotFound),
}
}

View file

@ -16,7 +16,6 @@ no-default = []
[dependencies]
palette = { workspace = true, features = ["serializing"] }
hex_color = { version = "3", features = ["serde"] }
almost = "0.2"
serde = { workspace = true, features = ["derive"] }
serde_json = { version = "1.0.149", optional = true, features = [

View file

@ -1,173 +0,0 @@
//! Color representation and serde helpers for the Cosmic theme
use hex_color::HexColor;
use palette::{Srgb, Srgba};
use serde::{Deserialize, Serialize};
/// A color in the Cosmic theme for serialization and deserialization
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum ColorRepr {
/// A color represented as a hex string
#[serde(with = "hex_color::rgba")]
Hex(HexColor),
/// A color represented as an RGBA value
Rgba(Srgba),
/// A color represented as an RGB value
Rgb(Srgb),
}
/// An optional color in the Cosmic theme for serialization and deserialization
#[repr(transparent)]
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
#[serde(transparent)]
pub struct ColorReprOption(Option<ColorRepr>);
impl From<Srgb> for ColorRepr {
fn from(color: Srgb) -> Self {
let rgb_u8: Srgb<u8> = color.into_format();
ColorRepr::Hex(HexColor {
r: rgb_u8.red,
g: rgb_u8.green,
b: rgb_u8.blue,
a: 255,
})
}
}
impl From<Srgba> for ColorRepr {
fn from(color: Srgba) -> Self {
let rgba_u8: Srgba<u8> = color.into_format();
ColorRepr::Hex(HexColor {
r: rgba_u8.red,
g: rgba_u8.green,
b: rgba_u8.blue,
a: rgba_u8.alpha,
})
}
}
impl From<ColorRepr> for Srgb {
fn from(value: ColorRepr) -> Self {
match value {
ColorRepr::Hex(hex) => Srgb::<u8>::new(hex.r, hex.g, hex.b).into_format(),
ColorRepr::Rgb(rgb) => rgb,
ColorRepr::Rgba(rgba) => Srgb::new(rgba.red, rgba.green, rgba.blue),
}
}
}
impl From<ColorRepr> for Srgba {
fn from(value: ColorRepr) -> Self {
match value {
ColorRepr::Hex(hex) => Srgba::<u8>::new(hex.r, hex.g, hex.b, hex.a).into_format(),
ColorRepr::Rgb(rgb) => Srgba::new(rgb.red, rgb.green, rgb.blue, 1.0),
ColorRepr::Rgba(rgba) => rgba,
}
}
}
impl From<ColorReprOption> for Option<Srgb> {
fn from(value: ColorReprOption) -> Self {
value.0.map(std::convert::Into::into)
}
}
impl From<ColorReprOption> for Option<Srgba> {
fn from(value: ColorReprOption) -> Self {
value.0.map(std::convert::Into::into)
}
}
impl From<Option<Srgb>> for ColorReprOption {
fn from(value: Option<Srgb>) -> Self {
ColorReprOption(value.map(std::convert::Into::into))
}
}
impl From<Option<Srgba>> for ColorReprOption {
fn from(value: Option<Srgba>) -> Self {
ColorReprOption(value.map(std::convert::Into::into))
}
}
/// A trait for converting between a color type and its representation for serialization and deserialization
pub trait ConvColorRepr: Sized {
/// Convert from a color representation to the color type
fn from_repr(repr: ColorRepr) -> Self;
/// Convert from the color type to its representation for serialization
fn to_repr(&self) -> ColorRepr;
}
impl ConvColorRepr for Srgba {
fn from_repr(repr: ColorRepr) -> Self {
repr.into()
}
fn to_repr(&self) -> ColorRepr {
(*self).into()
}
}
impl ConvColorRepr for Srgb {
fn from_repr(repr: ColorRepr) -> Self {
repr.into()
}
fn to_repr(&self) -> ColorRepr {
(*self).into()
}
}
/// Serde helpers for serializing and deserializing colors in the Cosmic theme
pub mod color_serde {
use super::*;
use serde::{Deserialize, Deserializer, Serializer};
/// Serialize a color to a hex string
pub fn serialize<T, S>(color: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: ConvColorRepr,
S: Serializer,
{
let repr = color.to_repr();
repr.serialize(serializer)
}
/// Deserialize a color from a hex string or RGB/RGBA
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: ConvColorRepr,
D: Deserializer<'de>,
{
let repr = ColorRepr::deserialize(deserializer)?;
Ok(T::from_repr(repr))
}
/// Serde helpers for serializing and deserializing optional colors in the Cosmic theme
pub mod option {
use super::*;
/// Serialize an optional color
pub fn serialize<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
T: ConvColorRepr,
S: Serializer,
{
match value {
Some(v) => Some(v.to_repr()).serialize(serializer),
None => serializer.serialize_none(),
}
}
/// Deserialize an optional color
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
T: ConvColorRepr,
D: Deserializer<'de>,
{
let opt = Option::<ColorRepr>::deserialize(deserializer)?;
Ok(opt.map(T::from_repr))
}
}
}

View file

@ -1,4 +1,3 @@
use crate::color::color_serde;
use palette::Srgba;
use serde::{Deserialize, Serialize};
use std::sync::LazyLock;
@ -96,107 +95,75 @@ pub struct CosmicPaletteInner {
/// Utility Colors
/// Colors used for various points of emphasis in the UI.
#[serde(with = "color_serde")]
pub bright_red: Srgba,
/// Colors used for various points of emphasis in the UI.
#[serde(with = "color_serde")]
pub bright_green: Srgba,
/// Colors used for various points of emphasis in the UI.
#[serde(with = "color_serde")]
pub bright_orange: Srgba,
/// Surface Grays
/// Colors used for three levels of surfaces in the UI.
#[serde(with = "color_serde")]
pub gray_1: Srgba,
/// Colors used for three levels of surfaces in the UI.
#[serde(with = "color_serde")]
pub gray_2: Srgba,
/// System Neutrals
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_0: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_1: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_2: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_3: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_4: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_5: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_6: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_7: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_8: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_9: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_10: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_blue: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_indigo: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_purple: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_pink: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_red: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_orange: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_yellow: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_green: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_warm_grey: Srgba,
/// Extended Color Palette
/// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_warm_grey: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_orange: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_yellow: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_blue: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_purple: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_pink: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_indigo: Srgba,
}

View file

@ -1 +1 @@
Dark((name: "cosmic-dark",bright_red: "#FFA09AFF",bright_green: "#5EDB8CFF",bright_orange: "#FFA37DFF",gray_1: "#1B1B1BFF",gray_2: "#262626FF",neutral_0: "#000000FF",neutral_1: "#030303FF",neutral_2: "#161616FF",neutral_3: "#2E2E2EFF",neutral_4: "#484848FF",neutral_5: "#636363FF",neutral_6: "#808080FF",neutral_7: "#9E9E9EFF",neutral_8: "#BEBEBEFF",neutral_9: "#DEDEDEFF",neutral_10: "#FFFFFFFF",accent_blue: "#63D0DFFF",accent_indigo: "#A1C0EBFF",accent_purple: "#E79CFEFF",accent_pink: "#FF9CB1FF",accent_red: "#FDA1A0FF",accent_orange: "#FFAD00FF",accent_yellow: "#F7E062FF",accent_green: "#92CF9CFF",accent_warm_grey: "#CABAB4FF",ext_warm_grey: "#9B8E8AFF",ext_orange: "#FFAD00FF",ext_yellow: "#FEDB40FF",ext_blue: "#48B9C7FF",ext_purple: "#CF7DFFFF",ext_pink: "#F93A83FF",ext_indigo: "#3E88FFFF",))
Dark((name:"cosmic-dark",bright_red:(red:1.0,green:0.62745098,blue:0.60392157,alpha:1.0),bright_green:(red:0.36862745,green:0.85882352,blue:0.54901960,alpha:1.0),bright_orange:(red:1.0,green:0.63921569,blue:0.49019608,alpha:1.0),gray_1:(red:0.10588235,green:0.10588235,blue:0.10588235,alpha:1.0),gray_2:(red:0.14901961,green:0.14901961,blue:0.14901961,alpha:1.0),neutral_0:(red:0.0,green:0.0,blue:0.0,alpha:1.0),neutral_1:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_2:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_3:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_4:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_7:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_8:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_9:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_10:(red:1.0,green:1.0,blue:1.0,alpha:1.0),accent_blue:(red:0.3882353,green:0.81568627,blue:0.87450981,alpha:1.0),accent_indigo:(red:0.63137255,green:0.75294118,blue:0.92156863,alpha:1.0),accent_purple:(red:0.90588235,green:0.61176471,blue:0.99607843,alpha:1.0),accent_pink:(red:1.0,green:0.61176471,blue:0.69411765,alpha:1.0),accent_red:(red:0.99215686,green:0.63137255,blue:0.62745098,alpha:1.0),accent_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),accent_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),accent_green:(red:0.57254902,green:0.81176471,blue:0.61176471,alpha:1.0),accent_warm_grey:(red:0.79215686,green:0.72941176,blue:0.70588235,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),ext_yellow:(red:0.99607843,green:0.85882353,blue:0.25098039,alpha:1.0),ext_blue:(red:0.28235294,green:0.72549020,blue:0.78039216,alpha:1.0),ext_purple:(red:0.81176471,green:0.49019608,blue:1.0,alpha:1.0),ext_pink:(red:0.97647059,green:0.22745098,blue:0.51372549,alpha:1.0),ext_indigo:(red:0.24313725,green:0.53333333,blue:1.0,alpha:1.0)))

View file

@ -1,4 +1,3 @@
use crate::color::color_serde;
use palette::{Srgba, WithAlpha};
use serde::{Deserialize, Serialize};
@ -9,18 +8,14 @@ use crate::composite::over;
#[must_use]
pub struct Container {
/// the color of the container
#[serde(with = "color_serde")]
pub base: Srgba,
/// the color of components in the container
pub component: Component,
/// the color of dividers in the container
#[serde(with = "color_serde")]
pub divider: Srgba,
/// the color of text in the container
#[serde(with = "color_serde")]
pub on: Srgba,
/// the color of @small_widget_container
#[serde(with = "color_serde")]
pub small_widget: Srgba,
}
@ -50,42 +45,30 @@ impl Container {
#[must_use]
pub struct Component {
/// The base color of the widget
#[serde(with = "color_serde")]
pub base: Srgba,
/// The color of the widget when it is hovered
#[serde(with = "color_serde")]
pub hover: Srgba,
/// the color of the widget when it is pressed
#[serde(with = "color_serde")]
pub pressed: Srgba,
/// the color of the widget when it is selected
#[serde(with = "color_serde")]
pub selected: Srgba,
/// the color of the widget when it is selected
#[serde(with = "color_serde")]
pub selected_text: Srgba,
/// the color of the widget when it is focused
#[serde(with = "color_serde")]
pub focus: Srgba,
/// the color of dividers for this widget
#[serde(with = "color_serde")]
pub divider: Srgba,
/// the color of text for this widget
#[serde(with = "color_serde")]
pub on: Srgba,
// the color of text with opacity 80 for this widget
// pub text_opacity_80: Srgba,
/// the color of the widget when it is disabled
#[serde(with = "color_serde")]
pub disabled: Srgba,
/// the color of text in the widget when it is disabled
#[serde(with = "color_serde")]
pub on_disabled: Srgba,
/// the color of the border for the widget
#[serde(with = "color_serde")]
pub border: Srgba,
/// the color of the border for the widget when it is disabled
#[serde(with = "color_serde")]
pub disabled_border: Srgba,
}

View file

@ -1 +1 @@
Light((name: "cosmic-light",bright_red: "#890418FF",bright_green: "#00572CFF",bright_orange: "#792C00FF",gray_1: "#D7D7D7FF",gray_2: "#E4E4E4FF",neutral_0: "#FFFFFFFF",neutral_1: "#DEDEDEFF",neutral_2: "#BEBEBEFF",neutral_3: "#9E9E9EFF",neutral_4: "#808080FF",neutral_5: "#636363FF",neutral_6: "#484848FF",neutral_7: "#2E2E2EFF",neutral_8: "#161616FF",neutral_9: "#030303FF",neutral_10: "#000000FF",accent_blue: "#00525AFF",accent_indigo: "#2E496DFF",accent_purple: "#68217CFF",accent_pink: "#86043AFF",accent_red: "#78292EFF",accent_orange: "#624000FF",accent_yellow: "#534800FF",accent_green: "#185529FF",accent_warm_grey: "#554742FF",ext_warm_grey: "#9B8E8AFF",ext_orange: "#FBB86CFF",ext_yellow: "#F7E062FF",ext_blue: "#6ACAD8FF",ext_purple: "#D58CFFFF",ext_pink: "#FF9CDDFF",ext_indigo: "#95C4FCFF",))
Light((name:"cosmic-light",bright_red:(red:0.53725490,green:0.01568627,blue:0.09411765,alpha:1.0),bright_green:(red:0.0,green:0.34117647,blue:0.17254901,alpha:1.0),bright_orange:(red:0.47450980,green:0.17254902,blue:0.0,alpha:1.0),gray_1:(red:0.84313725,green:0.84313725,blue:0.84313725,alpha:1.0),gray_2:(red:0.89411765,green:0.89411765,blue:0.89411765,alpha:1.0),neutral_0:(red:1.0,green:1.0,blue:1.0,alpha:1.0),neutral_1:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_2:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_3:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_4:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_7:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_8:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_9:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_10:(red:0.0,green:0.0,blue:0.0,alpha:1.0),accent_blue:(red:0.0,green:0.32156863,blue:0.35294118,alpha:1.0),accent_indigo:(red:0.18039216,green:0.28627451,blue:0.42745098,alpha:1.0),accent_purple:(red:0.40784314,green:0.12941176,blue:0.48627451,alpha:1.0),accent_pink:(red:0.52549020,green:0.01568627,blue:0.22745098,alpha:1.0),accent_red:(red:0.47058824,green:0.16078431,blue:0.18039216,alpha:1.0),accent_orange:(red:0.38431373,green:0.25098039,blue:0.0,alpha:1.0),accent_yellow:(red:0.32549020,green:0.28235294,blue:0.0,alpha:1.0),accent_green:(red:0.09411765,green:0.33333333,blue:0.16078431,alpha:1.0),accent_warm_grey:(red:0.33333333,green:0.27843137,blue:0.25882353,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:0.98431373,green:0.72156863,blue:0.42352941,alpha:1.0),ext_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),ext_blue:(red:0.41568627,green:0.79215686,blue:0.84705882,alpha:1.0),ext_purple:(red:0.83529412,green:0.54901961,blue:1.0,alpha:1.0),ext_pink:(red:1.0,green:0.61176471,blue:0.86666667,alpha:1.0),ext_indigo:(red:0.58431373,green:0.76862745,blue:0.98823529,alpha:1.0)))

View file

@ -5,7 +5,6 @@ pub use mode::*;
pub use spacing::*;
pub use theme::*;
pub mod color;
mod corner;
mod cosmic_palette;
mod derivation;

View file

@ -1,5 +1,3 @@
use crate::color::color_serde::option as color_serde_option;
use crate::color::{ColorRepr, ColorReprOption, color_serde};
use crate::composite::over;
use crate::steps::{color_index, get_small_widget_color, get_surface_color, get_text, steps};
use crate::{
@ -11,7 +9,6 @@ use palette::color_difference::Wcag21RelativeContrast;
use palette::rgb::Rgb;
use palette::{IntoColor, Oklcha, Srgb, Srgba, WithAlpha};
use serde::{Deserialize, Serialize};
use std::default;
use std::num::NonZeroUsize;
/// ID for the current dark `ThemeBuilder` config
@ -48,24 +45,16 @@ pub enum Layer {
PartialEq,
cosmic_config::cosmic_config_derive::CosmicConfigEntry,
)]
#[version = 2]
#[version = 1]
pub struct Theme {
/// name of the theme
pub name: String,
/// background element colors
pub(crate) background: Container,
pub background: Container,
/// primary element colors
pub(crate) primary: Container,
pub primary: Container,
/// secondary element colors
pub(crate) secondary: Container,
/// background element colors
pub(crate) transparent_background: Container,
/// primary element colors
pub(crate) transparent_primary: Container,
/// secondary element colors
pub(crate) transparent_secondary: Container,
/// button component styling
pub button: Component,
pub secondary: Container,
/// accent element colors
pub accent: Component,
/// suggested element colors
@ -90,6 +79,8 @@ pub struct Theme {
pub list_button: Component,
/// text button element colors
pub text_button: Component,
/// button component styling
pub button: Component,
/// palette
pub palette: CosmicPaletteInner,
/// spacing
@ -106,40 +97,16 @@ pub struct Theme {
pub active_hint: u32,
/// cosmic-comp custom window hint color
pub window_hint: Option<Srgb>,
#[serde(default)]
/// enables blurred transparency
pub frosted: BlurStrength,
#[serde(default)]
/// frosted windows
pub frosted_windows: bool,
#[serde(default)]
/// frosted system interface
pub frosted_system_interface: bool,
#[serde(default)]
/// frosted panel
pub frosted_panel: bool,
#[serde(default)]
/// frosted applet popups
pub frosted_applets: bool,
#[serde(default)]
/// alpha map
pub alpha_map: AlphaMap,
pub is_frosted: bool,
/// shade color for dialogs
#[serde(with = "color_serde")]
#[cosmic_config_entry(with = ColorRepr)]
pub shade: Srgba,
/// accent text colors
/// If None, accent base color is the accent text color.
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub accent_text: Option<Srgba>,
/// control tint color
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub control_tint: Option<Srgb>,
/// text tint color
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub text_tint: Option<Srgb>,
}
@ -205,39 +172,6 @@ impl Theme {
todo!();
}
#[allow(clippy::doc_markdown)]
#[inline]
/// get opaque or transparent background based on whether blur is active
pub fn background(&self, transparent: bool) -> &Container {
if transparent {
&self.transparent_background
} else {
&self.background
}
}
#[allow(clippy::doc_markdown)]
#[inline]
/// get opaque or transparent primary based on whether blur is active
pub fn primary(&self, transparent: bool) -> &Container {
if transparent {
&self.transparent_primary
} else {
&self.primary
}
}
#[allow(clippy::doc_markdown)]
#[inline]
/// get opaque or transparent secondary based on whether blur is active
pub fn secondary(&self, transparent: bool) -> &Container {
if transparent {
&self.transparent_secondary
} else {
&self.secondary
}
}
#[must_use]
#[allow(clippy::doc_markdown)]
#[inline]
@ -807,7 +741,7 @@ impl Theme {
if color_scheme.trim().contains("default") || color_scheme.trim().contains("light") {
return Self::light_default();
}
}
};
Self::dark_default()
}
@ -816,10 +750,10 @@ impl Theme {
pub fn preferred_theme() -> Self {
let current_desktop = std::env::var("XDG_CURRENT_DESKTOP");
if let Ok(desktop) = current_desktop
&& desktop.trim().to_lowercase().contains("gnome")
{
return Self::gtk_prefer_colorscheme();
if let Ok(desktop) = current_desktop {
if desktop.trim().to_lowercase().contains("gnome") {
return Self::gtk_prefer_colorscheme();
}
}
Self::dark_default()
@ -842,7 +776,7 @@ impl From<CosmicPalette> for Theme {
cosmic_config::cosmic_config_derive::CosmicConfigEntry,
PartialEq,
)]
#[version = 2]
#[version = 1]
pub struct ThemeBuilder {
/// override the palette for the builder
pub palette: CosmicPalette,
@ -851,67 +785,31 @@ pub struct ThemeBuilder {
/// override corner radii for the builder
pub corner_radii: CornerRadii,
/// override neutral_tint for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub neutral_tint: Option<Srgb>,
/// override bg_color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub bg_color: Option<Srgba>,
/// override the primary container bg color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub primary_container_bg: Option<Srgba>,
/// override the secontary container bg color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub secondary_container_bg: Option<Srgba>,
/// override the text tint for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub text_tint: Option<Srgb>,
/// override the accent color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub accent: Option<Srgb>,
/// override the success color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub success: Option<Srgb>,
/// override the warning color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub warning: Option<Srgb>,
/// override the destructive color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub destructive: Option<Srgb>,
#[serde(default)]
/// enabled blurred transparency
pub frosted: BlurStrength,
pub is_frosted: bool, // TODO handle
/// cosmic-comp window gaps size (outer, inner)
pub gaps: (u32, u32),
/// cosmic-comp active hint window outline width
pub active_hint: u32,
/// cosmic-comp custom window hint color
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub window_hint: Option<Srgb>,
#[serde(default)]
/// frosted windows
pub frosted_windows: bool,
#[serde(default)]
/// frosted system interface
pub frosted_system_interface: bool,
#[serde(default)]
/// frosted panel
pub frosted_panel: bool,
#[serde(default)]
/// frosted applet popups
pub frosted_applets: bool,
#[serde(default)]
/// alpha map
pub alpha_map: AlphaMap,
}
impl Default for ThemeBuilder {
@ -929,16 +827,11 @@ impl Default for ThemeBuilder {
success: Default::default(),
warning: Default::default(),
destructive: Default::default(),
frosted: BlurStrength::default(),
is_frosted: false,
// cosmic-comp theme settings
gaps: (0, 8),
active_hint: 3,
window_hint: None,
frosted_windows: false,
frosted_system_interface: false,
frosted_panel: false,
frosted_applets: false,
alpha_map: AlphaMap::default(),
}
}
}
@ -1086,22 +979,9 @@ impl ThemeBuilder {
gaps,
active_hint,
window_hint,
frosted,
frosted_windows,
frosted_system_interface,
frosted_panel,
frosted_applets,
alpha_map,
is_frosted,
} = self;
let container_alpha = alpha_map.blurred_alpha(frosted);
let actual_alpha =
if (frosted_windows || frosted_system_interface || frosted_panel || frosted_applets) {
container_alpha
} else {
1.0
};
let is_dark = palette.is_dark();
let is_high_contrast = palette.is_high_contrast();
@ -1156,10 +1036,6 @@ impl ThemeBuilder {
let step_array = steps(bg, NonZeroUsize::new(100).unwrap());
let bg_index = color_index(bg, step_array.len());
let mut transparent_bg = bg;
transparent_bg.alpha = container_alpha;
let transparent_bg_steps_array = steps(transparent_bg, NonZeroUsize::new(100).unwrap());
let mut component_hovered_overlay = if bg_index < 91 {
control_steps_array[10]
} else {
@ -1184,26 +1060,10 @@ impl ThemeBuilder {
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
);
let transparent_bg_component = get_surface_color(
bg_index,
8,
&transparent_bg_steps_array,
is_dark,
&p_ref.neutral_2,
);
let on_transparent_bg_component = get_text(
color_index(transparent_bg_component, transparent_bg_steps_array.len()),
&transparent_bg_steps_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
);
let primary = {
let mut container_bg = if let Some(primary_container_bg_color) = primary_container_bg {
let container_bg = if let Some(primary_container_bg_color) = primary_container_bg {
primary_container_bg_color
} else {
get_surface_color(bg_index, 5, &step_array, is_dark, &control_steps_array[1])
@ -1233,7 +1093,6 @@ impl ThemeBuilder {
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
container_alpha,
),
component_hovered_overlay,
component_pressed_overlay,
@ -1246,48 +1105,6 @@ impl ThemeBuilder {
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
container_alpha,
),
get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]),
is_high_contrast,
)
};
let transparent_primary = {
let mut container_bg = if let Some(primary_container_bg_color) = primary_container_bg {
primary_container_bg_color
} else {
get_surface_color(bg_index, 5, &step_array, is_dark, &control_steps_array[1])
};
container_bg.alpha = (container_alpha + if is_dark { 0.3 } else { 0.25 }).min(1.0);
let step_array = steps(container_bg, NonZeroUsize::new(100).unwrap());
let base_index: usize = color_index(container_bg, step_array.len());
let component_base =
get_surface_color(base_index, 6, &step_array, is_dark, &control_steps_array[3]);
Container::new(
Component::component(
component_base,
accent,
get_text(
color_index(component_base, step_array.len()),
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
),
Srgba::new(0., 0., 0., 0.0),
Srgba::new(0., 0., 0., 0.0),
is_high_contrast,
control_steps_array[8],
),
container_bg,
get_text(
base_index,
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
),
get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]),
is_high_contrast,
@ -1361,7 +1178,6 @@ impl ThemeBuilder {
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
),
get_small_widget_color(bg_index, 5, &neutral_steps, &control_steps_array[6]),
is_high_contrast,
@ -1398,7 +1214,6 @@ impl ThemeBuilder {
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
),
component_hovered_overlay,
component_pressed_overlay,
@ -1411,7 +1226,6 @@ impl ThemeBuilder {
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
),
get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]),
is_high_contrast,
@ -1424,24 +1238,14 @@ impl ThemeBuilder {
button_hovered_overlay,
button_pressed_overlay,
),
accent_button: {
let step_array = steps(control_steps_array[5], NonZeroUsize::new(100).unwrap());
Component::colored_button(
accent,
control_steps_array[1],
get_text(
color_index(accent, step_array.len()),
&step_array,
&step_array[0],
text_steps_array.as_deref(),
1., // accent is opaque anyway
),
accent,
button_hovered_overlay,
button_pressed_overlay,
)
},
accent_button: Component::colored_button(
accent,
control_steps_array[1],
control_steps_array[0],
accent,
button_hovered_overlay,
button_pressed_overlay,
),
button: Component::component(
button_bg,
accent,
@ -1545,79 +1349,10 @@ impl ThemeBuilder {
gaps,
active_hint,
window_hint,
frosted,
is_frosted,
accent_text,
control_tint: neutral_tint,
text_tint,
frosted_windows,
frosted_system_interface,
frosted_panel,
frosted_applets,
alpha_map,
transparent_background: Container::new(
Component::component(
transparent_bg_component,
accent,
on_transparent_bg_component,
component_hovered_overlay,
component_pressed_overlay,
is_high_contrast,
control_steps_array[8],
),
transparent_bg,
get_text(
bg_index,
&transparent_bg_steps_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
),
get_small_widget_color(bg_index, 5, &neutral_steps, &control_steps_array[6]),
is_high_contrast,
),
transparent_primary,
transparent_secondary: {
let mut container_bg = if let Some(secondary_container_bg) = secondary_container_bg
{
secondary_container_bg
} else {
get_surface_color(bg_index, 10, &step_array, is_dark, &control_steps_array[2])
};
container_bg.alpha = (container_alpha + if is_dark { 0.6 } else { 0.5 }).min(1.0);
let step_array = steps(container_bg, NonZeroUsize::new(100).unwrap());
let base_index = color_index(container_bg, step_array.len());
let secondary_component =
get_surface_color(base_index, 3, &step_array, is_dark, &control_steps_array[4]);
Container::new(
Component::component(
secondary_component,
accent,
get_text(
color_index(secondary_component, step_array.len()),
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
),
component_hovered_overlay,
component_pressed_overlay,
is_high_contrast,
control_steps_array[8],
),
container_bg,
get_text(
base_index,
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
),
get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]),
is_high_contrast,
)
},
};
theme.spacing = spacing;
theme.corner_radii = corner_radii;
@ -1636,181 +1371,3 @@ impl ThemeBuilder {
Config::new(LIGHT_THEME_BUILDER_ID, Self::VERSION)
}
}
/// Actual blur radius is decided by cosmic-comp,
/// but this represents the strength of the blur effect.
#[allow(missing_docs)]
#[repr(u8)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum BlurStrength {
ExtremelyLow,
ExtremelyLow2,
VeryLow,
VeryLow2,
Low,
Low2,
Medium,
Medium2,
High,
High2,
VeryHigh,
VeryHigh2,
ExtremelyHigh,
ExtremelyHigh2,
}
impl Default for BlurStrength {
fn default() -> Self {
Self::Medium
}
}
impl TryFrom<u8> for BlurStrength {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(BlurStrength::ExtremelyLow),
1 => Ok(BlurStrength::ExtremelyLow2),
2 => Ok(BlurStrength::VeryLow),
3 => Ok(BlurStrength::VeryLow2),
4 => Ok(BlurStrength::Low),
5 => Ok(BlurStrength::Low2),
6 => Ok(BlurStrength::Medium),
7 => Ok(BlurStrength::Medium2),
8 => Ok(BlurStrength::High),
9 => Ok(BlurStrength::High2),
10 => Ok(BlurStrength::VeryHigh),
11 => Ok(BlurStrength::VeryHigh2),
12 => Ok(BlurStrength::ExtremelyHigh),
13 => Ok(BlurStrength::ExtremelyHigh2),
_ => Err(()),
}
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct AlphaMap {
pub extremely_low: f32,
pub extremely_low_2: f32,
pub very_low: f32,
pub very_low_2: f32,
pub low: f32,
pub low_2: f32,
pub medium: f32,
pub medium_2: f32,
pub high: f32,
pub high_2: f32,
pub very_high: f32,
pub very_high_2: f32,
pub extremely_high: f32,
pub extremely_high_2: f32,
}
impl AlphaMap {
pub fn blurred_alpha(&self, blur: BlurStrength) -> f32 {
match blur {
BlurStrength::ExtremelyLow => self.extremely_low,
BlurStrength::ExtremelyLow2 => self.extremely_low_2,
BlurStrength::VeryLow => self.very_low,
BlurStrength::VeryLow2 => self.very_low_2,
BlurStrength::Low => self.low,
BlurStrength::Low2 => self.low_2,
BlurStrength::Medium => self.medium,
BlurStrength::Medium2 => self.medium_2,
BlurStrength::High => self.high,
BlurStrength::High2 => self.high_2,
BlurStrength::VeryHigh => self.very_high,
BlurStrength::VeryHigh2 => self.very_high_2,
BlurStrength::ExtremelyHigh => self.extremely_high,
BlurStrength::ExtremelyHigh2 => self.extremely_high_2,
}
}
pub fn offset(self, offset: f32) -> Self {
let Self {
mut extremely_low,
mut extremely_low_2,
mut very_low,
mut very_low_2,
mut low,
mut low_2,
mut medium,
mut medium_2,
mut high,
mut high_2,
mut very_high,
mut very_high_2,
mut extremely_high,
mut extremely_high_2,
} = self;
extremely_low += offset;
extremely_low_2 += offset;
very_low += offset;
very_low_2 += offset;
low += offset;
low_2 += offset;
medium += offset;
medium_2 += offset;
high += offset;
high_2 += offset;
very_high += offset;
very_high_2 += offset;
extremely_high += offset;
extremely_high_2 += offset;
Self {
extremely_low,
extremely_low_2,
very_low,
very_low_2,
low,
low_2,
medium,
medium_2,
high,
high_2,
very_high,
very_high_2,
extremely_high,
extremely_high_2,
}
}
pub fn guess_offset(&self) -> f32 {
let Self {
extremely_low,
extremely_high_2,
..
} = self;
let default_low = Self::default().extremely_low;
let default_high = Self::default().extremely_high_2;
if *extremely_low > default_low {
0.5 * (*extremely_low - default_low) / (1.0 - default_low) + 0.5
} else if *extremely_high_2 < default_high {
0.5 - (0.5 * (default_high - *extremely_high_2) / default_high)
} else {
0.5
}
}
}
impl Default for AlphaMap {
fn default() -> Self {
Self {
extremely_low: 0.90,
extremely_low_2: 0.87692,
very_low: 0.85385,
very_low_2: 0.83076,
low: 0.80769,
low_2: 0.78461,
medium: 0.76154,
medium_2: 0.73846,
high: 0.71538,
high_2: 0.69231,
very_high: 0.66023,
very_high_2: 0.64615,
extremely_high: 0.62308,
extremely_high_2: 0.6,
}
}
}

View file

@ -81,7 +81,7 @@ ForegroundPositive=0,87,44
ForegroundVisited=0,82,90
[Colors:Selection]
BackgroundAlternate=108,149,153
BackgroundAlternate=108,149,152
BackgroundNormal=0,82,90
DecorationFocus=0,82,90
DecorationHover=0,82,90

View file

@ -81,7 +81,6 @@ pub fn get_text(
step_array: &[Srgba],
fallback: &Srgba,
tint_array: Option<&[Srgba]>,
alpha: f32,
) -> Srgba {
assert!(step_array.len() == 100);
let step_array = if let Some(tint_array) = tint_array {
@ -91,28 +90,11 @@ pub fn get_text(
step_array
};
let alpha_extra_steps = if alpha < 1.0 {
((1. - alpha) * 100.0).round() as usize
} else {
0
};
let is_dark = base_index < 60;
let index = get_index(
base_index,
70 + alpha_extra_steps,
step_array.len(),
is_dark,
)
.or_else(|| {
get_index(
base_index,
50 + alpha_extra_steps,
step_array.len(),
is_dark,
)
})
.unwrap_or(if is_dark { 99 } else { 0 });
let index = get_index(base_index, 70, step_array.len(), is_dark)
.or_else(|| get_index(base_index, 50, step_array.len(), is_dark))
.unwrap_or(if is_dark { 99 } else { 0 });
*step_array.get(index).unwrap_or(fallback)
}

View file

@ -101,7 +101,6 @@ impl cosmic::Application for Window {
Message::Surface(destroy_popup(id))
} else {
Message::Surface(app_popup::<Window>(
|_| Default::default(),
move |state: &mut Window| {
let new_id = Id::unique();
state.popup = Some(new_id);

View file

@ -22,5 +22,4 @@ features = [
"surface-message",
"multi-window",
"wgpu",
"wayland",
]

View file

@ -7,7 +7,7 @@ use cosmic::app::Settings;
use cosmic::iced::{Alignment, Length, Size};
use cosmic::prelude::*;
use cosmic::widget::menu::{self, KeyBind};
use cosmic::widget::{nav_bar, RcElementWrapper};
use cosmic::widget::nav_bar;
use cosmic::{executor, iced, widget, Core};
use std::collections::HashMap;
use std::sync::LazyLock;
@ -43,7 +43,7 @@ pub enum Action {
impl widget::menu::Action for Action {
type Message = Message;
fn message(&self) -> Self::Message {
fn message(&self) -> Message {
match self {
Action::Hi => Message::Hi,
Action::Hi2 => Message::Hi2,
@ -52,25 +52,6 @@ impl widget::menu::Action for Action {
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Action2 {
Hi,
Hi2,
Hi3,
}
impl widget::menu::Action for Action2 {
type Message = cosmic::Action<Message>;
fn message(&self) -> Self::Message {
cosmic::Action::App(match self {
Action2::Hi => Message::Hi,
Action2::Hi2 => Message::Hi2,
Action2::Hi3 => Message::Hi3,
})
}
}
/// Runs application with these settings
#[rustfmt::skip]
fn main() -> Result<(), Box<dyn std::error::Error>> {
@ -103,7 +84,6 @@ pub enum Message {
Hi2,
Hi3,
Tick,
ValueChanged(f32),
}
/// The [`App`] stores application-specific state.
@ -115,7 +95,6 @@ pub struct App {
hidden: bool,
keybinds: HashMap<KeyBind, Action>,
progress: f32,
progress_slider: f32,
}
/// Implement [`cosmic::Application`] to integrate with COSMIC.
@ -158,7 +137,6 @@ impl cosmic::Application for App {
hidden: true,
keybinds: HashMap::new(),
progress: 0.0,
progress_slider: 0.0,
};
let command = app.update_title();
@ -177,70 +155,6 @@ impl cosmic::Application for App {
self.update_title()
}
fn nav_context_menu(&self) -> Option<Vec<menu::Tree<cosmic::Action<Self::Message>>>> {
Some(menu::nav_context(
&HashMap::new(),
vec![
vec![cosmic::widget::menu::Item::Button(
"Hi",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
)],
vec![
cosmic::widget::menu::Item::Button(
"Hi 2",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
cosmic::widget::menu::Item::Button(
"Hi 22",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
],
vec![
cosmic::widget::menu::Item::Button(
"Hi 3",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
cosmic::widget::menu::Item::Button(
"Hi 33",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
cosmic::widget::menu::Item::Button(
"Hi 333",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
],
vec![
cosmic::widget::menu::Item::Button(
"Hi 44",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
cosmic::widget::menu::Item::Button(
"Hi 4444",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
cosmic::widget::menu::Item::Button(
"Hi 444444",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
cosmic::widget::menu::Item::Button(
"Hi 444444444444",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
],
],
))
}
/// Handle application events here.
fn update(&mut self, message: Self::Message) -> cosmic::app::Task<Self::Message> {
match message {
@ -271,9 +185,6 @@ impl cosmic::Application for App {
Message::Tick => {
self.progress = (self.progress + 0.01) % 1.0;
}
Message::ValueChanged(value) => {
self.progress_slider = value;
}
}
Task::none()
}
@ -290,7 +201,7 @@ impl cosmic::Application for App {
.map_or("No page selected", String::as_str);
let centered = widget::container(
widget::column::with_capacity(16)
widget::column::with_capacity(14)
.push(widget::text::body(page_content))
.push(
widget::text_input::text_input("", &self.input_1)
@ -329,16 +240,6 @@ impl cosmic::Application for App {
widget::progress_bar::linear::Linear::new()
.girth(10.0)
.progress(self.progress)
.width(Length::Fill),
)
.push(
widget::slider(0.0..=1.0, self.progress_slider, Message::ValueChanged)
.step(0.001),
)
.push(
widget::progress_bar::linear::Linear::new()
.girth(10.0)
.progress(self.progress_slider)
.width(Length::Fill)
.markers([0.25, 0.5, 0.75])
.segment_spacing(2),
@ -365,7 +266,6 @@ impl cosmic::Application for App {
.progress(1.0)
.width(Length::Fill),
)
.push(widget::button::suggested("asdf").on_press(Message::Ignore))
.spacing(cosmic::theme::spacing().space_s)
.width(Length::Fill)
.height(Length::Shrink)

View file

@ -7,7 +7,7 @@ use std::collections::HashMap;
use cosmic::app::{Core, Settings, Task};
use cosmic::iced::Size;
use cosmic::widget::{menu, nav_bar, space};
use cosmic::widget::{menu, nav_bar};
use cosmic::{executor, iced, ApplicationExt, Element};
#[derive(Clone, Copy)]
@ -127,20 +127,18 @@ impl cosmic::Application for App {
Some(&self.nav_model)
}
/// The context menu list.
fn nav_context_menu(&self) -> Option<Vec<menu::Tree<cosmic::Action<Self::Message>>>> {
Some(menu::nav_context(
/// The context menu to display for the given nav bar item ID.
fn nav_context_menu(
&self,
id: nav_bar::Id,
) -> Option<Vec<menu::Tree<cosmic::Action<Self::Message>>>> {
Some(menu::items(
&HashMap::new(),
self.nav_model
.iter()
.map(|e| {
vec![
menu::Item::Button("Move Up", None, NavMenuAction::MoveUp(e)),
menu::Item::Button("Move Down", None, NavMenuAction::MoveDown(e)),
menu::Item::Button("Delete", None, NavMenuAction::Delete(e)),
]
})
.collect(),
vec![
menu::Item::Button("Move Up", None, NavMenuAction::MoveUp(id)),
menu::Item::Button("Move Down", None, NavMenuAction::MoveDown(id)),
menu::Item::Button("Delete", None, NavMenuAction::Delete(id)),
],
))
}

View file

@ -1,34 +0,0 @@
february = Reşemî { $year }
close = Bigire
documenters = Pêlbendvan
november = Mijdar { $year }
may = Gulan { $year }
april = Avrêl { $year }
translators = Wergêr
artists = Hunermend
license = Lîsans
december = Berfanbar { $year }
links = Girêdan
march = Adar { $year }
june = Pûşber { $year }
august = Tebax { $year }
developers = Pêşvebir
july = Tîrmeh { $year }
september = Îlon { $year }
designers = Nigarvan
october = Cotmeh { $year }
january = Çile { $year }
monday = Duşem
mon = Duş
tuesday = Sêşem
tue = Sêş
wednesday = Çarşem
wed = Çar
thursday = Pênçşem
thu = Pên
friday = Înî
fri = Înî
saturday = Şemî
sat = Şem
sunday = Yekşem
sun = Yek

2
iced

@ -1 +1 @@
Subproject commit 1a57634add6e820c299f1c59664cd98098a9c348
Subproject commit d9fe129f7316cea3ef6d2576e9421a305759b8bf

View file

@ -64,8 +64,6 @@ pub enum Action {
Unfocus(iced::window::Id),
/// Windowing system initialized
WindowingSystemInitialized,
/// Blur support enabled
BlurEnabled,
/// Updates the window maximized state
WindowMaximized(iced::window::Id, bool),
/// Updates the tracked window geometry.

File diff suppressed because it is too large Load diff

View file

@ -22,7 +22,7 @@ use crate::prelude::*;
use crate::theme::THEME;
use crate::widget::{container, id_container, menu, nav_bar, popover, space};
use apply::Apply;
use iced::{Color, Length, Subscription, theme, window};
use iced::{Length, Subscription, theme, window};
pub use settings::Settings;
use std::borrow::Cow;
use std::cell::RefCell;
@ -389,22 +389,10 @@ where
let mut nav =
crate::widget::nav_bar(nav_model, |id| crate::Action::Cosmic(Action::NavBar(id)))
.on_context(|id| crate::Action::Cosmic(Action::NavBarContext(id)))
.context_menu(self.nav_context_menu());
#[cfg(all(
feature = "multi-window",
feature = "wayland",
target_os = "linux",
feature = "surface-message"
))]
{
nav = nav
.window_id_maybe(self.core().main_window_id())
.on_surface_action(|m| crate::Action::Cosmic(crate::app::Action::Surface(m)))
}
let mut nav = nav
.into_container()
.width(iced::Length::Shrink)
.height(iced::Length::Fill);
.context_menu(self.nav_context_menu(self.core().nav_bar_context()))
.into_container()
.width(iced::Length::Shrink)
.height(iced::Length::Fill);
if !self.core().is_condensed() {
nav = nav.max_width(280);
@ -414,7 +402,10 @@ where
}
/// Shows a context menu for the active nav bar item.
fn nav_context_menu(&self) -> Option<Vec<menu::Tree<crate::Action<Self::Message>>>> {
fn nav_context_menu(
&self,
id: nav_bar::Id,
) -> Option<Vec<menu::Tree<crate::Action<Self::Message>>>> {
None
}
@ -624,7 +615,6 @@ impl<App: Application> ApplicationExt for App {
let content_container = core.window.content_container;
let show_context = core.window.show_context;
let nav_bar_active = core.nav_bar_active();
let transparent_header = core.window.transparent_header;
let focused = core
.focus_chain()
.iter()
@ -645,8 +635,10 @@ impl<App: Application> ApplicationExt for App {
let mut widgets = Vec::with_capacity(3);
// Insert nav bar onto the left side of the window.
let has_nav = if let Some(nav) = self.nav_bar() {
let nav = id_container(nav, iced_core::id::Id::new("COSMIC_nav_bar"));
let has_nav = if let Some(nav) = self
.nav_bar()
.map(|nav| id_container(nav, iced_core::id::Id::new("COSMIC_nav_bar")))
{
widgets.push(
container(nav)
.padding([
@ -833,11 +825,7 @@ impl<App: Application> ApplicationExt for App {
let cosmic = theme.cosmic();
container::Style {
background: Some(iced::Background::Color(
if transparent_header {
Color::TRANSPARENT
} else {
cosmic.background(theme.transparent).base.into()
},
cosmic.background.base.into(),
)),
border: iced::Border {
radius: [
@ -866,7 +854,7 @@ impl<App: Application> ApplicationExt for App {
container::Style {
background: if content_container {
Some(iced::Background::Color(
theme.cosmic().background(theme.transparent).base.into(),
theme.cosmic().background.base.into(),
))
} else {
None

View file

@ -221,7 +221,7 @@ impl Context {
let icon = widget::icon(icon)
.class(if symbolic {
theme::Svg::Custom(Rc::new(|theme| iced_widget::svg::Style {
color: Some(theme.cosmic().background(theme.transparent).on.into()),
color: Some(theme.cosmic().background.on.into()),
}))
} else {
theme::Svg::default()
@ -372,17 +372,16 @@ impl Context {
Container::<Message, _, Renderer>::new(content).style(|theme| {
let cosmic = theme.cosmic();
let corners = cosmic.corner_radii;
let mut bg = cosmic.background(theme.transparent).base;
iced_widget::container::Style {
text_color: Some(cosmic.background(theme.transparent).on.into()),
background: Some(Color::from(bg).into()),
text_color: Some(cosmic.background.on.into()),
background: Some(Color::from(cosmic.background.base).into()),
border: iced::Border {
radius: corners.radius_m.into(),
width: 1.0,
color: cosmic.background(theme.transparent).divider.into(),
color: cosmic.background.divider.into(),
},
shadow: Shadow::default(),
icon_color: Some(cosmic.background(theme.transparent).on.into()),
icon_color: Some(cosmic.background.on.into()),
snap: true,
}
}),
@ -560,7 +559,6 @@ pub fn run<App: Application>(flags: App::Flags) -> iced::Result {
core.window.show_maximize = false;
core.window.show_minimize = false;
core.window.use_template = false;
core.app_type = crate::core::AppType::Applet;
window_settings.decorations = false;
window_settings.exit_on_close_request = true;

View file

@ -63,6 +63,7 @@ pub fn file_transfer_send(
/// Returns a list of file paths.
#[cfg(feature = "xdg-portal")]
pub fn file_transfer_receive(key: String) -> iced::Task<ashpd::Result<Vec<String>>> {
dbg!(&key);
iced::Task::future(async move {
let file_transfer = ashpd::documents::FileTransfer::new().await?;
file_transfer.retrieve_files(&key).await

View file

@ -6,7 +6,6 @@ use std::collections::HashMap;
use crate::widget::nav_bar;
use cosmic_config::CosmicConfigEntry;
use cosmic_theme::ThemeMode;
use enumflags2::{self, BitFlags, bitflags};
use iced::{Limits, Size, window};
use iced_core::window::Id;
use palette::Srgba;
@ -39,24 +38,11 @@ pub struct Window {
pub show_close: bool,
pub show_maximize: bool,
pub show_minimize: bool,
pub transparent_header: bool,
pub is_maximized: bool,
height: f32,
width: f32,
}
#[bitflags]
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Auto {
/// Automatically apply effect to regular windows
Window,
/// Automatically apply effect to popups
Popup,
/// Automatically apply effect to system interface elements (layer shell surfaces)
System,
}
/// COSMIC-specific application settings
#[derive(Clone)]
pub struct Core {
@ -111,21 +97,8 @@ pub struct Core {
pub(crate) menu_bars: HashMap<crate::widget::Id, (Limits, Size)>,
pub(crate) auto_blur: BitFlags<Auto>,
pub(crate) auto_corner_radius: BitFlags<Auto>,
pub(crate) app_type: AppType,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AppType {
/// A regular application
Window,
/// A system application
System,
/// An applet
Applet,
#[cfg(all(feature = "wayland", target_os = "linux"))]
pub(crate) sync_window_border_radii_to_theme: bool,
}
impl Default for Core {
@ -168,7 +141,6 @@ impl Default for Core {
show_minimize: true,
show_window_menu: false,
is_maximized: false,
transparent_header: false,
height: 0.,
width: 0.,
},
@ -184,9 +156,8 @@ impl Default for Core {
main_window: None,
exit_on_main_window_closed: true,
menu_bars: HashMap::new(),
auto_blur: Auto::System | Auto::Popup | Auto::Window,
auto_corner_radius: Auto::System | Auto::Popup | Auto::Window,
app_type: AppType::Window,
#[cfg(all(feature = "wayland", target_os = "linux"))]
sync_window_border_radii_to_theme: true,
}
}
}
@ -512,112 +483,14 @@ impl Core {
crate::command::toggle_maximize(id)
}
// TODO should we emit tasks setting the corner radius or unsetting it if this is changed?
#[cfg(all(feature = "wayland", target_os = "linux"))]
pub fn set_sync_window_border_radii_to_theme(&mut self, sync: bool) {
self.sync_window_border_radii_to_theme = sync;
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
pub fn sync_window_border_radii_to_theme(&self) -> bool {
match self.app_type {
AppType::Window => self.auto_corner_radius.contains(Auto::Window),
AppType::System => self.auto_corner_radius.contains(Auto::System),
AppType::Applet => false,
}
}
pub fn set_auto_blur(&mut self, auto_blur: BitFlags<Auto>) {
self.auto_blur = auto_blur;
}
pub fn auto_blur(&self) -> BitFlags<Auto> {
self.auto_blur
}
pub fn set_auto_corner_radius(&mut self, auto_corner_radius: BitFlags<Auto>) {
self.auto_corner_radius = auto_corner_radius;
}
pub fn auto_corner_radius(&self) -> BitFlags<Auto> {
self.auto_corner_radius
}
pub fn set_app_type(&mut self, app_type: AppType) {
self.app_type = app_type;
}
pub fn app_type(&self) -> AppType {
self.app_type
}
#[must_use]
pub fn blur(
&self,
theme: &Theme,
surface_id_wrapper: Option<iced_winit::SurfaceIdWrapper>,
) -> bool {
use iced_winit::SurfaceIdWrapper;
let theme = theme.cosmic();
match surface_id_wrapper {
Some(SurfaceIdWrapper::LayerSurface(_)) => {
theme.frosted_system_interface && self.auto_blur.contains(Auto::System)
}
Some(SurfaceIdWrapper::Window(_)) => {
theme.frosted_windows && self.auto_blur.contains(Auto::Window)
}
Some(SurfaceIdWrapper::Popup(_))
if matches!(self.app_type, AppType::Window | AppType::System) =>
{
theme.frosted_windows && self.auto_blur.contains(Auto::Popup)
}
Some(SurfaceIdWrapper::Popup(_)) if matches!(self.app_type, AppType::Applet) => {
theme.frosted_applets && self.auto_blur.contains(Auto::Popup)
}
None => match self.app_type {
AppType::Window => theme.frosted_windows && self.auto_blur.contains(Auto::Window),
AppType::System => {
theme.frosted_system_interface && self.auto_blur.contains(Auto::System)
}
AppType::Applet => false,
},
_ => false,
}
}
/// Calculate suggested corners for each app type main window
#[must_use]
#[cfg(all(feature = "wayland", target_os = "linux"))]
pub fn corners(
&self,
theme: &Theme,
rounded: bool,
) -> Option<iced_runtime::platform_specific::wayland::CornerRadius> {
if !self.sync_window_border_radii_to_theme() {
return None;
}
let theme = theme.cosmic();
let ret = if let AppType::Applet = self.app_type {
let radius_l = theme.radius_l();
iced_runtime::platform_specific::wayland::CornerRadius {
top_left: radius_l[0].round() as u32,
top_right: radius_l[1].round() as u32,
bottom_right: radius_l[2].round() as u32,
bottom_left: radius_l[3].round() as u32,
}
} else if let AppType::Window = self.app_type
&& !rounded
{
let radius_0 = theme.radius_0();
iced_runtime::platform_specific::wayland::CornerRadius {
top_left: radius_0[0].round() as u32,
top_right: radius_0[1].round() as u32,
bottom_right: radius_0[2].round() as u32,
bottom_left: radius_0[3].round() as u32,
}
} else {
let radius_s = theme.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 });
iced_runtime::platform_specific::wayland::CornerRadius {
top_left: radius_s[0].round() as u32,
top_right: radius_s[1].round() as u32,
bottom_right: radius_s[2].round() as u32,
bottom_left: radius_s[3].round() as u32,
}
};
Some(ret)
self.sync_window_border_radii_to_theme
}
}

View file

@ -5,10 +5,6 @@ use super::Action;
use crate::Application;
use iced::window;
#[cfg(all(feature = "wayland", target_os = "linux"))]
use iced_runtime::platform_specific::wayland::CornerRadius;
#[cfg(all(feature = "wayland", target_os = "linux"))]
use iced_runtime::platform_specific::wayland::layer_surface::IcedMargin;
use std::any::Any;
use std::sync::Arc;
@ -31,40 +27,18 @@ pub fn destroy_window(id: iced_core::window::Id) -> Action {
Action::DestroyWindow(id)
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
#[must_use]
pub fn destroy_layer_shell(id: iced_core::window::Id) -> Action {
Action::DestroyLayerShell(id)
}
#[derive(Debug, Default, Copy, Clone)]
pub struct LiveSettings {
#[cfg(all(feature = "wayland", target_os = "linux"))]
/// Override the surface padding value for the surface type.
pub padding: Option<IcedMargin>,
#[cfg(all(feature = "wayland", target_os = "linux"))]
/// Override the default corner radius value for the surface type.
pub corners: Option<CornerRadius>,
/// Override the default blur setting for the surface type.
pub blur: Option<bool>,
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
type BoxedView<App> = Option<
Box<
dyn Fn(&App) -> crate::Element<'_, crate::Action<<App as Application>::Message>>
+ Send
+ Sync
+ 'static,
>,
>;
#[cfg(all(feature = "wayland", target_os = "linux"))]
#[must_use]
pub fn app_window<App: Application>(
live_settings: impl Fn(&App) -> LiveSettings + Send + Sync + 'static,
settings: impl Fn(&mut App) -> window::Settings + Send + Sync + 'static,
view: BoxedView<App>,
view: Option<
Box<
dyn for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action<App::Message>>
+ Send
+ Sync
+ 'static,
>,
>,
) -> (window::Id, Action) {
let id = window::Id::unique();
@ -72,16 +46,11 @@ pub fn app_window<App: Application>(
Box::new(settings);
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
let boxed_live: Box<dyn Fn(&App) -> LiveSettings + Send + Sync + 'static> =
Box::new(live_settings);
let boxed_live: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed_live);
(
id,
Action::AppWindow(
id,
Arc::new(boxed),
Arc::new(boxed_live),
view.map(|view| {
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(view);
Arc::new(boxed)
@ -94,7 +63,6 @@ pub fn app_window<App: Application>(
#[cfg(all(feature = "wayland", target_os = "linux"))]
#[must_use]
pub fn simple_window<Message: 'static>(
live_settings: impl Fn() -> LiveSettings + Send + Sync + 'static,
settings: impl Fn() -> window::Settings + Send + Sync + 'static,
view: Option<
impl Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
@ -105,15 +73,11 @@ pub fn simple_window<Message: 'static>(
let boxed: Box<dyn Fn() -> window::Settings + Send + Sync + 'static> = Box::new(settings);
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
let boxed_live: Box<dyn Fn() -> LiveSettings + Send + Sync + 'static> = Box::new(live_settings);
let boxed_live: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed_live);
(
id,
Action::Window(
id,
Arc::new(boxed),
Arc::new(boxed_live),
view.map(|view| {
let boxed: Box<
dyn Fn() -> crate::Element<'static, crate::Action<Message>>
@ -131,12 +95,18 @@ pub fn simple_window<Message: 'static>(
#[cfg(all(feature = "wayland", target_os = "linux"))]
#[must_use]
pub fn app_popup<App: Application>(
live_settings: impl Fn(&App) -> LiveSettings + Send + Sync + 'static,
settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
+ Send
+ Sync
+ 'static,
view: BoxedView<App>,
view: Option<
Box<
dyn for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action<App::Message>>
+ Send
+ Sync
+ 'static,
>,
>,
) -> Action {
let boxed: Box<
dyn Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
@ -146,13 +116,8 @@ pub fn app_popup<App: Application>(
> = Box::new(settings);
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
let boxed_live: Box<dyn Fn(&App) -> LiveSettings + Send + Sync + 'static> =
Box::new(live_settings);
let boxed_live: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed_live);
Action::AppPopup(
Arc::new(boxed),
Arc::new(boxed_live),
view.map(|view| {
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(view);
Arc::new(boxed)
@ -163,7 +128,7 @@ pub fn app_popup<App: Application>(
/// Used to create a subsurface message from within a widget.
#[cfg(all(feature = "wayland", target_os = "linux"))]
#[must_use]
pub fn simple_subsurface<Message: 'static>(
pub fn simple_subsurface<Message: 'static, V>(
settings: impl Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings
+ Send
+ Sync
@ -182,7 +147,6 @@ pub fn simple_subsurface<Message: 'static>(
Action::Subsurface(
Arc::new(boxed),
Arc::new(Box::new(LiveSettings::default)),
view.map(|view| {
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(view);
Arc::new(boxed)
@ -194,7 +158,6 @@ pub fn simple_subsurface<Message: 'static>(
#[cfg(all(feature = "wayland", target_os = "linux"))]
#[must_use]
pub fn simple_popup<Message: 'static>(
live_settings: impl Fn() -> LiveSettings + Send + Sync + 'static,
settings: impl Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
+ Send
+ Sync
@ -211,12 +174,8 @@ pub fn simple_popup<Message: 'static>(
> = Box::new(settings);
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
let boxed_live: Box<dyn Fn() -> LiveSettings + Send + Sync + 'static> = Box::new(live_settings);
let boxed_live: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed_live);
Action::Popup(
Arc::new(boxed),
Arc::new(boxed_live),
view.map(|view| {
let boxed: Box<
dyn Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
@ -238,7 +197,14 @@ pub fn subsurface<App: Application>(
+ Sync
+ 'static,
// XXX Boxed trait object is required for less cumbersome type inference, but we box it anyways.
view: BoxedView<App>,
view: Option<
Box<
dyn for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action<App::Message>>
+ Send
+ Sync
+ 'static,
>,
>,
) -> Action {
let boxed: Box<
dyn Fn(
@ -253,82 +219,6 @@ pub fn subsurface<App: Application>(
Action::AppSubsurface(
Arc::new(boxed),
Arc::new(Box::new(|_: &App| LiveSettings::default())),
view.map(|view| {
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(view);
Arc::new(boxed)
}),
)
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
#[must_use]
pub fn simple_layer_shell<Message: 'static>(
live_settings: impl Fn() -> LiveSettings + Send + Sync + 'static,
settings: impl Fn()
-> iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings
+ Send
+ Sync
+ 'static,
view: Option<
impl Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
>,
) -> Action {
let boxed: Box<
dyn Fn()
-> iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings
+ Send
+ Sync
+ 'static,
> = Box::new(settings);
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
let boxed_live: Box<dyn Fn() -> LiveSettings + Send + Sync + 'static> = Box::new(live_settings);
let boxed_live: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed_live);
Action::LayerShell(
Arc::new(boxed),
Arc::new(boxed_live),
view.map(|view| {
let boxed: Box<
dyn Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
> = Box::new(view);
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
Arc::new(boxed)
}),
)
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
#[must_use]
pub fn app_layer_shell<App: Application>(
live_settings: impl Fn(&App) -> LiveSettings + Send + Sync + 'static,
settings: impl Fn(
&mut App,
)
-> iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings
+ Send
+ Sync
+ 'static,
// XXX Boxed trait object is required for less cumbersome type inference, but we box it anyways.
view: BoxedView<App>,
) -> Action {
let boxed: Box<
dyn Fn(
&mut App,
)
-> iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings
+ Send
+ Sync
+ 'static,
> = Box::new(settings);
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
let boxed_live: Box<dyn Fn(&App) -> LiveSettings + Send + Sync + 'static> =
Box::new(live_settings);
let boxed_live: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed_live);
Action::AppLayerShell(
Arc::new(boxed),
Arc::new(boxed_live),
view.map(|view| {
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(view);
Arc::new(boxed)

View file

@ -1,86 +0,0 @@
use iced::Rectangle;
use iced_runtime::platform_specific::wayland::CornerRadius;
#[must_use]
pub fn rounded_rect_strips(rect: Rectangle<f32>, radius: CornerRadius) -> Vec<Rectangle<f32>> {
let mut out = Vec::new();
let w = rect.width.max(0.0);
let h = rect.height.max(0.0);
if w <= 0.0 || h <= 0.0 {
return out;
}
let tl = radius.top_left as i32;
let tr = radius.top_right as i32;
let bl = radius.bottom_left as i32;
let br = radius.bottom_right as i32;
let max_top = tl.max(tr);
let max_bottom = bl.max(br);
let center_y = rect.y + max_top as f32;
let center_h = (h as i32 - max_top - max_bottom).max(0) as f32;
if center_h > 0.0 {
out.push(Rectangle {
x: rect.x,
y: center_y,
width: w,
height: center_h,
});
}
for y in 0..max_top {
let left = if y < tl { circle_inset(tl, y) } else { 0 };
let right = if y < tr { circle_inset(tr, y) } else { 0 };
let strip_x = rect.x + left as f32;
let strip_w = (w as i32 - left - right).max(0) as f32;
if strip_w > 0.0 {
out.push(Rectangle {
x: strip_x,
y: rect.y + y as f32,
width: strip_w,
height: 1.0,
});
}
}
for y in 0..max_bottom {
let cy = max_bottom - 1 - y;
let left = if cy < bl { circle_inset(bl, cy) } else { 0 };
let right = if cy < br { circle_inset(br, cy) } else { 0 };
let strip_x = rect.x + left as f32;
let strip_w = (w as i32 - left - right).max(0) as f32;
if strip_w > 0.0 {
out.push(Rectangle {
x: strip_x,
y: rect.y + h - max_bottom as f32 + y as f32,
width: strip_w,
height: 1.0,
});
}
}
out
}
fn circle_inset(radius: i32, y: i32) -> i32 {
if radius <= 0 {
return 0;
}
let fy = y as f32 + 0.5;
let r = radius as f32;
let x = r - (r * r - (r - fy) * (r - fy)).sqrt();
x.floor() as i32
}

View file

@ -2,28 +2,36 @@
// SPDX-License-Identifier: MPL-2.0
pub mod action;
#[cfg(all(feature = "wayland", target_os = "linux"))]
pub mod corner_radius;
use iced::{Limits, Size, Task};
use std::any::Any;
use std::future::Future;
use std::sync::Arc;
type BoxedSetting = Arc<Box<dyn Any + Send + Sync + 'static>>;
/// Ignore this message in your application. It will be intercepted.
#[derive(Clone)]
pub enum Action {
/// Create a subsurface with a view function accepting the App as a parameter
AppSubsurface(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
AppSubsurface(
std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>,
Option<std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>>,
),
/// Create a subsurface with a view function
Subsurface(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
Subsurface(
std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>,
Option<std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>>,
),
/// Destroy a subsurface with a view function
DestroySubsurface(iced::window::Id),
/// Create a popup with a view function accepting the App as a parameter
AppPopup(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
AppPopup(
std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>,
Option<std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>>,
),
/// Create a popup
Popup(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
Popup(
std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>,
Option<std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>>,
),
/// Destroy a subsurface with a view function
DestroyPopup(iced::window::Id),
/// Destroys the global tooltip popup subsurface
@ -32,29 +40,18 @@ pub enum Action {
/// Create a window with a view function accepting the App as a parameter
AppWindow(
iced::window::Id,
BoxedSetting,
BoxedSetting,
Option<BoxedSetting>,
std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>,
Option<std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>>,
),
/// Create a window with a view function
Window(
iced::window::Id,
BoxedSetting,
BoxedSetting,
Option<BoxedSetting>,
std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>,
Option<std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>>,
),
/// Destroy a window
DestroyWindow(iced::window::Id),
/// Create a layer shell surface with a view function accepting the App as a parameter
AppLayerShell(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
/// Create a layer shell surface with a view function
LayerShell(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
/// Destroy a layer shell surface
DestroyLayerShell(iced::window::Id),
/// Responsive menu bar update
ResponsiveMenuBar {
/// Id of the menu bar
@ -65,45 +62,28 @@ pub enum Action {
size: Size,
},
Ignore,
SyncLiveSettings(iced::window::Id),
Task(Arc<dyn Fn() -> Task<Action> + Send + Sync>),
}
pub fn surface_task<M: Send + 'static>(action: Action) -> Task<crate::Action<M>> {
crate::task::message(crate::Action::Cosmic(crate::app::Action::Surface(action)))
}
impl std::fmt::Debug for Action {
#[cold]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AppSubsurface(arg0, arg1, arg2) => f
Self::AppSubsurface(arg0, arg1) => f
.debug_tuple("AppSubsurface")
.field(arg0)
.field(arg1)
.field(arg2)
.finish(),
Self::Subsurface(arg0, arg1, arg2) => f
.debug_tuple("Subsurface")
.field(arg0)
.field(arg1)
.field(arg2)
.finish(),
Self::Subsurface(arg0, arg1) => {
f.debug_tuple("Subsurface").field(arg0).field(arg1).finish()
}
Self::DestroySubsurface(arg0) => {
f.debug_tuple("DestroySubsurface").field(arg0).finish()
}
Self::AppPopup(arg0, arg1, arg2) => f
.debug_tuple("AppPopup")
.field(arg0)
.field(arg1)
.field(arg2)
.finish(),
Self::Popup(arg0, arg1, arg2) => f
.debug_tuple("Popup")
.field(arg0)
.field(arg1)
.field(arg2)
.finish(),
Self::AppPopup(arg0, arg1) => {
f.debug_tuple("AppPopup").field(arg0).field(arg1).finish()
}
Self::Popup(arg0, arg1) => f.debug_tuple("Popup").field(arg0).field(arg1).finish(),
Self::DestroyPopup(arg0) => f.debug_tuple("DestroyPopup").field(arg0).finish(),
Self::DestroyTooltipPopup => f.debug_tuple("DestroyTooltipPopup").finish(),
Self::ResponsiveMenuBar {
@ -117,38 +97,20 @@ impl std::fmt::Debug for Action {
.field("size", size)
.finish(),
Self::Ignore => write!(f, "Ignore"),
Self::AppWindow(id, arg0, arg1, arg2) => f
Self::AppWindow(id, arg0, arg1) => f
.debug_tuple("AppWindow")
.field(id)
.field(arg0)
.field(arg1)
.field(arg2)
.finish(),
Self::Window(id, arg0, arg1, arg2) => f
Self::Window(id, arg0, arg1) => f
.debug_tuple("Window")
.field(id)
.field(arg0)
.field(arg1)
.field(arg2)
.finish(),
Self::DestroyWindow(arg0) => f.debug_tuple("DestroyWindow").field(arg0).finish(),
Self::Task(_) => f.debug_tuple("Future").finish(),
Self::AppLayerShell(arg, arg1, arg2) => f
.debug_tuple("AppLayerShell")
.field(arg)
.field(arg1)
.field(arg2)
.finish(),
Self::LayerShell(arg, arg1, arg2) => f
.debug_tuple("LayerShell")
.field(arg)
.field(arg1)
.field(arg2)
.finish(),
Self::DestroyLayerShell(arg0) => {
f.debug_tuple("DestroyLayerShell").field(arg0).finish()
}
Self::SyncLiveSettings(arg0) => f.debug_tuple("SyncLiveSettings").field(arg0).finish(),
}
}
}

View file

@ -7,9 +7,9 @@
pub mod portal;
pub mod style;
use ::iced::Alignment;
use cosmic_config::CosmicConfigEntry;
use cosmic_config::{CosmicConfigEntry, config_subscription};
use cosmic_theme::{Component, LayeredTheme, Spacing, ThemeMode};
use iced_futures::Subscription;
use iced_runtime::{Appearance, DefaultStyle};
use std::sync::{Arc, LazyLock, Mutex};
pub use style::*;
@ -46,8 +46,6 @@ pub static TRANSPARENT_COMPONENT: LazyLock<Component> = LazyLock::new(|| Compone
pub(crate) static THEME: Mutex<Theme> = Mutex::new(Theme {
theme_type: ThemeType::Dark,
layer: cosmic_theme::Layer::Background,
transparent: false,
list_item_position: None,
});
/// Currently-defined theme.
@ -84,6 +82,34 @@ pub fn is_high_contrast() -> bool {
active_type().is_high_contrast()
}
// /// Watches for changes to the system's theme preference.
// #[cold]
// pub fn subscription(is_dark: bool) -> Subscription<crate::theme::Theme> {
// config_subscription::<_, crate::cosmic_theme::Theme>(
// (
// std::any::TypeId::of::<crate::cosmic_theme::Theme>(),
// is_dark,
// ),
// if is_dark {
// cosmic_theme::DARK_THEME_ID
// } else {
// cosmic_theme::LIGHT_THEME_ID
// }
// .into(),
// crate::cosmic_theme::Theme::VERSION,
// )
// .map(|res| {
// for error in res.errors.into_iter().filter(cosmic_config::Error::is_err) {
// tracing::error!(
// ?error,
// "error while watching system theme preference changes"
// );
// }
// Theme::system(Arc::new(res.config))
// })
// }
pub fn system_dark() -> Theme {
let Ok(helper) = crate::cosmic_theme::Theme::dark_config() else {
return Theme::dark();
@ -183,9 +209,6 @@ impl ThemeType {
pub struct Theme {
pub theme_type: ThemeType,
pub layer: cosmic_theme::Layer,
pub transparent: bool,
/// Only meaningful for widgets that must be in a list. Otherwise it should be ignored.
pub list_item_position: Option<(Alignment, usize)>,
}
impl Theme {
@ -256,9 +279,9 @@ impl Theme {
/// can be used in a component that is intended to be a child of a `CosmicContainer`
pub fn current_container(&self) -> &cosmic_theme::Container {
match self.layer {
cosmic_theme::Layer::Background => self.cosmic().background(self.transparent),
cosmic_theme::Layer::Primary => self.cosmic().primary(self.transparent),
cosmic_theme::Layer::Secondary => self.cosmic().secondary(self.transparent),
cosmic_theme::Layer::Background => &self.cosmic().background,
cosmic_theme::Layer::Primary => &self.cosmic().primary,
cosmic_theme::Layer::Secondary => &self.cosmic().secondary,
}
}
@ -267,14 +290,6 @@ impl Theme {
pub fn set_theme(&mut self, theme: ThemeType) {
self.theme_type = theme;
}
#[inline]
/// Clone theme with a list position
pub fn with_list_item_position(&self, position: Option<(Alignment, usize)>) -> Self {
let mut new = self.clone();
new.list_item_position = position;
new
}
}
impl LayeredTheme for Theme {

View file

@ -126,20 +126,20 @@ pub fn appearance(
let (background, _, _) = color(&cosmic.text_button);
appearance.background = Some(Background::Color(background));
appearance.icon_color = Some(cosmic.background(theme.transparent).on.into());
appearance.text_color = Some(cosmic.background(theme.transparent).on.into());
appearance.icon_color = Some(cosmic.background.on.into());
appearance.text_color = Some(cosmic.background.on.into());
corner_radii = &cosmic.corner_radii.radius_0;
}
Button::AppletIcon => {
let (background, _, _) = color(&cosmic.text_button);
appearance.background = Some(Background::Color(background));
appearance.icon_color = Some(cosmic.background(theme.transparent).on.into());
appearance.text_color = Some(cosmic.background(theme.transparent).on.into());
appearance.icon_color = Some(cosmic.background.on.into());
appearance.text_color = Some(cosmic.background.on.into());
}
Button::MenuFolder => {
// Menu folders cannot be disabled, ignore customized icon and text color
let component = &cosmic.background(theme.transparent).component;
let component = &cosmic.background.component;
let (background, _, _) = color(component);
appearance.background = Some(Background::Color(background));
appearance.icon_color = Some(component.on.into());
@ -151,9 +151,8 @@ pub fn appearance(
let (background, text, icon) = color(&cosmic.list_button);
if selected {
appearance.background = Some(Background::Color(
cosmic.primary(theme.transparent).component.hover.into(),
));
appearance.background =
Some(Background::Color(cosmic.primary.component.hover.into()));
appearance.icon_color = Some(cosmic.accent.base.into());
appearance.text_color = Some(cosmic.accent_text_color().into());
} else {
@ -163,7 +162,7 @@ pub fn appearance(
}
}
Button::MenuItem => {
let (background, text, icon) = color(&cosmic.background(theme.transparent).component);
let (background, text, icon) = color(&cosmic.background.component);
appearance.background = Some(Background::Color(background));
appearance.icon_color = icon;
appearance.text_color = text;
@ -279,6 +278,6 @@ impl Catalog for crate::Theme {
}
fn selection_background(&self) -> Background {
Background::Color(self.cosmic().primary(self.transparent).base.into())
Background::Color(self.cosmic().primary.base.into())
}
}

View file

@ -13,28 +13,18 @@ impl dropdown::menu::StyleSheet for Theme {
dropdown::menu::Appearance {
text_color: cosmic.on_bg_color().into(),
background: Background::Color(
cosmic.background(self.transparent).component.base.into(),
),
background: Background::Color(cosmic.background.component.base.into()),
border_width: 0.0,
border_radius: cosmic.corner_radii.radius_m.into(),
border_color: Color::TRANSPARENT,
hovered_text_color: cosmic.on_bg_color().into(),
hovered_background: Background::Color(
cosmic.primary(self.transparent).component.hover.into(),
),
hovered_background: Background::Color(cosmic.primary.component.hover.into()),
selected_text_color: cosmic.accent_text_color().into(),
selected_background: Background::Color(
cosmic.primary(self.transparent).component.hover.into(),
),
selected_background: Background::Color(cosmic.primary.component.hover.into()),
description_color: cosmic
.primary(self.transparent)
.component
.on_disabled
.into(),
description_color: cosmic.primary.component.on_disabled.into(),
}
}
}

View file

@ -224,11 +224,11 @@ impl iced_checkbox::Catalog for Theme {
},
Checkbox::Secondary => iced_checkbox::Style {
background: Background::Color(if is_checked {
cosmic.background(self.transparent).component.base.into()
cosmic.background.component.base.into()
} else {
self.current_container().small_widget.into()
}),
icon_color: cosmic.background(self.transparent).on.into(),
icon_color: cosmic.background.on.into(),
border: Border {
radius: corners.radius_xs.into(),
width: if is_checked { 0.0 } else { 1.0 },
@ -386,11 +386,9 @@ pub enum Container<'a> {
WindowBackground,
Background,
Card,
ContextDrawer {
transparent: bool,
},
ContextDrawer,
Custom(Box<dyn Fn(&Theme) -> iced_container::Style + 'a>),
Dialog(bool),
Dialog,
Dropdown,
HeaderBar {
focused: bool,
@ -411,13 +409,11 @@ impl<'a> Container<'a> {
}
#[must_use]
pub fn background(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style {
pub fn background(theme: &cosmic_theme::Theme) -> iced_container::Style {
iced_container::Style {
icon_color: Some(Color::from(theme.background(transparent).on)),
text_color: Some(Color::from(theme.background(transparent).on)),
background: Some(iced::Background::Color(
theme.background(transparent).base.into(),
)),
icon_color: Some(Color::from(theme.background.on)),
text_color: Some(Color::from(theme.background.on)),
background: Some(iced::Background::Color(theme.background.base.into())),
border: Border {
radius: theme.corner_radii.radius_s.into(),
..Default::default()
@ -428,13 +424,11 @@ impl<'a> Container<'a> {
}
#[must_use]
pub fn primary(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style {
pub fn primary(theme: &cosmic_theme::Theme) -> iced_container::Style {
iced_container::Style {
icon_color: Some(Color::from(theme.primary(transparent).on)),
text_color: Some(Color::from(theme.primary(transparent).on)),
background: Some(iced::Background::Color(
theme.primary(transparent).base.into(),
)),
icon_color: Some(Color::from(theme.primary.on)),
text_color: Some(Color::from(theme.primary.on)),
background: Some(iced::Background::Color(theme.primary.base.into())),
border: Border {
radius: theme.corner_radii.radius_s.into(),
..Default::default()
@ -445,13 +439,11 @@ impl<'a> Container<'a> {
}
#[must_use]
pub fn secondary(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style {
pub fn secondary(theme: &cosmic_theme::Theme) -> iced_container::Style {
iced_container::Style {
icon_color: Some(Color::from(theme.secondary(transparent).on)),
text_color: Some(Color::from(theme.secondary(transparent).on)),
background: Some(iced::Background::Color(
theme.secondary(transparent).base.into(),
)),
icon_color: Some(Color::from(theme.secondary.on)),
text_color: Some(Color::from(theme.secondary.on)),
background: Some(iced::Background::Color(theme.secondary.base.into())),
border: Border {
radius: theme.corner_radii.radius_s.into(),
..Default::default()
@ -482,30 +474,14 @@ impl iced_container::Catalog for Theme {
let window_corner_radius = cosmic.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 });
match class {
Container::Transparent => {
let component = &self.current_container().component;
iced_container::Style {
icon_color: Some(component.on.into()),
text_color: Some(component.on.into()),
background: None,
border: Border {
radius: 0.into(),
..Default::default()
},
shadow: Shadow::default(),
snap: true,
}
}
Container::Transparent => iced_container::Style::default(),
Container::Custom(f) => f(self),
Container::WindowBackground => iced_container::Style {
icon_color: Some(Color::from(cosmic.background(self.transparent).on)),
text_color: Some(Color::from(cosmic.background(self.transparent).on)),
background: Some(iced::Background::Color(
cosmic.background(self.transparent).base.into(),
)),
icon_color: Some(Color::from(cosmic.background.on)),
text_color: Some(Color::from(cosmic.background.on)),
background: Some(iced::Background::Color(cosmic.background.base.into())),
border: Border {
radius: [
cosmic.corner_radii.radius_0[0],
@ -543,13 +519,12 @@ impl iced_container::Catalog for Theme {
let (icon_color, text_color) = if *focused {
(
Color::from(cosmic.accent_text_color()),
Color::from(cosmic.background(self.transparent).on),
Color::from(cosmic.background.on),
)
} else {
use crate::ext::ColorExt;
let unfocused_color =
Color::from(cosmic.background(self.transparent).component.on)
.blend_alpha(cosmic.background(self.transparent).base.into(), 0.5);
let unfocused_color = Color::from(cosmic.background.component.on)
.blend_alpha(cosmic.background.base.into(), 0.5);
(unfocused_color, unfocused_color)
};
@ -559,9 +534,7 @@ impl iced_container::Catalog for Theme {
background: if *transparent {
None
} else {
Some(iced::Background::Color(
cosmic.background(self.transparent).base.into(),
))
Some(iced::Background::Color(cosmic.background.base.into()))
},
border: Border {
radius: [
@ -586,21 +559,21 @@ impl iced_container::Catalog for Theme {
}
}
Container::ContextDrawer { transparent } => {
let mut a = Container::primary(cosmic, self.transparent && *transparent);
Container::ContextDrawer => {
let mut a = Container::primary(cosmic);
if cosmic.is_high_contrast {
a.border.width = 1.;
a.border.color = cosmic.primary(self.transparent).divider.into();
a.border.color = cosmic.primary.divider.into();
}
a
}
Container::Background => Container::background(cosmic, self.transparent),
Container::Background => Container::background(cosmic),
Container::Primary => Container::primary(cosmic, self.transparent),
Container::Primary => Container::primary(cosmic),
Container::Secondary => Container::secondary(cosmic, self.transparent),
Container::Secondary => Container::secondary(cosmic),
Container::Dropdown => iced_container::Style {
icon_color: None,
@ -632,14 +605,10 @@ impl iced_container::Catalog for Theme {
match self.layer {
cosmic_theme::Layer::Background => iced_container::Style {
icon_color: Some(Color::from(
cosmic.background(self.transparent).component.on,
)),
text_color: Some(Color::from(
cosmic.background(self.transparent).component.on,
)),
icon_color: Some(Color::from(cosmic.background.component.on)),
text_color: Some(Color::from(cosmic.background.component.on)),
background: Some(iced::Background::Color(
cosmic.background(self.transparent).component.base.into(),
cosmic.background.component.base.into(),
)),
border: Border {
radius: cosmic.corner_radii.radius_s.into(),
@ -649,14 +618,10 @@ impl iced_container::Catalog for Theme {
snap: true,
},
cosmic_theme::Layer::Primary => iced_container::Style {
icon_color: Some(Color::from(
cosmic.primary(self.transparent).component.on,
)),
text_color: Some(Color::from(
cosmic.primary(self.transparent).component.on,
)),
icon_color: Some(Color::from(cosmic.primary.component.on)),
text_color: Some(Color::from(cosmic.primary.component.on)),
background: Some(iced::Background::Color(
cosmic.primary(self.transparent).component.base.into(),
cosmic.primary.component.base.into(),
)),
border: Border {
radius: cosmic.corner_radii.radius_s.into(),
@ -666,14 +631,10 @@ impl iced_container::Catalog for Theme {
snap: true,
},
cosmic_theme::Layer::Secondary => iced_container::Style {
icon_color: Some(Color::from(
cosmic.secondary(self.transparent).component.on,
)),
text_color: Some(Color::from(
cosmic.secondary(self.transparent).component.on,
)),
icon_color: Some(Color::from(cosmic.secondary.component.on)),
text_color: Some(Color::from(cosmic.secondary.component.on)),
background: Some(iced::Background::Color(
cosmic.secondary(self.transparent).component.base.into(),
cosmic.secondary.component.base.into(),
)),
border: Border {
radius: cosmic.corner_radii.radius_s.into(),
@ -685,17 +646,12 @@ impl iced_container::Catalog for Theme {
}
}
Container::Dialog(is_overlay) => iced_container::Style {
icon_color: Some(Color::from(cosmic.primary(self.transparent).on)),
text_color: Some(Color::from(cosmic.primary(self.transparent).on)),
background: Some(iced::Background::Color(
cosmic.primary(self.transparent && !is_overlay).base.into(),
)),
Container::Dialog => iced_container::Style {
icon_color: Some(Color::from(cosmic.primary.on)),
text_color: Some(Color::from(cosmic.primary.on)),
background: Some(iced::Background::Color(cosmic.primary.base.into())),
border: Border {
color: cosmic
.primary(self.transparent && !is_overlay)
.divider
.into(),
color: cosmic.primary.divider.into(),
width: 1.0,
radius: cosmic.corner_radii.radius_m.into(),
},
@ -837,15 +793,13 @@ impl menu::Catalog for Theme {
menu::Style {
text_color: cosmic.on_bg_color().into(),
background: Background::Color(cosmic.background(self.transparent).base.into()),
background: Background::Color(cosmic.background.base.into()),
border: Border {
radius: cosmic.corner_radii.radius_m.into(),
..Default::default()
},
selected_text_color: cosmic.accent_text_color().into(),
selected_background: Background::Color(
cosmic.background(self.transparent).component.hover.into(),
),
selected_background: Background::Color(cosmic.background.component.hover.into()),
shadow: Default::default(),
}
}
@ -883,7 +837,7 @@ impl pick_list::Catalog for Theme {
match status {
pick_list::Status::Active => appearance,
pick_list::Status::Hovered => pick_list::Style {
background: Background::Color(cosmic.background(self.transparent).base.into()),
background: Background::Color(cosmic.background.base.into()),
..appearance
},
pick_list::Status::Opened { is_hovered: _ } => appearance,
@ -1079,10 +1033,7 @@ impl progress_bar::Catalog for Theme {
},
)
} else {
(
theme.accent.base,
theme.background(self.transparent).divider,
)
(theme.accent.base, theme.background.divider)
};
let border = Border {
radius: theme.corner_radii.radius_xl.into(),
@ -1562,7 +1513,7 @@ impl iced_widget::text_editor::Catalog for Theme {
let selection = cosmic.accent.base.into();
let value = cosmic.palette.neutral_9.into();
let placeholder = cosmic.palette.neutral_9.with_alpha(0.7).into();
let _icon: Color = cosmic.background(self.transparent).on.into();
let _icon: Color = cosmic.background.on.into();
// TODO do we need to add icon color back?
match status {

View file

@ -31,7 +31,7 @@ pub trait StyleSheet {
type Style: Default;
/// Produces the [`Appearance`] of a menu bar and its menus.
fn appearance(&self, style: &Self::Style, is_overlay: bool) -> Appearance;
fn appearance(&self, style: &Self::Style) -> Appearance;
}
/// The style of a menu bar and its menus
@ -54,7 +54,7 @@ impl From<fn(&Theme) -> Appearance> for MenuBarStyle {
impl StyleSheet for fn(&Theme) -> Appearance {
type Style = Theme;
fn appearance(&self, style: &Self::Style, _is_overlay: bool) -> Appearance {
fn appearance(&self, style: &Self::Style) -> Appearance {
(self)(style)
}
}
@ -62,14 +62,13 @@ impl StyleSheet for fn(&Theme) -> Appearance {
impl StyleSheet for Theme {
type Style = MenuBarStyle;
fn appearance(&self, style: &Self::Style, is_overlay: bool) -> Appearance {
fn appearance(&self, style: &Self::Style) -> Appearance {
let cosmic = self.cosmic();
let component = &cosmic.background(!is_overlay && self.transparent).component;
let bg = component.base;
let component = &cosmic.background.component;
match style {
MenuBarStyle::Default => Appearance {
background: bg.into(),
background: component.base.into(),
border_width: 1.0,
bar_border_radius: cosmic.corner_radii.radius_xl,
menu_border_radius: cosmic.corner_radii.radius_s,
@ -77,7 +76,7 @@ impl StyleSheet for Theme {
background_expand: [1; 4],
path: component.hover.into(),
},
MenuBarStyle::Custom(c) => c.appearance(self, is_overlay),
MenuBarStyle::Custom(c) => c.appearance(self),
}
}
}

View file

@ -6,7 +6,6 @@
//!
//! A [`Button`] has some local [`State`].
use iced::Alignment;
use iced_runtime::core::widget::Id;
use iced_runtime::{Action, Task, keyboard, task};
@ -449,7 +448,7 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
let state = tree.state.downcast_ref::<State>();
let mut styling = if !is_enabled {
let styling = if !is_enabled {
theme.disabled(&self.style)
} else if is_mouse_over {
if state.is_pressed {
@ -471,24 +470,6 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
theme.active(state.is_focused, self.selected, &self.style)
};
if matches!(
self.style,
crate::theme::Button::MenuItem | crate::theme::Button::MenuFolder
) {
match theme.list_item_position {
Some((Alignment::Start, _)) => {
styling.border_radius =
styling.border_radius.bottom(theme.cosmic().radius_0()[3]);
}
Some((Alignment::End, _)) => {
styling.border_radius = styling.border_radius.top(theme.cosmic().radius_0()[0]);
}
Some((Alignment::Center, _)) => {}
None => {
styling.border_radius = theme.cosmic().radius_0().into();
}
}
}
let mut icon_color = styling.icon_color.unwrap_or(renderer_style.icon_color);

View file

@ -31,26 +31,16 @@ impl crate::widget::card::style::Catalog for crate::Theme {
match self.layer {
cosmic_theme::Layer::Background => crate::widget::card::style::Style {
card_1: Background::Color(
cosmic.background(self.transparent).component.hover.into(),
),
card_2: Background::Color(
cosmic.background(self.transparent).component.pressed.into(),
),
card_1: Background::Color(cosmic.background.component.hover.into()),
card_2: Background::Color(cosmic.background.component.pressed.into()),
},
cosmic_theme::Layer::Primary => crate::widget::card::style::Style {
card_1: Background::Color(cosmic.primary(self.transparent).component.hover.into()),
card_2: Background::Color(
cosmic.primary(self.transparent).component.pressed.into(),
),
card_1: Background::Color(cosmic.primary.component.hover.into()),
card_2: Background::Color(cosmic.primary.component.pressed.into()),
},
cosmic_theme::Layer::Secondary => crate::widget::card::style::Style {
card_1: Background::Color(
cosmic.secondary(self.transparent).component.hover.into(),
),
card_2: Background::Color(
cosmic.secondary(self.transparent).component.pressed.into(),
),
card_1: Background::Color(cosmic.secondary.component.hover.into()),
card_2: Background::Color(cosmic.secondary.component.pressed.into()),
},
}
}

View file

@ -2,7 +2,7 @@
use std::time::Duration;
use crate::anim;
use crate::theme::THEME;
use crate::widget::card::style::Style;
use crate::widget::icon::{self, Handle};
use crate::widget::{button, column, row, text};
use float_cmp::approx_eq;
@ -19,6 +19,7 @@ const TOP_SPACING: u16 = 4;
const VERTICAL_SPACING: f32 = 8.0;
const PADDING: u16 = 16;
const BG_CARD_VISIBLE_HEIGHT: f32 = 4.0;
const BG_CARD_BORDER_RADIUS: f32 = 8.0;
const BG_CARD_MARGIN_STEP: f32 = 8.0;
/// get an expandable stack of cards
@ -92,7 +93,7 @@ where
/// Get an expandable stack of cards
#[allow(clippy::too_many_arguments)]
pub fn new<F, G>(
id: widget::Id,
_id: widget::Id,
card_inner_elements: Vec<Element<'a, Message, crate::Theme, crate::Renderer>>,
on_clear_all: Message,
on_show_more: Option<F>,
@ -111,7 +112,7 @@ where
Self {
can_show_more,
id,
id: Id::unique(),
show_less_button: {
let mut show_less_children = Vec::with_capacity(3);
if let Some(source) = show_less_icon {
@ -336,15 +337,14 @@ where
for i in 1..self.elements.len().min(3) {
// height must be 16px for 8px padding
// but we only want 4px visible
let guard = THEME.lock().unwrap();
let radius_xs = guard.cosmic().radius_xs();
let margin = f32::from(u8::try_from(i).unwrap()) * BG_CARD_MARGIN_STEP;
let node = Node::new(Size::new(width - 2.0 * margin, radius_xs[0] * 2.0))
.translate(Vector::new(
margin,
size.height - radius_xs[0] * 2.0 + BG_CARD_VISIBLE_HEIGHT,
));
let node =
Node::new(Size::new(width - 2.0 * margin, BG_CARD_BORDER_RADIUS * 2.0))
.translate(Vector::new(
margin,
size.height - BG_CARD_BORDER_RADIUS * 2.0 + BG_CARD_VISIBLE_HEIGHT,
));
size.height += BG_CARD_VISIBLE_HEIGHT;
children.push(node);
}
@ -424,19 +424,22 @@ where
_ = tree_children.next();
}
let radius_xs = theme.cosmic().radius_xs();
// Draw first to appear behind
if fully_unexpanded {
use crate::widget::card::style::Catalog as _;
let card_layout = layout.next().unwrap();
let appearance = theme.default();
let appearance = Style::default();
let bg_layout = layout.collect::<Vec<_>>();
for (i, layout) in (0..2).zip(bg_layout).rev() {
for (i, layout) in (0..2).zip(bg_layout.into_iter()).rev() {
renderer.fill_quad(
Quad {
bounds: layout.bounds(),
border: Border {
radius: Radius::from([0.0, 0.0, radius_xs[2], radius_xs[3]]),
radius: Radius::from([
0.0,
0.0,
BG_CARD_BORDER_RADIUS,
BG_CARD_BORDER_RADIUS,
]),
..Default::default()
},
shadow: Shadow::default(),

View file

@ -5,12 +5,13 @@
use std::borrow::Cow;
use std::rc::Rc;
use std::sync::LazyLock;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, Instant};
use crate::Element;
use crate::theme::iced::Slider;
use crate::theme::{Button, THEME, Theme};
use crate::theme::{Button, THEME};
use crate::widget::button::Catalog;
use crate::widget::segmented_button::Entity;
use crate::widget::{container, slider};
@ -38,6 +39,37 @@ use super::{Icon, button, segmented_control, text, text_input, tooltip};
#[doc(inline)]
pub use ColorPickerModel as Model;
// TODO is this going to look correct enough?
pub static HSV_RAINBOW: LazyLock<Vec<Color>> = LazyLock::new(|| {
(0u16..8)
.map(|h| {
Color::from(palette::Srgba::from_color(palette::Hsv::new_srgb_const(
RgbHue::new(f32::from(h) * 360.0 / 7.0),
1.0,
1.0,
)))
})
.collect()
});
fn hsv_rainbow(low_hue: f32, high_hue: f32) -> Vec<ColorStop> {
let mut colors = Vec::new();
let steps: u8 = 7;
let step_size = (high_hue - low_hue) / f32::from(steps);
for i in 0..=steps {
let hue = low_hue + step_size * f32::from(i);
colors.push(ColorStop {
color: Color::from(palette::Srgba::from_color(palette::Hsv::new_srgb_const(
RgbHue::new(hue),
1.0,
1.0,
))),
offset: f32::from(i) / f32::from(steps),
});
}
colors
}
const MAX_RECENT: usize = 20;
#[derive(Debug, Clone)]
@ -269,67 +301,23 @@ where
copy_to_clipboard_label: T,
copied_to_clipboard_label: T,
) -> ColorPicker<'a, Message> {
fn rail_backgrounds(hue: f32) -> (Background, Background) {
let low_range = hsv_rainbow(0., hue);
let high_range = hsv_rainbow(hue, 360.);
(
Background::Gradient(iced::Gradient::Linear(
Linear::new(Radians(90.0)).add_stops(low_range),
)),
Background::Gradient(iced::Gradient::Linear(
Linear::new(Radians(90.0)).add_stops(high_range),
)),
)
}
let on_update = self.on_update;
let spacing = THEME.lock().unwrap().cosmic().spacing;
let color_slider_style = Rc::new(move |t: &Theme| {
let cosmic = t.cosmic();
let mut a = slider::Catalog::style(t, &Slider::default(), slider::Status::Active);
a.rail.backgrounds = (
// active track
Background::Gradient(iced::Gradient::Linear(
Linear::new(Radians(90.0)).add_stops((0..8_u8).map(|index| {
let offset;
let hue = self.active_color.hue.into_positive_degrees();
let new_hue: f32;
if hue <= f32::from(index) * 360.0 / 7.0 {
offset = 1.0;
new_hue = hue;
} else {
offset = (f32::from(index) / 7.0) * (360.0 / hue);
new_hue = f32::from(index) * 360.0 / 7.0;
}
ColorStop {
color: Color::from(palette::Srgba::from_color(
palette::Hsv::new_srgb_const(RgbHue::new(new_hue), 1.0, 1.0),
)),
offset,
}
})),
)),
// inactive track
Background::Gradient(iced::Gradient::Linear(
Linear::new(Radians(90.0)).add_stops((0..8_u8).map(|index| {
let offset;
let hue = self.active_color.hue.into_positive_degrees();
let new_hue: f32;
if hue >= f32::from(index) * 360.0 / 7.0 {
offset = 0.0;
new_hue = hue;
} else {
offset =
((f32::from(index) / 7.0) - (hue / 360.0)) / (1.0 - (hue / 360.0));
new_hue = f32::from(index) * 360.0 / 7.0;
}
ColorStop {
color: Color::from(palette::Srgba::from_color(
palette::Hsv::new_srgb_const(RgbHue::new(new_hue), 1.0, 1.0),
)),
offset,
}
})),
)),
);
a.rail.width = 8.0;
a.handle.background = Background::Color(Color::from(palette::Srgba::from_color(
palette::Hsv::new_srgb_const(self.active_color.hue, 1.0, 1.0),
)));
a.handle.shape = HandleShape::Circle { radius: 8.0 };
a.handle.border_color = cosmic.palette.neutral_10.into();
a.handle.border_width = 4.0;
a
});
let mut inner = column![
// segmented buttons
segmented_control::horizontal(self.model)
@ -344,22 +332,56 @@ where
.width(self.width)
.height(self.height),
slider(
0.0..=359.99,
0.001..=359.99,
self.active_color.hue.into_positive_degrees(),
move |v| {
let mut new = self.active_color;
new.hue = v.into();
on_update(ColorPickerUpdate::ActiveColor(new))
},
}
)
.on_release(on_update(ColorPickerUpdate::ActionFinished))
.class(Slider::Custom {
active: color_slider_style.clone(),
hovered: color_slider_style.clone(),
dragging: color_slider_style,
active: Rc::new(move |t| {
let cosmic = t.cosmic();
let mut a =
slider::Catalog::style(t, &Slider::default(), slider::Status::Active);
let hue = self.active_color.hue.into_positive_degrees();
a.rail.backgrounds = rail_backgrounds(hue);
a.rail.width = 8.0;
a.handle.background = Color::TRANSPARENT.into();
a.handle.shape = HandleShape::Circle { radius: 8.0 };
a.handle.border_color = cosmic.palette.neutral_10.into();
a.handle.border_width = 4.0;
a
}),
hovered: Rc::new(move |t| {
let cosmic = t.cosmic();
let mut a =
slider::Catalog::style(t, &Slider::default(), slider::Status::Active);
let hue = self.active_color.hue.into_positive_degrees();
a.rail.backgrounds = rail_backgrounds(hue);
a.rail.width = 8.0;
a.handle.background = Color::TRANSPARENT.into();
a.handle.shape = HandleShape::Circle { radius: 8.0 };
a.handle.border_color = cosmic.palette.neutral_10.into();
a.handle.border_width = 4.0;
a
}),
dragging: Rc::new(move |t| {
let cosmic = t.cosmic();
let mut a =
slider::Catalog::style(t, &Slider::default(), slider::Status::Active);
let hue = self.active_color.hue.into_positive_degrees();
a.rail.backgrounds = rail_backgrounds(hue);
a.rail.width = 8.0;
a.handle.background = Color::TRANSPARENT.into();
a.handle.shape = HandleShape::Circle { radius: 8.0 };
a.handle.border_color = cosmic.palette.neutral_10.into();
a.handle.border_width = 4.0;
a
}),
})
.step(4.0 / 17.0)
.shift_step(64.0 / 17.0)
.width(self.width),
text_input("", self.input_color)
.on_input(move |s| on_update(ColorPickerUpdate::Input(s)))

View file

@ -31,24 +31,6 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
on_close: Message,
max_width: f32,
) -> Element<'a, Message>
where
Drawer: Into<Element<'a, Message>>,
{
Self::new_inner_overlay(
title, actions, header, footer, drawer, on_close, max_width, false,
)
}
pub fn new_inner_overlay<Drawer>(
title: Option<Cow<'a, str>>,
actions: Option<Element<'a, Message>>,
header: Option<Element<'a, Message>>,
footer: Option<Element<'a, Message>>,
drawer: Drawer,
on_close: Message,
max_width: f32,
overlay: bool,
) -> Element<'a, Message>
where
Drawer: Into<Element<'a, Message>>,
{
@ -61,7 +43,6 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
drawer: Element<'a, Message>,
on_close: Message,
max_width: f32,
overlay: bool,
) -> Element<'a, Message> {
let cosmic_theme::Spacing {
space_xxs,
@ -124,9 +105,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
container(
LayerContainer::new(pane)
.layer(cosmic_theme::Layer::Primary)
.class(crate::style::Container::ContextDrawer {
transparent: !overlay,
})
.class(crate::style::Container::ContextDrawer)
.width(Length::Fill)
.height(Length::Fill)
.max_width(max_width),
@ -145,7 +124,6 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
drawer.into(),
on_close,
max_width,
overlay,
)
}
@ -164,9 +142,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
Content: Into<Element<'a, Message>>,
Drawer: Into<Element<'a, Message>>,
{
let drawer = Self::new_inner_overlay(
title, actions, header, footer, drawer, on_close, max_width, true,
);
let drawer = Self::new_inner(title, actions, header, footer, drawer, on_close, max_width);
ContextDrawer {
id: None,

View file

@ -79,10 +79,8 @@ impl<Message: Clone + 'static> ContextMenu<'_, Message> {
my_state: &mut LocalState,
) {
if self.window_id != window::Id::NONE && self.on_surface_action.is_some() {
use crate::surface::action::{LiveSettings, destroy_popup};
use crate::theme::THEME;
use crate::widget::menu::{Menu, StyleSheet as _};
use iced_runtime::platform_specific::wayland::CornerRadius;
use crate::surface::action::destroy_popup;
use crate::widget::menu::Menu;
use iced_runtime::platform_specific::wayland::popup::{
SctkPopupSettings, SctkPositioner,
};
@ -185,22 +183,8 @@ impl<Message: Clone + 'static> ContextMenu<'_, Message> {
..Default::default()
};
let parent = self.window_id;
let t = THEME.lock().unwrap();
let styling = t.appearance(&crate::theme::menu_bar::MenuBarStyle::Default, false);
drop(t);
let rad = styling.menu_border_radius;
shell.publish((self.on_surface_action.as_ref().unwrap())(
crate::surface::action::simple_popup(
move || LiveSettings {
corners: Some(CornerRadius {
top_left: rad[0] as u32,
top_right: rad[1] as u32,
bottom_left: rad[2] as u32,
bottom_right: rad[3] as u32,
}),
..Default::default()
},
move || SctkPopupSettings {
parent,
id,

View file

@ -18,7 +18,6 @@ pub struct Dialog<'a, Message> {
height: Option<Length>,
max_width: Option<Pixels>,
max_height: Option<Pixels>,
is_overlay: bool,
}
impl<Message> Default for Dialog<'_, Message> {
@ -41,7 +40,6 @@ impl<'a, Message> Dialog<'a, Message> {
height: None,
max_width: None,
max_height: None,
is_overlay: true,
}
}
@ -99,11 +97,6 @@ impl<'a, Message> Dialog<'a, Message> {
self.max_width = Some(max_width.into());
self
}
pub fn is_overlay(mut self, is_overlay: bool) -> Self {
self.is_overlay = is_overlay;
self
}
}
impl<'a, Message: Clone + 'static> From<Dialog<'a, Message>> for Element<'a, Message> {
@ -164,7 +157,7 @@ impl<'a, Message: Clone + 'static> From<Dialog<'a, Message>> for Element<'a, Mes
let mut container = widget::container(
widget::column::with_children([content_row.into(), button_row.into()]).spacing(space_l),
)
.class(style::Container::Dialog(dialog.is_overlay))
.class(style::Container::Dialog)
.padding(space_m)
.width(dialog.width.unwrap_or(Length::Fixed(570.0)));

View file

@ -6,7 +6,7 @@ use iced_core::window;
use crate::Element;
use crate::widget::{Id, Widget, container};
use iced::clipboard::dnd::{DndAction, DndEvent, SourceEvent};
use iced::{Event, Length, Point, Rectangle, Vector, mouse, overlay};
use iced::{Event, Length, Point, Rectangle, Vector, event, mouse, overlay};
use iced_core::widget::{Tree, tree};
use iced_core::{self, Clipboard, Shell, layout, renderer};

View file

@ -579,8 +579,6 @@ pub fn update<
use iced_runtime::platform_specific::wayland::popup::{
SctkPopupSettings, SctkPositioner,
};
use crate::surface::action::LiveSettings;
let bounds = layout.bounds();
let anchor_rect = Rectangle {
x: bounds.x as i32,
@ -607,7 +605,6 @@ pub fn update<
let on_surface_action_clone = on_surface_action.clone();
let translation = layout.virtual_offset();
let get_popup_action = surface::action::simple_popup::<AppMessage>(
|| LiveSettings::default(),
move || {
SctkPopupSettings {
parent,

View file

@ -1,4 +1,4 @@
use iced_core::event::Event;
use iced_core::event::{self, Event};
use iced_core::widget::{Id, Operation, Tree};
use iced_core::{
Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay,

View file

@ -1,7 +1,7 @@
use crate::Theme;
use cosmic_theme::LayeredTheme;
use iced::widget::Container;
use iced_core::event::Event;
use iced_core::event::{self, Event};
use iced_core::widget::Tree;
use iced_core::{
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget,

View file

@ -3,7 +3,7 @@
use crate::widget::container::Catalog;
use crate::widget::space::vertical;
use crate::widget::{DndDestination, DndSource, button, column, container, divider, row};
use crate::widget::{button, column, container, divider, row};
use crate::{Apply, Element, theme};
use iced::{Length, Padding};
@ -11,27 +11,14 @@ use iced::{Length, Padding};
pub struct ListButton<'a, Message> {
content: Element<'a, Message>,
on_press: Option<Message>,
dnd_source_builder: Option<Box<DndSourceBuilder<'a, Message>>>,
dnd_destination_builder: Option<Box<DndDestinationBuilder<'a, Message>>>,
selected: bool,
}
/// Builds a DndSource, wrapping an element
pub type DndSourceBuilder<'a, Message> =
dyn FnOnce(
Element<'a, Message>,
) -> DndSource<'a, Message, Box<dyn iced::clipboard::mime::AsMimeTypes + Send>>;
/// Builds a DndDestination, wrapping an element
pub type DndDestinationBuilder<'a, Message> =
dyn FnOnce(Element<'a, Message>) -> DndDestination<'a, Message>;
/// Creates a [`ListButton`] with the given content.
pub fn button<'a, Message>(content: impl Into<Element<'a, Message>>) -> ListButton<'a, Message> {
ListButton {
content: content.into(),
on_press: None,
dnd_source_builder: None,
dnd_destination_builder: None,
selected: false,
}
}
@ -51,19 +38,6 @@ impl<'a, Message: 'static> ListButton<'a, Message> {
self.selected = selected;
self
}
pub fn with_dnd_source(mut self, builder: Box<DndSourceBuilder<'a, Message>>) -> Self {
self.dnd_source_builder = Some(builder);
self
}
pub fn with_dnd_destination(
mut self,
builder: Box<DndDestinationBuilder<'a, Message>>,
) -> Self {
self.dnd_destination_builder = Some(builder);
self
}
}
pub enum ListItem<'a, Message> {
@ -201,10 +175,8 @@ impl<'a, Message: Clone + 'static> ListColumn<'a, Message> {
content,
on_press,
selected,
dnd_source_builder,
dnd_destination_builder,
}) => {
let mut button: Element<'a, Message> = content_row(content)
}) => col.push(
content_row(content)
.apply(button::custom)
.padding(item_padding)
.width(Length::Fill)
@ -214,18 +186,8 @@ impl<'a, Message: Clone + 'static> ListColumn<'a, Message> {
radius_s,
i == 0,
i == last_index,
)))
.into();
if let Some(builder) = dnd_source_builder {
button = builder(button).into();
}
if let Some(builder) = dnd_destination_builder {
button = builder(button).into();
}
col.push(button)
}
))),
),
};
}

View file

@ -66,13 +66,10 @@ mod menu_bar;
pub(crate) use menu_bar::MenuBarState;
pub use menu_bar::{MenuBar, menu_bar as bar};
pub mod menu_column;
mod menu_inner;
mod menu_tree;
pub use menu_tree::{
MenuItem as Item, MenuTree as Tree, menu_button, menu_items as items, menu_root as root,
nav_context,
};
pub use crate::style::menu_bar::{Appearance, StyleSheet};

View file

@ -146,7 +146,6 @@ impl fmt::Display for KeyBind {
write!(f, "{:?} + ", modifier)?;
}
match &self.key {
Key::Character(c) if c.as_str() == " " => write!(f, "Space"),
Key::Character(c) => write!(f, "{}", c.to_uppercase()),
Key::Named(named) => write!(f, "{:?}", named),
other => write!(f, "{:?}", other),
@ -217,7 +216,6 @@ mod test {
};
assert!(bind.matches(Modifiers::CTRL, &Key::Character(" ".into()), None,));
assert!(format!("{}", bind) == String::from("Ctrl + Space"))
}
#[test]

View file

@ -18,8 +18,10 @@ use crate::Renderer;
use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem};
use crate::style::menu_bar::StyleSheet;
use crate::widget::RcWrapper;
use crate::widget::dropdown::menu::{self, State};
use crate::widget::menu::menu_inner::init_root_menu;
use iced::event::Status;
use iced::{Point, Shadow, Vector, window};
use iced_core::Border;
use iced_widget::core::layout::{Limits, Node};
@ -216,7 +218,7 @@ where
path_highlight: Some(PathHighlight::MenuActive),
menu_roots,
style: <crate::Theme as StyleSheet>::Style::default(),
window_id: window::Id::RESERVED,
window_id: window::Id::NONE,
#[cfg(all(
feature = "multi-window",
feature = "wayland",
@ -368,9 +370,7 @@ where
my_state: &mut MenuBarState,
) {
if self.window_id != window::Id::NONE && self.on_surface_action.is_some() {
use crate::surface::action::{LiveSettings, destroy_popup};
use crate::theme::THEME;
use iced_runtime::platform_specific::wayland::CornerRadius;
use crate::surface::action::destroy_popup;
use iced_runtime::platform_specific::wayland::popup::{
SctkPopupSettings, SctkPositioner,
};
@ -485,22 +485,7 @@ where
..Default::default()
};
let parent = self.window_id;
let t = THEME.lock().unwrap();
let styling = t.appearance(&crate::theme::menu_bar::MenuBarStyle::Default, false);
drop(t);
let rad = styling.menu_border_radius;
shell.publish((surface_action)(crate::surface::action::simple_popup(
move || LiveSettings {
corners: Some(CornerRadius {
top_left: rad[0] as u32,
top_right: rad[1] as u32,
bottom_left: rad[2] as u32,
bottom_right: rad[3] as u32,
}),
..Default::default()
},
move || SctkPopupSettings {
parent,
id,
@ -709,20 +694,7 @@ where
// draw path highlight
if self.path_highlight.is_some() {
let mut is_overlay = true;
#[cfg(all(
feature = "multi-window",
feature = "wayland",
target_os = "linux",
feature = "surface-message"
))]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
&& self.on_surface_action.is_some()
&& self.window_id != window::Id::NONE
{
is_overlay = true;
};
let styling = theme.appearance(&self.style, is_overlay);
let styling = theme.appearance(&self.style);
if let Some(active) = state.active_root.first() {
let active_bounds = layout
.children()
@ -766,7 +738,7 @@ where
tree: &'b mut Tree,
layout: Layout<'b>,
_renderer: &Renderer,
_viewport: &Rectangle,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
#[cfg(all(

View file

@ -1,411 +0,0 @@
//! Distribute content vertically.
use crate::iced;
use iced::core::alignment::{self, Alignment};
use iced::core::event::Event;
use iced::core::widget::{Operation, Tree};
use iced::core::{
Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget,
layout, mouse, overlay, renderer,
};
#[allow(missing_debug_implementations)]
#[must_use]
pub struct MenuColumn<'a, Message, Renderer = iced::Renderer> {
spacing: f32,
padding: Padding,
width: Length,
height: Length,
max_width: f32,
align: Alignment,
clip: bool,
children: Vec<Element<'a, Message, crate::Theme, Renderer>>,
}
impl<'a, Message, Renderer> MenuColumn<'a, Message, Renderer>
where
Renderer: iced::core::Renderer,
{
/// Creates an empty [`MenuColumn`].
pub fn new() -> Self {
Self::from_vec(Vec::new())
}
/// Creates a [`MenuColumn`] with the given capacity.
pub fn with_capacity(capacity: usize) -> Self {
Self::from_vec(Vec::with_capacity(capacity))
}
/// Creates a [`MenuColumn`] with the given elements.
pub fn with_children(
children: impl IntoIterator<Item = Element<'a, Message, crate::Theme, Renderer>>,
) -> Self {
let iterator = children.into_iter();
Self::with_capacity(iterator.size_hint().0).extend(iterator)
}
/// Creates a [`MenuColumn`] from an already allocated [`Vec`].
///
/// Keep in mind that the [`MenuColumn`] will not inspect the [`Vec`], which means
/// it won't automatically adapt to the sizing strategy of its contents.
///
/// If any of the children have a [`Length::Fill`] strategy, you will need to
/// call [`MenuColumn::width`] or [`MenuColumn::height`] accordingly.
pub fn from_vec(children: Vec<Element<'a, Message, crate::Theme, Renderer>>) -> Self {
Self {
spacing: 0.0,
padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
max_width: f32::INFINITY,
align: Alignment::Start,
clip: false,
children,
}
}
/// Sets the vertical spacing _between_ elements.
///
/// Custom margins per element do not exist in iced. You should use this
/// method instead! While less flexible, it helps you keep spacing between
/// elements consistent.
pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self {
self.spacing = amount.into().0;
self
}
/// Sets the [`Padding`] of the [`MenuColumn`].
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.padding = padding.into();
self
}
/// Sets the width of the [`MenuColumn`].
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
/// Sets the height of the [`MenuColumn`].
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}
/// Sets the maximum width of the [`MenuColumn`].
pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self {
self.max_width = max_width.into().0;
self
}
/// Sets the horizontal alignment of the contents of the [`MenuColumn`] .
pub fn align_x(mut self, align: impl Into<alignment::Horizontal>) -> Self {
self.align = Alignment::from(align.into());
self
}
/// Sets whether the contents of the [`MenuColumn`] should be clipped on
/// overflow.
pub fn clip(mut self, clip: bool) -> Self {
self.clip = clip;
self
}
/// Adds an element to the [`MenuColumn`].
pub fn push(mut self, child: impl Into<Element<'a, Message, crate::Theme, Renderer>>) -> Self {
let child = child.into();
let child_size = child.as_widget().size_hint();
self.width = self.width.enclose(child_size.width);
self.height = self.height.enclose(child_size.height);
self.children.push(child);
self
}
/// Adds an element to the [`MenuColumn`], if `Some`.
#[must_use]
pub fn push_maybe(
self,
child: Option<impl Into<Element<'a, Message, crate::Theme, Renderer>>>,
) -> Self {
if let Some(child) = child {
self.push(child)
} else {
self
}
}
/// Extends the [`MenuColumn`] with the given children.
pub fn extend(
self,
children: impl IntoIterator<Item = Element<'a, Message, crate::Theme, Renderer>>,
) -> Self {
children.into_iter().fold(self, Self::push)
}
}
impl<Message, Renderer> Default for MenuColumn<'_, Message, Renderer>
where
Renderer: iced::core::Renderer,
{
fn default() -> Self {
Self::new()
}
}
impl<'a, Message, Renderer: iced::core::Renderer>
FromIterator<Element<'a, Message, crate::Theme, Renderer>>
for MenuColumn<'a, Message, Renderer>
{
fn from_iter<T: IntoIterator<Item = Element<'a, Message, crate::Theme, Renderer>>>(
iter: T,
) -> Self {
Self::with_children(iter)
}
}
impl<Message, Renderer> Widget<Message, crate::Theme, Renderer>
for MenuColumn<'_, Message, Renderer>
where
Renderer: iced::core::Renderer,
{
fn children(&self) -> Vec<Tree> {
self.children.iter().map(Tree::new).collect()
}
fn diff(&mut self, tree: &mut Tree) {
tree.diff_children(self.children.as_mut_slice());
}
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
}
fn layout(
&mut self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.max_width(self.max_width);
layout::flex::resolve(
layout::flex::Axis::Vertical,
renderer,
&limits,
self.width,
self.height,
self.padding,
self.spacing,
self.align,
&mut self.children,
&mut tree.children,
)
}
fn operate(
&mut self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation,
) {
operation.container(None, layout.bounds());
operation.traverse(&mut |operation| {
self.children
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
.for_each(|((child, state), c_layout)| {
child.as_widget_mut().operate(
state,
c_layout.with_virtual_offset(layout.virtual_offset()),
renderer,
operation,
);
});
});
}
fn update(
&mut self,
tree: &mut Tree,
event: &Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) {
for (((_i, child), state), c_layout) in self
.children
.iter_mut()
.enumerate()
.zip(&mut tree.children)
.zip(layout.children())
{
child.as_widget_mut().update(
state,
&event,
c_layout.with_virtual_offset(layout.virtual_offset()),
cursor,
renderer,
clipboard,
shell,
viewport,
);
}
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.children
.iter()
.zip(&tree.children)
.zip(layout.children())
.map(|((child, state), c_layout)| {
child.as_widget().mouse_interaction(
state,
c_layout.with_virtual_offset(layout.virtual_offset()),
cursor,
viewport,
renderer,
)
})
.max()
.unwrap_or_default()
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &crate::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
if let Some(clipped_viewport) = layout.bounds().intersection(viewport) {
let viewport = if self.clip {
&clipped_viewport
} else {
viewport
};
for (i, ((child, state), c_layout)) in self
.children
.iter()
.zip(&tree.children)
.zip(layout.children())
.filter(|(_, layout)| layout.bounds().intersects(viewport))
.enumerate()
{
let t = theme.with_list_item_position(if self.children.len() == 1 {
Some((Alignment::Center, i))
} else if 0 == i {
Some((Alignment::Start, i))
} else if i == self.children.len() - 1 {
Some((Alignment::End, i))
} else {
None
});
child.as_widget().draw(
state,
renderer,
&t,
style,
c_layout.with_virtual_offset(layout.virtual_offset()),
cursor,
viewport,
);
}
}
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'b>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
overlay::from_children(
&mut self.children,
tree,
layout,
renderer,
viewport,
translation,
)
}
#[cfg(feature = "a11y")]
/// get the a11y nodes for the widget
fn a11y_nodes(
&self,
layout: Layout<'_>,
state: &Tree,
cursor: mouse::Cursor,
) -> iced_accessibility::A11yTree {
use iced_accessibility::A11yTree;
A11yTree::join(
self.children
.iter()
.zip(layout.children())
.zip(state.children.iter())
.map(|((c, c_layout), state)| {
c.as_widget().a11y_nodes(
c_layout.with_virtual_offset(layout.virtual_offset()),
state,
cursor,
)
}),
)
}
fn drag_destinations(
&self,
state: &Tree,
layout: Layout<'_>,
renderer: &Renderer,
dnd_rectangles: &mut iced::core::clipboard::DndDestinationRectangles,
) {
for ((e, c_layout), state) in self
.children
.iter()
.zip(layout.children())
.zip(state.children.iter())
{
e.as_widget().drag_destinations(
state,
c_layout.with_virtual_offset(layout.virtual_offset()),
renderer,
dnd_rectangles,
);
}
}
}
impl<'a, Message, Renderer> From<MenuColumn<'a, Message, Renderer>>
for Element<'a, Message, crate::Theme, Renderer>
where
Message: 'a,
Renderer: iced::core::Renderer + 'a,
{
fn from(column: MenuColumn<'a, Message, Renderer>) -> Self {
Self::new(column)
}
}

View file

@ -15,7 +15,7 @@ use super::menu_tree::MenuTree;
use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem};
use crate::style::menu_bar::StyleSheet;
use iced::{Alignment, window};
use iced::window;
use iced_core::{Border, Renderer as IcedRenderer, Shadow, Widget};
use iced_widget::core::layout::{Limits, Node};
use iced_widget::core::mouse::{self, Cursor};
@ -524,7 +524,6 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
);
let node_size = children_node.size();
intrinsic_size.height += node_size.height;
intrinsic_size.width = intrinsic_size.width.max(node_size.width);
nodes.push(children_node);
@ -562,7 +561,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
shell: &mut Shell<'_, Message>,
) -> Option<(usize, MenuState)> {
use event::Event::{Mouse, Touch};
use event::Status::{Captured, Ignored};
use mouse::Button::Left;
use mouse::Event::{ButtonPressed, ButtonReleased, CursorMoved, WheelScrolled};
use touch::Event::{FingerLifted, FingerMoved, FingerPressed};
@ -580,7 +579,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
let viewport_size = viewport.size();
let overlay_offset = Point::ORIGIN - viewport.position();
let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset;
let _menu_roots = match &mut self.menu_roots {
let menu_roots = match &mut self.menu_roots {
Cow::Borrowed(_) => panic!(),
Cow::Owned(o) => o.as_mut_slice(),
};
@ -741,7 +740,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
Rectangle::new(Point::ORIGIN, Size::INFINITE)
};
let styling = theme.appearance(&self.style, self.is_overlay);
let styling = theme.appearance(&self.style);
let roots = active_root.iter().skip(1).fold(
&self.menu_roots[active_root[0]].children,
|mt, next_active_root| &mt[*next_active_root].children,
@ -816,27 +815,13 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
.children()
.nth(active.saturating_sub(start_index))
{
let i = active.saturating_sub(start_index);
let mut rad = styling.menu_border_radius;
let rad_0 = theme.cosmic().radius_0();
if start_index != end_index {
if 0 == i {
rad[0] = rad_0[0];
rad[1] = rad_0[1];
} else if i == end_index - start_index {
rad[2] = rad_0[2];
rad[3] = rad_0[3];
} else {
rad = rad_0;
}
}
let path_quad = renderer::Quad {
bounds: active_layout
.bounds()
.intersection(&viewport)
.unwrap_or_default(),
border: Border {
radius: rad.into(),
radius: styling.menu_border_radius.into(),
..Default::default()
},
shadow: Shadow::default(),
@ -849,25 +834,12 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
// draw item
menu_roots[start_index..=end_index]
.iter()
.enumerate()
.zip(children_layout.children())
.for_each(|((i, mt), clo)| {
let t = theme.with_list_item_position(
if start_index == end_index {
Some((Alignment::Center, i))
} else if 0 == i {
Some((Alignment::Start, i))
} else if i == end_index - start_index {
Some((Alignment::End, i))
} else {
None
},
);
.for_each(|(mt, clo)| {
mt.item.draw(
&state.tree.children[active_root[0]].children[mt.index],
r,
&t,
theme,
style,
clo,
view_cursor,
@ -977,7 +949,7 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
#[allow(clippy::too_many_lines)]
fn update(
&mut self,
_tree: &mut Tree,
tree: &mut Tree,
event: &iced::Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
@ -995,25 +967,21 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
target_os = "linux"
))]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
&& let Some((new_root, _new_ms)) = new_root
&& let Some((new_root, new_ms)) = new_root
{
use iced_runtime::platform_specific::wayland::CornerRadius;
use iced_runtime::platform_specific::wayland::popup::{
SctkPopupSettings, SctkPositioner,
};
use crate::surface::action::LiveSettings;
use crate::theme::THEME;
let overlay_offset = Point::ORIGIN - viewport.position();
let _overlay_cursor = cursor.position().unwrap_or_default() - overlay_offset;
let overlay_cursor = cursor.position().unwrap_or_default() - overlay_offset;
let Some((mut menu, popup_id)) = self.tree.inner.with_data_mut(|state| {
let popup_id = *state
.popup_id
.entry(self.window_id)
.or_insert_with(window::Id::unique);
let _active_roots = state
let active_roots = state
.active_root
.get(self.depth)
.cloned()
@ -1027,7 +995,7 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
.map(|lo| lo.bounds())
.collect();
let popup_menu = Menu {
let mut popup_menu = Menu {
tree: self.tree.clone(),
menu_roots: Cow::Owned(Cow::into_owned(self.menu_roots.clone())),
bounds_expand: self.bounds_expand,
@ -1117,23 +1085,8 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
// disable slide_x if it is set in the default
positioner.constraint_adjustment &= !(1 << 0);
let parent = self.window_id;
let t = THEME.lock().unwrap();
let styling = t.appearance(&crate::theme::menu_bar::MenuBarStyle::Default, false);
drop(t);
let rad = styling.menu_border_radius;
shell.publish((self.on_surface_action.as_ref().unwrap())(
crate::surface::action::simple_popup(
move || LiveSettings {
corners: Some(CornerRadius {
top_left: rad[0] as u32,
top_right: rad[1] as u32,
bottom_left: rad[2] as u32,
bottom_right: rad[3] as u32,
}),
..Default::default()
},
move || SctkPopupSettings {
parent,
id: popup_id,
@ -1647,7 +1600,7 @@ fn process_scroll_events<Message>(
) where
Message: Clone,
{
use event::Status::{Captured, Ignored};
use mouse::ScrollDelta;
menu.tree.inner.with_data_mut(|state| {

View file

@ -230,7 +230,7 @@ pub fn menu_items<
}
fn key_style(theme: &crate::Theme) -> TextStyle {
let mut color = theme.cosmic().background(theme.transparent).component.on;
let mut color = theme.cosmic().background.component.on;
color.alpha *= 0.75;
TextStyle {
color: Some(color.into()),
@ -401,19 +401,3 @@ pub fn menu_items<
})
.collect()
}
/// Create a menu tree from a widget and a vector of sub trees
pub fn nav_context<
A: MenuAction<Message = Message>,
L: Into<Cow<'static, str>> + From<&'static str> + 'static,
Message: 'static + std::clone::Clone,
>(
key_binds: &HashMap<KeyBind, A>,
children: Vec<Vec<MenuItem<A, L>>>,
) -> Vec<MenuTree<Message>> {
let menus = children
.into_iter()
.map(|m| MenuItem::<A, L>::Folder(L::from(""), m));
let root = vec![MenuItem::<A, L>::Folder(L::from(""), menus.collect())];
menu_items(key_binds, root)
}

View file

@ -8,7 +8,7 @@
use apply::Apply;
use iced::clipboard::dnd::DndAction;
use iced::clipboard::mime::AllowedMimeTypes;
use iced::{Background, Length, window};
use iced::{Background, Length};
use iced_core::{Border, Color, Shadow};
use crate::widget::{Container, Icon, container, menu, scrollable, segmented_button};
@ -55,7 +55,7 @@ where
}
#[must_use]
pub struct NavBar<'a, Message: Clone + 'static> {
pub struct NavBar<'a, Message> {
segmented_button:
segmented_button::VerticalSegmentedButton<'a, segmented_button::SingleSelect, Message>,
}
@ -133,41 +133,6 @@ impl<'a, Message: Clone + 'static> NavBar<'a, Message> {
self.segmented_button = self.segmented_button.on_dnd_leave(handler);
self
}
#[cfg(all(
feature = "multi-window",
feature = "wayland",
target_os = "linux"
))]
pub fn with_positioner(
mut self,
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
) -> Self {
self.segmented_button = self.segmented_button.with_positioner(positioner);
self
}
#[must_use]
pub fn window_id(mut self, id: window::Id) -> Self {
self.segmented_button = self.segmented_button.window_id(id);
self
}
#[must_use]
pub fn window_id_maybe(mut self, id: Option<window::Id>) -> Self {
self.segmented_button = self.segmented_button.window_id_maybe(id);
self
}
#[must_use]
pub fn on_surface_action(
mut self,
handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static,
) -> Self {
self.segmented_button = self.segmented_button.on_surface_action(handler);
self
}
}
impl<'a, Message: Clone + 'static> From<NavBar<'a, Message>>
@ -207,9 +172,7 @@ pub fn nav_bar_style(theme: &Theme) -> iced_widget::container::Style {
iced_widget::container::Style {
icon_color: Some(cosmic.on_bg_color().into()),
text_color: Some(cosmic.on_bg_color().into()),
background: Some(Background::Color(
cosmic.primary(theme.transparent).base.into(),
)),
background: Some(Background::Color(cosmic.primary.base.into())),
border: Border {
width: 0.0,
color: Color::TRANSPARENT,

View file

@ -4,7 +4,7 @@
//! A container which displays an overlay when a popup widget is attached.
use iced::widget;
use iced_core::event::Event;
use iced_core::event::{self, Event};
use iced_core::widget::{Operation, Tree};
use iced_core::{
Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, layout,
@ -401,26 +401,6 @@ where
&& matches!(event, Event::Mouse(_) | Event::Touch(_))
&& !cursor_position.is_over(layout.bounds())
{
// Swallow new presses outside the popup, but still forward other
// events so an interaction started inside it (such as a selection
// drag) receives its release once the cursor leaves the bounds.
let is_press = matches!(
event,
Event::Mouse(mouse::Event::ButtonPressed(_))
| Event::Touch(touch::Event::FingerPressed { .. })
);
if !is_press {
self.content.as_widget_mut().update(
self.tree,
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
&layout.bounds(),
);
}
shell.capture_event();
return;
}

View file

@ -7,21 +7,30 @@ const LAG: f32 = 0.1;
#[derive(Default)]
pub struct Progress {
pub current: f32,
target: Option<f32>,
last: Option<Instant>,
}
impl Progress {
/// Chases `target` using exponential decay.
/// Smoothly chases `target` using exponential decay.
/// Returns `true` if a redraw should be requested.
pub fn update(&mut self, target: f32, now: Instant) -> bool {
// Don't animate on start
let Some(last) = self.last else {
self.current = target;
self.target = Some(target);
self.last = Some(now);
return false;
};
let dt = (now - last).as_secs_f32().min(1.0 / 60.0);
// Sync animation clock when target changes
if self.target != Some(target) {
self.target = Some(target);
self.last = Some(now);
return true;
}
let dt = (now - last).as_secs_f32();
self.last = Some(now);
let diff = target - self.current;

View file

@ -3,7 +3,7 @@ use super::animation::{Animation, Progress};
use super::style::StyleSheet;
use iced::advanced::widget::tree::{self, Tree};
use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer};
use iced::{Border, Color, Element, Event, Length, Pixels, Rectangle, Size, mouse, window};
use iced::{Element, Event, Length, Pixels, Rectangle, Size, mouse, window};
use std::time::Duration;
@ -196,13 +196,54 @@ where
};
let border_color = custom_style.border_color.unwrap_or(custom_style.bar_color);
let radius = custom_style.border_radius;
let track_color = custom_style.track_color;
let bar_color = custom_style.bar_color;
let draw_quad = |renderer: &mut Renderer, rect: Rectangle, border: Border, color: Color| {
let mut draw_quad = |x: f32,
width: f32,
color: iced::Color,
mut border: iced::Border,
is_track: bool,
total_progress_width: f32| {
let mut height = bounds.height;
if !is_track {
// For progress that is at the end of completion
if total_progress_width > bounds.width - radius {
let border_radius =
radius.min(bounds.height / 2.0) - (bounds.width - total_progress_width);
border.radius.top_right = border_radius;
border.radius.bottom_right = border_radius;
} else {
border.radius.top_right = 0.0;
border.radius.bottom_right = 0.0;
}
// For indeterminate mode or when progress has just started
if x < radius.min(bounds.height / 2.0) {
let border_radius = radius.min(bounds.height / 2.0) - x;
border.radius.top_left = border_radius;
border.radius.bottom_left = border_radius;
if total_progress_width < radius.min(bounds.height / 2.0) {
height = bounds.height - 2.0 * radius.min(bounds.height / 2.0)
+ total_progress_width * 2.0;
}
} else {
border.radius.top_left = 0.0;
border.radius.bottom_left = 0.0;
}
if x > bounds.width - radius.min(bounds.height / 2.0) {
height = bounds.height - 2.0 * radius.min(bounds.height / 2.0) + width * 2.0;
}
}
renderer.fill_quad(
renderer::Quad {
bounds: rect,
bounds: Rectangle {
x: bounds.x + x,
y: bounds.y + (bounds.height - height) / 2.0,
width,
height,
},
border,
snap: true,
..renderer::Quad::default()
@ -210,22 +251,21 @@ where
color,
);
};
let to_rect = |x: f32, width: f32| Rectangle {
x: bounds.x + x * bounds.width,
y: bounds.y,
width: width * bounds.width,
height: bounds.height,
};
// determinate progress bar
if self.progress.is_some() {
let current_p = state.progress.current;
let len = self.markers.len();
let spacing = self.segment_spacing;
let gap = spacing / bounds.width;
let drawable = 1.0 - gap * len as f32;
let radius_inner = radius.min(spacing);
let gap = if len != 0 {
spacing / bounds.width
} else {
0.0
};
let drawable = 1.0 - gap * len as f32;
let mut absolute_width = 0.0;
for i in 0..=len {
let (seg_lo, r_left) = if i == 0 {
(0.0, radius)
@ -239,45 +279,62 @@ where
};
let x_start = seg_lo * drawable + i as f32 * gap;
let x_width = (seg_hi - seg_lo) * drawable;
let rect = to_rect(x_start, x_width);
let segment_radius = [r_left, r_right, r_right, r_left].into();
let border = Border {
width: border_width,
color: border_color,
radius: segment_radius,
let mut segment_radius = if i == 0 && len == 0 {
[r_left, r_right, r_right, r_left].into()
} else if i == 0 {
[r_left, 0.0, 0.0, r_left].into()
} else if i == len {
[0.0, r_right, r_right, 0.0].into()
} else {
[0.0, 0.0, 0.0, 0.0].into()
};
// empty segment
if current_p < seg_lo {
draw_quad(renderer, rect, border, track_color);
}
// filled segment
else if current_p > seg_hi {
draw_quad(renderer, rect, border, bar_color);
}
// partially filled segment
else {
let fill = (current_p - seg_lo) / (seg_hi - seg_lo);
draw_quad(renderer, rect, border, track_color);
renderer.with_layer(to_rect(x_start, x_width * fill), |renderer| {
draw_quad(renderer, rect, border, bar_color);
});
// draw track segment
draw_quad(
x_start * bounds.width,
x_width * bounds.width,
custom_style.track_color,
iced::Border {
width: border_width,
color: border_color,
radius: segment_radius,
},
true,
bounds.width,
);
// draw bar segment
if current_p > seg_lo {
let fill = ((current_p - seg_lo) / (seg_hi - seg_lo)).min(1.0);
absolute_width += x_width * fill + if i == 0 { 0.0 } else { gap };
segment_radius = [r_left, r_right, r_right, r_left].into();
draw_quad(
x_start * bounds.width,
x_width * fill * bounds.width,
custom_style.bar_color,
iced::Border {
radius: segment_radius,
..iced::Border::default()
},
false,
absolute_width * bounds.width,
);
}
}
}
// indeterminate progress bar
else {
} else {
// draw track
draw_quad(
renderer,
bounds,
Border {
0.0,
bounds.width,
custom_style.track_color,
iced::Border {
width: border_width,
color: border_color,
radius: radius.into(),
},
track_color,
true,
bounds.width,
);
// draw bar
@ -289,17 +346,27 @@ where
let start = bar_start % 1.0;
let right_width = (1.0 - start).min(length);
let left_width = length - right_width;
let border = Border {
let border = iced::Border {
radius: radius.into(),
..Border::default()
..iced::Border::default()
};
renderer.with_layer(to_rect(start, right_width), |renderer| {
draw_quad(renderer, bounds, border, bar_color);
});
renderer.with_layer(to_rect(0.0, left_width), |renderer| {
draw_quad(renderer, bounds, border, bar_color);
});
draw_quad(
start * bounds.width,
right_width * bounds.width,
custom_style.bar_color,
border,
false,
(right_width + start) * bounds.width,
);
draw_quad(
0.0,
left_width * bounds.width,
custom_style.bar_color,
border,
false,
left_width * bounds.width,
);
}
}
}

View file

@ -1,7 +1,7 @@
//! Create choices using radio buttons.
use crate::{Theme, theme};
use iced::border;
use iced_core::event::Event;
use iced_core::event::{self, Event};
use iced_core::widget::tree::Tree;
use iced_core::{
Border, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Vector, Widget,

View file

@ -5,7 +5,7 @@ use iced::futures::channel::mpsc::UnboundedSender;
use iced::widget::Container;
pub use subscription::*;
use iced_core::event::Event;
use iced_core::event::{self, Event};
use iced_core::widget::Tree;
use iced_core::{
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, layout,

View file

@ -1,7 +1,7 @@
//! Responsive Container, which will notify of size changes.
use iced::{Limits, Size};
use iced_core::event::Event;
use iced_core::event::{self, Event};
use iced_core::widget::{Id, Operation, Tree, tree};
use iced_core::{
Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay,

View file

@ -21,7 +21,7 @@ pub struct Horizontal;
/// Horizontal implementation of the [`SegmentedButton`].
///
/// For details on the model, see the [`segmented_button`](super) module for more details.
pub fn horizontal<SelectionMode: Default, Message: Clone + 'static>(
pub fn horizontal<SelectionMode: Default, Message>(
model: &Model<SelectionMode>,
) -> SegmentedButton<'_, Horizontal, SelectionMode, Message>
where
@ -30,7 +30,7 @@ where
SegmentedButton::new(model)
}
impl<SelectionMode, Message: Clone + 'static> SegmentedVariant
impl<SelectionMode, Message> SegmentedVariant
for SegmentedButton<'_, Horizontal, SelectionMode, Message>
where
Model<SelectionMode>: Selectable,

View file

@ -20,7 +20,7 @@ pub type VerticalSegmentedButton<'a, SelectionMode, Message> =
/// Vertical implementation of the [`SegmentedButton`].
///
/// For details on the model, see the [`segmented_button`](super) module for more details.
pub fn vertical<SelectionMode, Message: Clone + 'static>(
pub fn vertical<SelectionMode, Message>(
model: &Model<SelectionMode>,
) -> SegmentedButton<'_, Vertical, SelectionMode, Message>
where
@ -30,7 +30,7 @@ where
SegmentedButton::new(model)
}
impl<SelectionMode, Message: Clone + 'static> SegmentedVariant
impl<SelectionMode, Message> SegmentedVariant
for SegmentedButton<'_, Vertical, SelectionMode, Message>
where
Model<SelectionMode>: Selectable,

View file

@ -38,7 +38,6 @@ use std::collections::HashSet;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::sync::Arc;
use std::time::{Duration, Instant};
thread_local! {
@ -108,7 +107,7 @@ pub trait SegmentedVariant {
/// A conjoined group of items that function together as a button.
#[derive(Setters)]
#[must_use]
pub struct SegmentedButton<'a, Variant, SelectionMode, Message: Clone + 'static>
pub struct SegmentedButton<'a, Variant, SelectionMode, Message>
where
Model<SelectionMode>: Selectable,
SelectionMode: Default,
@ -194,27 +193,14 @@ where
pub(super) tab_drag: Option<TabDragSource<Message>>,
#[setters(skip)]
pub(super) on_drop_hint: Option<Box<dyn Fn(Option<(Entity, bool)>) -> Message + 'static>>,
#[setters(skip)]
pub(super) on_reorder: Option<Box<dyn Fn(ReorderEvent) -> Message + 'static>>,
#[setters(skip)]
window_id: window::Id,
#[cfg(all(
feature = "multi-window",
feature = "wayland",
target_os = "linux"
))]
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
#[setters(skip)]
pub(crate) on_surface_action:
Option<Arc<dyn Fn(crate::surface::Action) -> Message + Send + Sync + 'static>>,
/// Defines the implementation of this struct
variant: PhantomData<Variant>,
}
impl<'a, Variant, SelectionMode, Message: Clone + 'static>
SegmentedButton<'a, Variant, SelectionMode, Message>
impl<'a, Variant, SelectionMode, Message> SegmentedButton<'a, Variant, SelectionMode, Message>
where
Self: SegmentedVariant,
Model<SelectionMode>: Selectable,
@ -262,14 +248,6 @@ where
tab_drag: None,
on_drop_hint: None,
on_reorder: None,
window_id: window::Id::RESERVED,
#[cfg(all(
feature = "multi-window",
feature = "wayland",
target_os = "linux"
))]
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(),
on_surface_action: None,
}
}
@ -333,7 +311,12 @@ where
where
Message: Clone + 'static,
{
self.context_menu = context_menu;
self.context_menu = context_menu.map(|menus| {
vec![menu::Tree::with_children(
crate::Element::from(crate::widget::Row::new()),
menus,
)]
});
if let Some(ref mut context_menu) = self.context_menu {
context_menu.iter_mut().for_each(menu::Tree::set_index);
@ -895,267 +878,6 @@ where
|id| id.0,
)
}
#[cfg(all(
feature = "multi-window",
feature = "wayland",
target_os = "linux"
))]
pub fn with_positioner(
mut self,
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
) -> Self {
self.positioner = positioner;
self
}
#[must_use]
pub fn window_id(mut self, id: window::Id) -> Self {
self.window_id = id;
self
}
#[must_use]
pub fn window_id_maybe(mut self, id: Option<window::Id>) -> Self {
if let Some(id) = id {
self.window_id = id;
}
self
}
#[must_use]
pub fn on_surface_action(
mut self,
handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static,
) -> Self {
self.on_surface_action = Some(Arc::new(handler));
self
}
#[cfg(all(
feature = "multi-window",
feature = "wayland",
target_os = "linux",
feature = "surface-message"
))]
#[allow(clippy::too_many_lines)]
fn create_popup<'b>(
&mut self,
layout: Layout<'b>,
view_cursor: mouse::Cursor,
renderer: &'b Renderer,
shell: &'b mut Shell<'_, Message>,
viewport: &'b Rectangle,
tree: &'b mut Tree,
) {
let state = tree.state.downcast_mut::<LocalState>();
let my_state = state.menu_state.clone();
if self.window_id != window::Id::NONE {
use crate::surface::action::{LiveSettings, destroy_popup};
use crate::widget::menu::StyleSheet;
use iced_runtime::platform_specific::wayland::CornerRadius;
use iced_runtime::platform_specific::wayland::popup::{
SctkPopupSettings, SctkPositioner,
};
let Some(surface_action) = self.on_surface_action.as_ref() else {
return;
};
let id = my_state.inner.with_data_mut(|state| {
if let Some(id) = state.popup_id.get(&self.window_id).copied() {
// close existing popups
state.menu_states.clear();
state.active_root.clear();
shell.publish(surface_action(destroy_popup(id)));
state.view_cursor = view_cursor;
id
} else {
window::Id::unique()
}
});
let Some(entity) = state.show_context else {
return;
};
let Some((mut bounds, i)) = self
.variant_bounds(state, layout.bounds())
.enumerate()
.find_map(|(i, item)| match item {
ItemBounds::Button(e, bounds) if e == entity => Some((bounds, i)),
_ => None,
})
else {
return;
};
assert!(
self.context_menu
.as_ref()
.is_none_or(|m| m[0].children.len() == self.model.len()),
"model length must match the number of context menus"
);
let menu = self
.context_menu
.as_mut()
.map(|m| m[0].children[i].clone())
.unwrap();
bounds.x = state.context_cursor.x;
bounds.y = state.context_cursor.y;
let mut popup_menu: menu::Menu<'static, _> = menu::Menu {
tree: my_state.clone(),
menu_roots: std::borrow::Cow::Owned(vec![menu]),
bounds_expand: 0,
menu_overlays_parent: false,
close_condition: CloseCondition {
leave: false,
click_outside: true,
click_inside: true,
},
item_width: ItemWidth::Uniform(240),
item_height: ItemHeight::Dynamic(40),
bar_bounds: bounds,
main_offset: 0,
cross_offset: 0,
root_bounds_list: vec![bounds],
path_highlight: Some(PathHighlight::MenuActive),
style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default),
position: Point::new(0., 0.),
is_overlay: false,
window_id: id,
depth: 0,
on_surface_action: self.on_surface_action.clone(),
};
menu::init_root_menu(
&mut popup_menu,
renderer,
shell,
view_cursor.position().unwrap(),
viewport.size(),
Vector::new(0., 0.),
bounds,
0., // TODO offset?
);
let (anchor_rect, gravity) = my_state.inner.with_data_mut(|state| {
state.popup_id.insert(self.window_id, id);
(state
.menu_states
.iter()
.find(|s| s.index.is_none())
.map(|s| s.menu_bounds.parent_bounds)
.map_or_else(
|| {
let bounds = layout.bounds();
Rectangle {
x: bounds.x as i32,
y: bounds.y as i32,
width: 1,
height: 1,
}
},
|r| Rectangle {
x: r.x as i32,
y: r.y as i32,
width: 1,
height: 1,
},
), match (state.horizontal_direction, state.vertical_direction) {
(menu::Direction::Positive, menu::Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight,
(menu::Direction::Positive, menu::Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight,
(menu::Direction::Negative, menu::Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft,
(menu::Direction::Negative, menu::Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft,
})
});
let menu_node =
popup_menu.layout(renderer, layout::Limits::NONE.min_width(1.).min_height(1.));
let popup_size = menu_node.size();
let positioner = SctkPositioner {
size: Some((
popup_size.width.ceil() as u32 + 2,
popup_size.height.ceil() as u32 + 2,
)),
anchor_rect,
anchor:
cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::BottomLeft,
gravity,
reactive: true,
..Default::default()
};
let parent = self.window_id;
let t = THEME.lock().unwrap();
let styling = t.appearance(&crate::theme::menu_bar::MenuBarStyle::Default, false);
drop(t);
let rad = styling.menu_border_radius;
/// Used to create a popup message from within a widget.
#[cfg(all(feature = "wayland", target_os = "linux"))]
#[must_use]
pub fn simple_popup<Message: 'static>(
live_settings: impl Fn() -> LiveSettings + Send + Sync + 'static,
settings: impl Fn()
-> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
+ Send
+ Sync
+ 'static,
view: Option<impl Fn() -> crate::Element<'static, Message> + Send + Sync + 'static>,
) -> crate::surface::Action {
use std::any::Any;
let boxed: Box<
dyn Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
+ Send
+ Sync
+ 'static,
> = Box::new(settings);
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
let boxed_live: Box<dyn Fn() -> LiveSettings + Send + Sync + 'static> =
Box::new(live_settings);
let boxed_live: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed_live);
crate::surface::Action::Popup(
Arc::new(boxed),
Arc::new(boxed_live),
view.map(|view| {
let boxed: Box<
dyn Fn() -> crate::Element<'static, Message> + Send + Sync + 'static,
> = Box::new(view);
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
Arc::new(boxed)
}),
)
}
shell.publish((surface_action)(simple_popup(
move || LiveSettings {
corners: Some(CornerRadius {
top_left: rad[0] as u32,
top_right: rad[1] as u32,
bottom_left: rad[2] as u32,
bottom_right: rad[3] as u32,
}),
..Default::default()
},
move || SctkPopupSettings {
parent,
id,
positioner: positioner.clone(),
parent_size: None,
grab: true,
close_with_children: false,
input_zone: None,
},
Some(move || {
Element::from(crate::widget::container(popup_menu.clone()).center(Length::Fill))
}),
)));
}
}
}
impl<Variant, SelectionMode, Message> Widget<Message, crate::Theme, Renderer>
@ -1268,10 +990,10 @@ where
mut event: &Event,
layout: Layout<'_>,
cursor_position: mouse::Cursor,
renderer: &Renderer,
_renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &iced::Rectangle,
_viewport: &iced::Rectangle,
) {
let my_bounds = layout.bounds();
let state = tree.state.downcast_mut::<LocalState>();
@ -1676,41 +1398,6 @@ where
state.unfocus();
}
#[cfg(all(
feature = "multi-window",
feature = "wayland",
target_os = "linux",
feature = "surface-message"
))]
if is_pressed(event)
&& let Some(_on_context) = self.on_context.as_ref()
{
let (was_open, id) = state.menu_state.inner.with_data_mut(|data| {
let was_open = data.open;
data.reset();
data.open = false;
data.view_cursor = cursor_position;
let root = data.popup_id.remove(&self.window_id);
data.popup_id.clear();
(was_open, root)
});
if let Some(w) = id
&& let Some(surface_action) = self.on_surface_action.as_ref()
&& was_open
{
use crate::surface::action::destroy_popup;
shell.publish((surface_action)(destroy_popup(w)));
return;
}
}
#[cfg(all(
feature = "multi-window",
feature = "wayland",
target_os = "linux",
feature = "surface-message"
))]
if let Some(on_activate) = self.on_activate.as_ref() {
if is_pressed(event) {
state.pressed_item = Some(Item::Tab(key));
@ -1769,28 +1456,9 @@ where
shell.publish(on_context(key));
shell.capture_event();
#[cfg(all(
feature = "multi-window",
feature = "wayland",
target_os = "linux",
feature = "surface-message"
))]
if matches!(
crate::app::cosmic::WINDOWING_SYSTEM.get(),
Some(crate::app::cosmic::WindowingSystem::Wayland)
) {
self.create_popup(
layout,
cursor_position,
renderer,
shell,
viewport,
tree,
);
}
return;
}
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) =
event
{
@ -1908,33 +1576,6 @@ where
}
}
if (matches!(event, Event::Mouse(mouse::Event::ButtonReleased(_))) || (touch_lifted(event)))
&& let Some(_id) = state
.menu_state
.inner
.with_data_mut(|ms| ms.popup_id.remove(&self.window_id))
{
#[cfg(all(
feature = "wayland",
target_os = "linux",
feature = "surface-message"
))]
{
let surface_action = self.on_surface_action.as_ref().unwrap();
shell.capture_event();
shell.publish(surface_action(crate::surface::action::destroy_popup(_id)));
}
state.show_context = None;
state.menu_state.inner.with_data_mut(|data| {
// Clear stale MenuBounds from any previous context menu before opening a new one.
data.reset();
data.open = false;
data.view_cursor = cursor_position;
});
}
if matches!(
event,
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
@ -2552,43 +2193,19 @@ where
_viewport: &iced_core::Rectangle,
translation: Vector,
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, Renderer>> {
#[cfg(all(
feature = "multi-window",
feature = "wayland",
target_os = "linux",
feature = "surface-message"
))]
if matches!(
crate::app::cosmic::WINDOWING_SYSTEM.get(),
Some(crate::app::cosmic::WindowingSystem::Wayland)
) && self.on_surface_action.is_some()
&& self.window_id != window::Id::NONE
{
return None;
}
let state = tree.state.downcast_mut::<LocalState>();
let menu_state = state.menu_state.clone();
let entity = state.show_context?;
let (mut bounds, i) = self
.variant_bounds(state, layout.bounds())
.enumerate()
.find_map(|(i, item)| match item {
ItemBounds::Button(e, bounds) if e == entity => Some((bounds, i)),
_ => None,
})?;
let mut bounds =
self.variant_bounds(state, layout.bounds())
.find_map(|item| match item {
ItemBounds::Button(e, bounds) if e == entity => Some(bounds),
_ => None,
})?;
assert!(
self.context_menu
.as_ref()
.is_none_or(|m| m[0].children.len() == self.model.len())
);
let menu = self
.context_menu
.as_mut()
.map(|m| m[0].children[i].clone())?;
let context_menu = self.context_menu.as_mut()?;
if !menu_state.inner.with_data(|data| data.open) {
// If the menu is not open, we don't need to show it.
@ -2606,7 +2223,7 @@ where
Some(
crate::widget::menu::Menu {
tree: menu_state,
menu_roots: std::borrow::Cow::Owned(vec![menu]),
menu_roots: std::borrow::Cow::Owned(context_menu.clone()),
bounds_expand: 16,
menu_overlays_parent: true,
close_condition: CloseCondition {
@ -2892,7 +2509,6 @@ mod tests {
where
Model<SelectionMode>: Selectable,
SelectionMode: Default,
Message: Clone,
{
const VERTICAL: bool = false;

View file

@ -14,7 +14,7 @@ use super::segmented_button::{
/// The data for the widget comes from a model that is maintained the application.
///
/// For details on the model, see the [`segmented_button`] module for more details.
pub fn horizontal<SelectionMode: Default, Message: Clone + 'static>(
pub fn horizontal<SelectionMode: Default, Message>(
model: &Model<SelectionMode>,
) -> HorizontalSegmentedButton<'_, SelectionMode, Message>
where
@ -37,7 +37,7 @@ where
/// The data for the widget comes from a model that is maintained the application.
///
/// For details on the model, see the [`segmented_button`] module for more details.
pub fn vertical<SelectionMode, Message: Clone + 'static>(
pub fn vertical<SelectionMode, Message>(
model: &Model<SelectionMode>,
) -> VerticalSegmentedButton<'_, SelectionMode, Message>
where

View file

@ -14,7 +14,7 @@ use super::segmented_button::{
/// The data for the widget comes from a model supplied by the application.
///
/// For details on the model, see the [`segmented_button`] module for more details.
pub fn horizontal<SelectionMode: Default, Message: Clone + 'static>(
pub fn horizontal<SelectionMode: Default, Message>(
model: &Model<SelectionMode>,
) -> HorizontalSegmentedButton<'_, SelectionMode, Message>
where
@ -35,7 +35,7 @@ where
///
/// The data for the widget comes from a model that is maintained the application.
/// For details on the model, see the [`segmented_button`] module for more details.
pub fn vertical<SelectionMode, Message: Clone + 'static>(
pub fn vertical<SelectionMode, Message>(
model: &Model<SelectionMode>,
) -> VerticalSegmentedButton<'_, SelectionMode, Message>
where

View file

@ -1811,22 +1811,9 @@ pub fn update<'a, Message: Clone + 'static>(
focus.updated_at = Instant::now();
LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
// Ctrl/Command+A/C/V/X, plus the traditional alternate clipboard
let clip_key = match key.as_ref() {
keyboard::Key::Named(keyboard::key::Named::Insert) if modifiers.shift() => {
Some('v')
}
keyboard::Key::Named(keyboard::key::Named::Insert) if modifiers.command() => {
Some('c')
}
keyboard::Key::Named(keyboard::key::Named::Delete) if modifiers.shift() => {
Some('x')
}
_ if modifiers.command() => key.to_latin(*physical_key),
_ => None,
};
{
match clip_key {
// Check if Ctrl/Command+A/C/V/X was pressed.
if state.keyboard_modifiers.command() {
match key.to_latin(*physical_key) {
Some('c') => {
if !is_secure
&& let Some((start, end)) = state.cursor.selection(value) {

View file

@ -4,7 +4,7 @@
use iced::{Limits, Size};
use iced_core::layout::Node;
use iced_core::event::Event;
use iced_core::event::{self, Event};
use iced_core::renderer::{self};
use iced_core::widget::Operation;
use iced_core::widget::tree::Tree;
@ -152,7 +152,7 @@ where
translation,
)
} else {
let _bounds = layout.bounds();
let bounds = layout.bounds();
Some(overlay::Element::new(Box::new(ToasterOverlay::new(
&mut state.children[1],

View file

@ -1,17 +1,18 @@
//! Show toggle controls using togglers.
use std::time::Duration;
use std::time::{Duration, Instant};
use crate::{Element, anim};
use iced_core::renderer::{self, Renderer};
use iced_core::widget::{self, Tree, tree};
use iced_core::{
Border, Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment, layout, mouse, text, touch, window,
Border, Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment,
event, layout, mouse, text, touch, window,
};
use iced_widget::Id;
use iced_widget::toggler::Status;
pub use iced_widget::toggler::Catalog;
pub use iced_widget::toggler::{Catalog, Style};
pub fn toggler<'a, Message>(is_checked: bool) -> Toggler<'a, Message> {
Toggler::new(is_checked)
@ -259,7 +260,7 @@ impl<'a, Message> Widget<Message, crate::Theme, crate::Renderer> for Toggler<'a,
shell.capture_event();
}
}
Event::Window(window::Event::RedrawRequested(_now)) => {
Event::Window(window::Event::RedrawRequested(now)) => {
state.anim.anim_done(self.duration);
if state.anim.last_change.is_some() {
shell.request_redraw();
@ -369,7 +370,7 @@ impl<'a, Message> Widget<Message, crate::Theme, crate::Renderer> for Toggler<'a,
},
style.background,
);
let t = state.anim.t(self.duration, self.is_toggled);
let mut t = state.anim.t(self.duration, self.is_toggled);
let toggler_foreground_bounds = Rectangle {
x: bounds.x

View file

@ -13,20 +13,25 @@ use std::time::Duration;
use iced::Task;
use iced_runtime::core::widget::Id;
use iced_core::event::Event;
use iced_core::event::{self, Event};
use iced_core::widget::Operation;
use iced_core::widget::tree::{self, Tree};
use iced_core::{
Background, Border, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shadow, Shell,
Vector, Widget, layout, mouse, overlay, renderer, touch,
Vector, Widget, layout, mouse, overlay, renderer, svg, touch,
};
use iced_runtime::platform_specific::wayland::CornerRadius;
use crate::surface::action::LiveSettings;
use crate::theme::THEME;
pub use super::{Catalog, Style};
/// Internally defines different button widget variants.
enum Variant<Message> {
Normal,
Image {
close_icon: svg::Handle,
on_remove: Option<Message>,
},
}
/// A generic button which emits a message when pressed.
#[allow(missing_debug_implementations)]
#[must_use]
@ -301,7 +306,7 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone>
}
let content_layout = layout.children().next().unwrap();
let _state = tree.state.downcast_ref::<State>();
let state = tree.state.downcast_ref::<State>();
let styling = theme.style(&self.style);
@ -516,26 +521,8 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>(
> = Box::new(move || s(bounds));
let boxed: Box<dyn Any + Send + Sync + 'static> =
Box::new(boxed);
let theme = THEME.lock().unwrap();
let corners = theme.cosmic().corner_radii.radius_s;
let boxed_live: Box<
dyn Fn() -> LiveSettings + Send + Sync + 'static,
> = Box::new(move || LiveSettings {
corners: Some(CornerRadius {
top_left: corners[0] as u32,
top_right: corners[1] as u32,
bottom_left: corners[3] as u32,
bottom_right: corners[2] as u32,
}),
..Default::default()
});
let boxed_live: Box<dyn Any + Send + Sync + 'static> =
Box::new(boxed_live);
crate::surface::Action::Popup(
Arc::new(boxed),
Arc::new(boxed_live),
Some({
let boxed: Box<
dyn Fn() -> crate::Element<
@ -566,25 +553,9 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>(
+ 'static,
> = Box::new(move || s(bounds));
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
let theme = THEME.lock().unwrap();
let corners = theme.cosmic().corner_radii.radius_s;
let boxed_live: Box<dyn Fn() -> LiveSettings + Send + Sync + 'static> =
Box::new(move || LiveSettings {
corners: Some(CornerRadius {
top_left: corners[0] as u32,
top_right: corners[1] as u32,
bottom_left: corners[3] as u32,
bottom_right: corners[2] as u32,
}),
..Default::default()
});
let boxed_live: Box<dyn Any + Send + Sync + 'static> =
Box::new(boxed_live);
let sm = crate::surface::Action::Popup(
Arc::new(boxed),
Arc::new(boxed_live),
Some({
let boxed: Box<
dyn Fn() -> crate::Element<

View file

@ -4,7 +4,7 @@ use std::rc::Rc;
use std::thread::{self, ThreadId};
use crate::Element;
use iced::{Length, Rectangle, Size};
use iced::{Length, Rectangle, Size, event};
use iced_core::id::Id;
use iced_core::widget::tree;
use iced_core::{Widget, widget};