Compare commits
81 commits
3daac73eff
...
2fe2d35286
| Author | SHA1 | Date | |
|---|---|---|---|
| 2fe2d35286 | |||
| 9e9ae942c4 | |||
| 18c2326f34 | |||
| 37829fd899 | |||
| ca66c7c1b0 | |||
| ce5e3b6eca | |||
| 2c91d818c9 | |||
| 9512f77a58 | |||
| 4e1080108c | |||
| c85efc6180 | |||
| 7c0b3ff1b7 | |||
| 695c602ba1 | |||
| 45eaaf1605 | |||
| 261a211647 | |||
| b7c81907ba | |||
| 6219084eb6 | |||
|
|
20dfd7c236 | ||
|
|
be499d01af | ||
|
|
bb0145c566 | ||
|
|
a365d45091 | ||
|
|
cad4be9401 | ||
|
|
822d3b2a0f | ||
|
|
46b25d4218 | ||
|
|
9855d8b6fd | ||
|
|
5ea8a86959 | ||
|
|
e3a4e739e3 | ||
|
|
46c717fd57 | ||
|
|
94d2c18a23 | ||
|
|
cde1989e78 | ||
|
|
f5fc392ba4 | ||
|
|
f04437cee5 | ||
|
|
e939d334f6 | ||
|
|
217b4abe99 | ||
|
|
0c336d77e4 | ||
|
|
00067dc5b8 | ||
|
|
19f4a289e5 | ||
|
|
43de9e3e77 | ||
|
|
033dfe6c57 | ||
|
|
76954a6524 | ||
|
|
4a7ed015f6 | ||
|
|
991a8f2c78 | ||
|
|
8d2dbb3f23 | ||
|
|
ac4a1ab259 | ||
|
|
f268bb86cf | ||
|
|
58980231b9 | ||
|
|
bd56f82ca0 | ||
|
|
90ac265ee0 | ||
|
|
40792f21de | ||
|
|
7d62b193f4 | ||
|
|
408d37169a | ||
|
|
5fca59df70 | ||
|
|
5938776c26 | ||
|
|
0ee5131670 | ||
|
|
fb4628e460 | ||
|
|
1fafd57578 | ||
|
|
bf5ffcc2af | ||
|
|
2d7f66d528 | ||
|
|
732785a50c | ||
|
|
cadb773377 | ||
|
|
9e77210740 | ||
|
|
cfd5220c1e | ||
|
|
19142fd38e | ||
|
|
d0b164d216 | ||
|
|
439109db04 | ||
|
|
9395a23a0b | ||
|
|
7ac8f74825 | ||
|
|
72ddeaa5fb | ||
|
|
7ecc6a32d6 | ||
|
|
aa337c2a16 | ||
|
|
18d76659f2 | ||
|
|
18a7a75e33 | ||
|
|
e7f37f3e81 | ||
|
|
13fb598517 | ||
|
|
417923fbaf | ||
|
|
76bc13ff40 | ||
|
|
6e116097c0 | ||
|
|
e7f278d17f | ||
|
|
8df1858e5d | ||
|
|
f79ccb8cba | ||
|
|
a991d7bb43 | ||
|
|
4d36a2ee5f |
70 changed files with 3744 additions and 934 deletions
58
.github/workflows/ci.yml
vendored
58
.github/workflows/ci.yml
vendored
|
|
@ -6,26 +6,20 @@ on:
|
|||
- master
|
||||
pull_request:
|
||||
|
||||
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
- name: Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
- name: Cargo cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-rust_stable-${{ hashFiles('**/Cargo.toml') }}
|
||||
rustflags: ""
|
||||
- name: Format
|
||||
run: cargo fmt -- --check
|
||||
run: cargo +nightly fmt -- --check
|
||||
|
||||
tests:
|
||||
needs:
|
||||
|
|
@ -44,28 +38,18 @@ jobs:
|
|||
- --no-default-features --features "desktop,smol"
|
||||
- --no-default-features --features "desktop,tokio"
|
||||
- -p cosmic-theme
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
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: dtolnay/rust-toolchain@stable
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
rustflags: ""
|
||||
- name: Test features
|
||||
run: cargo test ${{ matrix.test_args }} -- --test-threads=1
|
||||
env:
|
||||
|
|
@ -82,28 +66,18 @@ jobs:
|
|||
- "open-dialog"
|
||||
- "context-menu"
|
||||
- "nav-context"
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
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: dtolnay/rust-toolchain@stable
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
rustflags: ""
|
||||
- name: Check example
|
||||
run: cargo check -p "${{ matrix.examples }}"
|
||||
env:
|
||||
|
|
|
|||
7
.github/workflows/pages.yml
vendored
7
.github/workflows/pages.yml
vendored
|
|
@ -11,13 +11,14 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install Rust nightly
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
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
|
||||
|
|
@ -29,7 +30,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@v3
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./target/doc
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ 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]
|
||||
|
|
@ -289,7 +290,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
|
||||
|
|
|
|||
|
|
@ -1,17 +1,32 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{self};
|
||||
use syn;
|
||||
|
||||
#[proc_macro_derive(CosmicConfigEntry, attributes(version, id))]
|
||||
#[proc_macro_derive(CosmicConfigEntry, attributes(version, id, cosmic_config_entry))]
|
||||
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
|
||||
|
|
@ -48,14 +63,48 @@ 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)?;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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,
|
||||
|
|
@ -63,11 +112,35 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
|
|||
Err(e) => errors.push(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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)) {
|
||||
|
|
@ -77,8 +150,7 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
|
|||
}
|
||||
self.#field_name = value;
|
||||
},
|
||||
Err(e) => {
|
||||
errors.push(e);
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ 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 ..
|
||||
|
|
@ -193,9 +194,13 @@ 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 =
|
||||
|
|
@ -205,6 +210,13 @@ 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
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -212,6 +224,10 @@ 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));
|
||||
|
||||
|
|
@ -236,15 +252,29 @@ 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;
|
||||
let mut user_path = custom_path.clone();
|
||||
user_path.push("cosmic");
|
||||
user_path.push(path);
|
||||
// Create new configuration directory if not found.
|
||||
|
|
@ -254,6 +284,13 @@ 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
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -263,6 +300,10 @@ 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));
|
||||
|
||||
|
|
@ -276,11 +317,27 @@ 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));
|
||||
|
||||
|
|
@ -294,6 +351,13 @@ 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
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -404,7 +468,13 @@ impl ConfigGet for Config {
|
|||
Ok(ron::from_str(&data)?)
|
||||
}
|
||||
|
||||
_ => Err(Error::NotFound),
|
||||
_ => {
|
||||
if let Some(previous) = self.previous.as_ref() {
|
||||
previous.get_local(key)
|
||||
} else {
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ 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 = [
|
||||
|
|
|
|||
173
cosmic-theme/src/model/color.rs
Normal file
173
cosmic-theme/src/model/color.rs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
//! 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::color::color_serde;
|
||||
use palette::Srgba;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::LazyLock;
|
||||
|
|
@ -95,75 +96,107 @@ 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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
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)))
|
||||
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",))
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::color::color_serde;
|
||||
use palette::{Srgba, WithAlpha};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -8,14 +9,18 @@ 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,
|
||||
}
|
||||
|
||||
|
|
@ -45,30 +50,42 @@ 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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
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)))
|
||||
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",))
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ pub use mode::*;
|
|||
pub use spacing::*;
|
||||
pub use theme::*;
|
||||
|
||||
pub mod color;
|
||||
mod corner;
|
||||
mod cosmic_palette;
|
||||
mod derivation;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
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::{
|
||||
|
|
@ -9,6 +11,7 @@ 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
|
||||
|
|
@ -45,16 +48,24 @@ pub enum Layer {
|
|||
PartialEq,
|
||||
cosmic_config::cosmic_config_derive::CosmicConfigEntry,
|
||||
)]
|
||||
#[version = 1]
|
||||
#[version = 2]
|
||||
pub struct Theme {
|
||||
/// name of the theme
|
||||
pub name: String,
|
||||
/// background element colors
|
||||
pub background: Container,
|
||||
pub(crate) background: Container,
|
||||
/// primary element colors
|
||||
pub primary: Container,
|
||||
pub(crate) primary: Container,
|
||||
/// secondary element colors
|
||||
pub secondary: Container,
|
||||
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,
|
||||
/// accent element colors
|
||||
pub accent: Component,
|
||||
/// suggested element colors
|
||||
|
|
@ -79,8 +90,6 @@ 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
|
||||
|
|
@ -97,16 +106,40 @@ pub struct Theme {
|
|||
pub active_hint: u32,
|
||||
/// cosmic-comp custom window hint color
|
||||
pub window_hint: Option<Srgb>,
|
||||
#[serde(default)]
|
||||
/// enables blurred transparency
|
||||
pub is_frosted: bool,
|
||||
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,
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
|
|
@ -172,6 +205,39 @@ 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]
|
||||
|
|
@ -741,7 +807,7 @@ impl Theme {
|
|||
if color_scheme.trim().contains("default") || color_scheme.trim().contains("light") {
|
||||
return Self::light_default();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Self::dark_default()
|
||||
}
|
||||
|
|
@ -750,11 +816,11 @@ impl Theme {
|
|||
pub fn preferred_theme() -> Self {
|
||||
let current_desktop = std::env::var("XDG_CURRENT_DESKTOP");
|
||||
|
||||
if let Ok(desktop) = current_desktop {
|
||||
if desktop.trim().to_lowercase().contains("gnome") {
|
||||
if let Ok(desktop) = current_desktop
|
||||
&& desktop.trim().to_lowercase().contains("gnome")
|
||||
{
|
||||
return Self::gtk_prefer_colorscheme();
|
||||
}
|
||||
}
|
||||
|
||||
Self::dark_default()
|
||||
}
|
||||
|
|
@ -776,7 +842,7 @@ impl From<CosmicPalette> for Theme {
|
|||
cosmic_config::cosmic_config_derive::CosmicConfigEntry,
|
||||
PartialEq,
|
||||
)]
|
||||
#[version = 1]
|
||||
#[version = 2]
|
||||
pub struct ThemeBuilder {
|
||||
/// override the palette for the builder
|
||||
pub palette: CosmicPalette,
|
||||
|
|
@ -785,31 +851,67 @@ 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 is_frosted: bool, // TODO handle
|
||||
pub frosted: BlurStrength,
|
||||
/// 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 {
|
||||
|
|
@ -827,11 +929,16 @@ impl Default for ThemeBuilder {
|
|||
success: Default::default(),
|
||||
warning: Default::default(),
|
||||
destructive: Default::default(),
|
||||
is_frosted: false,
|
||||
frosted: BlurStrength::default(),
|
||||
// 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -979,9 +1086,22 @@ impl ThemeBuilder {
|
|||
gaps,
|
||||
active_hint,
|
||||
window_hint,
|
||||
is_frosted,
|
||||
frosted,
|
||||
frosted_windows,
|
||||
frosted_system_interface,
|
||||
frosted_panel,
|
||||
frosted_applets,
|
||||
alpha_map,
|
||||
} = 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();
|
||||
|
||||
|
|
@ -1036,6 +1156,10 @@ 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 {
|
||||
|
|
@ -1060,10 +1184,26 @@ 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 container_bg = if let Some(primary_container_bg_color) = primary_container_bg {
|
||||
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])
|
||||
|
|
@ -1093,6 +1233,7 @@ impl ThemeBuilder {
|
|||
&step_array,
|
||||
&control_steps_array[8],
|
||||
text_steps_array.as_deref(),
|
||||
container_alpha,
|
||||
),
|
||||
component_hovered_overlay,
|
||||
component_pressed_overlay,
|
||||
|
|
@ -1105,6 +1246,48 @@ 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,
|
||||
|
|
@ -1178,6 +1361,7 @@ 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,
|
||||
|
|
@ -1214,6 +1398,7 @@ impl ThemeBuilder {
|
|||
&step_array,
|
||||
&control_steps_array[8],
|
||||
text_steps_array.as_deref(),
|
||||
actual_alpha,
|
||||
),
|
||||
component_hovered_overlay,
|
||||
component_pressed_overlay,
|
||||
|
|
@ -1226,6 +1411,7 @@ 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,
|
||||
|
|
@ -1238,14 +1424,24 @@ impl ThemeBuilder {
|
|||
button_hovered_overlay,
|
||||
button_pressed_overlay,
|
||||
),
|
||||
accent_button: Component::colored_button(
|
||||
accent_button: {
|
||||
let step_array = steps(control_steps_array[5], NonZeroUsize::new(100).unwrap());
|
||||
|
||||
Component::colored_button(
|
||||
accent,
|
||||
control_steps_array[1],
|
||||
control_steps_array[0],
|
||||
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,
|
||||
),
|
||||
)
|
||||
},
|
||||
button: Component::component(
|
||||
button_bg,
|
||||
accent,
|
||||
|
|
@ -1349,10 +1545,79 @@ impl ThemeBuilder {
|
|||
gaps,
|
||||
active_hint,
|
||||
window_hint,
|
||||
is_frosted,
|
||||
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;
|
||||
|
|
@ -1371,3 +1636,181 @@ 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ ForegroundPositive=0,87,44
|
|||
ForegroundVisited=0,82,90
|
||||
|
||||
[Colors:Selection]
|
||||
BackgroundAlternate=108,149,152
|
||||
BackgroundAlternate=108,149,153
|
||||
BackgroundNormal=0,82,90
|
||||
DecorationFocus=0,82,90
|
||||
DecorationHover=0,82,90
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ 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 {
|
||||
|
|
@ -90,10 +91,27 @@ 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, step_array.len(), is_dark)
|
||||
.or_else(|| get_index(base_index, 50, step_array.len(), is_dark))
|
||||
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 });
|
||||
|
||||
*step_array.get(index).unwrap_or(fallback)
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ 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);
|
||||
|
|
|
|||
|
|
@ -22,4 +22,5 @@ features = [
|
|||
"surface-message",
|
||||
"multi-window",
|
||||
"wgpu",
|
||||
"wayland",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
use cosmic::widget::{nav_bar, RcElementWrapper};
|
||||
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) -> Message {
|
||||
fn message(&self) -> Self::Message {
|
||||
match self {
|
||||
Action::Hi => Message::Hi,
|
||||
Action::Hi2 => Message::Hi2,
|
||||
|
|
@ -52,6 +52,25 @@ 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>> {
|
||||
|
|
@ -84,6 +103,7 @@ pub enum Message {
|
|||
Hi2,
|
||||
Hi3,
|
||||
Tick,
|
||||
ValueChanged(f32),
|
||||
}
|
||||
|
||||
/// The [`App`] stores application-specific state.
|
||||
|
|
@ -95,6 +115,7 @@ pub struct App {
|
|||
hidden: bool,
|
||||
keybinds: HashMap<KeyBind, Action>,
|
||||
progress: f32,
|
||||
progress_slider: f32,
|
||||
}
|
||||
|
||||
/// Implement [`cosmic::Application`] to integrate with COSMIC.
|
||||
|
|
@ -137,6 +158,7 @@ impl cosmic::Application for App {
|
|||
hidden: true,
|
||||
keybinds: HashMap::new(),
|
||||
progress: 0.0,
|
||||
progress_slider: 0.0,
|
||||
};
|
||||
|
||||
let command = app.update_title();
|
||||
|
|
@ -155,6 +177,70 @@ 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 {
|
||||
|
|
@ -185,6 +271,9 @@ impl cosmic::Application for App {
|
|||
Message::Tick => {
|
||||
self.progress = (self.progress + 0.01) % 1.0;
|
||||
}
|
||||
Message::ValueChanged(value) => {
|
||||
self.progress_slider = value;
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
|
@ -201,7 +290,7 @@ impl cosmic::Application for App {
|
|||
.map_or("No page selected", String::as_str);
|
||||
|
||||
let centered = widget::container(
|
||||
widget::column::with_capacity(14)
|
||||
widget::column::with_capacity(16)
|
||||
.push(widget::text::body(page_content))
|
||||
.push(
|
||||
widget::text_input::text_input("", &self.input_1)
|
||||
|
|
@ -240,6 +329,16 @@ 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),
|
||||
|
|
@ -266,6 +365,7 @@ 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)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use std::collections::HashMap;
|
|||
|
||||
use cosmic::app::{Core, Settings, Task};
|
||||
use cosmic::iced::Size;
|
||||
use cosmic::widget::{menu, nav_bar};
|
||||
use cosmic::widget::{menu, nav_bar, space};
|
||||
use cosmic::{executor, iced, ApplicationExt, Element};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
|
@ -127,18 +127,20 @@ impl cosmic::Application for App {
|
|||
Some(&self.nav_model)
|
||||
}
|
||||
|
||||
/// 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(
|
||||
/// The context menu list.
|
||||
fn nav_context_menu(&self) -> Option<Vec<menu::Tree<cosmic::Action<Self::Message>>>> {
|
||||
Some(menu::nav_context(
|
||||
&HashMap::new(),
|
||||
self.nav_model
|
||||
.iter()
|
||||
.map(|e| {
|
||||
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)),
|
||||
],
|
||||
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(),
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
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
2
iced
|
|
@ -1 +1 @@
|
|||
Subproject commit d9fe129f7316cea3ef6d2576e9421a305759b8bf
|
||||
Subproject commit 1a57634add6e820c299f1c59664cd98098a9c348
|
||||
|
|
@ -64,6 +64,8 @@ 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
|
|
@ -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::{Length, Subscription, theme, window};
|
||||
use iced::{Color, Length, Subscription, theme, window};
|
||||
pub use settings::Settings;
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
|
|
@ -389,7 +389,19 @@ 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(self.core().nav_bar_context()))
|
||||
.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);
|
||||
|
|
@ -402,10 +414,7 @@ where
|
|||
}
|
||||
|
||||
/// Shows a context menu for the active nav bar item.
|
||||
fn nav_context_menu(
|
||||
&self,
|
||||
id: nav_bar::Id,
|
||||
) -> Option<Vec<menu::Tree<crate::Action<Self::Message>>>> {
|
||||
fn nav_context_menu(&self) -> Option<Vec<menu::Tree<crate::Action<Self::Message>>>> {
|
||||
None
|
||||
}
|
||||
|
||||
|
|
@ -615,6 +624,7 @@ 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()
|
||||
|
|
@ -635,10 +645,8 @@ 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()
|
||||
.map(|nav| id_container(nav, iced_core::id::Id::new("COSMIC_nav_bar")))
|
||||
{
|
||||
let has_nav = if let Some(nav) = self.nav_bar() {
|
||||
let nav = id_container(nav, iced_core::id::Id::new("COSMIC_nav_bar"));
|
||||
widgets.push(
|
||||
container(nav)
|
||||
.padding([
|
||||
|
|
@ -825,7 +833,11 @@ impl<App: Application> ApplicationExt for App {
|
|||
let cosmic = theme.cosmic();
|
||||
container::Style {
|
||||
background: Some(iced::Background::Color(
|
||||
cosmic.background.base.into(),
|
||||
if transparent_header {
|
||||
Color::TRANSPARENT
|
||||
} else {
|
||||
cosmic.background(theme.transparent).base.into()
|
||||
},
|
||||
)),
|
||||
border: iced::Border {
|
||||
radius: [
|
||||
|
|
@ -854,7 +866,7 @@ impl<App: Application> ApplicationExt for App {
|
|||
container::Style {
|
||||
background: if content_container {
|
||||
Some(iced::Background::Color(
|
||||
theme.cosmic().background.base.into(),
|
||||
theme.cosmic().background(theme.transparent).base.into(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
|
|
|
|||
|
|
@ -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.on.into()),
|
||||
color: Some(theme.cosmic().background(theme.transparent).on.into()),
|
||||
}))
|
||||
} else {
|
||||
theme::Svg::default()
|
||||
|
|
@ -372,16 +372,17 @@ 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.on.into()),
|
||||
background: Some(Color::from(cosmic.background.base).into()),
|
||||
text_color: Some(cosmic.background(theme.transparent).on.into()),
|
||||
background: Some(Color::from(bg).into()),
|
||||
border: iced::Border {
|
||||
radius: corners.radius_m.into(),
|
||||
width: 1.0,
|
||||
color: cosmic.background.divider.into(),
|
||||
color: cosmic.background(theme.transparent).divider.into(),
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
icon_color: Some(cosmic.background.on.into()),
|
||||
icon_color: Some(cosmic.background(theme.transparent).on.into()),
|
||||
snap: true,
|
||||
}
|
||||
}),
|
||||
|
|
@ -559,6 +560,7 @@ 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;
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ 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
|
||||
|
|
|
|||
149
src/core.rs
149
src/core.rs
|
|
@ -6,6 +6,7 @@ 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;
|
||||
|
|
@ -38,11 +39,24 @@ 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 {
|
||||
|
|
@ -97,8 +111,21 @@ pub struct Core {
|
|||
|
||||
pub(crate) menu_bars: HashMap<crate::widget::Id, (Limits, Size)>,
|
||||
|
||||
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||
pub(crate) sync_window_border_radii_to_theme: bool,
|
||||
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,
|
||||
}
|
||||
|
||||
impl Default for Core {
|
||||
|
|
@ -141,6 +168,7 @@ impl Default for Core {
|
|||
show_minimize: true,
|
||||
show_window_menu: false,
|
||||
is_maximized: false,
|
||||
transparent_header: false,
|
||||
height: 0.,
|
||||
width: 0.,
|
||||
},
|
||||
|
|
@ -156,8 +184,9 @@ impl Default for Core {
|
|||
main_window: None,
|
||||
exit_on_main_window_closed: true,
|
||||
menu_bars: HashMap::new(),
|
||||
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||
sync_window_border_radii_to_theme: true,
|
||||
auto_blur: Auto::System | Auto::Popup | Auto::Window,
|
||||
auto_corner_radius: Auto::System | Auto::Popup | Auto::Window,
|
||||
app_type: AppType::Window,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -483,14 +512,112 @@ 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 {
|
||||
self.sync_window_border_radii_to_theme
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ 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;
|
||||
|
||||
|
|
@ -29,16 +33,38 @@ pub fn destroy_window(id: iced_core::window::Id) -> Action {
|
|||
|
||||
#[cfg(all(feature = "wayland", target_os = "linux"))]
|
||||
#[must_use]
|
||||
pub fn app_window<App: Application>(
|
||||
settings: impl Fn(&mut App) -> window::Settings + Send + Sync + 'static,
|
||||
view: Option<
|
||||
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 for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action<App::Message>>
|
||||
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>,
|
||||
) -> (window::Id, Action) {
|
||||
let id = window::Id::unique();
|
||||
|
||||
|
|
@ -46,11 +72,16 @@ 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)
|
||||
|
|
@ -63,6 +94,7 @@ 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,
|
||||
|
|
@ -73,11 +105,15 @@ 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>>
|
||||
|
|
@ -95,18 +131,12 @@ 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: Option<
|
||||
Box<
|
||||
dyn for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action<App::Message>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
>,
|
||||
>,
|
||||
view: BoxedView<App>,
|
||||
) -> Action {
|
||||
let boxed: Box<
|
||||
dyn Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
|
||||
|
|
@ -116,8 +146,13 @@ 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)
|
||||
|
|
@ -128,7 +163,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, V>(
|
||||
pub fn simple_subsurface<Message: 'static>(
|
||||
settings: impl Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings
|
||||
+ Send
|
||||
+ Sync
|
||||
|
|
@ -147,6 +182,7 @@ pub fn simple_subsurface<Message: 'static, V>(
|
|||
|
||||
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)
|
||||
|
|
@ -158,6 +194,7 @@ pub fn simple_subsurface<Message: 'static, V>(
|
|||
#[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
|
||||
|
|
@ -174,8 +211,12 @@ 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,
|
||||
|
|
@ -197,14 +238,7 @@ pub fn subsurface<App: Application>(
|
|||
+ Sync
|
||||
+ 'static,
|
||||
// XXX Boxed trait object is required for less cumbersome type inference, but we box it anyways.
|
||||
view: Option<
|
||||
Box<
|
||||
dyn for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action<App::Message>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
>,
|
||||
>,
|
||||
view: BoxedView<App>,
|
||||
) -> Action {
|
||||
let boxed: Box<
|
||||
dyn Fn(
|
||||
|
|
@ -219,6 +253,82 @@ 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)
|
||||
|
|
|
|||
86
src/surface/corner_radius.rs
Normal file
86
src/surface/corner_radius.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
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
|
||||
}
|
||||
|
|
@ -2,36 +2,28 @@
|
|||
// 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::future::Future;
|
||||
use std::any::Any;
|
||||
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(
|
||||
std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>,
|
||||
Option<std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>>,
|
||||
),
|
||||
AppSubsurface(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
||||
/// Create a subsurface with a view function
|
||||
Subsurface(
|
||||
std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>,
|
||||
Option<std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>>,
|
||||
),
|
||||
Subsurface(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
||||
/// 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(
|
||||
std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>,
|
||||
Option<std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>>,
|
||||
),
|
||||
AppPopup(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
||||
/// Create a popup
|
||||
Popup(
|
||||
std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>,
|
||||
Option<std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>>,
|
||||
),
|
||||
Popup(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
||||
/// Destroy a subsurface with a view function
|
||||
DestroyPopup(iced::window::Id),
|
||||
/// Destroys the global tooltip popup subsurface
|
||||
|
|
@ -40,18 +32,29 @@ pub enum Action {
|
|||
/// Create a window with a view function accepting the App as a parameter
|
||||
AppWindow(
|
||||
iced::window::Id,
|
||||
std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>,
|
||||
Option<std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>>,
|
||||
BoxedSetting,
|
||||
BoxedSetting,
|
||||
Option<BoxedSetting>,
|
||||
),
|
||||
/// Create a window with a view function
|
||||
Window(
|
||||
iced::window::Id,
|
||||
std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>,
|
||||
Option<std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>>,
|
||||
BoxedSetting,
|
||||
BoxedSetting,
|
||||
Option<BoxedSetting>,
|
||||
),
|
||||
/// 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
|
||||
|
|
@ -62,28 +65,45 @@ 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) => f
|
||||
Self::AppSubsurface(arg0, arg1, arg2) => 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) => {
|
||||
f.debug_tuple("AppPopup").field(arg0).field(arg1).finish()
|
||||
}
|
||||
Self::Popup(arg0, arg1) => f.debug_tuple("Popup").field(arg0).field(arg1).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::DestroyPopup(arg0) => f.debug_tuple("DestroyPopup").field(arg0).finish(),
|
||||
Self::DestroyTooltipPopup => f.debug_tuple("DestroyTooltipPopup").finish(),
|
||||
Self::ResponsiveMenuBar {
|
||||
|
|
@ -97,20 +117,38 @@ impl std::fmt::Debug for Action {
|
|||
.field("size", size)
|
||||
.finish(),
|
||||
Self::Ignore => write!(f, "Ignore"),
|
||||
Self::AppWindow(id, arg0, arg1) => f
|
||||
Self::AppWindow(id, arg0, arg1, arg2) => f
|
||||
.debug_tuple("AppWindow")
|
||||
.field(id)
|
||||
.field(arg0)
|
||||
.field(arg1)
|
||||
.field(arg2)
|
||||
.finish(),
|
||||
Self::Window(id, arg0, arg1) => f
|
||||
Self::Window(id, arg0, arg1, arg2) => 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
pub mod portal;
|
||||
pub mod style;
|
||||
|
||||
use cosmic_config::{CosmicConfigEntry, config_subscription};
|
||||
use ::iced::Alignment;
|
||||
use cosmic_config::CosmicConfigEntry;
|
||||
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,6 +46,8 @@ 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.
|
||||
|
|
@ -82,34 +84,6 @@ 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();
|
||||
|
|
@ -209,6 +183,9 @@ 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 {
|
||||
|
|
@ -279,9 +256,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,
|
||||
cosmic_theme::Layer::Primary => &self.cosmic().primary,
|
||||
cosmic_theme::Layer::Secondary => &self.cosmic().secondary,
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -290,6 +267,14 @@ 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 {
|
||||
|
|
|
|||
|
|
@ -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.on.into());
|
||||
appearance.text_color = Some(cosmic.background.on.into());
|
||||
appearance.icon_color = Some(cosmic.background(theme.transparent).on.into());
|
||||
appearance.text_color = Some(cosmic.background(theme.transparent).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.on.into());
|
||||
appearance.text_color = Some(cosmic.background.on.into());
|
||||
appearance.icon_color = Some(cosmic.background(theme.transparent).on.into());
|
||||
appearance.text_color = Some(cosmic.background(theme.transparent).on.into());
|
||||
}
|
||||
Button::MenuFolder => {
|
||||
// Menu folders cannot be disabled, ignore customized icon and text color
|
||||
let component = &cosmic.background.component;
|
||||
let component = &cosmic.background(theme.transparent).component;
|
||||
let (background, _, _) = color(component);
|
||||
appearance.background = Some(Background::Color(background));
|
||||
appearance.icon_color = Some(component.on.into());
|
||||
|
|
@ -151,8 +151,9 @@ pub fn appearance(
|
|||
let (background, text, icon) = color(&cosmic.list_button);
|
||||
|
||||
if selected {
|
||||
appearance.background =
|
||||
Some(Background::Color(cosmic.primary.component.hover.into()));
|
||||
appearance.background = Some(Background::Color(
|
||||
cosmic.primary(theme.transparent).component.hover.into(),
|
||||
));
|
||||
appearance.icon_color = Some(cosmic.accent.base.into());
|
||||
appearance.text_color = Some(cosmic.accent_text_color().into());
|
||||
} else {
|
||||
|
|
@ -162,7 +163,7 @@ pub fn appearance(
|
|||
}
|
||||
}
|
||||
Button::MenuItem => {
|
||||
let (background, text, icon) = color(&cosmic.background.component);
|
||||
let (background, text, icon) = color(&cosmic.background(theme.transparent).component);
|
||||
appearance.background = Some(Background::Color(background));
|
||||
appearance.icon_color = icon;
|
||||
appearance.text_color = text;
|
||||
|
|
@ -278,6 +279,6 @@ impl Catalog for crate::Theme {
|
|||
}
|
||||
|
||||
fn selection_background(&self) -> Background {
|
||||
Background::Color(self.cosmic().primary.base.into())
|
||||
Background::Color(self.cosmic().primary(self.transparent).base.into())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,18 +13,28 @@ impl dropdown::menu::StyleSheet for Theme {
|
|||
|
||||
dropdown::menu::Appearance {
|
||||
text_color: cosmic.on_bg_color().into(),
|
||||
background: Background::Color(cosmic.background.component.base.into()),
|
||||
background: Background::Color(
|
||||
cosmic.background(self.transparent).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.component.hover.into()),
|
||||
hovered_background: Background::Color(
|
||||
cosmic.primary(self.transparent).component.hover.into(),
|
||||
),
|
||||
|
||||
selected_text_color: cosmic.accent_text_color().into(),
|
||||
selected_background: Background::Color(cosmic.primary.component.hover.into()),
|
||||
selected_background: Background::Color(
|
||||
cosmic.primary(self.transparent).component.hover.into(),
|
||||
),
|
||||
|
||||
description_color: cosmic.primary.component.on_disabled.into(),
|
||||
description_color: cosmic
|
||||
.primary(self.transparent)
|
||||
.component
|
||||
.on_disabled
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -224,11 +224,11 @@ impl iced_checkbox::Catalog for Theme {
|
|||
},
|
||||
Checkbox::Secondary => iced_checkbox::Style {
|
||||
background: Background::Color(if is_checked {
|
||||
cosmic.background.component.base.into()
|
||||
cosmic.background(self.transparent).component.base.into()
|
||||
} else {
|
||||
self.current_container().small_widget.into()
|
||||
}),
|
||||
icon_color: cosmic.background.on.into(),
|
||||
icon_color: cosmic.background(self.transparent).on.into(),
|
||||
border: Border {
|
||||
radius: corners.radius_xs.into(),
|
||||
width: if is_checked { 0.0 } else { 1.0 },
|
||||
|
|
@ -386,9 +386,11 @@ pub enum Container<'a> {
|
|||
WindowBackground,
|
||||
Background,
|
||||
Card,
|
||||
ContextDrawer,
|
||||
ContextDrawer {
|
||||
transparent: bool,
|
||||
},
|
||||
Custom(Box<dyn Fn(&Theme) -> iced_container::Style + 'a>),
|
||||
Dialog,
|
||||
Dialog(bool),
|
||||
Dropdown,
|
||||
HeaderBar {
|
||||
focused: bool,
|
||||
|
|
@ -409,11 +411,13 @@ impl<'a> Container<'a> {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn background(theme: &cosmic_theme::Theme) -> iced_container::Style {
|
||||
pub fn background(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style {
|
||||
iced_container::Style {
|
||||
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())),
|
||||
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(),
|
||||
)),
|
||||
border: Border {
|
||||
radius: theme.corner_radii.radius_s.into(),
|
||||
..Default::default()
|
||||
|
|
@ -424,11 +428,13 @@ impl<'a> Container<'a> {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn primary(theme: &cosmic_theme::Theme) -> iced_container::Style {
|
||||
pub fn primary(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style {
|
||||
iced_container::Style {
|
||||
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())),
|
||||
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(),
|
||||
)),
|
||||
border: Border {
|
||||
radius: theme.corner_radii.radius_s.into(),
|
||||
..Default::default()
|
||||
|
|
@ -439,11 +445,13 @@ impl<'a> Container<'a> {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn secondary(theme: &cosmic_theme::Theme) -> iced_container::Style {
|
||||
pub fn secondary(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style {
|
||||
iced_container::Style {
|
||||
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())),
|
||||
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(),
|
||||
)),
|
||||
border: Border {
|
||||
radius: theme.corner_radii.radius_s.into(),
|
||||
..Default::default()
|
||||
|
|
@ -474,14 +482,30 @@ 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 => iced_container::Style::default(),
|
||||
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::Custom(f) => f(self),
|
||||
|
||||
Container::WindowBackground => iced_container::Style {
|
||||
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())),
|
||||
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(),
|
||||
)),
|
||||
border: Border {
|
||||
radius: [
|
||||
cosmic.corner_radii.radius_0[0],
|
||||
|
|
@ -519,12 +543,13 @@ impl iced_container::Catalog for Theme {
|
|||
let (icon_color, text_color) = if *focused {
|
||||
(
|
||||
Color::from(cosmic.accent_text_color()),
|
||||
Color::from(cosmic.background.on),
|
||||
Color::from(cosmic.background(self.transparent).on),
|
||||
)
|
||||
} else {
|
||||
use crate::ext::ColorExt;
|
||||
let unfocused_color = Color::from(cosmic.background.component.on)
|
||||
.blend_alpha(cosmic.background.base.into(), 0.5);
|
||||
let unfocused_color =
|
||||
Color::from(cosmic.background(self.transparent).component.on)
|
||||
.blend_alpha(cosmic.background(self.transparent).base.into(), 0.5);
|
||||
(unfocused_color, unfocused_color)
|
||||
};
|
||||
|
||||
|
|
@ -534,7 +559,9 @@ impl iced_container::Catalog for Theme {
|
|||
background: if *transparent {
|
||||
None
|
||||
} else {
|
||||
Some(iced::Background::Color(cosmic.background.base.into()))
|
||||
Some(iced::Background::Color(
|
||||
cosmic.background(self.transparent).base.into(),
|
||||
))
|
||||
},
|
||||
border: Border {
|
||||
radius: [
|
||||
|
|
@ -559,21 +586,21 @@ impl iced_container::Catalog for Theme {
|
|||
}
|
||||
}
|
||||
|
||||
Container::ContextDrawer => {
|
||||
let mut a = Container::primary(cosmic);
|
||||
Container::ContextDrawer { transparent } => {
|
||||
let mut a = Container::primary(cosmic, self.transparent && *transparent);
|
||||
|
||||
if cosmic.is_high_contrast {
|
||||
a.border.width = 1.;
|
||||
a.border.color = cosmic.primary.divider.into();
|
||||
a.border.color = cosmic.primary(self.transparent).divider.into();
|
||||
}
|
||||
a
|
||||
}
|
||||
|
||||
Container::Background => Container::background(cosmic),
|
||||
Container::Background => Container::background(cosmic, self.transparent),
|
||||
|
||||
Container::Primary => Container::primary(cosmic),
|
||||
Container::Primary => Container::primary(cosmic, self.transparent),
|
||||
|
||||
Container::Secondary => Container::secondary(cosmic),
|
||||
Container::Secondary => Container::secondary(cosmic, self.transparent),
|
||||
|
||||
Container::Dropdown => iced_container::Style {
|
||||
icon_color: None,
|
||||
|
|
@ -605,10 +632,14 @@ impl iced_container::Catalog for Theme {
|
|||
|
||||
match self.layer {
|
||||
cosmic_theme::Layer::Background => iced_container::Style {
|
||||
icon_color: Some(Color::from(cosmic.background.component.on)),
|
||||
text_color: Some(Color::from(cosmic.background.component.on)),
|
||||
icon_color: Some(Color::from(
|
||||
cosmic.background(self.transparent).component.on,
|
||||
)),
|
||||
text_color: Some(Color::from(
|
||||
cosmic.background(self.transparent).component.on,
|
||||
)),
|
||||
background: Some(iced::Background::Color(
|
||||
cosmic.background.component.base.into(),
|
||||
cosmic.background(self.transparent).component.base.into(),
|
||||
)),
|
||||
border: Border {
|
||||
radius: cosmic.corner_radii.radius_s.into(),
|
||||
|
|
@ -618,10 +649,14 @@ impl iced_container::Catalog for Theme {
|
|||
snap: true,
|
||||
},
|
||||
cosmic_theme::Layer::Primary => iced_container::Style {
|
||||
icon_color: Some(Color::from(cosmic.primary.component.on)),
|
||||
text_color: Some(Color::from(cosmic.primary.component.on)),
|
||||
icon_color: Some(Color::from(
|
||||
cosmic.primary(self.transparent).component.on,
|
||||
)),
|
||||
text_color: Some(Color::from(
|
||||
cosmic.primary(self.transparent).component.on,
|
||||
)),
|
||||
background: Some(iced::Background::Color(
|
||||
cosmic.primary.component.base.into(),
|
||||
cosmic.primary(self.transparent).component.base.into(),
|
||||
)),
|
||||
border: Border {
|
||||
radius: cosmic.corner_radii.radius_s.into(),
|
||||
|
|
@ -631,10 +666,14 @@ impl iced_container::Catalog for Theme {
|
|||
snap: true,
|
||||
},
|
||||
cosmic_theme::Layer::Secondary => iced_container::Style {
|
||||
icon_color: Some(Color::from(cosmic.secondary.component.on)),
|
||||
text_color: Some(Color::from(cosmic.secondary.component.on)),
|
||||
icon_color: Some(Color::from(
|
||||
cosmic.secondary(self.transparent).component.on,
|
||||
)),
|
||||
text_color: Some(Color::from(
|
||||
cosmic.secondary(self.transparent).component.on,
|
||||
)),
|
||||
background: Some(iced::Background::Color(
|
||||
cosmic.secondary.component.base.into(),
|
||||
cosmic.secondary(self.transparent).component.base.into(),
|
||||
)),
|
||||
border: Border {
|
||||
radius: cosmic.corner_radii.radius_s.into(),
|
||||
|
|
@ -646,12 +685,17 @@ impl iced_container::Catalog for Theme {
|
|||
}
|
||||
}
|
||||
|
||||
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())),
|
||||
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(),
|
||||
)),
|
||||
border: Border {
|
||||
color: cosmic.primary.divider.into(),
|
||||
color: cosmic
|
||||
.primary(self.transparent && !is_overlay)
|
||||
.divider
|
||||
.into(),
|
||||
width: 1.0,
|
||||
radius: cosmic.corner_radii.radius_m.into(),
|
||||
},
|
||||
|
|
@ -793,13 +837,15 @@ impl menu::Catalog for Theme {
|
|||
|
||||
menu::Style {
|
||||
text_color: cosmic.on_bg_color().into(),
|
||||
background: Background::Color(cosmic.background.base.into()),
|
||||
background: Background::Color(cosmic.background(self.transparent).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.component.hover.into()),
|
||||
selected_background: Background::Color(
|
||||
cosmic.background(self.transparent).component.hover.into(),
|
||||
),
|
||||
shadow: Default::default(),
|
||||
}
|
||||
}
|
||||
|
|
@ -837,7 +883,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.base.into()),
|
||||
background: Background::Color(cosmic.background(self.transparent).base.into()),
|
||||
..appearance
|
||||
},
|
||||
pick_list::Status::Opened { is_hovered: _ } => appearance,
|
||||
|
|
@ -1033,7 +1079,10 @@ impl progress_bar::Catalog for Theme {
|
|||
},
|
||||
)
|
||||
} else {
|
||||
(theme.accent.base, theme.background.divider)
|
||||
(
|
||||
theme.accent.base,
|
||||
theme.background(self.transparent).divider,
|
||||
)
|
||||
};
|
||||
let border = Border {
|
||||
radius: theme.corner_radii.radius_xl.into(),
|
||||
|
|
@ -1513,7 +1562,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.on.into();
|
||||
let _icon: Color = cosmic.background(self.transparent).on.into();
|
||||
// TODO do we need to add icon color back?
|
||||
|
||||
match status {
|
||||
|
|
|
|||
|
|
@ -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) -> Appearance;
|
||||
fn appearance(&self, style: &Self::Style, is_overlay: bool) -> 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) -> Appearance {
|
||||
fn appearance(&self, style: &Self::Style, _is_overlay: bool) -> Appearance {
|
||||
(self)(style)
|
||||
}
|
||||
}
|
||||
|
|
@ -62,13 +62,14 @@ impl StyleSheet for fn(&Theme) -> Appearance {
|
|||
impl StyleSheet for Theme {
|
||||
type Style = MenuBarStyle;
|
||||
|
||||
fn appearance(&self, style: &Self::Style) -> Appearance {
|
||||
fn appearance(&self, style: &Self::Style, is_overlay: bool) -> Appearance {
|
||||
let cosmic = self.cosmic();
|
||||
let component = &cosmic.background.component;
|
||||
let component = &cosmic.background(!is_overlay && self.transparent).component;
|
||||
let bg = component.base;
|
||||
|
||||
match style {
|
||||
MenuBarStyle::Default => Appearance {
|
||||
background: component.base.into(),
|
||||
background: bg.into(),
|
||||
border_width: 1.0,
|
||||
bar_border_radius: cosmic.corner_radii.radius_xl,
|
||||
menu_border_radius: cosmic.corner_radii.radius_s,
|
||||
|
|
@ -76,7 +77,7 @@ impl StyleSheet for Theme {
|
|||
background_expand: [1; 4],
|
||||
path: component.hover.into(),
|
||||
},
|
||||
MenuBarStyle::Custom(c) => c.appearance(self),
|
||||
MenuBarStyle::Custom(c) => c.appearance(self, is_overlay),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//!
|
||||
//! A [`Button`] has some local [`State`].
|
||||
|
||||
use iced::Alignment;
|
||||
use iced_runtime::core::widget::Id;
|
||||
use iced_runtime::{Action, Task, keyboard, task};
|
||||
|
||||
|
|
@ -448,7 +449,7 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
|
||||
let styling = if !is_enabled {
|
||||
let mut styling = if !is_enabled {
|
||||
theme.disabled(&self.style)
|
||||
} else if is_mouse_over {
|
||||
if state.is_pressed {
|
||||
|
|
@ -470,6 +471,24 @@ 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);
|
||||
|
||||
|
|
|
|||
|
|
@ -31,16 +31,26 @@ 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.component.hover.into()),
|
||||
card_2: Background::Color(cosmic.background.component.pressed.into()),
|
||||
card_1: Background::Color(
|
||||
cosmic.background(self.transparent).component.hover.into(),
|
||||
),
|
||||
card_2: Background::Color(
|
||||
cosmic.background(self.transparent).component.pressed.into(),
|
||||
),
|
||||
},
|
||||
cosmic_theme::Layer::Primary => crate::widget::card::style::Style {
|
||||
card_1: Background::Color(cosmic.primary.component.hover.into()),
|
||||
card_2: Background::Color(cosmic.primary.component.pressed.into()),
|
||||
card_1: Background::Color(cosmic.primary(self.transparent).component.hover.into()),
|
||||
card_2: Background::Color(
|
||||
cosmic.primary(self.transparent).component.pressed.into(),
|
||||
),
|
||||
},
|
||||
cosmic_theme::Layer::Secondary => crate::widget::card::style::Style {
|
||||
card_1: Background::Color(cosmic.secondary.component.hover.into()),
|
||||
card_2: Background::Color(cosmic.secondary.component.pressed.into()),
|
||||
card_1: Background::Color(
|
||||
cosmic.secondary(self.transparent).component.hover.into(),
|
||||
),
|
||||
card_2: Background::Color(
|
||||
cosmic.secondary(self.transparent).component.pressed.into(),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use crate::anim;
|
||||
use crate::widget::card::style::Style;
|
||||
use crate::theme::THEME;
|
||||
use crate::widget::icon::{self, Handle};
|
||||
use crate::widget::{button, column, row, text};
|
||||
use float_cmp::approx_eq;
|
||||
|
|
@ -19,7 +19,6 @@ 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
|
||||
|
|
@ -93,7 +92,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>,
|
||||
|
|
@ -112,7 +111,7 @@ where
|
|||
|
||||
Self {
|
||||
can_show_more,
|
||||
id: Id::unique(),
|
||||
id,
|
||||
show_less_button: {
|
||||
let mut show_less_children = Vec::with_capacity(3);
|
||||
if let Some(source) = show_less_icon {
|
||||
|
|
@ -337,13 +336,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, BG_CARD_BORDER_RADIUS * 2.0))
|
||||
let node = Node::new(Size::new(width - 2.0 * margin, radius_xs[0] * 2.0))
|
||||
.translate(Vector::new(
|
||||
margin,
|
||||
size.height - BG_CARD_BORDER_RADIUS * 2.0 + BG_CARD_VISIBLE_HEIGHT,
|
||||
size.height - radius_xs[0] * 2.0 + BG_CARD_VISIBLE_HEIGHT,
|
||||
));
|
||||
size.height += BG_CARD_VISIBLE_HEIGHT;
|
||||
children.push(node);
|
||||
|
|
@ -424,22 +424,19 @@ 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 = Style::default();
|
||||
let appearance = theme.default();
|
||||
let bg_layout = layout.collect::<Vec<_>>();
|
||||
for (i, layout) in (0..2).zip(bg_layout.into_iter()).rev() {
|
||||
for (i, layout) in (0..2).zip(bg_layout).rev() {
|
||||
renderer.fill_quad(
|
||||
Quad {
|
||||
bounds: layout.bounds(),
|
||||
border: Border {
|
||||
radius: Radius::from([
|
||||
0.0,
|
||||
0.0,
|
||||
BG_CARD_BORDER_RADIUS,
|
||||
BG_CARD_BORDER_RADIUS,
|
||||
]),
|
||||
radius: Radius::from([0.0, 0.0, radius_xs[2], radius_xs[3]]),
|
||||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
|
|
|
|||
|
|
@ -5,13 +5,12 @@
|
|||
|
||||
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};
|
||||
use crate::theme::{Button, THEME, Theme};
|
||||
use crate::widget::button::Catalog;
|
||||
use crate::widget::segmented_button::Entity;
|
||||
use crate::widget::{container, slider};
|
||||
|
|
@ -39,37 +38,6 @@ 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)]
|
||||
|
|
@ -301,23 +269,67 @@ 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)
|
||||
|
|
@ -332,56 +344,22 @@ where
|
|||
.width(self.width)
|
||||
.height(self.height),
|
||||
slider(
|
||||
0.001..=359.99,
|
||||
0.0..=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: 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
|
||||
}),
|
||||
active: color_slider_style.clone(),
|
||||
hovered: color_slider_style.clone(),
|
||||
dragging: color_slider_style,
|
||||
})
|
||||
.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)))
|
||||
|
|
|
|||
|
|
@ -31,6 +31,24 @@ 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>>,
|
||||
{
|
||||
|
|
@ -43,6 +61,7 @@ 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,
|
||||
|
|
@ -105,7 +124,9 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
|
|||
container(
|
||||
LayerContainer::new(pane)
|
||||
.layer(cosmic_theme::Layer::Primary)
|
||||
.class(crate::style::Container::ContextDrawer)
|
||||
.class(crate::style::Container::ContextDrawer {
|
||||
transparent: !overlay,
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.max_width(max_width),
|
||||
|
|
@ -124,6 +145,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
|
|||
drawer.into(),
|
||||
on_close,
|
||||
max_width,
|
||||
overlay,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +164,9 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
|
|||
Content: Into<Element<'a, Message>>,
|
||||
Drawer: Into<Element<'a, Message>>,
|
||||
{
|
||||
let drawer = Self::new_inner(title, actions, header, footer, drawer, on_close, max_width);
|
||||
let drawer = Self::new_inner_overlay(
|
||||
title, actions, header, footer, drawer, on_close, max_width, true,
|
||||
);
|
||||
|
||||
ContextDrawer {
|
||||
id: None,
|
||||
|
|
|
|||
|
|
@ -79,8 +79,10 @@ 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::destroy_popup;
|
||||
use crate::widget::menu::Menu;
|
||||
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 iced_runtime::platform_specific::wayland::popup::{
|
||||
SctkPopupSettings, SctkPositioner,
|
||||
};
|
||||
|
|
@ -183,8 +185,22 @@ 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,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ 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> {
|
||||
|
|
@ -40,6 +41,7 @@ impl<'a, Message> Dialog<'a, Message> {
|
|||
height: None,
|
||||
max_width: None,
|
||||
max_height: None,
|
||||
is_overlay: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -97,6 +99,11 @@ 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> {
|
||||
|
|
@ -157,7 +164,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)
|
||||
.class(style::Container::Dialog(dialog.is_overlay))
|
||||
.padding(space_m)
|
||||
.width(dialog.width.unwrap_or(Length::Fixed(570.0)));
|
||||
|
||||
|
|
|
|||
|
|
@ -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, event, mouse, overlay};
|
||||
use iced::{Event, Length, Point, Rectangle, Vector, mouse, overlay};
|
||||
use iced_core::widget::{Tree, tree};
|
||||
use iced_core::{self, Clipboard, Shell, layout, renderer};
|
||||
|
||||
|
|
|
|||
|
|
@ -579,6 +579,8 @@ 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,
|
||||
|
|
@ -605,6 +607,7 @@ 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,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use iced_core::event::{self, Event};
|
||||
use iced_core::event::Event;
|
||||
use iced_core::widget::{Id, Operation, Tree};
|
||||
use iced_core::{
|
||||
Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::Theme;
|
||||
use cosmic_theme::LayeredTheme;
|
||||
use iced::widget::Container;
|
||||
use iced_core::event::{self, Event};
|
||||
use iced_core::event::Event;
|
||||
use iced_core::widget::Tree;
|
||||
use iced_core::{
|
||||
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
use crate::widget::container::Catalog;
|
||||
use crate::widget::space::vertical;
|
||||
use crate::widget::{button, column, container, divider, row};
|
||||
use crate::widget::{DndDestination, DndSource, button, column, container, divider, row};
|
||||
use crate::{Apply, Element, theme};
|
||||
use iced::{Length, Padding};
|
||||
|
||||
|
|
@ -11,14 +11,27 @@ 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,
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +51,19 @@ 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> {
|
||||
|
|
@ -175,8 +201,10 @@ impl<'a, Message: Clone + 'static> ListColumn<'a, Message> {
|
|||
content,
|
||||
on_press,
|
||||
selected,
|
||||
}) => col.push(
|
||||
content_row(content)
|
||||
dnd_source_builder,
|
||||
dnd_destination_builder,
|
||||
}) => {
|
||||
let mut button: Element<'a, Message> = content_row(content)
|
||||
.apply(button::custom)
|
||||
.padding(item_padding)
|
||||
.width(Length::Fill)
|
||||
|
|
@ -186,8 +214,18 @@ 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)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -66,10 +66,13 @@ 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};
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ 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),
|
||||
|
|
@ -216,6 +217,7 @@ mod test {
|
|||
};
|
||||
|
||||
assert!(bind.matches(Modifiers::CTRL, &Key::Character(" ".into()), None,));
|
||||
assert!(format!("{}", bind) == String::from("Ctrl + Space"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -18,10 +18,8 @@ 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};
|
||||
|
|
@ -218,7 +216,7 @@ where
|
|||
path_highlight: Some(PathHighlight::MenuActive),
|
||||
menu_roots,
|
||||
style: <crate::Theme as StyleSheet>::Style::default(),
|
||||
window_id: window::Id::NONE,
|
||||
window_id: window::Id::RESERVED,
|
||||
#[cfg(all(
|
||||
feature = "multi-window",
|
||||
feature = "wayland",
|
||||
|
|
@ -370,7 +368,9 @@ where
|
|||
my_state: &mut MenuBarState,
|
||||
) {
|
||||
if self.window_id != window::Id::NONE && self.on_surface_action.is_some() {
|
||||
use crate::surface::action::destroy_popup;
|
||||
use crate::surface::action::{LiveSettings, destroy_popup};
|
||||
use crate::theme::THEME;
|
||||
use iced_runtime::platform_specific::wayland::CornerRadius;
|
||||
use iced_runtime::platform_specific::wayland::popup::{
|
||||
SctkPopupSettings, SctkPositioner,
|
||||
};
|
||||
|
|
@ -485,7 +485,22 @@ 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,
|
||||
|
|
@ -694,7 +709,20 @@ where
|
|||
|
||||
// draw path highlight
|
||||
if self.path_highlight.is_some() {
|
||||
let styling = theme.appearance(&self.style);
|
||||
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);
|
||||
if let Some(active) = state.active_root.first() {
|
||||
let active_bounds = layout
|
||||
.children()
|
||||
|
|
@ -738,7 +766,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(
|
||||
|
|
|
|||
411
src/widget/menu/menu_column.rs
Normal file
411
src/widget/menu/menu_column.rs
Normal file
|
|
@ -0,0 +1,411 @@
|
|||
//! 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ use super::menu_tree::MenuTree;
|
|||
use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem};
|
||||
use crate::style::menu_bar::StyleSheet;
|
||||
|
||||
use iced::window;
|
||||
use iced::{Alignment, 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,6 +524,7 @@ 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);
|
||||
|
|
@ -561,7 +562,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};
|
||||
|
|
@ -579,7 +580,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(),
|
||||
};
|
||||
|
|
@ -740,7 +741,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
|||
Rectangle::new(Point::ORIGIN, Size::INFINITE)
|
||||
};
|
||||
|
||||
let styling = theme.appearance(&self.style);
|
||||
let styling = theme.appearance(&self.style, self.is_overlay);
|
||||
let roots = active_root.iter().skip(1).fold(
|
||||
&self.menu_roots[active_root[0]].children,
|
||||
|mt, next_active_root| &mt[*next_active_root].children,
|
||||
|
|
@ -815,13 +816,27 @@ 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: styling.menu_border_radius.into(),
|
||||
radius: rad.into(),
|
||||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
|
|
@ -834,12 +849,25 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
|||
// draw item
|
||||
menu_roots[start_index..=end_index]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.zip(children_layout.children())
|
||||
.for_each(|(mt, clo)| {
|
||||
.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
|
||||
},
|
||||
);
|
||||
|
||||
mt.item.draw(
|
||||
&state.tree.children[active_root[0]].children[mt.index],
|
||||
r,
|
||||
theme,
|
||||
&t,
|
||||
style,
|
||||
clo,
|
||||
view_cursor,
|
||||
|
|
@ -949,7 +977,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,
|
||||
|
|
@ -967,21 +995,25 @@ 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()
|
||||
|
|
@ -995,7 +1027,7 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
|
|||
.map(|lo| lo.bounds())
|
||||
.collect();
|
||||
|
||||
let mut popup_menu = Menu {
|
||||
let popup_menu = Menu {
|
||||
tree: self.tree.clone(),
|
||||
menu_roots: Cow::Owned(Cow::into_owned(self.menu_roots.clone())),
|
||||
bounds_expand: self.bounds_expand,
|
||||
|
|
@ -1085,8 +1117,23 @@ 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,
|
||||
|
|
@ -1600,7 +1647,7 @@ fn process_scroll_events<Message>(
|
|||
) where
|
||||
Message: Clone,
|
||||
{
|
||||
use event::Status::{Captured, Ignored};
|
||||
|
||||
use mouse::ScrollDelta;
|
||||
|
||||
menu.tree.inner.with_data_mut(|state| {
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ pub fn menu_items<
|
|||
}
|
||||
|
||||
fn key_style(theme: &crate::Theme) -> TextStyle {
|
||||
let mut color = theme.cosmic().background.component.on;
|
||||
let mut color = theme.cosmic().background(theme.transparent).component.on;
|
||||
color.alpha *= 0.75;
|
||||
TextStyle {
|
||||
color: Some(color.into()),
|
||||
|
|
@ -401,3 +401,19 @@ 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
use apply::Apply;
|
||||
use iced::clipboard::dnd::DndAction;
|
||||
use iced::clipboard::mime::AllowedMimeTypes;
|
||||
use iced::{Background, Length};
|
||||
use iced::{Background, Length, window};
|
||||
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> {
|
||||
pub struct NavBar<'a, Message: Clone + 'static> {
|
||||
segmented_button:
|
||||
segmented_button::VerticalSegmentedButton<'a, segmented_button::SingleSelect, Message>,
|
||||
}
|
||||
|
|
@ -133,6 +133,41 @@ 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>>
|
||||
|
|
@ -172,7 +207,9 @@ 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.base.into())),
|
||||
background: Some(Background::Color(
|
||||
cosmic.primary(theme.transparent).base.into(),
|
||||
)),
|
||||
border: Border {
|
||||
width: 0.0,
|
||||
color: Color::TRANSPARENT,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
//! A container which displays an overlay when a popup widget is attached.
|
||||
|
||||
use iced::widget;
|
||||
use iced_core::event::{self, Event};
|
||||
use iced_core::event::Event;
|
||||
use iced_core::widget::{Operation, Tree};
|
||||
use iced_core::{
|
||||
Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, layout,
|
||||
|
|
@ -401,6 +401,26 @@ 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,30 +7,21 @@ const LAG: f32 = 0.1;
|
|||
#[derive(Default)]
|
||||
pub struct Progress {
|
||||
pub current: f32,
|
||||
target: Option<f32>,
|
||||
last: Option<Instant>,
|
||||
}
|
||||
|
||||
impl Progress {
|
||||
/// Smoothly chases `target` using exponential decay.
|
||||
/// Chases `target` using exponential decay.
|
||||
/// Returns `true` if a redraw should be requested.
|
||||
pub fn update(&mut self, target: f32, now: Instant) -> bool {
|
||||
// Don't animate on start
|
||||
let Some(last) = self.last else {
|
||||
self.current = target;
|
||||
self.target = Some(target);
|
||||
self.last = Some(now);
|
||||
return false;
|
||||
};
|
||||
|
||||
// Sync animation clock when target changes
|
||||
if self.target != Some(target) {
|
||||
self.target = Some(target);
|
||||
self.last = Some(now);
|
||||
return true;
|
||||
}
|
||||
|
||||
let dt = (now - last).as_secs_f32();
|
||||
let dt = (now - last).as_secs_f32().min(1.0 / 60.0);
|
||||
self.last = Some(now);
|
||||
let diff = target - self.current;
|
||||
|
||||
|
|
|
|||
|
|
@ -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::{Element, Event, Length, Pixels, Rectangle, Size, mouse, window};
|
||||
use iced::{Border, Color, Element, Event, Length, Pixels, Rectangle, Size, mouse, window};
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
@ -196,54 +196,13 @@ 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 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;
|
||||
}
|
||||
}
|
||||
|
||||
let draw_quad = |renderer: &mut Renderer, rect: Rectangle, border: Border, color: Color| {
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + x,
|
||||
y: bounds.y + (bounds.height - height) / 2.0,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
bounds: rect,
|
||||
border,
|
||||
snap: true,
|
||||
..renderer::Quad::default()
|
||||
|
|
@ -251,21 +210,22 @@ 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)
|
||||
|
|
@ -279,62 +239,45 @@ 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 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()
|
||||
};
|
||||
|
||||
// draw track segment
|
||||
draw_quad(
|
||||
x_start * bounds.width,
|
||||
x_width * bounds.width,
|
||||
custom_style.track_color,
|
||||
iced::Border {
|
||||
let border = 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,
|
||||
);
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
// indeterminate progress bar
|
||||
else {
|
||||
// draw track
|
||||
draw_quad(
|
||||
0.0,
|
||||
bounds.width,
|
||||
custom_style.track_color,
|
||||
iced::Border {
|
||||
renderer,
|
||||
bounds,
|
||||
Border {
|
||||
width: border_width,
|
||||
color: border_color,
|
||||
radius: radius.into(),
|
||||
},
|
||||
true,
|
||||
bounds.width,
|
||||
track_color,
|
||||
);
|
||||
|
||||
// draw bar
|
||||
|
|
@ -346,27 +289,17 @@ where
|
|||
let start = bar_start % 1.0;
|
||||
let right_width = (1.0 - start).min(length);
|
||||
let left_width = length - right_width;
|
||||
let border = iced::Border {
|
||||
let border = Border {
|
||||
radius: radius.into(),
|
||||
..iced::Border::default()
|
||||
..Border::default()
|
||||
};
|
||||
|
||||
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,
|
||||
);
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Create choices using radio buttons.
|
||||
use crate::{Theme, theme};
|
||||
use iced::border;
|
||||
use iced_core::event::{self, Event};
|
||||
use iced_core::event::Event;
|
||||
use iced_core::widget::tree::Tree;
|
||||
use iced_core::{
|
||||
Border, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Vector, Widget,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use iced::futures::channel::mpsc::UnboundedSender;
|
|||
use iced::widget::Container;
|
||||
pub use subscription::*;
|
||||
|
||||
use iced_core::event::{self, Event};
|
||||
use iced_core::event::Event;
|
||||
use iced_core::widget::Tree;
|
||||
use iced_core::{
|
||||
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, layout,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Responsive Container, which will notify of size changes.
|
||||
|
||||
use iced::{Limits, Size};
|
||||
use iced_core::event::{self, Event};
|
||||
use iced_core::event::Event;
|
||||
use iced_core::widget::{Id, Operation, Tree, tree};
|
||||
use iced_core::{
|
||||
Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay,
|
||||
|
|
|
|||
|
|
@ -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>(
|
||||
pub fn horizontal<SelectionMode: Default, Message: Clone + 'static>(
|
||||
model: &Model<SelectionMode>,
|
||||
) -> SegmentedButton<'_, Horizontal, SelectionMode, Message>
|
||||
where
|
||||
|
|
@ -30,7 +30,7 @@ where
|
|||
SegmentedButton::new(model)
|
||||
}
|
||||
|
||||
impl<SelectionMode, Message> SegmentedVariant
|
||||
impl<SelectionMode, Message: Clone + 'static> SegmentedVariant
|
||||
for SegmentedButton<'_, Horizontal, SelectionMode, Message>
|
||||
where
|
||||
Model<SelectionMode>: Selectable,
|
||||
|
|
|
|||
|
|
@ -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>(
|
||||
pub fn vertical<SelectionMode, Message: Clone + 'static>(
|
||||
model: &Model<SelectionMode>,
|
||||
) -> SegmentedButton<'_, Vertical, SelectionMode, Message>
|
||||
where
|
||||
|
|
@ -30,7 +30,7 @@ where
|
|||
SegmentedButton::new(model)
|
||||
}
|
||||
|
||||
impl<SelectionMode, Message> SegmentedVariant
|
||||
impl<SelectionMode, Message: Clone + 'static> SegmentedVariant
|
||||
for SegmentedButton<'_, Vertical, SelectionMode, Message>
|
||||
where
|
||||
Model<SelectionMode>: Selectable,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ 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! {
|
||||
|
|
@ -107,7 +108,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>
|
||||
pub struct SegmentedButton<'a, Variant, SelectionMode, Message: Clone + 'static>
|
||||
where
|
||||
Model<SelectionMode>: Selectable,
|
||||
SelectionMode: Default,
|
||||
|
|
@ -193,14 +194,27 @@ 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> SegmentedButton<'a, Variant, SelectionMode, Message>
|
||||
impl<'a, Variant, SelectionMode, Message: Clone + 'static>
|
||||
SegmentedButton<'a, Variant, SelectionMode, Message>
|
||||
where
|
||||
Self: SegmentedVariant,
|
||||
Model<SelectionMode>: Selectable,
|
||||
|
|
@ -248,6 +262,14 @@ 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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -311,12 +333,7 @@ where
|
|||
where
|
||||
Message: Clone + 'static,
|
||||
{
|
||||
self.context_menu = context_menu.map(|menus| {
|
||||
vec![menu::Tree::with_children(
|
||||
crate::Element::from(crate::widget::Row::new()),
|
||||
menus,
|
||||
)]
|
||||
});
|
||||
self.context_menu = context_menu;
|
||||
|
||||
if let Some(ref mut context_menu) = self.context_menu {
|
||||
context_menu.iter_mut().for_each(menu::Tree::set_index);
|
||||
|
|
@ -878,6 +895,267 @@ 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>
|
||||
|
|
@ -990,10 +1268,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>();
|
||||
|
|
@ -1398,6 +1676,41 @@ 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));
|
||||
|
|
@ -1456,9 +1769,28 @@ 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
|
||||
{
|
||||
|
|
@ -1576,6 +1908,33 @@ 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))
|
||||
|
|
@ -2193,19 +2552,43 @@ 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 =
|
||||
self.variant_bounds(state, layout.bounds())
|
||||
.find_map(|item| match item {
|
||||
ItemBounds::Button(e, bounds) if e == entity => Some(bounds),
|
||||
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 context_menu = self.context_menu.as_mut()?;
|
||||
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())?;
|
||||
|
||||
if !menu_state.inner.with_data(|data| data.open) {
|
||||
// If the menu is not open, we don't need to show it.
|
||||
|
|
@ -2223,7 +2606,7 @@ where
|
|||
Some(
|
||||
crate::widget::menu::Menu {
|
||||
tree: menu_state,
|
||||
menu_roots: std::borrow::Cow::Owned(context_menu.clone()),
|
||||
menu_roots: std::borrow::Cow::Owned(vec![menu]),
|
||||
bounds_expand: 16,
|
||||
menu_overlays_parent: true,
|
||||
close_condition: CloseCondition {
|
||||
|
|
@ -2509,6 +2892,7 @@ mod tests {
|
|||
where
|
||||
Model<SelectionMode>: Selectable,
|
||||
SelectionMode: Default,
|
||||
Message: Clone,
|
||||
{
|
||||
const VERTICAL: bool = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -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>(
|
||||
pub fn horizontal<SelectionMode: Default, Message: Clone + 'static>(
|
||||
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>(
|
||||
pub fn vertical<SelectionMode, Message: Clone + 'static>(
|
||||
model: &Model<SelectionMode>,
|
||||
) -> VerticalSegmentedButton<'_, SelectionMode, Message>
|
||||
where
|
||||
|
|
|
|||
|
|
@ -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>(
|
||||
pub fn horizontal<SelectionMode: Default, Message: Clone + 'static>(
|
||||
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>(
|
||||
pub fn vertical<SelectionMode, Message: Clone + 'static>(
|
||||
model: &Model<SelectionMode>,
|
||||
) -> VerticalSegmentedButton<'_, SelectionMode, Message>
|
||||
where
|
||||
|
|
|
|||
|
|
@ -1811,9 +1811,22 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
focus.updated_at = Instant::now();
|
||||
LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
|
||||
|
||||
// Check if Ctrl/Command+A/C/V/X was pressed.
|
||||
if state.keyboard_modifiers.command() {
|
||||
match key.to_latin(*physical_key) {
|
||||
// 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 {
|
||||
Some('c') => {
|
||||
if !is_secure
|
||||
&& let Some((start, end)) = state.cursor.selection(value) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
use iced::{Limits, Size};
|
||||
use iced_core::layout::Node;
|
||||
|
||||
use iced_core::event::{self, Event};
|
||||
use iced_core::event::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],
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
//! Show toggle controls using togglers.
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
use std::time::Duration;
|
||||
|
||||
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,
|
||||
event, layout, mouse, text, touch, window,
|
||||
Border, Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment, layout, mouse, text, touch, window,
|
||||
};
|
||||
use iced_widget::Id;
|
||||
use iced_widget::toggler::Status;
|
||||
|
||||
pub use iced_widget::toggler::{Catalog, Style};
|
||||
pub use iced_widget::toggler::Catalog;
|
||||
|
||||
pub fn toggler<'a, Message>(is_checked: bool) -> Toggler<'a, Message> {
|
||||
Toggler::new(is_checked)
|
||||
|
|
@ -260,7 +259,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();
|
||||
|
|
@ -370,7 +369,7 @@ impl<'a, Message> Widget<Message, crate::Theme, crate::Renderer> for Toggler<'a,
|
|||
},
|
||||
style.background,
|
||||
);
|
||||
let mut t = state.anim.t(self.duration, self.is_toggled);
|
||||
let t = state.anim.t(self.duration, self.is_toggled);
|
||||
|
||||
let toggler_foreground_bounds = Rectangle {
|
||||
x: bounds.x
|
||||
|
|
|
|||
|
|
@ -13,25 +13,20 @@ use std::time::Duration;
|
|||
use iced::Task;
|
||||
use iced_runtime::core::widget::Id;
|
||||
|
||||
use iced_core::event::{self, Event};
|
||||
use iced_core::event::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, svg, touch,
|
||||
Vector, Widget, layout, mouse, overlay, renderer, 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]
|
||||
|
|
@ -306,7 +301,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);
|
||||
|
||||
|
|
@ -521,8 +516,26 @@ 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<
|
||||
|
|
@ -553,9 +566,25 @@ 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<
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use std::rc::Rc;
|
|||
use std::thread::{self, ThreadId};
|
||||
|
||||
use crate::Element;
|
||||
use iced::{Length, Rectangle, Size, event};
|
||||
use iced::{Length, Rectangle, Size};
|
||||
use iced_core::id::Id;
|
||||
use iced_core::widget::tree;
|
||||
use iced_core::{Widget, widget};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue