Compare commits

..

No commits in common. "master" and "prev-master-0.14.0-dev" have entirely different histories.

137 changed files with 3443 additions and 7287 deletions

View file

@ -33,17 +33,16 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
test_args: features:
- --no-default-features --features "" # for cosmic-comp, don't remove! - "" # for cosmic-comp, don't remove!
- --no-default-features --features "winit_debug" - 'winit_debug'
- --no-default-features --features "winit_tokio" - 'winit_tokio'
- --no-default-features --features "winit" - winit
- --no-default-features --features "winit_wgpu" - winit_wgpu
- --no-default-features --features "wayland" - wayland
- --no-default-features --features "applet" - applet
- --no-default-features --features "desktop,smol" - desktop,smol
- --no-default-features --features "desktop,tokio" - desktop,tokio
- -p cosmic-theme
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout sources - name: Checkout sources
@ -67,7 +66,7 @@ jobs:
- name: Rust toolchain - name: Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Test features - name: Test features
run: cargo test ${{ matrix.test_args }} -- --test-threads=1 run: cargo test --no-default-features --features "${{ matrix.features }}"
env: env:
RUST_BACKTRACE: full RUST_BACKTRACE: full
@ -104,7 +103,7 @@ jobs:
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
- name: Rust toolchain - name: Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Check example - name: Test example
run: cargo check -p "${{ matrix.examples }}" run: cargo check -p "${{ matrix.examples }}"
env: env:
RUST_BACKTRACE: full RUST_BACKTRACE: full

View file

@ -7,30 +7,19 @@ on:
jobs: jobs:
pages: pages:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- name: Install Rust nightly - name: Build documentation
uses: dtolnay/rust-toolchain@master run: cargo doc --verbose --features tokio,winit
with: - name: Deploy documentation
toolchain: nightly-2025-07-31 uses: peaceiris/actions-gh-pages@v3
- name: System dependencies with:
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Build documentation publish_dir: ./target/doc
run: | force_orphan: true
RUSTDOCFLAGS="--cfg docsrs" \
cargo +nightly-2025-07-31 doc --no-deps \
-p cosmic-client-toolkit \
-p cosmic-protocols \
-p libcosmic \
--verbose --features tokio,winit,wayland,desktop,single-instance,applet,xdg-portal,multi-window
- name: Deploy documentation
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./target/doc
force_orphan: true

View file

@ -8,29 +8,13 @@ rust-version = "1.90"
name = "cosmic" name = "cosmic"
[features] [features]
default = [ default = ["dbus-config", "multi-window", "a11y"]
"winit",
"tokio",
"a11y",
"dbus-config",
"x11",
"iced-wayland",
"multi-window",
]
advanced-shaping = ["iced/advanced-shaping"]
# Accessibility support # Accessibility support
a11y = ["iced/a11y", "iced_accessibility"] a11y = ["iced/a11y", "iced_accessibility"]
# Enable about widget # Enable about widget
about = [] about = []
# Builds support for animated images # Builds support for animated images
animated-image = [ animated-image = ["dep:async-fs", "image/gif", "tokio?/io-util", "tokio?/fs"]
"dep:async-fs",
"image/gif",
"image/webp",
"image/png",
"tokio?/io-util",
"tokio?/fs",
]
# XXX autosize should not be used on winit windows unless dialogs # XXX autosize should not be used on winit windows unless dialogs
autosize = [] autosize = []
applet = [ applet = [
@ -58,7 +42,6 @@ desktop = [
"process", "process",
"dep:cosmic-settings-config", "dep:cosmic-settings-config",
"dep:freedesktop-desktop-entry", "dep:freedesktop-desktop-entry",
"dep:image-extras",
"dep:mime", "dep:mime",
"dep:shlex", "dep:shlex",
"tokio?/io-util", "tokio?/io-util",
@ -82,24 +65,18 @@ tokio = [
] ]
# Tokio async runtime # Tokio async runtime
# Wayland window support # Wayland window support
iced-wayland = [ wayland = [
"ashpd?/wayland", "ashpd?/wayland",
"autosize", "autosize",
"iced_runtime/wayland",
"iced/wayland", "iced/wayland",
"iced_winit/wayland", "iced_winit/wayland",
"cctk",
"surface-message", "surface-message",
] ]
wayland = [
"iced-wayland",
"iced_runtime/cctk",
"iced_winit/cctk",
"iced_wgpu/cctk",
"iced/cctk",
"dep:cctk",
]
surface-message = [] surface-message = []
# multi-window support # multi-window support
multi-window = [] multi-window = ["iced/multi-window"]
# Render with wgpu # Render with wgpu
wgpu = ["iced/wgpu", "iced_wgpu"] wgpu = ["iced/wgpu", "iced_wgpu"]
# X11 window support via winit # X11 window support via winit
@ -119,15 +96,14 @@ async-std = [
"zbus?/async-io", "zbus?/async-io",
"iced/async-std", "iced/async-std",
] ]
x11 = ["iced/x11", "iced_winit/x11"]
[dependencies] [dependencies]
apply = "0.3.0" apply = "0.3.0"
ashpd = { version = "0.12.3", default-features = false, optional = true } ashpd = { version = "0.12.1", default-features = false, optional = true }
async-fs = { version = "2.2", optional = true } async-fs = { version = "2.2", optional = true }
async-std = { version = "1.13", optional = true } async-std = { version = "1.13", optional = true }
auto_enums = "0.8.8" auto_enums = "0.8.7"
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true } cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d0e95be", optional = true }
jiff = "0.2" jiff = "0.2"
cosmic-config = { path = "cosmic-config" } cosmic-config = { path = "cosmic-config" }
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true }
@ -139,21 +115,17 @@ i18n-embed = { version = "0.16.0", features = [
i18n-embed-fl = "0.10" i18n-embed-fl = "0.10"
rust-embed = "8.11.0" rust-embed = "8.11.0"
css-color = "0.2.8" css-color = "0.2.8"
derive_setters = "0.1.9" derive_setters = "0.1.8"
futures = "0.3" futures = "0.3"
image = { version = "0.25.10", default-features = false, features = [ image = { version = "0.25.9", default-features = false, features = [
"ico",
"jpeg", "jpeg",
"png", "png",
] } ] }
image-extras = { version = "0.1.0", default-features = false, features = [ libc = { version = "0.2.180", optional = true }
"xpm",
"xbm",
], optional = true }
libc = { version = "0.2.183", optional = true }
log = "0.4" log = "0.4"
mime = { version = "0.3.17", optional = true } mime = { version = "0.3.17", optional = true }
palette = "0.7.6" palette = "0.7.6"
raw-window-handle = "0.6"
rfd = { version = "0.16.0", default-features = false, features = [ rfd = { version = "0.16.0", default-features = false, features = [
"xdg-portal", "xdg-portal",
], optional = true } ], optional = true }
@ -163,25 +135,24 @@ slotmap = "1.1.1"
smol = { version = "2.0.2", optional = true } smol = { version = "2.0.2", optional = true }
thiserror = "2.0.18" thiserror = "2.0.18"
taffy = { version = "0.9.2", features = ["grid"] } taffy = { version = "0.9.2", features = ["grid"] }
tokio = { version = "1.50.0", optional = true } tokio = { version = "1.49.0", optional = true }
tracing = "0.1.44" tracing = "0.1.44"
unicode-segmentation = "1.12" unicode-segmentation = "1.12"
url = "2.5.8" url = "2.5.8"
zbus = { version = "5.14.0", default-features = false, optional = true } zbus = { version = "5.13.2", default-features = false, optional = true }
float-cmp = "0.10.0"
# Enable DBus feature on Linux targets # Enable DBus feature on Linux targets
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
cosmic-config = { path = "cosmic-config", features = ["dbus"] } cosmic-config = { path = "cosmic-config", features = ["dbus"] }
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" }
zbus = { version = "5.14.0", default-features = false } zbus = { version = "5.13.2", default-features = false }
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies] [target.'cfg(unix)'.dependencies]
freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" }
freedesktop-desktop-entry = { version = "0.8.1", optional = true } freedesktop-desktop-entry = { version = "0.8.1", optional = true }
shlex = { version = "1.3.0", optional = true } shlex = { version = "1.3.0", optional = true }
[target.'cfg(any(not(unix), target_os = "macos"))'.dependencies] [target.'cfg(not(unix))'.dependencies]
# Used to embed bundled icons for non-unix platforms. # Used to embed bundled icons for non-unix platforms.
phf = { version = "0.13.1", features = ["macros"] } phf = { version = "0.13.1", features = ["macros"] }
@ -254,4 +225,4 @@ exclude = ["iced"]
dirs = "6.0.0" dirs = "6.0.0"
[dev-dependencies] [dev-dependencies]
tempfile = "3.27.0" tempfile = "3.24.0"

View file

@ -3,9 +3,7 @@ use std::env;
fn main() { fn main() {
println!("cargo::rerun-if-changed=build.rs"); println!("cargo::rerun-if-changed=build.rs");
if env::var_os("CARGO_CFG_UNIX").is_none() if env::var_os("CARGO_CFG_UNIX").is_none() {
|| env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos")
{
generate_bundled_icons(); generate_bundled_icons();
} }
} }

View file

@ -11,9 +11,9 @@ subscription = ["iced_futures"]
[dependencies] [dependencies]
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
zbus = { version = "5.14.0", default-features = false, optional = true } zbus = { version = "5.13.2", default-features = false, optional = true }
atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" } atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" }
calloop = { version = "0.14.4", optional = true } calloop = { version = "0.14.3", optional = true }
notify = "8.2.0" notify = "8.2.0"
ron = "0.12.0" ron = "0.12.0"
serde = "1.0.228" serde = "1.0.228"
@ -22,7 +22,7 @@ iced = { path = "../iced/", default-features = false, optional = true }
iced_futures = { path = "../iced/futures/", default-features = false, optional = true } iced_futures = { path = "../iced/futures/", default-features = false, optional = true }
futures-util = { version = "0.3", optional = true } futures-util = { version = "0.3", optional = true }
dirs.workspace = true dirs.workspace = true
tokio = { version = "1.50", optional = true, features = ["time"] } tokio = { version = "1.49", optional = true, features = ["time"] }
async-std = { version = "1.13", optional = true } async-std = { version = "1.13", optional = true }
tracing = "0.1" tracing = "0.1"
@ -30,4 +30,4 @@ tracing = "0.1"
xdg = "3.0" xdg = "3.0"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
known-folders = "1.4.2" known-folders = "1.4.0"

View file

@ -1,11 +1,11 @@
use std::{any::TypeId, ops::Deref}; use std::ops::Deref;
use crate::{CosmicConfigEntry, Update}; use crate::{CosmicConfigEntry, Update};
use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy}; use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy};
use futures_util::SinkExt; use futures_util::SinkExt;
use iced_futures::{ use iced_futures::{
Subscription, Subscription,
futures::{self, StreamExt, future::pending}, futures::{self, Stream, StreamExt, future::pending},
stream, stream,
}; };
@ -57,20 +57,6 @@ impl Watcher {
} }
} }
#[derive(Clone)]
struct Wrapper(
TypeId,
CosmicSettingsDaemonProxy<'static>,
&'static str,
bool,
);
impl std::hash::Hash for Wrapper {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
pub fn watcher_subscription<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone>( pub fn watcher_subscription<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone>(
settings_daemon: CosmicSettingsDaemonProxy<'static>, settings_daemon: CosmicSettingsDaemonProxy<'static>,
@ -78,185 +64,166 @@ pub fn watcher_subscription<T: CosmicConfigEntry + Send + Sync + Default + 'stat
is_state: bool, is_state: bool,
) -> iced_futures::Subscription<Update<T>> { ) -> iced_futures::Subscription<Update<T>> {
let id = std::any::TypeId::of::<T>(); let id = std::any::TypeId::of::<T>();
Subscription::run_with( Subscription::run_with_id(
Wrapper(id, settings_daemon, config_id, is_state), (id, config_id),
|&Wrapper(_, ref settings_daemon, ref config_id, ref is_state)| { watcher_stream(settings_daemon, config_id, is_state),
let is_state = *is_state;
let config_id = *config_id;
let settings_daemon = settings_daemon.clone();
enum Change {
Changes(Changed),
OwnerChanged(bool),
}
stream::channel(
5,
move |mut tx: futures::channel::mpsc::Sender<Update<T>>| async move {
let version = T::VERSION;
let Ok(cosmic_config) = (if is_state {
crate::Config::new_state(config_id, version)
} else {
crate::Config::new(config_id, version)
}) else {
pending::<()>().await;
unreachable!();
};
let mut attempts = 0;
loop {
let watcher = if is_state {
Watcher::new_state(&settings_daemon, config_id, version).await
} else {
Watcher::new_config(&settings_daemon, config_id, version).await
};
let Ok(watcher) = watcher else {
tracing::error!("Failed to create watcher for {config_id}");
#[cfg(feature = "tokio")]
::tokio::time::sleep(::tokio::time::Duration::from_secs(
2_u64.pow(attempts),
))
.await;
#[cfg(feature = "async-std")]
async_std::task::sleep(std::time::Duration::from_secs(
2_u64.pow(attempts),
))
.await;
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
{
pending::<()>().await;
unreachable!();
}
attempts += 1;
// The settings daemon has exited
continue;
};
let Ok(changes) = watcher.receive_changed().await else {
tracing::error!("Failed to listen for changes for {config_id}");
#[cfg(feature = "tokio")]
::tokio::time::sleep(::tokio::time::Duration::from_secs(
2_u64.pow(attempts),
))
.await;
#[cfg(feature = "async-std")]
async_std::task::sleep(std::time::Duration::from_secs(
2_u64.pow(attempts),
))
.await;
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
{
pending::<()>().await;
unreachable!();
}
attempts += 1;
// The settings daemon has exited
continue;
};
let mut changes = changes.map(Change::Changes).fuse();
let Ok(owner_changed) = watcher.inner().receive_owner_changed().await
else {
tracing::error!("Failed to listen for owner changes for {config_id}");
#[cfg(feature = "tokio")]
::tokio::time::sleep(::tokio::time::Duration::from_secs(
2_u64.pow(attempts),
))
.await;
#[cfg(feature = "async-std")]
async_std::task::sleep(std::time::Duration::from_secs(
2_u64.pow(attempts),
))
.await;
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
{
pending::<()>().await;
unreachable!();
}
attempts += 1;
// The settings daemon has exited
continue;
};
let mut owner_changed = owner_changed
.map(|c| Change::OwnerChanged(c.is_some()))
.fuse();
// update now, just in case we missed changes while setting up stream
let mut config = match T::get_entry(&cosmic_config) {
Ok(config) => config,
Err((errors, default)) => {
for why in &errors {
if why.is_err() {
if let crate::Error::GetKey(_, err) = &why {
if err.kind() == std::io::ErrorKind::NotFound {
// No system default config installed; don't error
continue;
}
}
tracing::error!("error getting config: {config_id} {why}");
}
}
default
}
};
if let Err(err) = tx
.send(Update {
errors: Vec::new(),
keys: Vec::new(),
config: config.clone(),
})
.await
{
tracing::error!("Failed to send config: {err}");
}
loop {
let change: Changed = futures::select! {
c = changes.next() => {
let Some(Change::Changes(c)) = c else {
break;
};
c
}
c = owner_changed.next() => {
let Some(Change::OwnerChanged(cont)) = c else {
break;
};
if cont {
continue;
} else {
// The settings daemon has exited
break;
}
},
};
// Reset the attempts counter if we received a change
attempts = 0;
let Ok(args) = change.args() else {
// The settings daemon has exited
break;
};
let (errors, keys) = config.update_keys(&cosmic_config, &[args.key]);
if !keys.is_empty() {
if let Err(err) = tx
.send(Update {
errors,
keys,
config: config.clone(),
})
.await
{
tracing::error!("Failed to send config update: {err}");
}
}
}
}
},
)
},
) )
} }
fn watcher_stream<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone>(
settings_daemon: CosmicSettingsDaemonProxy<'static>,
config_id: &'static str,
is_state: bool,
) -> impl Stream<Item = Update<T>> {
enum Change {
Changes(Changed),
OwnerChanged(bool),
}
stream::channel(5, move |mut tx| async move {
let version = T::VERSION;
let Ok(cosmic_config) = (if is_state {
crate::Config::new_state(config_id, version)
} else {
crate::Config::new(config_id, version)
}) else {
pending::<()>().await;
unreachable!();
};
let mut attempts = 0;
loop {
let watcher = if is_state {
Watcher::new_state(&settings_daemon, config_id, version).await
} else {
Watcher::new_config(&settings_daemon, config_id, version).await
};
let Ok(watcher) = watcher else {
tracing::error!("Failed to create watcher for {config_id}");
#[cfg(feature = "tokio")]
::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts))).await;
#[cfg(feature = "async-std")]
async_std::task::sleep(std::time::Duration::from_secs(2_u64.pow(attempts))).await;
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
{
pending::<()>().await;
unreachable!();
}
attempts += 1;
// The settings daemon has exited
continue;
};
let Ok(changes) = watcher.receive_changed().await else {
tracing::error!("Failed to listen for changes for {config_id}");
#[cfg(feature = "tokio")]
::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts))).await;
#[cfg(feature = "async-std")]
async_std::task::sleep(std::time::Duration::from_secs(2_u64.pow(attempts))).await;
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
{
pending::<()>().await;
unreachable!();
}
attempts += 1;
// The settings daemon has exited
continue;
};
let mut changes = changes.map(Change::Changes).fuse();
let Ok(owner_changed) = watcher.inner().receive_owner_changed().await else {
tracing::error!("Failed to listen for owner changes for {config_id}");
#[cfg(feature = "tokio")]
::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts))).await;
#[cfg(feature = "async-std")]
async_std::task::sleep(std::time::Duration::from_secs(2_u64.pow(attempts))).await;
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
{
pending::<()>().await;
unreachable!();
}
attempts += 1;
// The settings daemon has exited
continue;
};
let mut owner_changed = owner_changed
.map(|c| Change::OwnerChanged(c.is_some()))
.fuse();
// update now, just in case we missed changes while setting up stream
let mut config = match T::get_entry(&cosmic_config) {
Ok(config) => config,
Err((errors, default)) => {
for why in &errors {
if why.is_err() {
if let crate::Error::GetKey(_, err) = &why {
if err.kind() == std::io::ErrorKind::NotFound {
// No system default config installed; don't error
continue;
}
}
tracing::error!("error getting config: {config_id} {why}");
}
}
default
}
};
if let Err(err) = tx
.send(Update {
errors: Vec::new(),
keys: Vec::new(),
config: config.clone(),
})
.await
{
tracing::error!("Failed to send config: {err}");
}
loop {
let change: Changed = futures::select! {
c = changes.next() => {
let Some(Change::Changes(c)) = c else {
break;
};
c
}
c = owner_changed.next() => {
let Some(Change::OwnerChanged(cont)) = c else {
break;
};
if cont {
continue;
} else {
// The settings daemon has exited
break;
}
},
};
// Reset the attempts counter if we received a change
attempts = 0;
let Ok(args) = change.args() else {
// The settings daemon has exited
break;
};
let (errors, keys) = config.update_keys(&cosmic_config, &[args.key]);
if !keys.is_empty() {
if let Err(err) = tx
.send(Update {
errors,
keys,
config: config.clone(),
})
.await
{
tracing::error!("Failed to send config update: {err}");
}
}
}
}
})
}

View file

@ -25,24 +25,7 @@ pub fn config_subscription<
config_id: Cow<'static, str>, config_id: Cow<'static, str>,
config_version: u64, config_version: u64,
) -> iced_futures::Subscription<crate::Update<T>> { ) -> iced_futures::Subscription<crate::Update<T>> {
iced_futures::Subscription::run_with( iced_futures::Subscription::run_with_id(id, watcher_stream(config_id, config_version, false))
(id, config_id, config_version, false),
// FIXME there are type issues related to the 'static lifetime of the Cow if this is extracted to a named function...
|(_, config_id, config_version, is_state)| {
let config_id = config_id.clone();
let config_version = *config_version;
let is_state = *is_state;
stream::channel(100, move |mut output| async move {
let config_id = config_id.clone();
let mut state = ConfigState::Init(config_id, config_version, is_state);
loop {
state = start_listening::<T>(state, &mut output).await;
}
})
},
)
} }
#[cold] #[cold]
@ -54,23 +37,25 @@ pub fn config_state_subscription<
config_id: Cow<'static, str>, config_id: Cow<'static, str>,
config_version: u64, config_version: u64,
) -> iced_futures::Subscription<crate::Update<T>> { ) -> iced_futures::Subscription<crate::Update<T>> {
iced_futures::Subscription::run_with( iced_futures::Subscription::run_with_id(id, watcher_stream(config_id, config_version, true))
(id, config_id, config_version, true), }
|(_, config_id, config_version, is_state)| {
fn watcher_stream<T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry>(
config_id: Cow<'static, str>,
config_version: u64,
is_state: bool,
) -> impl Stream<Item = crate::Update<T>> {
stream::channel(100, move |mut output| {
let config_id = config_id.clone();
async move {
let config_id = config_id.clone(); let config_id = config_id.clone();
let config_version = *config_version; let mut state = ConfigState::Init(config_id, config_version, is_state);
let is_state = *is_state;
stream::channel(100, move |mut output| async move { loop {
let config_id = config_id.clone(); state = start_listening::<T>(state, &mut output).await;
let mut state = ConfigState::Init(config_id, config_version, is_state); }
}
loop { })
state = start_listening::<T>(state, &mut output).await;
}
})
},
)
} }
async fn start_listening<T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry>( async fn start_listening<T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry>(

View file

@ -22,7 +22,7 @@ serde_json = { version = "1.0.149", optional = true, features = [
"preserve_order", "preserve_order",
] } ] }
ron = "0.12.0" ron = "0.12.0"
csscolorparser = { version = "0.8.3", features = ["serde"] } csscolorparser = { version = "0.8.1", features = ["serde"] }
cosmic-config = { path = "../cosmic-config/", default-features = false, features = [ cosmic-config = { path = "../cosmic-config/", default-features = false, features = [
"subscription", "subscription",
"macro", "macro",
@ -30,10 +30,3 @@ cosmic-config = { path = "../cosmic-config/", default-features = false, features
configparser = "3.1.0" configparser = "3.1.0"
dirs.workspace = true dirs.workspace = true
thiserror = "2.0.18" thiserror = "2.0.18"
[dev-dependencies]
insta = "1.47.2"
[profile.dev.package]
insta.opt-level = 3
similar.opt-level = 3

View file

@ -986,19 +986,19 @@ impl ThemeBuilder {
let success = if let Some(success) = success { let success = if let Some(success) = success {
success.into_color() success.into_color()
} else { } else {
palette.as_ref().bright_green palette.as_ref().accent_green
}; };
let warning = if let Some(warning) = warning { let warning = if let Some(warning) = warning {
warning.into_color() warning.into_color()
} else { } else {
palette.as_ref().bright_orange palette.as_ref().accent_yellow
}; };
let destructive = if let Some(destructive) = destructive { let destructive = if let Some(destructive) = destructive {
destructive.into_color() destructive.into_color()
} else { } else {
palette.as_ref().bright_red palette.as_ref().accent_red
}; };
let text_steps_array = text_tint.map(|c| steps(c, NonZeroUsize::new(100).unwrap())); let text_steps_array = text_tint.map(|c| steps(c, NonZeroUsize::new(100).unwrap()));

View file

@ -46,10 +46,8 @@ impl Theme {
pub fn write_exports(&self) -> Result<(), OutputError> { pub fn write_exports(&self) -> Result<(), OutputError> {
let gtk_res = self.write_gtk4(); let gtk_res = self.write_gtk4();
let qt_res = self.write_qt(); let qt_res = self.write_qt();
let qt56ct_res = self.write_qt56ct();
gtk_res?; gtk_res?;
qt_res?; qt_res?;
qt56ct_res?;
Ok(()) Ok(())
} }
@ -58,10 +56,8 @@ impl Theme {
pub fn reset_exports() -> Result<(), OutputError> { pub fn reset_exports() -> Result<(), OutputError> {
let gtk_res = Theme::reset_gtk(); let gtk_res = Theme::reset_gtk();
let qt_res = Theme::reset_qt(); let qt_res = Theme::reset_qt();
let qt56ct_res = Theme::reset_qt56ct();
gtk_res?; gtk_res?;
qt_res?; qt_res?;
qt56ct_res?;
Ok(()) Ok(())
} }
} }

View file

@ -1,11 +1,8 @@
use crate::Theme; use crate::Theme;
use configparser::ini::Ini; use configparser::ini::Ini;
use palette::{Mix, Srgba, WithAlpha, blend::Compose, rgb::Rgba};
use std::{ use std::{
fs::{self, File}, fs::{self, File},
io::Write,
path::PathBuf, path::PathBuf,
vec,
}; };
use super::{OutputError, qt_settings_ini_style}; use super::{OutputError, qt_settings_ini_style};
@ -18,117 +15,7 @@ impl Theme {
/// Increment this value when changes to qt{5,6}ct.conf are needed. /// Increment this value when changes to qt{5,6}ct.conf are needed.
/// If the config's version is outdated, we update several sections. /// If the config's version is outdated, we update several sections.
/// Otherwise, only the light/dark mode is updated. /// Otherwise, only the light/dark mode is updated.
const COSMIC_QT_VERSION: u64 = 2; const COSMIC_QT_VERSION: u64 = 1;
/// Produces a QPalette ini file for qt5ct and qt6ct.
///
/// Example file: https://github.com/trialuser02/qt6ct/blob/master/colors/airy.conf
#[must_use]
#[cold]
pub fn as_qpalette(&self) -> String {
let lightest = if self.is_dark {
self.background.on
} else {
self.background.base
};
let darkest = if self.is_dark {
self.background.base
} else {
self.background.on
};
let active = QPaletteGroup {
window_text: self.background.on,
button: self.button.base,
light: self.button.base.mix(lightest, 0.1),
midlight: self.button.base.mix(lightest, 0.05),
dark: self.button.base.mix(darkest, 0.1),
mid: self.button.base.mix(darkest, 0.05),
text: self.background.component.on,
bright_text: lightest,
button_text: self.button.on,
base: self.background.component.base,
window: self.background.base,
shadow: darkest,
// selection colors are swapped to fix menu bar contrast
highlight: self.background.component.selected_text,
highlighted_text: self.background.component.selected,
link: self.link_button.on,
link_visited: self.link_button.on.mix(self.secondary.component.base, 0.2),
alternate_base: self.background.base.mix(self.accent.base, 0.05),
no_role: self.background.component.disabled,
tool_tip_base: self.background.component.base,
tool_tip_text: self.background.component.on,
placeholder_text: self.background.component.on.with_alpha(0.5),
};
let inactive = QPaletteGroup {
window_text: active.window_text.with_alpha(0.8),
text: active.text.with_alpha(0.8),
highlighted_text: active.highlighted_text.with_alpha(0.8),
tool_tip_text: active.tool_tip_text.with_alpha(0.8),
..active
};
let disabled = QPaletteGroup {
button: self.button.disabled,
text: self.background.component.on_disabled,
button_text: self.button.on_disabled,
base: self.background.component.disabled,
highlighted_text: active.highlighted_text.with_alpha(0.5),
link: self.link_button.on_disabled,
link_visited: self
.link_button
.on_disabled
.mix(self.secondary.component.disabled, 0.2),
alternate_base: self.background.base.mix(self.accent.disabled, 0.05),
tool_tip_base: self.background.component.disabled,
tool_tip_text: self.background.component.on_disabled,
placeholder_text: self.background.component.on_disabled.with_alpha(0.5),
..inactive
};
format!(
r#"# GENERATED BY COSMIC
[ColorScheme]
active_colors={}
disabled_colors={}
inactive_colors={}
"#,
active.as_list(),
disabled.as_list(),
inactive.as_list(),
)
}
/// Writes the QPalette ini files to:
/// - `~/.config/qt6ct/colors/`
/// - `~/.config/qt5ct/colors/`
#[cold]
pub fn write_qt56ct(&self) -> Result<(), OutputError> {
let qpalette = self.as_qpalette();
let qt5ct_res = self.write_ct("qt5ct", &qpalette);
let qt6ct_res = self.write_ct("qt6ct", &qpalette);
qt5ct_res?;
qt6ct_res?;
Ok(())
}
#[must_use]
#[cold]
fn write_ct(&self, ct: &str, qpalette: &str) -> Result<(), OutputError> {
let file_path = Self::get_qpalette_path(ct, self.is_dark)?;
let tmp_file_path = file_path.with_extension("conf.new");
let mut tmp_file = File::create(&tmp_file_path).map_err(OutputError::Io)?;
let res = tmp_file
.write_all(qpalette.as_bytes())
.and_then(|_| tmp_file.flush())
.and_then(|_| std::fs::rename(&tmp_file_path, file_path));
if let Err(e) = res {
_ = std::fs::remove_file(&tmp_file_path);
return Err(OutputError::Io(e));
}
Ok(())
}
/// Edits qt{5,6}ct.conf to use COSMIC styles if needed. /// Edits qt{5,6}ct.conf to use COSMIC styles if needed.
#[cold] #[cold]
@ -152,7 +39,7 @@ inactive_colors={}
.map_err(OutputError::Ini)? .map_err(OutputError::Ini)?
.unwrap_or_default(); .unwrap_or_default();
let color_scheme_path = Self::get_qpalette_path(ct, is_dark)?; let color_scheme_path = Self::get_qt_colors_path(is_dark)?;
let icon_theme = if is_dark { "breeze-dark" } else { "breeze" }; let icon_theme = if is_dark { "breeze-dark" } else { "breeze" };
ini.set( ini.set(
@ -204,48 +91,11 @@ inactive_colors={}
Ok(()) Ok(())
} }
/// Reset the applied qt56ct config by removing COSMIC-specific entries from the config file.
#[cold]
pub fn reset_qt56ct() -> Result<(), OutputError> {
let qt5ct_res = Self::reset_ct("qt5ct");
let qt6ct_res = Self::reset_ct("qt6ct");
qt5ct_res?;
qt6ct_res?;
Ok(())
}
#[must_use]
#[cold]
fn reset_ct(ct: &str) -> Result<(), OutputError> {
let path = Self::get_conf_path(ct)?;
let file_content = fs::read_to_string(&path).map_err(OutputError::Io)?;
let mut ini = Ini::new_cs();
ini.read(file_content).map_err(OutputError::Ini)?;
let old_version = ini
.getuint("Appearance", "cosmic_qt_version")
.map_err(OutputError::Ini)?
.unwrap_or_default();
if old_version == 0 {
return Ok(());
}
ini.remove_key("Appearance", "cosmic_qt_version");
ini.remove_key("Appearance", "color_scheme_path");
ini.remove_key("Appearance", "icon_theme");
ini.pretty_write(path, &qt_settings_ini_style())
.map_err(OutputError::Io)?;
Ok(())
}
/// Returns the file paths of the form `~/.config/ct/ct.conf`: /// Returns the file paths of the form `~/.config/ct/ct.conf`:
/// e.g. `~/.config/qt6ct/qt6ct.conf`. /// e.g. `~/.config/qt6ct/qt6ct.conf`.
/// ///
/// The file and its parent directory are created if they don't exist. /// The file and its parent directory are created if they don't exist.
#[cold]
fn get_conf_path(ct: &str) -> Result<PathBuf, OutputError> { fn get_conf_path(ct: &str) -> Result<PathBuf, OutputError> {
assert!(ct == "qt5ct" || ct == "qt6ct");
let Some(mut config_dir) = dirs::config_dir() else { let Some(mut config_dir) = dirs::config_dir() else {
return Err(OutputError::MissingConfigDir); return Err(OutputError::MissingConfigDir);
}; };
@ -261,155 +111,4 @@ inactive_colors={}
Ok(file_path) Ok(file_path)
} }
/// Gets a path like `~/.config/qt6ct/colors/CosmicDark.conf`
///
/// Its parent directory is created if it doesn't exist.
#[cold]
fn get_qpalette_path(ct: &str, is_dark: bool) -> Result<PathBuf, OutputError> {
assert!(ct == "qt5ct" || ct == "qt6ct");
let Some(mut config_dir) = dirs::config_dir() else {
return Err(OutputError::MissingConfigDir);
};
config_dir.push(&ct);
config_dir.push("colors");
if !config_dir.exists() {
fs::create_dir_all(&config_dir).map_err(OutputError::Io)?;
}
let file_name = if is_dark {
"CosmicDark.conf"
} else {
"CosmicLight.conf"
};
Ok(config_dir.join(file_name))
}
}
/// Defines the different symbolic color roles used in current GUIs.
///
/// qt5ct and qt6ct consume this as a list of colors, ordered by ColorRole:
/// - https://doc.qt.io/qt-6/qpalette.html#ColorRole-enum
/// - https://doc.qt.io/archives/qt-5.15/qpalette.html#ColorRole-enum
struct QPaletteGroup {
/// A general foreground color.
window_text: Srgba,
/// The general button background color.
button: Srgba,
/// Lighter than [button] color, used mostly for 3D bevel and shadow effects.
light: Srgba,
/// Between [button] and [light], used mostly for 3D bevel and shadow effects.
midlight: Srgba,
/// Darker than [button], used mostly for 3D bevel and shadow effects.
dark: Srgba,
/// Between [button] and [dark], used mostly for 3D bevel and shadow effects.
mid: Srgba,
/// The foreground color used with [base].
text: Srgba,
/// A text color that is very different from [window_text], and contrasts well with e.g. [dark].
/// Typically used for text that needs to be drawn where [text] or [window_text] would give poor contrast, such as on pressed push buttons.
bright_text: Srgba,
/// A foreground color used with the [button] color.
button_text: Srgba,
/// Used mostly as the background color for text entry widgets, but can also be used for other painting -
/// such as the background of combobox drop down lists and toolbar handles.
base: Srgba,
/// A general background color.
window: Srgba,
/// A very dark color, used mostly for 3D bevel and shadow effects.
/// Opaque black by default.
shadow: Srgba,
/// A color to indicate a selected item or the current item.
highlight: Srgba,
/// A text color that contrasts with [highlight].
highlighted_text: Srgba,
/// A text color used for unvisited hyperlinks.
link: Srgba,
/// A text color used for already visited hyperlinks.
link_visited: Srgba,
/// Used as the alternate background color in views with alternating row colors.
alternate_base: Srgba,
/// No role; this special role is often used to indicate that a role has not been assigned.
no_role: Srgba,
/// Used as the background color for QToolTip and QWhatsThis.
/// Tool tips use the inactive color group of QPalette, because tool tips are not active windows.
tool_tip_base: Srgba,
/// Used as the foreground color for QToolTip and QWhatsThis.
/// Tool tips use the inactive color group of QPalette, because tool tips are not active windows.
tool_tip_text: Srgba,
/// Used as the placeholder color for various text input widgets.
placeholder_text: Srgba,
// /// [accent] only exists since Qt 6.6. Including it here breaks qt5ct.
// /// When omitted, it defaults to [highlight].
// accent: Srgba,
}
impl QPaletteGroup {
/// Returns a comma-separated list of the colors as hex codes.
/// E.g. `#ff000000, #ffdcdcdc, ...`
///
/// Any transparent colors are flattened with [base] to avoid issues with
/// the Fusion style.
fn as_list(&self) -> String {
let colors = vec![
to_argb_hex(self.window_text.over(self.base)),
to_argb_hex(self.button.over(self.base)),
to_argb_hex(self.light.over(self.base)),
to_argb_hex(self.midlight.over(self.base)),
to_argb_hex(self.dark.over(self.base)),
to_argb_hex(self.mid.over(self.base)),
to_argb_hex(self.text.over(self.base)),
to_argb_hex(self.bright_text.over(self.base)),
to_argb_hex(self.button_text.over(self.base)),
to_argb_hex(self.base.over(self.base)),
to_argb_hex(self.window.over(self.base)),
to_argb_hex(self.shadow.over(self.base)),
to_argb_hex(self.highlight.over(self.base)),
to_argb_hex(self.highlighted_text.over(self.base)),
to_argb_hex(self.link.over(self.base)),
to_argb_hex(self.link_visited.over(self.base)),
to_argb_hex(self.alternate_base.over(self.base)),
to_argb_hex(self.no_role.over(self.base)),
to_argb_hex(self.tool_tip_base.over(self.base)),
to_argb_hex(self.tool_tip_text.over(self.base)),
to_argb_hex(self.placeholder_text.over(self.base)),
];
colors.join(", ")
}
}
/// Converts a color to a hex string in the format `#AARRGGBB`.
/// Do not use [to_hex] since that uses the format `RRGGBBAA`.
fn to_argb_hex(c: Srgba) -> String {
let c_u8: Rgba<palette::encoding::Srgb, u8> = c.into_format();
format!(
"#{:02x}{:02x}{:02x}{:02x}",
c_u8.alpha, c_u8.red, c_u8.green, c_u8.blue
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_color_to_argb_hex() {
let color = Srgba::new(0x33, 0x55, 0x77, 0xff);
let argb = to_argb_hex(color.into());
assert_eq!(argb, "#ff335577");
}
#[test]
fn test_light_default_qpalette() {
let light_default_qpalette = Theme::light_default().as_qpalette();
insta::assert_snapshot!(light_default_qpalette);
}
#[test]
fn test_dark_default_qpalette() {
let dark_default_qpalette = Theme::dark_default().as_qpalette();
insta::assert_snapshot!(dark_default_qpalette);
}
} }

View file

@ -14,11 +14,10 @@ impl Theme {
/// Produces a color scheme ini file for Qt. /// Produces a color scheme ini file for Qt.
/// ///
/// Some high-level documentation for this file can be found at: /// Some high-level documentation for this file can be found at:
/// - https://api.kde.org/kcolorscheme.html /// https://web.archive.org/web/20250402234329/https://docs.kde.org/stable5/en/plasma-workspace/kcontrol/colors/
/// - https://web.archive.org/web/20250402234329/https://docs.kde.org/stable5/en/plasma-workspace/kcontrol/colors/
#[must_use] #[must_use]
#[cold] #[cold]
pub fn as_kcolorscheme(&self) -> String { pub fn as_qt(&self) -> String {
// Usually, disabled elements will have strongly reduced contrast and are often notably darker or lighter // Usually, disabled elements will have strongly reduced contrast and are often notably darker or lighter
let disabled_color_effects = IniColorEffects { let disabled_color_effects = IniColorEffects {
color: self.button.disabled, color: self.button.disabled,
@ -42,7 +41,7 @@ impl Theme {
let bg = self.background.base; let bg = self.background.base;
// the background container // the background container
let window_colors = IniColors { let view_colors = IniColors {
background_alternate: bg.mix(self.accent.base, 0.05), background_alternate: bg.mix(self.accent.base, 0.05),
background_normal: bg, background_normal: bg,
decoration_focus: self.accent_text_color(), decoration_focus: self.accent_text_color(),
@ -57,17 +56,16 @@ impl Theme {
foreground_visited: self.accent_text_color(), foreground_visited: self.accent_text_color(),
}; };
// components inside the background container // components inside the background container
let view_colors = IniColors { let window_colors = IniColors {
background_alternate: self.background.component.base.mix(self.accent.base, 0.05), background_alternate: self.background.component.base.mix(self.accent.base, 0.05),
background_normal: self.background.component.base, background_normal: self.background.component.base,
..window_colors ..view_colors
}; };
// selected text and items // selected text and items
let selection_colors = { let selection_colors = {
// selection colors are swapped to fix menu bar contrast let selected = self.background.component.selected;
let selected = self.background.component.selected_text; let selected_text = self.background.component.selected_text;
let selected_text = self.background.component.selected;
IniColors { IniColors {
background_alternate: selected.mix(bg, 0.5), background_alternate: selected.mix(bg, 0.5),
background_normal: selected, background_normal: selected,
@ -94,11 +92,8 @@ impl Theme {
let complementary_colors = { let complementary_colors = {
let dark = if self.is_dark { let dark = if self.is_dark {
self.clone() self.clone()
} else if cfg!(test) {
// For reproducible results in tests, use the default dark theme
Theme::dark_default()
} else { } else {
Theme::dark_config() Theme::light_config()
.ok() .ok()
.as_ref() .as_ref()
.and_then(|conf| Theme::get_entry(conf).ok()) .and_then(|conf| Theme::get_entry(conf).ok())
@ -121,10 +116,10 @@ impl Theme {
}; };
// headers in cosmic don't have a background // headers in cosmic don't have a background
let header_colors = &window_colors; let header_colors = &view_colors;
let header_colors_inactive = &window_colors; let header_colors_inactive = &view_colors;
// tool tips, "What's This" tips, and similar elements // tool tips, "What's This" tips, and similar elements
let tooltip_colors = &view_colors; let tooltip_colors = &window_colors;
let general_color_scheme = if self.is_dark { let general_color_scheme = if self.is_dark {
"CosmicDark" "CosmicDark"
@ -203,7 +198,7 @@ widgetStyle=qt6ct-style
format_ini_colors(&tooltip_colors, bg), format_ini_colors(&tooltip_colors, bg),
format_ini_colors(&view_colors, bg), format_ini_colors(&view_colors, bg),
format_ini_colors(&window_colors, bg), format_ini_colors(&window_colors, bg),
format_ini_wm_colors(&window_colors, self.is_dark), format_ini_wm_colors(&view_colors, self.is_dark),
) )
} }
@ -217,14 +212,14 @@ widgetStyle=qt6ct-style
/// Returns an `OutputError` if there is an error writing the colors file. /// Returns an `OutputError` if there is an error writing the colors file.
#[cold] #[cold]
pub fn write_qt(&self) -> Result<(), OutputError> { pub fn write_qt(&self) -> Result<(), OutputError> {
let kcolorscheme = self.as_kcolorscheme(); let colors = self.as_qt();
let file_path = Self::get_kcolorscheme_path(self.is_dark)?; let file_path = Self::get_qt_colors_path(self.is_dark)?;
let tmp_file_path = file_path.with_extension("colors.new"); let tmp_file_path = file_path.with_extension("colors.new");
// Write to tmp_file_path first, then move it to file_path // Write to tmp_file_path first, then move it to file_path
let mut tmp_file = File::create(&tmp_file_path).map_err(OutputError::Io)?; let mut tmp_file = File::create(&tmp_file_path).map_err(OutputError::Io)?;
let res = tmp_file let res = tmp_file
.write_all(kcolorscheme.as_bytes()) .write_all(colors.as_bytes())
.and_then(|_| tmp_file.flush()) .and_then(|_| tmp_file.flush())
.and_then(|_| std::fs::rename(&tmp_file_path, file_path)); .and_then(|_| std::fs::rename(&tmp_file_path, file_path));
if let Err(e) = res { if let Err(e) = res {
@ -250,7 +245,7 @@ widgetStyle=qt6ct-style
let kdeglobals_file = config_dir.join("kdeglobals"); let kdeglobals_file = config_dir.join("kdeglobals");
let mut kdeglobals_ini = Self::read_ini(&kdeglobals_file)?; let mut kdeglobals_ini = Self::read_ini(&kdeglobals_file)?;
let src_file = Self::get_kcolorscheme_path(is_dark)?; let src_file = Self::get_qt_colors_path(is_dark)?;
let src_ini = Self::read_ini(&src_file)?; let src_ini = Self::read_ini(&src_file)?;
Self::backup_non_cosmic_kdeglobals(&kdeglobals_ini, &kdeglobals_file) Self::backup_non_cosmic_kdeglobals(&kdeglobals_ini, &kdeglobals_file)
@ -293,7 +288,7 @@ widgetStyle=qt6ct-style
} }
let is_dark = false; // doesn't matter since we're only reading keys let is_dark = false; // doesn't matter since we're only reading keys
let src_file = Self::get_kcolorscheme_path(is_dark)?; let src_file = Self::get_qt_colors_path(is_dark)?;
let src_ini = Self::read_ini(&src_file)?; let src_ini = Self::read_ini(&src_file)?;
for (section, key_value) in src_ini.get_map_ref() { for (section, key_value) in src_ini.get_map_ref() {
@ -308,8 +303,8 @@ widgetStyle=qt6ct-style
Ok(()) Ok(())
} }
/// Gets a path like `~/.local/share/color-schemes/CosmicDark.colors` /// Gets a path like `~/.config/color-schemes/CosmicDark.colors`
fn get_kcolorscheme_path(is_dark: bool) -> Result<PathBuf, OutputError> { pub fn get_qt_colors_path(is_dark: bool) -> Result<PathBuf, OutputError> {
let Some(mut data_dir) = dirs::data_dir() else { let Some(mut data_dir) = dirs::data_dir() else {
return Err(OutputError::MissingDataDir); return Err(OutputError::MissingDataDir);
}; };
@ -525,44 +520,3 @@ impl ColorEffect {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_opaque_color_to_rgb() {
let color = Srgba::new(30.0 / 255.0, 50.0 / 255.0, 70.0 / 255.0, 1.0);
let bg = Srgba::new(1.0, 1.0, 1.0, 1.0);
let result = to_rgb(color, bg);
assert_eq!(result, "30,50,70");
}
#[test]
fn test_transparent_color_to_rgb() {
let color = Srgba::new(0.0, 0.0, 0.0, 0.0);
let bg = Srgba::new(1.0, 1.0, 1.0, 1.0);
let result = to_rgb(color, bg);
assert_eq!(result, "255,255,255");
}
#[test]
fn test_translucent_color_to_rgb() {
let color = Srgba::new(0.0, 0.0, 0.0, 0.9);
let bg = Srgba::new(1.0, 1.0, 1.0, 1.0);
let result = to_rgb(color, bg);
assert_eq!(result, "26,26,26");
}
#[test]
fn test_light_default_kcolorscheme() {
let light_default_kcolorscheme = Theme::light_default().as_kcolorscheme();
insta::assert_snapshot!(light_default_kcolorscheme);
}
#[test]
fn test_dark_default_kcolorscheme() {
let dark_default_kcolorscheme = Theme::dark_default().as_kcolorscheme();
insta::assert_snapshot!(dark_default_kcolorscheme);
}
}

View file

@ -1,10 +0,0 @@
---
source: cosmic-theme/src/output/qt56ct_output.rs
expression: dark_default_qpalette
---
# GENERATED BY COSMIC
[ColorScheme]
active_colors=#ffe7e7e7, #ff4a4a4a, #ff555555, #ff505050, #ff4f4f4f, #ff4d4d4d, #ffc0c0c0, #ffe7e7e7, #ffc0c0c0, #ff2e2e2e, #ff1b1b1b, #ff1b1b1b, #ff63d0df, #ff434343, #ff63d0df, #ff5bb2be, #ff1f2425, #ff2e2e2e, #ff2e2e2e, #ffc0c0c0, #ff777777
disabled_colors=#e6d3d3d3, #8f474747, #a9696969, #a4626262, #a95f5f5f, #a45d5d5d, #d2a1a1a1, #ffe7e7e7, #d2a1a1a1, #bf2e2e2e, #ff1b1b1b, #ff1b1b1b, #ff63d0df, #bf3c3c3c, #bf30555a, #bf324f53, #ff1f2425, #bf2e2e2e, #bf2e2e2e, #d2a1a1a1, #bf909090
inactive_colors=#ffc2c2c2, #ff4a4a4a, #ff555555, #ff505050, #ff4f4f4f, #ff4d4d4d, #ffa3a3a3, #ffe7e7e7, #ffc0c0c0, #ff2e2e2e, #ff1b1b1b, #ff1b1b1b, #ff63d0df, #ff3f3f3f, #ff63d0df, #ff5bb2be, #ff1f2425, #ff2e2e2e, #ff2e2e2e, #ffa3a3a3, #ff777777

View file

@ -1,10 +0,0 @@
---
source: cosmic-theme/src/output/qt56ct_output.rs
expression: light_default_qpalette
---
# GENERATED BY COSMIC
[ColorScheme]
active_colors=#ff121212, #ffc3c3c3, #ffbababa, #ffbebebe, #ffb3b3b3, #ffbbbbbb, #ff272727, #ffd7d7d7, #ff272727, #fff5f5f5, #ffd7d7d7, #ff121212, #ff00525a, #fff6f6f6, #ff00525a, #ff317379, #ffccd0d1, #fff5f5f5, #fff5f5f5, #ff272727, #ff8e8e8e
disabled_colors=#e62b2b2b, #8fc9c9c9, #a99b9b9b, #a4a0a0a0, #a9929292, #a49b9b9b, #d2535353, #ffd7d7d7, #d2535353, #bff5f5f5, #ffd7d7d7, #ff121212, #ff00525a, #bff6f6f6, #bf526d70, #bf72888a, #ffccd0d1, #bff5f5f5, #bff5f5f5, #d2535353, #bf6c6c6c
inactive_colors=#ff3f3f3f, #ffc3c3c3, #ffbababa, #ffbebebe, #ffb3b3b3, #ffbbbbbb, #ff505050, #ffd7d7d7, #ff272727, #fff5f5f5, #ffd7d7d7, #ff121212, #ff00525a, #fff6f6f6, #ff00525a, #ff317379, #ffccd0d1, #fff5f5f5, #fff5f5f5, #ff505050, #ff8e8e8e

View file

@ -1,157 +0,0 @@
---
source: cosmic-theme/src/output/qt_output.rs
expression: dark_default_kcolorscheme
---
# GENERATED BY COSMIC
[ColorEffects:Disabled]
Color=43,43,43
ColorAmount=0
ColorEffect=0
ContrastAmount=0.65
ContrastEffect=1
IntensityAmount=0.1
IntensityEffect=2
[ColorEffects:Inactive]
ChangeSelectionColor=false
Enable=false
Color=27,27,27
ColorAmount=0.025
ColorEffect=2
ContrastAmount=0.1
ContrastEffect=2
IntensityAmount=0
IntensityEffect=0
[Colors:Button]
BackgroundAlternate=99,208,223
BackgroundNormal=60,60,60
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=99,208,223
ForegroundInactive=211,211,211
ForegroundLink=99,208,223
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=231,231,231
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[Colors:Complementary]
BackgroundAlternate=99,208,223
BackgroundNormal=27,27,27
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=99,208,223
ForegroundInactive=211,211,211
ForegroundLink=99,208,223
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=231,231,231
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[Colors:Header]
BackgroundAlternate=31,36,37
BackgroundNormal=27,27,27
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=99,208,223
ForegroundInactive=211,211,211
ForegroundLink=99,208,223
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=231,231,231
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[Colors:Header][Inactive]
BackgroundAlternate=31,36,37
BackgroundNormal=27,27,27
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=99,208,223
ForegroundInactive=211,211,211
ForegroundLink=99,208,223
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=231,231,231
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[Colors:Selection]
BackgroundAlternate=63,118,125
BackgroundNormal=99,208,223
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=67,67,67
ForegroundInactive=83,138,145
ForegroundLink=27,27,27
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=67,67,67
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[Colors:Tooltip]
BackgroundAlternate=49,55,55
BackgroundNormal=46,46,46
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=99,208,223
ForegroundInactive=211,211,211
ForegroundLink=99,208,223
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=231,231,231
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[Colors:View]
BackgroundAlternate=49,55,55
BackgroundNormal=46,46,46
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=99,208,223
ForegroundInactive=211,211,211
ForegroundLink=99,208,223
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=231,231,231
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[Colors:Window]
BackgroundAlternate=31,36,37
BackgroundNormal=27,27,27
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=99,208,223
ForegroundInactive=211,211,211
ForegroundLink=99,208,223
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=231,231,231
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[General]
ColorScheme=CosmicDark
Name=COSMIC Dark
shadeSortColumn=true
[Icons]
Theme=breeze-dark
[KDE]
contrast=4
widgetStyle=qt6ct-style
[WM]
activeBackground=27,27,27
activeBlend=99,208,223
activeForeground=99,208,223
inactiveBackground=27,27,27
inactiveBlend=99,208,223
inactiveForeground=99,208,223

View file

@ -1,157 +0,0 @@
---
source: cosmic-theme/src/output/qt_output.rs
expression: light_default_kcolorscheme
---
# GENERATED BY COSMIC
[ColorEffects:Disabled]
Color=194,194,194
ColorAmount=0
ColorEffect=0
ContrastAmount=0.65
ContrastEffect=1
IntensityAmount=0.1
IntensityEffect=2
[ColorEffects:Inactive]
ChangeSelectionColor=false
Enable=false
Color=215,215,215
ColorAmount=0.025
ColorEffect=2
ContrastAmount=0.1
ContrastEffect=2
IntensityAmount=0
IntensityEffect=0
[Colors:Button]
BackgroundAlternate=0,82,90
BackgroundNormal=173,173,173
DecorationFocus=0,82,90
DecorationHover=0,82,90
ForegroundActive=0,82,90
ForegroundInactive=38,38,38
ForegroundLink=0,82,90
ForegroundNegative=137,4,24
ForegroundNeutral=121,44,0
ForegroundNormal=18,18,18
ForegroundPositive=0,87,44
ForegroundVisited=0,82,90
[Colors:Complementary]
BackgroundAlternate=99,208,223
BackgroundNormal=27,27,27
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=99,208,223
ForegroundInactive=211,211,211
ForegroundLink=99,208,223
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=231,231,231
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[Colors:Header]
BackgroundAlternate=204,208,209
BackgroundNormal=215,215,215
DecorationFocus=0,82,90
DecorationHover=0,82,90
ForegroundActive=0,82,90
ForegroundInactive=38,38,38
ForegroundLink=0,82,90
ForegroundNegative=137,4,24
ForegroundNeutral=121,44,0
ForegroundNormal=18,18,18
ForegroundPositive=0,87,44
ForegroundVisited=0,82,90
[Colors:Header][Inactive]
BackgroundAlternate=204,208,209
BackgroundNormal=215,215,215
DecorationFocus=0,82,90
DecorationHover=0,82,90
ForegroundActive=0,82,90
ForegroundInactive=38,38,38
ForegroundLink=0,82,90
ForegroundNegative=137,4,24
ForegroundNeutral=121,44,0
ForegroundNormal=18,18,18
ForegroundPositive=0,87,44
ForegroundVisited=0,82,90
[Colors:Selection]
BackgroundAlternate=108,149,152
BackgroundNormal=0,82,90
DecorationFocus=0,82,90
DecorationHover=0,82,90
ForegroundActive=246,246,246
ForegroundInactive=123,164,168
ForegroundLink=215,215,215
ForegroundNegative=137,4,24
ForegroundNeutral=121,44,0
ForegroundNormal=246,246,246
ForegroundPositive=0,87,44
ForegroundVisited=0,82,90
[Colors:Tooltip]
BackgroundAlternate=233,237,237
BackgroundNormal=245,245,245
DecorationFocus=0,82,90
DecorationHover=0,82,90
ForegroundActive=0,82,90
ForegroundInactive=38,38,38
ForegroundLink=0,82,90
ForegroundNegative=137,4,24
ForegroundNeutral=121,44,0
ForegroundNormal=18,18,18
ForegroundPositive=0,87,44
ForegroundVisited=0,82,90
[Colors:View]
BackgroundAlternate=233,237,237
BackgroundNormal=245,245,245
DecorationFocus=0,82,90
DecorationHover=0,82,90
ForegroundActive=0,82,90
ForegroundInactive=38,38,38
ForegroundLink=0,82,90
ForegroundNegative=137,4,24
ForegroundNeutral=121,44,0
ForegroundNormal=18,18,18
ForegroundPositive=0,87,44
ForegroundVisited=0,82,90
[Colors:Window]
BackgroundAlternate=204,208,209
BackgroundNormal=215,215,215
DecorationFocus=0,82,90
DecorationHover=0,82,90
ForegroundActive=0,82,90
ForegroundInactive=38,38,38
ForegroundLink=0,82,90
ForegroundNegative=137,4,24
ForegroundNeutral=121,44,0
ForegroundNormal=18,18,18
ForegroundPositive=0,87,44
ForegroundVisited=0,82,90
[General]
ColorScheme=CosmicLight
Name=COSMIC Light
shadeSortColumn=true
[Icons]
Theme=breeze
[KDE]
contrast=4
widgetStyle=qt6ct-style
[WM]
activeBackground=215,215,215
activeBlend=215,215,215
activeForeground=0,82,90
inactiveBackground=215,215,215
inactiveBlend=215,215,215
inactiveForeground=0,82,90

View file

@ -145,6 +145,7 @@ pub fn is_valid_srgb(c: Srgba) -> bool {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use almost::equal;
use palette::{OklabHue, Srgba}; use palette::{OklabHue, Srgba};
use super::{is_valid_srgb, oklch_to_srgba_nearest_chroma}; use super::{is_valid_srgb, oklch_to_srgba_nearest_chroma};
@ -172,57 +173,57 @@ mod tests {
fn test_conversion_boundaries() { fn test_conversion_boundaries() {
let c1 = palette::Oklcha::new(0.0, 0.288, OklabHue::from_degrees(0.0), 1.0); let c1 = palette::Oklcha::new(0.0, 0.288, OklabHue::from_degrees(0.0), 1.0);
let srgb = oklch_to_srgba_nearest_chroma(c1); let srgb = oklch_to_srgba_nearest_chroma(c1);
almost::zero(srgb.red); equal(srgb.red, 0.0);
almost::zero(srgb.blue); equal(srgb.blue, 0.0);
almost::zero(srgb.green); equal(srgb.green, 0.0);
let c1 = palette::Oklcha::new(1.0, 0.288, OklabHue::from_degrees(0.0), 1.0); let c1 = palette::Oklcha::new(1.0, 0.288, OklabHue::from_degrees(0.0), 1.0);
let srgb = oklch_to_srgba_nearest_chroma(c1); let srgb = oklch_to_srgba_nearest_chroma(c1);
almost::equal(srgb.red, 1.0); equal(srgb.red, 1.0);
almost::equal(srgb.blue, 1.0); equal(srgb.blue, 1.0);
almost::equal(srgb.green, 1.0); equal(srgb.green, 1.0);
} }
#[test] #[test]
fn test_conversion_colors() { fn test_conversion_colors() {
let c1 = palette::Oklcha::new(0.4608, 0.11111, OklabHue::new(57.31), 1.0); let c1 = palette::Oklcha::new(0.4608, 0.11111, OklabHue::new(57.31), 1.0);
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>(); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
assert_eq!(srgb.red, 133); assert!(srgb.red == 133);
assert_eq!(srgb.green, 69); assert!(srgb.green == 69);
assert_eq!(srgb.blue, 0); assert!(srgb.blue == 0);
let c1 = palette::Oklcha::new(0.30, 0.08, OklabHue::new(35.0), 1.0); let c1 = palette::Oklcha::new(0.30, 0.08, OklabHue::new(35.0), 1.0);
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>(); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
assert_eq!(srgb.red, 78); assert!(srgb.red == 78);
assert_eq!(srgb.green, 27); assert!(srgb.green == 27);
assert_eq!(srgb.blue, 15); assert!(srgb.blue == 15);
let c1 = palette::Oklcha::new(0.757, 0.146, OklabHue::new(301.2), 1.0); let c1 = palette::Oklcha::new(0.757, 0.146, OklabHue::new(301.2), 1.0);
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>(); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
assert_eq!(srgb.red, 192); assert!(srgb.red == 192);
assert_eq!(srgb.green, 153); assert!(srgb.green == 153);
assert_eq!(srgb.blue, 253); assert!(srgb.blue == 253);
} }
#[test] #[test]
fn test_conversion_fallback_colors() { fn test_conversion_fallback_colors() {
let c1 = palette::Oklcha::new(0.70, 0.284, OklabHue::new(35.0), 1.0); let c1 = palette::Oklcha::new(0.70, 0.284, OklabHue::new(35.0), 1.0);
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>(); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
assert_eq!(srgb.red, 255); assert!(srgb.red == 255);
assert_eq!(srgb.green, 102); assert!(srgb.green == 103);
assert_eq!(srgb.blue, 65); assert!(srgb.blue == 65);
let c1 = palette::Oklcha::new(0.757, 0.239, OklabHue::new(301.2), 1.0); let c1 = palette::Oklcha::new(0.757, 0.239, OklabHue::new(301.2), 1.0);
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>(); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
assert_eq!(srgb.red, 193); assert!(srgb.red == 193);
assert_eq!(srgb.green, 152); assert!(srgb.green == 152);
assert_eq!(srgb.blue, 255); assert!(srgb.blue == 255);
let c1 = palette::Oklcha::new(0.163, 0.333, OklabHue::new(141.0), 1.0); let c1 = palette::Oklcha::new(0.163, 0.333, OklabHue::new(141.0), 1.0);
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>(); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
assert_eq!(srgb.red, 1); assert!(srgb.red == 1);
assert_eq!(srgb.green, 19); assert!(srgb.green == 19);
assert_eq!(srgb.blue, 0); assert!(srgb.blue == 0);
} }
} }

View file

@ -132,7 +132,7 @@ impl cosmic::Application for App {
fn view(&self) -> Element<'_, Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
let show_about_button = widget::button::text("Show about").on_press(Message::ToggleAbout); let show_about_button = widget::button::text("Show about").on_press(Message::ToggleAbout);
let centered = cosmic::widget::container( let centered = cosmic::widget::container(
widget::column::with_capacity(1) widget::column()
.push(show_about_button) .push(show_about_button)
.width(Length::Fill) .width(Length::Fill)
.height(Length::Shrink) .height(Length::Shrink)

View file

@ -13,6 +13,6 @@ env_logger = "0.10.2"
log = "0.4.29" log = "0.4.29"
[dependencies.libcosmic] [dependencies.libcosmic]
path = "../../" git = "https://github.com/pop-os/libcosmic"
default-features = false default-features = false
features = ["applet-token"] features = ["applet-token"]

View file

@ -1,8 +1,8 @@
use cosmic::app::{Core, Task}; use cosmic::app::{Core, Task};
use cosmic::iced::core::window;
use cosmic::iced::window::Id; use cosmic::iced::window::Id;
use cosmic::iced::{Length, Rectangle}; use cosmic::iced::{Length, Rectangle};
use cosmic::iced_runtime::core::window;
use cosmic::surface::action::{app_popup, destroy_popup}; use cosmic::surface::action::{app_popup, destroy_popup};
use cosmic::widget::{dropdown::popup_dropdown, list_column, settings, toggler}; use cosmic::widget::{dropdown::popup_dropdown, list_column, settings, toggler};
use cosmic::Element; use cosmic::Element;
@ -13,7 +13,6 @@ pub struct Window {
core: Core, core: Core,
popup: Option<Id>, popup: Option<Id>,
example_row: bool, example_row: bool,
toggle: bool,
selected: Option<usize>, selected: Option<usize>,
} }
@ -23,7 +22,6 @@ impl Default for Window {
core: Core::default(), core: Core::default(),
popup: None, popup: None,
example_row: false, example_row: false,
toggle: false,
selected: None, selected: None,
} }
} }
@ -35,7 +33,6 @@ pub enum Message {
ToggleExampleRow(bool), ToggleExampleRow(bool),
Selected(usize), Selected(usize),
Surface(cosmic::surface::Action), Surface(cosmic::surface::Action),
Toggle(bool),
} }
impl cosmic::Application for Window { impl cosmic::Application for Window {
@ -74,6 +71,7 @@ impl cosmic::Application for Window {
Message::ToggleExampleRow(toggled) => { Message::ToggleExampleRow(toggled) => {
self.example_row = toggled; self.example_row = toggled;
} }
Message::Surface(a) => { Message::Surface(a) => {
return cosmic::task::message(cosmic::Action::Cosmic( return cosmic::task::message(cosmic::Action::Cosmic(
cosmic::app::Action::Surface(a), cosmic::app::Action::Surface(a),
@ -82,9 +80,6 @@ impl cosmic::Application for Window {
Message::Selected(i) => { Message::Selected(i) => {
self.selected = Some(i); self.selected = Some(i);
} }
Message::Toggle(v) => {
self.toggle = v;
}
}; };
Task::none() Task::none()
} }
@ -128,8 +123,9 @@ impl cosmic::Application for Window {
"Example row", "Example row",
cosmic::widget::container( cosmic::widget::container(
toggler(state.example_row) toggler(state.example_row)
.on_toggle(Message::ToggleExampleRow), .on_toggle(|value| Message::ToggleExampleRow(value)),
), )
.height(Length::Fixed(50.)),
)) ))
.add(popup_dropdown( .add(popup_dropdown(
&["1", "asdf", "hello", "test"], &["1", "asdf", "hello", "test"],
@ -159,7 +155,7 @@ impl cosmic::Application for Window {
"oops".into() "oops".into()
} }
fn style(&self) -> Option<cosmic::iced::theme::Style> { fn style(&self) -> Option<cosmic::iced_runtime::Appearance> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }
} }

View file

@ -8,7 +8,9 @@ default = ["wayland"]
wayland = ["libcosmic/wayland"] wayland = ["libcosmic/wayland"]
[dependencies] [dependencies]
env_logger = "0.11" tracing = "0.1.44"
tracing-subscriber = "0.3.22"
tracing-log = "0.2.0"
[dependencies.libcosmic] [dependencies.libcosmic]
path = "../../" path = "../../"
@ -18,8 +20,7 @@ features = [
"tokio", "tokio",
"xdg-portal", "xdg-portal",
"a11y", "a11y",
"wgpu",
"single-instance", "single-instance",
"surface-message", "surface-message",
"multi-window",
"wgpu",
] ]

View file

@ -54,9 +54,8 @@ impl widget::menu::Action for Action {
/// Runs application with these settings /// Runs application with these settings
#[rustfmt::skip] #[rustfmt::skip]
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
// tracing_subscriber::fmt::init();
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); // let _ = tracing_log::LogTracer::init();
let input = vec![ let input = vec![
(Page::Page1, "🖖 Hello from libcosmic.".into()), (Page::Page1, "🖖 Hello from libcosmic.".into()),
@ -67,7 +66,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let settings = Settings::default() let settings = Settings::default()
.size(Size::new(1024., 768.)); .size(Size::new(1024., 768.));
cosmic::app::run::<App>(settings, input).unwrap();
cosmic::app::run::<App>(settings, input)?;
Ok(()) Ok(())
} }
@ -82,7 +83,6 @@ pub enum Message {
Hi, Hi,
Hi2, Hi2,
Hi3, Hi3,
Tick,
} }
/// The [`App`] stores application-specific state. /// The [`App`] stores application-specific state.
@ -93,7 +93,6 @@ pub struct App {
input_2: String, input_2: String,
hidden: bool, hidden: bool,
keybinds: HashMap<KeyBind, Action>, keybinds: HashMap<KeyBind, Action>,
progress: f32,
} }
/// Implement [`cosmic::Application`] to integrate with COSMIC. /// Implement [`cosmic::Application`] to integrate with COSMIC.
@ -135,7 +134,6 @@ impl cosmic::Application for App {
input_2: String::new(), input_2: String::new(),
hidden: true, hidden: true,
keybinds: HashMap::new(), keybinds: HashMap::new(),
progress: 0.0,
}; };
let command = app.update_title(); let command = app.update_title();
@ -181,17 +179,10 @@ impl cosmic::Application for App {
Message::Hi3 => { Message::Hi3 => {
dbg!("hi 3"); dbg!("hi 3");
} }
Message::Tick => {
self.progress = (self.progress + 0.01) % 1.0;
}
} }
Task::none() Task::none()
} }
fn subscription(&self) -> iced::Subscription<Self::Message> {
iced::time::every(std::time::Duration::from_millis(64)).map(|_| Message::Tick)
}
/// Creates a view after each update. /// Creates a view after each update.
fn view(&self) -> Element<'_, Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
let page_content = self let page_content = self
@ -200,7 +191,7 @@ impl cosmic::Application for App {
.map_or("No page selected", String::as_str); .map_or("No page selected", String::as_str);
let centered = widget::container( let centered = widget::container(
widget::column::with_capacity(14) widget::column()
.push(widget::text::body(page_content)) .push(widget::text::body(page_content))
.push( .push(
widget::text_input::text_input("", &self.input_1) widget::text_input::text_input("", &self.input_1)
@ -222,47 +213,6 @@ impl cosmic::Application for App {
.on_input(Message::Input2) .on_input(Message::Input2)
.on_clear(Message::Ignore), .on_clear(Message::Ignore),
) )
.push(widget::progress_bar::circular::Circular::new().size(50.0))
.push(widget::progress_bar::circular::Circular::new().size(20.0))
.push(
widget::progress_bar::linear::Linear::new()
.girth(10.0)
.width(Length::Fill),
)
.push(
widget::progress_bar::circular::Circular::new()
.bar_height(10.0)
.size(50.0)
.progress(self.progress),
)
.push(
widget::progress_bar::linear::Linear::new()
.girth(10.0)
.progress(self.progress)
.width(Length::Fill),
)
.push(
widget::progress_bar::circular::Circular::new()
.size(50.0)
.progress(0.0),
)
.push(
widget::progress_bar::linear::Linear::new()
.girth(10.0)
.progress(0.0)
.width(Length::Fill),
)
.push(
widget::progress_bar::circular::Circular::new()
.size(50.0)
.progress(1.0),
)
.push(
widget::progress_bar::linear::Linear::new()
.girth(10.0)
.progress(1.0)
.width(Length::Fill),
)
.spacing(cosmic::theme::spacing().space_s) .spacing(cosmic::theme::spacing().space_s)
.width(Length::Fill) .width(Length::Fill)
.height(Length::Shrink) .height(Length::Shrink)

View file

@ -85,6 +85,8 @@ impl cosmic::Application for App {
/// Creates a view after each update. /// Creates a view after each update.
fn view(&self) -> Element<'_, Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
let mut content = cosmic::widget::column().spacing(12);
let calendar = cosmic::widget::calendar( let calendar = cosmic::widget::calendar(
&self.calendar_model, &self.calendar_model,
|date| Message::DateSelected(date), |date| Message::DateSelected(date),
@ -93,7 +95,9 @@ impl cosmic::Application for App {
Weekday::Sunday, Weekday::Sunday,
); );
let centered = cosmic::widget::container(calendar) content = content.push(calendar);
let centered = cosmic::widget::container(content)
.width(iced::Length::Fill) .width(iced::Length::Fill)
.height(iced::Length::Shrink) .height(iced::Length::Shrink)
.align_x(iced::Alignment::Center) .align_x(iced::Alignment::Center)

View file

@ -4,7 +4,7 @@
//! Application API example //! Application API example
use cosmic::app::{Core, Settings, Task}; use cosmic::app::{Core, Settings, Task};
use cosmic::iced::Size; use cosmic::iced_core::Size;
use cosmic::widget::menu; use cosmic::widget::menu;
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{executor, iced, ApplicationExt, Element};
use std::collections::HashMap; use std::collections::HashMap;

View file

@ -28,14 +28,13 @@ impl State {
column!( column!(
list_column().add(settings::item( list_column().add(settings::item(
"Bluetooth", "Bluetooth",
toggler(self.enabled).on_toggle(Message::Enable) toggler(None, self.enabled, Message::Enable)
)), )),
text("Now visible as \"TODO\", just kidding") text("Now visible as \"TODO\", just kidding")
) )
.spacing(8) .spacing(8)
.into(), .into(),
settings::section() settings::view_section("Devices")
.title("Devices")
.add(settings::item("No devices found", text(""))) .add(settings::item("No devices found", text("")))
.into(), .into(),
]) ])

View file

@ -258,13 +258,12 @@ impl State {
match self.tab_bar.active_data() { match self.tab_bar.active_data() {
None => panic!("no tab is active"), None => panic!("no tab is active"),
Some(DemoView::TabA) => settings::view_column(vec![ Some(DemoView::TabA) => settings::view_column(vec![
settings::section() settings::view_section("Debug")
.title("Debug")
.add(settings::item("Debug theme", choose_theme)) .add(settings::item("Debug theme", choose_theme))
.add(settings::item("Debug icon theme", choose_icon_theme)) .add(settings::item("Debug icon theme", choose_icon_theme))
.add(settings::item( .add(settings::item(
"Debug layout", "Debug layout",
toggler(window.debug).on_toggle(Message::Debug), toggler(None, window.debug, Message::Debug),
)) ))
.add(settings::item( .add(settings::item(
"Scaling Factor", "Scaling Factor",
@ -277,11 +276,10 @@ impl State {
.into(), .into(),
])) ]))
.into(), .into(),
settings::section() settings::view_section("Controls")
.title("Controls")
.add(settings::item( .add(settings::item(
"Toggler", "Toggler",
toggler(self.toggler_value).on_toggle(Message::TogglerToggled), toggler(None, self.toggler_value, Message::TogglerToggled),
)) ))
.add(settings::item( .add(settings::item(
"Pick List (TODO)", "Pick List (TODO)",
@ -301,13 +299,15 @@ impl State {
.add(settings::item( .add(settings::item(
"Progress", "Progress",
progress_bar(0.0..=100.0, self.slider_value) progress_bar(0.0..=100.0, self.slider_value)
.length(Length::Fixed(250.0)) .width(Length::Fixed(250.0))
.girth(Length::Fixed(4.0)), .height(Length::Fixed(4.0)),
)) ))
.add(settings::item_row(vec![checkbox(self.checkbox_value) .add(settings::item_row(vec![checkbox(
.label("Checkbox") "Checkbox",
.on_toggle(Message::CheckboxToggled) self.checkbox_value,
.into()])) Message::CheckboxToggled,
)
.into()]))
.add(settings::item( .add(settings::item(
format!( format!(
"Spin Button (Range {}:{})", "Spin Button (Range {}:{})",
@ -354,7 +354,8 @@ impl State {
.width(Length::Shrink) .width(Length::Shrink)
.on_activate(Message::MultiSelection) .on_activate(Message::MultiSelection)
.apply(container) .apply(container)
.center_x(Length::Fill) .center_x()
.width(Length::Fill)
.into(), .into(),
text("Vertical With Spacing").into(), text("Vertical With Spacing").into(),
cosmic::iced::widget::row(vec![ cosmic::iced::widget::row(vec![
@ -423,12 +424,13 @@ impl State {
]) ])
.padding(0) .padding(0)
.into(), .into(),
Some(DemoView::TabC) => settings::view_column(vec![settings::section() Some(DemoView::TabC) => {
.title("Tab C") settings::view_column(vec![settings::view_section("Tab C")
.add(text("Nothing here yet").width(Length::Fill)) .add(text("Nothing here yet").width(Length::Fill))
.into()]) .into()])
.padding(0) .padding(0)
.into(), .into()
}
}, },
container(text("Background container with some text").size(24)) container(text("Background container with some text").size(24))
.layer(cosmic_theme::Layer::Background) .layer(cosmic_theme::Layer::Background)

View file

@ -147,8 +147,7 @@ impl State {
fn view_desktop_options<'a>(&'a self, window: &'a Window) -> Element<'a, Message> { fn view_desktop_options<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
settings::view_column(vec![ settings::view_column(vec![
window.parent_page_button(DesktopPage::DesktopOptions), window.parent_page_button(DesktopPage::DesktopOptions),
settings::section() settings::view_section("Super Key Action")
.title("Super Key Action")
.add(settings::item("Launcher", horizontal_space(Length::Fill))) .add(settings::item("Launcher", horizontal_space(Length::Fill)))
.add(settings::item("Workspaces", horizontal_space(Length::Fill))) .add(settings::item("Workspaces", horizontal_space(Length::Fill)))
.add(settings::item( .add(settings::item(
@ -156,34 +155,38 @@ impl State {
horizontal_space(Length::Fill), horizontal_space(Length::Fill),
)) ))
.into(), .into(),
settings::section() settings::view_section("Hot Corner")
.title("Hot Corner")
.add(settings::item( .add(settings::item(
"Enable top-left hot corner for Workspaces", "Enable top-left hot corner for Workspaces",
toggler(self.top_left_hot_corner).on_toggle(Message::TopLeftHotCorner), toggler(None, self.top_left_hot_corner, Message::TopLeftHotCorner),
)) ))
.into(), .into(),
settings::section() settings::view_section("Top Panel")
.title("Top Panel")
.add(settings::item( .add(settings::item(
"Show Workspaces Button", "Show Workspaces Button",
toggler(self.show_workspaces_button).on_toggle(Message::ShowWorkspacesButton), toggler(
None,
self.show_workspaces_button,
Message::ShowWorkspacesButton,
),
)) ))
.add(settings::item( .add(settings::item(
"Show Applications Button", "Show Applications Button",
toggler(self.show_applications_button) toggler(
.on_toggle(Message::ShowApplicationsButton), None,
self.show_applications_button,
Message::ShowApplicationsButton,
),
)) ))
.into(), .into(),
settings::section() settings::view_section("Window Controls")
.title("Window Controls")
.add(settings::item( .add(settings::item(
"Show Minimize Button", "Show Minimize Button",
toggler(self.show_minimize_button).on_toggle(Message::ShowMinimizeButton), toggler(None, self.show_minimize_button, Message::ShowMinimizeButton),
)) ))
.add(settings::item( .add(settings::item(
"Show Maximize Button", "Show Maximize Button",
toggler(self.show_maximize_button).on_toggle(Message::ShowMaximizeButton), toggler(None, self.show_maximize_button, Message::ShowMaximizeButton),
)) ))
.into(), .into(),
]) ])
@ -242,12 +245,12 @@ impl State {
list_column() list_column()
.add(settings::item( .add(settings::item(
"Same background on all displays", "Same background on all displays",
toggler(self.same_background).on_toggle(Message::SameBackground), toggler(None, self.same_background, Message::SameBackground),
)) ))
.add(settings::item("Background fit", text("TODO"))) .add(settings::item("Background fit", text("TODO")))
.add(settings::item( .add(settings::item(
"Slideshow", "Slideshow",
toggler(self.slideshow).on_toggle(Message::Slideshow), toggler(None, self.slideshow, Message::Slideshow),
)) ))
.into(), .into(),
column(image_column).spacing(16).into(), column(image_column).spacing(16).into(),
@ -258,8 +261,7 @@ impl State {
fn view_desktop_workspaces<'a>(&'a self, window: &'a Window) -> Element<'a, Message> { fn view_desktop_workspaces<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
settings::view_column(vec![ settings::view_column(vec![
window.parent_page_button(DesktopPage::Wallpaper), window.parent_page_button(DesktopPage::Wallpaper),
settings::section() settings::view_section("Workspace Behavior")
.title("Workspace Behavior")
.add(settings::item( .add(settings::item(
"Dynamic workspaces", "Dynamic workspaces",
horizontal_space(Length::Fill), horizontal_space(Length::Fill),
@ -269,8 +271,7 @@ impl State {
horizontal_space(Length::Fill), horizontal_space(Length::Fill),
)) ))
.into(), .into(),
settings::section() settings::view_section("Multi-monitor Behavior")
.title("Multi-monitor Behavior")
.add(settings::item( .add(settings::item(
"Workspaces Span Displays", "Workspaces Span Displays",
horizontal_space(Length::Fill), horizontal_space(Length::Fill),

View file

@ -69,16 +69,14 @@ impl State {
list_column() list_column()
.add(settings::item("Device name", text("TODO"))) .add(settings::item("Device name", text("TODO")))
.into(), .into(),
settings::section() settings::view_section("Hardware")
.title("Hardware")
.add(settings::item("Hardware model", text("TODO"))) .add(settings::item("Hardware model", text("TODO")))
.add(settings::item("Memory", text("TODO"))) .add(settings::item("Memory", text("TODO")))
.add(settings::item("Processor", text("TODO"))) .add(settings::item("Processor", text("TODO")))
.add(settings::item("Graphics", text("TODO"))) .add(settings::item("Graphics", text("TODO")))
.add(settings::item("Disk Capacity", text("TODO"))) .add(settings::item("Disk Capacity", text("TODO")))
.into(), .into(),
settings::section() settings::view_section("Operating System")
.title("Operating System")
.add(settings::item("Operating system", text("TODO"))) .add(settings::item("Operating system", text("TODO")))
.add(settings::item( .add(settings::item(
"Operating system architecture", "Operating system architecture",
@ -87,8 +85,7 @@ impl State {
.add(settings::item("Desktop environment", text("TODO"))) .add(settings::item("Desktop environment", text("TODO")))
.add(settings::item("Windowing system", text("TODO"))) .add(settings::item("Windowing system", text("TODO")))
.into(), .into(),
settings::section() settings::view_section("Related settings")
.title("Related settings")
.add(settings::item("Get support", text("TODO"))) .add(settings::item("Get support", text("TODO")))
.into(), .into(),
]) ])

View file

@ -80,7 +80,7 @@ impl cosmic::Application for App {
/// Creates a view after each update. /// Creates a view after each update.
fn view(&self) -> Element<'_, Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
let mut content = cosmic::widget::column::with_capacity(self.images.len()).spacing(12); let mut content = cosmic::widget::column().spacing(12);
for (id, image) in self.images.iter().enumerate() { for (id, image) in self.images.iter().enumerate() {
content = content.push( content = content.push(

View file

@ -7,10 +7,10 @@ use std::collections::HashMap;
use std::{env, process}; use std::{env, process};
use cosmic::app::{Core, Settings, Task}; use cosmic::app::{Core, Settings, Task};
use cosmic::iced::alignment::{Horizontal, Vertical};
use cosmic::iced::keyboard::Key;
use cosmic::iced::window; use cosmic::iced::window;
use cosmic::iced::{Length, Size}; use cosmic::iced_core::alignment::{Horizontal, Vertical};
use cosmic::iced_core::keyboard::Key;
use cosmic::iced_core::{Length, Size};
use cosmic::widget::menu::action::MenuAction; use cosmic::widget::menu::action::MenuAction;
use cosmic::widget::menu::key_bind::KeyBind; use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::widget::menu::key_bind::Modifier; use cosmic::widget::menu::key_bind::Modifier;

View file

@ -2,9 +2,9 @@ use std::collections::HashMap;
use cosmic::{ use cosmic::{
app::Core, app::Core,
iced::core::{id, Alignment, Length, Point},
iced::widget::{column, container, scrollable, text},
iced::{self, event, window, Subscription}, iced::{self, event, window, Subscription},
iced_core::{id, Alignment, Length, Point},
iced_widget::{column, container, scrollable, text},
prelude::*, prelude::*,
widget::{button, header_bar}, widget::{button, header_bar},
}; };

View file

@ -6,7 +6,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use cosmic::app::{Core, Settings, Task}; use cosmic::app::{Core, Settings, Task};
use cosmic::iced::Size; use cosmic::iced_core::Size;
use cosmic::widget::{menu, nav_bar}; use cosmic::widget::{menu, nav_bar};
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{executor, iced, ApplicationExt, Element};

View file

@ -6,7 +6,7 @@
use apply::Apply; use apply::Apply;
use cosmic::app::{Core, Settings, Task}; use cosmic::app::{Core, Settings, Task};
use cosmic::dialog::file_chooser::{self, FileFilter}; use cosmic::dialog::file_chooser::{self, FileFilter};
use cosmic::iced::Length; use cosmic::iced_core::Length;
use cosmic::widget::button; use cosmic::widget::button;
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{executor, iced, ApplicationExt, Element};
use std::sync::Arc; use std::sync::Arc;
@ -207,7 +207,7 @@ impl cosmic::Application for App {
); );
content.push( content.push(
iced::widget::space::vertical() iced::widget::vertical_space()
.height(Length::Fixed(12.0)) .height(Length::Fixed(12.0))
.into(), .into(),
); );

View file

@ -64,7 +64,7 @@ impl cosmic::Application for App {
/// Creates a view after each update. /// Creates a view after each update.
fn view(&self) -> Element<'_, Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
widget::Row::new().into() widget::row().into()
} }
} }

View file

@ -7,7 +7,7 @@ use std::collections::HashMap;
use chrono::Datelike; use chrono::Datelike;
use cosmic::app::{Core, Settings, Task}; use cosmic::app::{Core, Settings, Task};
use cosmic::iced::Size; use cosmic::iced_core::Size;
use cosmic::prelude::*; use cosmic::prelude::*;
use cosmic::widget::table; use cosmic::widget::table;
use cosmic::widget::{self, nav_bar}; use cosmic::widget::{self, nav_bar};

View file

@ -99,9 +99,7 @@ impl cosmic::Application for App {
let inline = cosmic::widget::inline_input("", &self.input).on_input(Message::Input); let inline = cosmic::widget::inline_input("", &self.input).on_input(Message::Input);
let column = cosmic::widget::column::with_capacity(2) let column = cosmic::widget::column().push(editable).push(inline);
.push(editable)
.push(inline);
let centered = cosmic::widget::container(column.width(200)) let centered = cosmic::widget::container(column.width(200))
.width(iced::Length::Fill) .width(iced::Length::Fill)

View file

@ -6,7 +6,7 @@ links = Links
developers = Entwickler(innen) developers = Entwickler(innen)
designers = Designer(innen) designers = Designer(innen)
artists = Künstler(innen) artists = Künstler(innen)
translators = Übersetzer(innen) translators = Übersetzer*innen
documenters = Dokumentierer(innen) documenters = Dokumentierer(innen)
# Calendar # Calendar
january = Januar { $year } january = Januar { $year }
@ -21,8 +21,8 @@ september = September { $year }
october = Oktober { $year } october = Oktober { $year }
november = November { $year } november = November { $year }
december = Dezember { $year } december = Dezember { $year }
monday = Montag monday = Mo
tuesday = Dienstag tuesday = Di
wednesday = Mittwoch wednesday = Mittwoch
thursday = Donnerstag thursday = Donnerstag
friday = Freitag friday = Freitag
@ -33,5 +33,3 @@ thu = Do
fri = Fr fri = Fr
sat = Sa sat = Sa
sun = So sun = So
tue = Di
mon = Mo

View file

View file

@ -1,33 +0,0 @@
close = Mdel
license = Turagt
links = Iseɣwan
developers = Ineflayen
artists = Inaẓuren
translators = Imsuqlen
january = Yennayer { $year }
february = Fuṛar { $year }
march = Meɣres { $year }
april = Yebrir { $year }
may = Mayyu { $year }
june = Yunyu { $year }
july = Yulyu { $year }
august = Ɣuct { $year }
september = Ctembeṛ { $year }
october = Tubeṛ { $year }
november = Wambeṛ { $year }
december = Dujembeṛ { $year }
documenters = Imeskaren
monday = Arim
mon = Ari
tuesday = Aram
tue = Ara
wednesday = Ahad
wed = Aha
thursday = Amhad
thu = Amh
friday = Sem
fri = Sm
saturday = Sed
sat = Sd
sunday = Acer
sun = Ace

View file

@ -2,33 +2,26 @@ february = { $year }년 2월
close = 닫기 close = 닫기
documenters = 문서 작성자 documenters = 문서 작성자
november = { $year }년 11월 november = { $year }년 11월
friday = 금요일 friday = 금
tuesday = 화요일 tuesday = 화
may = { $year }년 5월 may = { $year }년 5월
wednesday = 수요일 wednesday = 수
april = { $year }년 4월 april = { $year }년 4월
monday = 월요일 monday = 월
translators = 번역가 translators = 번역가
artists = 아티스트 artists = 아티스트
license = 라이선스 license = 라이선스
december = { $year }년 12월 december = { $year }년 12월
sunday = 일요일 sunday = 일
links = 링크 links = 링크
march = { $year }년 3월 march = { $year }년 3월
june = { $year }년 6월 june = { $year }년 6월
saturday = 토요일 saturday = 토
august = { $year }년 8월 august = { $year }년 8월
developers = 개발자 developers = 개발자
july = { $year }년 7월 july = { $year }년 7월
thursday = 목요일 thursday = 목
september = { $year }년 9월 september = { $year }년 9월
designers = 디자이너 designers = 디자이너
october = { $year }년 10월 october = { $year }년 10월
january = { $year }년 1월 january = { $year }년 1월
mon = 월
tue = 화
wed = 수
thu = 목
fri = 금
sat = 토
sun = 일

View file

@ -2,33 +2,26 @@ february = Vasaris { $year }
close = Uždaryti close = Uždaryti
documenters = Dokumentuotojai documenters = Dokumentuotojai
november = Lapkritis { $year } november = Lapkritis { $year }
friday = Penktadienis friday = Penk
tuesday = Antradienis tuesday = Antr
may = Gegužė { $year } may = Gegužė { $year }
wednesday = Trečiadienis wednesday = Treč
april = Balandis { $year } april = Balandis { $year }
monday = Pirmadienis monday = Pirm
translators = Vertėjai translators = Vertėjai
artists = Menininkai artists = Menininkai
license = Licencija license = Licencija
december = Gruodis { $year } december = Gruodis { $year }
sunday = Sekmadienis sunday = Sekm
links = Nuorodos links = Nuorodos
march = Kovas { $year } march = Kovas { $year }
june = Birželis { $year } june = Birželis { $year }
saturday = Šeštadienis saturday = Šešt
august = Rugpjūtis { $year } august = Rugpjūtis { $year }
developers = Kūrėjai developers = Kūrėjai
july = Liepa { $year } july = Liepa { $year }
thursday = Ketvirtadienis thursday = Ketv
september = Rugsėjis { $year } september = Rugsėjis { $year }
designers = Dizaineriai designers = Dizaineriai
october = Spalis { $year } october = Spalis { $year }
january = Sausis { $year } january = Sausis { $year }
mon = Pirm
tue = Antr
wed = Treč
thu = Ketv
fri = Penkt
sat = Šešt
sun = Sekm

View file

@ -1,34 +0,0 @@
close = ਬੰਦ ਕਰੋ
license = ਲਸੰਸ
links = ਲਿੰਕ
developers = ਡਿਵੈਲਪਰ
designers = ਡਿਜ਼ਾਇਨਰ
artists = ਕਲਾਕਾਰ
translators = ਅਨੁਵਾਦਕ
documenters = ਦਸਤਾਵੇਜ਼ ਤਿਆਰ ਕਰਤਾ
january = ਜਨਵਰੀ { $year }
february = ਫਰਵਰੀ { $year }
march = ਮਾਰਚ { $year }
april = ਅਪਰੈਲ { $year }
may = ਮਈ { $year }
june = ਜੂਨ { $year }
july = ਜੁਲਾਈ { $year }
august = ਅਗਸਤ { $year }
september = ਸਤੰਬਰ { $year }
october = ਅਕਤੂਬਰ { $year }
november = ਨਵੰਬਰ { $year }
december = ਦਸੰਬਰ { $year }
monday = ਸੋਮਵਾਰ
mon = ਸੋਮ
tuesday = ਮੰਗਲਵਾਰ
tue = ਮੰਗਲ
wednesday = ਬੁੱਧਵਾਰ
wed = ਬੁੱਧ
thursday = ਵੀਰਵਾਰ
thu = ਵੀਰ
friday = ਸ਼ੁੱਕਰਵਾਰ
fri = ਸ਼ੁੱਕਰ
saturday = ਸ਼ਨਿੱਚਰਵਾਰ
sat = ਸ਼ਨਿੱਚਰ
sunday = ਐਤਵਾਰ
sun = ਐਤ

View file

@ -1,34 +0,0 @@
close = 關閉
developers = 開發人員
designers = 設計人員
artists = 美編設計
translators = 翻譯人員
documenters = 文件編輯人員
january = { $year } 年 1 月
monday = 星期一
tuesday = 星期二
wednesday = 星期三
thursday = 星期四
friday = 星期五
saturday = 星期六
sunday = 星期日
mon = 週一
tue = 週二
wed = 週三
thu = 週四
fri = 週五
sat = 週六
sun = 週日
license = 授權
links = 連結
february = { $year } 年 2 月
march = { $year } 年 3 月
april = { $year } 年 4 月
may = { $year } 年 5 月
june = { $year } 年 6 月
july = { $year } 年 7 月
august = { $year } 年 8 月
september = { $year } 年 9 月
october = { $year } 年 10 月
november = { $year } 年 11 月
december = { $year } 年 12 月

2
iced

@ -1 +1 @@
Subproject commit 78caabba7ef91cd1030da6f70b41d266704ffece Subproject commit d36e4df47f2e277fafcd3505229d53438c7f128d

View file

@ -1,51 +0,0 @@
use std::time::{Duration, Instant};
/// A simple linear interpolation calculation function.
/// p = `percent_complete` in decimal form
#[must_use]
pub fn lerp(start: f32, end: f32, p: f32) -> f32 {
(1.0 - p) * start + p * end
}
/// A fast smooth interpolation calculation function.
/// p = `percent_complete` in decimal form
#[must_use]
pub fn slerp(start: f32, end: f32, p: f32) -> f32 {
let t = smootherstep(p);
(1.0 - t) * start + t * end
}
/// utility function which maps a value [0, 1] -> [0, 1] using the smootherstep function
pub fn smootherstep(t: f32) -> f32 {
(6.0 * t.powi(5) - 15.0 * t.powi(4) + 10.0 * t.powi(3)).clamp(0.0, 1.0)
}
#[derive(Default, Debug)]
pub struct State {
pub last_change: Option<Instant>,
}
impl State {
pub fn changed(&mut self, dur: Duration) {
let t = self.t(dur, false);
let diff = dur.mul_f32(t.abs());
let now = Instant::now();
self.last_change = Some(now.checked_sub(diff).unwrap_or(now));
}
pub fn anim_done(&mut self, dur: Duration) {
if self
.last_change
.is_some_and(|t| Instant::now().duration_since(t) > dur)
{
self.last_change = None;
}
}
pub fn t(&self, dur: Duration, forward: bool) -> f32 {
let res = self.last_change.map_or(1., |t| {
Instant::now().duration_since(t).as_millis() as f32 / dur.as_millis() as f32
});
if forward { res } else { 1. - res }
}
}

View file

@ -5,9 +5,11 @@ use crate::surface;
use crate::theme::Theme; use crate::theme::Theme;
use crate::widget::nav_bar; use crate::widget::nav_bar;
use crate::{config::CosmicTk, keyboard_nav}; use crate::{config::CosmicTk, keyboard_nav};
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState};
use cosmic_theme::ThemeMode; use cosmic_theme::ThemeMode;
#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
use iced::Application as IcedApplication;
/// A message managed internally by COSMIC. /// A message managed internally by COSMIC.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -69,10 +71,10 @@ pub enum Action {
/// Updates the tracked window geometry. /// Updates the tracked window geometry.
WindowResize(iced::window::Id, f32, f32), WindowResize(iced::window::Id, f32, f32),
/// Tracks updates to window state. /// Tracks updates to window state.
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
WindowState(iced::window::Id, WindowState), WindowState(iced::window::Id, WindowState),
/// Capabilities the window manager supports /// Capabilities the window manager supports
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
WmCapabilities(iced::window::Id, WindowManagerCapabilities), WmCapabilities(iced::window::Id, WindowManagerCapabilities),
#[cfg(feature = "xdg-portal")] #[cfg(feature = "xdg-portal")]
DesktopSettings(crate::theme::portal::Desktop), DesktopSettings(crate::theme::portal::Desktop),

View file

@ -8,16 +8,16 @@ use std::sync::Arc;
use super::{Action, Application, ApplicationExt, Subscription}; use super::{Action, Application, ApplicationExt, Subscription};
use crate::theme::{THEME, Theme, ThemeType}; use crate::theme::{THEME, Theme, ThemeType};
use crate::{Core, Element, keyboard_nav}; use crate::{Core, Element, keyboard_nav};
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState};
use cosmic_theme::ThemeMode; use cosmic_theme::ThemeMode;
#[cfg(not(any(feature = "multi-window", feature = "wayland", target_os = "linux")))] #[cfg(not(any(feature = "multi-window", feature = "wayland")))]
use iced::Application as IcedApplication; use iced::Application as IcedApplication;
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
use iced::event::wayland; use iced::event::wayland;
use iced::{Task, theme, window}; use iced::{Task, window};
use iced_futures::event::listen_with; use iced_futures::event::listen_with;
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
use iced_winit::SurfaceIdWrapper; use iced_winit::SurfaceIdWrapper;
use palette::color_difference::EuclideanDistance; use palette::color_difference::EuclideanDistance;
@ -49,8 +49,8 @@ pub fn windowing_system() -> Option<WindowingSystem> {
WINDOWING_SYSTEM.get().copied() WINDOWING_SYSTEM.get().copied()
} }
fn init_windowing_system<M>(handle: window::raw_window_handle::WindowHandle) -> crate::Action<M> { fn init_windowing_system<M>(handle: raw_window_handle::WindowHandle) -> crate::Action<M> {
let raw = handle.as_ref(); let raw: &raw_window_handle::RawWindowHandle = handle.as_ref();
let system = match raw { let system = match raw {
window::raw_window_handle::RawWindowHandle::UiKit(_) => WindowingSystem::UiKit, window::raw_window_handle::RawWindowHandle::UiKit(_) => WindowingSystem::UiKit,
window::raw_window_handle::RawWindowHandle::AppKit(_) => WindowingSystem::AppKit, window::raw_window_handle::RawWindowHandle::AppKit(_) => WindowingSystem::AppKit,
@ -83,7 +83,7 @@ fn init_windowing_system<M>(handle: window::raw_window_handle::WindowHandle) ->
#[derive(Default)] #[derive(Default)]
pub struct Cosmic<App: Application> { pub struct Cosmic<App: Application> {
pub app: App, pub app: App,
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
pub surface_views: HashMap< pub surface_views: HashMap<
window::Id, window::Id,
( (
@ -138,7 +138,7 @@ where
) -> iced::Task<crate::Action<T::Message>> { ) -> iced::Task<crate::Action<T::Message>> {
#[cfg(feature = "surface-message")] #[cfg(feature = "surface-message")]
match _surface_message { match _surface_message {
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
crate::surface::Action::AppSubsurface(settings, view) => { crate::surface::Action::AppSubsurface(settings, view) => {
let Some(settings) = std::sync::Arc::try_unwrap(settings) let Some(settings) = std::sync::Arc::try_unwrap(settings)
.ok() .ok()
@ -168,7 +168,7 @@ where
iced_winit::commands::subsurface::get_subsurface(settings(&mut self.app)) iced_winit::commands::subsurface::get_subsurface(settings(&mut self.app))
} }
} }
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
crate::surface::Action::Subsurface(settings, view) => { crate::surface::Action::Subsurface(settings, view) => {
let Some(settings) = std::sync::Arc::try_unwrap(settings) let Some(settings) = std::sync::Arc::try_unwrap(settings)
.ok() .ok()
@ -196,7 +196,7 @@ where
iced_winit::commands::subsurface::get_subsurface(settings()) iced_winit::commands::subsurface::get_subsurface(settings())
} }
} }
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
crate::surface::Action::AppPopup(settings, view) => { crate::surface::Action::AppPopup(settings, view) => {
let Some(settings) = std::sync::Arc::try_unwrap(settings) let Some(settings) = std::sync::Arc::try_unwrap(settings)
.ok() .ok()
@ -225,26 +225,15 @@ where
iced_winit::commands::popup::get_popup(settings(&mut self.app)) iced_winit::commands::popup::get_popup(settings(&mut self.app))
} }
} }
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
crate::surface::Action::DestroyPopup(id) => { crate::surface::Action::DestroyPopup(id) => {
iced_winit::commands::popup::destroy_popup(id) iced_winit::commands::popup::destroy_popup(id)
} }
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
crate::surface::Action::DestroyTooltipPopup => {
#[cfg(feature = "applet")]
{
iced_winit::commands::popup::destroy_popup(*crate::applet::TOOLTIP_WINDOW_ID)
}
#[cfg(not(feature = "applet"))]
{
Task::none()
}
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
crate::surface::Action::DestroySubsurface(id) => { crate::surface::Action::DestroySubsurface(id) => {
iced_winit::commands::subsurface::destroy_subsurface(id) iced_winit::commands::subsurface::destroy_subsurface(id)
} }
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
crate::surface::Action::DestroyWindow(id) => iced::window::close(id), crate::surface::Action::DestroyWindow(id) => iced::window::close(id),
crate::surface::Action::ResponsiveMenuBar { crate::surface::Action::ResponsiveMenuBar {
menu_bar, menu_bar,
@ -255,7 +244,7 @@ where
core.menu_bars.insert(menu_bar, (limits, size)); core.menu_bars.insert(menu_bar, (limits, size));
iced::Task::none() iced::Task::none()
} }
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
crate::surface::Action::Popup(settings, view) => { crate::surface::Action::Popup(settings, view) => {
let Some(settings) = std::sync::Arc::try_unwrap(settings) let Some(settings) = std::sync::Arc::try_unwrap(settings)
.ok() .ok()
@ -282,7 +271,7 @@ where
iced_winit::commands::popup::get_popup(settings()) iced_winit::commands::popup::get_popup(settings())
} }
} }
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
crate::surface::Action::AppWindow(id, settings, view) => { crate::surface::Action::AppWindow(id, settings, view) => {
let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| { let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| {
s.downcast::<Box<dyn Fn(&mut T) -> iced::window::Settings + Send + Sync>>() s.downcast::<Box<dyn Fn(&mut T) -> iced::window::Settings + Send + Sync>>()
@ -321,7 +310,7 @@ where
.discard() .discard()
} }
} }
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
crate::surface::Action::Window(id, settings, view) => { crate::surface::Action::Window(id, settings, view) => {
let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| { let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| {
s.downcast::<Box<dyn Fn() -> iced::window::Settings + Send + Sync>>() s.downcast::<Box<dyn Fn() -> iced::window::Settings + Send + Sync>>()
@ -408,16 +397,15 @@ where
f64::from(self.app.core().scale_factor()) f64::from(self.app.core().scale_factor())
} }
pub fn style(&self, theme: &Theme) -> theme::Style { pub fn style(&self, theme: &Theme) -> iced_runtime::Appearance {
if let Some(style) = self.app.style() { if let Some(style) = self.app.style() {
style style
} else if self.app.core().window.is_maximized { } else if self.app.core().window.is_maximized {
let theme = THEME.lock().unwrap(); let theme = THEME.lock().unwrap();
crate::style::iced::application::style(theme.borrow()) crate::style::iced::application::appearance(theme.borrow())
} else { } else {
let theme = THEME.lock().unwrap(); let theme = THEME.lock().unwrap();
iced_runtime::Appearance {
theme::Style {
background_color: iced_core::Color::TRANSPARENT, background_color: iced_core::Color::TRANSPARENT,
icon_color: theme.cosmic().on_bg_color().into(), icon_color: theme.cosmic().on_bg_color().into(),
text_color: theme.cosmic().on_bg_color().into(), text_color: theme.cosmic().on_bg_color().into(),
@ -441,7 +429,7 @@ where
} }
iced::Event::Window(window::Event::Focused) => return Some(Action::Focus(id)), iced::Event::Window(window::Event::Focused) => return Some(Action::Focus(id)),
iced::Event::Window(window::Event::Unfocused) => return Some(Action::Unfocus(id)), iced::Event::Window(window::Event::Unfocused) => return Some(Action::Unfocus(id)),
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => { iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => {
match event { match event {
wayland::Event::Popup(wayland::PopupEvent::Done, _, id) wayland::Event::Popup(wayland::PopupEvent::Done, _, id)
@ -454,7 +442,7 @@ where
) => { ) => {
return Some(Action::SuggestedBounds(b)); return Some(Action::SuggestedBounds(b));
} }
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
wayland::Event::Window(iced::event::wayland::WindowEvent::WindowState( wayland::Event::Window(iced::event::wayland::WindowEvent::WindowState(
s, s,
)) => { )) => {
@ -571,7 +559,7 @@ where
#[cfg(feature = "multi-window")] #[cfg(feature = "multi-window")]
pub fn view(&self, id: window::Id) -> Element<'_, crate::Action<T::Message>> { pub fn view(&self, id: window::Id) -> Element<'_, crate::Action<T::Message>> {
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
if let Some((_, _, v)) = self.surface_views.get(&id) { if let Some((_, _, v)) = self.surface_views.get(&id) {
return v(&self.app); return v(&self.app);
} }
@ -622,7 +610,7 @@ impl<T: Application> Cosmic<T> {
fn cosmic_update(&mut self, message: Action) -> iced::Task<crate::Action<T::Message>> { fn cosmic_update(&mut self, message: Action) -> iced::Task<crate::Action<T::Message>> {
match message { match message {
Action::WindowMaximized(id, maximized) => { Action::WindowMaximized(id, maximized) => {
#[cfg(not(all(feature = "wayland", target_os = "linux")))] #[cfg(not(feature = "wayland"))]
if self if self
.app .app
.core() .core()
@ -647,12 +635,12 @@ impl<T: Application> Cosmic<T> {
self.app.on_window_resize(id, width, height); self.app.on_window_resize(id, width, height);
//TODO: more efficient test of maximized (winit has no event for maximize if set by the OS) //TODO: more efficient test of maximized (winit has no event for maximize if set by the OS)
return iced::window::is_maximized(id).map(move |maximized| { return iced::window::get_maximized(id).map(move |maximized| {
crate::Action::Cosmic(Action::WindowMaximized(id, maximized)) crate::Action::Cosmic(Action::WindowMaximized(id, maximized))
}); });
} }
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
Action::WindowState(id, state) => { Action::WindowState(id, state) => {
if self if self
.app .app
@ -704,7 +692,7 @@ impl<T: Application> Cosmic<T> {
} }
} }
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
Action::WmCapabilities(id, capabilities) => { Action::WmCapabilities(id, capabilities) => {
if self if self
.app .app
@ -723,10 +711,10 @@ impl<T: Application> Cosmic<T> {
Action::KeyboardNav(message) => match message { Action::KeyboardNav(message) => match message {
keyboard_nav::Action::FocusNext => { keyboard_nav::Action::FocusNext => {
return iced::widget::operation::focus_next().map(crate::Action::Cosmic); return iced::widget::focus_next().map(crate::Action::Cosmic);
} }
keyboard_nav::Action::FocusPrevious => { keyboard_nav::Action::FocusPrevious => {
return iced::widget::operation::focus_previous().map(crate::Action::Cosmic); return iced::widget::focus_previous().map(crate::Action::Cosmic);
} }
keyboard_nav::Action::Escape => return self.app.on_escape(), keyboard_nav::Action::Escape => return self.app.on_escape(),
keyboard_nav::Action::Search => return self.app.on_search(), keyboard_nav::Action::Search => return self.app.on_search(),
@ -811,7 +799,7 @@ impl<T: Application> Cosmic<T> {
new_theme.theme_type.prefer_dark(prefer_dark); new_theme.theme_type.prefer_dark(prefer_dark);
cosmic_theme.set_theme(new_theme.theme_type); cosmic_theme.set_theme(new_theme.theme_type);
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
if self.app.core().sync_window_border_radii_to_theme() { if self.app.core().sync_window_border_radii_to_theme() {
use iced_runtime::platform_specific::wayland::CornerRadius; use iced_runtime::platform_specific::wayland::CornerRadius;
use iced_winit::platform_specific::commands::corner_radius::corner_radius; use iced_winit::platform_specific::commands::corner_radius::corner_radius;
@ -957,7 +945,7 @@ impl<T: Application> Cosmic<T> {
// Only apply update if the theme is set to load a system theme // Only apply update if the theme is set to load a system theme
if let ThemeType::System { .. } = cosmic_theme.theme_type { if let ThemeType::System { .. } = cosmic_theme.theme_type {
cosmic_theme.set_theme(new_theme.theme_type); cosmic_theme.set_theme(new_theme.theme_type);
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
if self.app.core().sync_window_border_radii_to_theme() { if self.app.core().sync_window_border_radii_to_theme() {
use iced_runtime::platform_specific::wayland::CornerRadius; use iced_runtime::platform_specific::wayland::CornerRadius;
use iced_winit::platform_specific::commands::corner_radius::corner_radius; use iced_winit::platform_specific::commands::corner_radius::corner_radius;
@ -1051,7 +1039,7 @@ impl<T: Application> Cosmic<T> {
// Unminimize window before requesting to activate it. // Unminimize window before requesting to activate it.
let mut task = iced_runtime::window::minimize(id, false); let mut task = iced_runtime::window::minimize(id, false);
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
{ {
task = task.chain( task = task.chain(
iced_winit::platform_specific::commands::activation::activate( iced_winit::platform_specific::commands::activation::activate(
@ -1062,7 +1050,7 @@ impl<T: Application> Cosmic<T> {
) )
} }
#[cfg(not(all(feature = "wayland", target_os = "linux")))] #[cfg(not(feature = "wayland"))]
{ {
task = task.chain(iced_runtime::window::gain_focus(id)); task = task.chain(iced_runtime::window::gain_focus(id));
} }
@ -1079,7 +1067,7 @@ impl<T: Application> Cosmic<T> {
*v == 0 *v == 0
}) { }) {
self.opened_surfaces.remove(&id); self.opened_surfaces.remove(&id);
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
self.surface_views.remove(&id); self.surface_views.remove(&id);
self.tracked_windows.remove(&id); self.tracked_windows.remove(&id);
} }
@ -1201,8 +1189,7 @@ impl<T: Application> Cosmic<T> {
#[cfg(all( #[cfg(all(
feature = "wayland", feature = "wayland",
feature = "multi-window", feature = "multi-window",
feature = "surface-message", feature = "surface-message"
target_os = "linux"
))] ))]
if let Some(( if let Some((
parent, parent,
@ -1247,7 +1234,7 @@ impl<T: Application> Cosmic<T> {
core.applet.suggested_bounds = b; core.applet.suggested_bounds = b;
} }
Action::Opened(id) => { Action::Opened(id) => {
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
if self.app.core().sync_window_border_radii_to_theme() { if self.app.core().sync_window_border_radii_to_theme() {
use iced_runtime::platform_specific::wayland::CornerRadius; use iced_runtime::platform_specific::wayland::CornerRadius;
use iced_winit::platform_specific::commands::corner_radius::corner_radius; use iced_winit::platform_specific::commands::corner_radius::corner_radius;
@ -1296,14 +1283,14 @@ impl<App: Application> Cosmic<App> {
pub fn new(app: App) -> Self { pub fn new(app: App) -> Self {
Self { Self {
app, app,
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
surface_views: HashMap::new(), surface_views: HashMap::new(),
tracked_windows: HashSet::new(), tracked_windows: HashSet::new(),
opened_surfaces: HashMap::new(), opened_surfaces: HashMap::new(),
} }
} }
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
/// Create a subsurface /// Create a subsurface
pub fn get_subsurface( pub fn get_subsurface(
&mut self, &mut self,
@ -1326,7 +1313,7 @@ impl<App: Application> Cosmic<App> {
get_subsurface(settings) get_subsurface(settings)
} }
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
/// Create a subsurface /// Create a subsurface
pub fn get_popup( pub fn get_popup(
&mut self, &mut self,
@ -1348,7 +1335,7 @@ impl<App: Application> Cosmic<App> {
get_popup(settings) get_popup(settings)
} }
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
/// Create a window surface /// Create a window surface
pub fn get_window( pub fn get_window(
&mut self, &mut self,

View file

@ -11,8 +11,9 @@ pub use action::Action;
use cosmic_config::CosmicConfigEntry; use cosmic_config::CosmicConfigEntry;
pub mod context_drawer; pub mod context_drawer;
pub use context_drawer::{ContextDrawer, context_drawer}; pub use context_drawer::{ContextDrawer, context_drawer};
use iced::application::BootFn;
pub mod cosmic; pub mod cosmic;
#[cfg(all(feature = "winit", feature = "multi-window"))]
pub(crate) mod multi_window;
pub mod settings; pub mod settings;
pub type Task<M> = iced::Task<crate::Action<M>>; pub type Task<M> = iced::Task<crate::Action<M>>;
@ -20,13 +21,12 @@ pub type Task<M> = iced::Task<crate::Action<M>>;
pub use crate::Core; pub use crate::Core;
use crate::prelude::*; use crate::prelude::*;
use crate::theme::THEME; use crate::theme::THEME;
use crate::widget::{container, id_container, menu, nav_bar, popover, space}; use crate::widget::{container, horizontal_space, id_container, menu, nav_bar, popover};
use apply::Apply; use apply::Apply;
use iced::window;
use iced::{Length, Subscription}; use iced::{Length, Subscription};
use iced::{theme, window};
pub use settings::Settings; pub use settings::Settings;
use std::borrow::Cow; use std::borrow::Cow;
use std::{cell::RefCell, rc::Rc};
#[cold] #[cold]
pub(crate) fn iced_settings<App: Application>( pub(crate) fn iced_settings<App: Application>(
@ -82,7 +82,7 @@ pub(crate) fn iced_settings<App: Application>(
window_settings.min_size = Some(min_size); window_settings.min_size = Some(min_size);
} }
let max_size = settings.size_limits.max(); let max_size = settings.size_limits.max();
if max_size != iced::Size::INFINITE { if max_size != iced::Size::INFINITY {
window_settings.max_size = Some(max_size); window_settings.max_size = Some(max_size);
} }
@ -90,99 +90,51 @@ pub(crate) fn iced_settings<App: Application>(
(iced, (core, flags), window_settings) (iced, (core, flags), window_settings)
} }
pub(crate) struct BootDataInner<A: crate::app::Application> {
pub flags: A::Flags,
pub core: Core,
pub settings: window::Settings,
}
pub(crate) struct BootData<A: crate::app::Application>(pub Rc<RefCell<Option<BootDataInner<A>>>>);
impl<A: crate::app::Application> BootFn<cosmic::Cosmic<A>, crate::Action<A::Message>>
for BootData<A>
{
fn boot(&self) -> (cosmic::Cosmic<A>, iced::Task<crate::Action<A::Message>>) {
let mut data = self.0.borrow_mut();
let mut data = data.take().unwrap();
let mut tasks = Vec::new();
#[cfg(feature = "multi-window")]
if data.core.main_window_id().is_some() {
let window_task = iced_runtime::task::oneshot(|channel| {
iced_runtime::Action::Window(iced_runtime::window::Action::Open(
window::Id::RESERVED,
data.settings,
channel,
))
});
data.core.set_main_window_id(Some(window::Id::RESERVED));
tasks.push(window_task.discard());
}
let (a, t) = cosmic::Cosmic::<A>::init((data.core, data.flags));
tasks.push(t);
(a, Task::batch(tasks))
}
}
/// Launch a COSMIC application with the given [`Settings`]. /// Launch a COSMIC application with the given [`Settings`].
/// ///
/// # Errors /// # Errors
/// ///
/// Returns error on application failure. /// Returns error on application failure.
pub fn run<App: Application>(settings: Settings, flags: App::Flags) -> iced::Result { pub fn run<App: Application>(settings: Settings, flags: App::Flags) -> iced::Result {
#[cfg(feature = "desktop")]
image_extras::register();
#[cfg(all(target_env = "gnu", not(target_os = "windows")))] #[cfg(all(target_env = "gnu", not(target_os = "windows")))]
if let Some(threshold) = settings.default_mmap_threshold { if let Some(threshold) = settings.default_mmap_threshold {
crate::malloc::limit_mmap_threshold(threshold); crate::malloc::limit_mmap_threshold(threshold);
} }
let default_font = settings.default_font; let default_font = settings.default_font;
let (settings, (mut core, flags), window_settings) = iced_settings::<App>(settings, flags); let (settings, mut flags, window_settings) = iced_settings::<App>(settings, flags);
#[cfg(not(feature = "multi-window"))] #[cfg(not(feature = "multi-window"))]
{ {
core.main_window = Some(iced::window::Id::RESERVED); flags.0.main_window = Some(iced::window::Id::RESERVED);
iced::application( iced::application(
BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> { cosmic::Cosmic::title,
flags,
core,
settings: window_settings.clone(),
})))),
cosmic::Cosmic::update, cosmic::Cosmic::update,
cosmic::Cosmic::view, cosmic::Cosmic::view,
) )
.subscription(cosmic::Cosmic::subscription) .subscription(cosmic::Cosmic::subscription)
.title(cosmic::Cosmic::title)
.style(cosmic::Cosmic::style) .style(cosmic::Cosmic::style)
.theme(cosmic::Cosmic::theme) .theme(cosmic::Cosmic::theme)
.window_size((500.0, 800.0)) .window_size((500.0, 800.0))
.settings(settings) .settings(settings)
.window(window_settings) .window(window_settings)
.run() .run_with(move || cosmic::Cosmic::<App>::init(flags))
} }
#[cfg(feature = "multi-window")] #[cfg(feature = "multi-window")]
{ {
let no_main_window = core.main_window.is_none(); let mut app = multi_window::multi_window::<_, _, _, _, App::Executor>(
if no_main_window { cosmic::Cosmic::title,
// app = app.window(window_settings);
core.main_window = Some(iced_core::window::Id::RESERVED);
}
let app = iced::daemon(
BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> {
flags,
core,
settings: window_settings,
})))),
cosmic::Cosmic::update, cosmic::Cosmic::update,
cosmic::Cosmic::view, cosmic::Cosmic::view,
); );
if flags.0.main_window.is_none() {
app = app.window(window_settings);
flags.0.main_window = Some(iced_core::window::Id::RESERVED);
}
app.subscription(cosmic::Cosmic::subscription) app.subscription(cosmic::Cosmic::subscription)
.title(cosmic::Cosmic::title)
.style(cosmic::Cosmic::style) .style(cosmic::Cosmic::style)
.theme(cosmic::Cosmic::theme) .theme(cosmic::Cosmic::theme)
.settings(settings) .settings(settings)
.run() .run_with(move || cosmic::Cosmic::<App>::init(flags))
} }
} }
@ -197,9 +149,6 @@ where
App::Flags: CosmicFlags, App::Flags: CosmicFlags,
App::Message: Clone + std::fmt::Debug + Send + 'static, App::Message: Clone + std::fmt::Debug + Send + 'static,
{ {
#[cfg(feature = "desktop")]
image_extras::register();
use std::collections::HashMap; use std::collections::HashMap;
let activation_token = std::env::var("XDG_ACTIVATION_TOKEN").ok(); let activation_token = std::env::var("XDG_ACTIVATION_TOKEN").ok();
@ -255,17 +204,13 @@ where
tracing::info!("Another instance is running"); tracing::info!("Another instance is running");
Ok(()) Ok(())
} else { } else {
let (settings, (mut core, flags), window_settings) = iced_settings::<App>(settings, flags); let (settings, mut flags, window_settings) = iced_settings::<App>(settings, flags);
core.single_instance = true; flags.0.single_instance = true;
#[cfg(not(feature = "multi-window"))] #[cfg(not(feature = "multi-window"))]
{ {
iced::application( iced::application(
BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> { cosmic::Cosmic::title,
flags,
core,
settings: window_settings.clone(),
})))),
cosmic::Cosmic::update, cosmic::Cosmic::update,
cosmic::Cosmic::view, cosmic::Cosmic::view,
) )
@ -275,31 +220,24 @@ where
.window_size((500.0, 800.0)) .window_size((500.0, 800.0))
.settings(settings) .settings(settings)
.window(window_settings) .window(window_settings)
.run() .run_with(move || cosmic::Cosmic::<App>::init(flags))
} }
#[cfg(feature = "multi-window")] #[cfg(feature = "multi-window")]
{ {
let no_main_window = core.main_window.is_none(); let mut app = multi_window::multi_window::<_, _, _, _, App::Executor>(
if no_main_window { cosmic::Cosmic::title,
// app = app.window(window_settings);
core.main_window = Some(iced_core::window::Id::RESERVED);
}
let mut app = iced::daemon(
BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> {
flags,
core,
settings: window_settings,
})))),
cosmic::Cosmic::update, cosmic::Cosmic::update,
cosmic::Cosmic::view, cosmic::Cosmic::view,
); );
if flags.0.main_window.is_none() {
app = app.window(window_settings);
flags.0.main_window = Some(iced_core::window::Id::RESERVED);
}
app.subscription(cosmic::Cosmic::subscription) app.subscription(cosmic::Cosmic::subscription)
.style(cosmic::Cosmic::style) .style(cosmic::Cosmic::style)
.title(cosmic::Cosmic::title)
.theme(cosmic::Cosmic::theme) .theme(cosmic::Cosmic::theme)
.settings(settings) .settings(settings)
.run() .run_with(move || cosmic::Cosmic::<App>::init(flags))
} }
} }
} }
@ -391,8 +329,9 @@ where
.on_context(|id| crate::Action::Cosmic(Action::NavBarContext(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(self.core().nav_bar_context()))
.into_container() .into_container()
// XXX both must be shrink to avoid flex layout from ignoring it
.width(iced::Length::Shrink) .width(iced::Length::Shrink)
.height(iced::Length::Fill); .height(iced::Length::Shrink);
if !self.core().is_condensed() { if !self.core().is_condensed() {
nav = nav.max_width(280); nav = nav.max_width(280);
@ -489,7 +428,7 @@ where
} }
/// Overrides the default style for applications /// Overrides the default style for applications
fn style(&self) -> Option<theme::Style> { fn style(&self) -> Option<iced_runtime::Appearance> {
None None
} }
@ -725,17 +664,16 @@ impl<App: Application> ApplicationExt for App {
[0, 0, 0, 0] [0, 0, 0, 0]
}) })
.into(), .into(),
); )
} else { } else {
//TODO: this element is added to workaround state issues //TODO: this element is added to workaround state issues
widgets.push(space::horizontal().width(Length::Shrink).into()); widgets.push(horizontal_space().width(Length::Shrink).into());
} }
} }
} }
widgets widgets
}); });
let content_col = crate::widget::column::with_capacity(2) let content_col = crate::widget::column::with_capacity(2)
.push(content_row) .push(content_row)
.push_maybe(self.footer().map(|footer| { .push_maybe(self.footer().map(|footer| {
@ -748,6 +686,7 @@ impl<App: Application> ApplicationExt for App {
})); }));
let content: Element<_> = if content_container { let content: Element<_> = if content_container {
content_col content_col
.apply(container)
.width(iced::Length::Fill) .width(iced::Length::Fill)
.height(iced::Length::Fill) .height(iced::Length::Fill)
.apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_content_container"))) .apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_content_container")))
@ -777,7 +716,8 @@ impl<App: Application> ApplicationExt for App {
.title(&core.window.header_title) .title(&core.window.header_title)
.on_drag(crate::Action::Cosmic(Action::Drag)) .on_drag(crate::Action::Cosmic(Action::Drag))
.on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu)) .on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu))
.on_double_click(crate::Action::Cosmic(Action::Maximize)); .on_double_click(crate::Action::Cosmic(Action::Maximize))
.is_condensed(is_condensed);
if self.nav_model().is_some() { if self.nav_model().is_some() {
let toggle = crate::widget::nav_bar_toggle() let toggle = crate::widget::nav_bar_toggle()

244
src/app/multi_window.rs Normal file
View file

@ -0,0 +1,244 @@
// Copyright 2024 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
//! Create and run daemons that run in the background.
//! Copied from iced 0.13, but adds optional initial window
use iced::application;
use iced::window;
use iced::{
self, Program,
program::{self, with_style, with_subscription, with_theme, with_title},
runtime::{Appearance, DefaultStyle},
};
use iced::{Element, Result, Settings, Subscription, Task};
use std::marker::PhantomData;
pub(crate) struct Instance<State, Message, Theme, Renderer, Update, View, Executor> {
update: Update,
view: View,
_state: PhantomData<State>,
_message: PhantomData<Message>,
_theme: PhantomData<Theme>,
_renderer: PhantomData<Renderer>,
_executor: PhantomData<Executor>,
}
/// Creates an iced [`MultiWindow`] given its title, update, and view logic.
pub fn multi_window<State, Message, Theme, Renderer, Executor>(
title: impl Title<State>,
update: impl application::Update<State, Message>,
view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>,
) -> MultiWindow<impl Program<State = State, Message = Message, Theme = Theme>>
where
State: 'static,
Message: Send + std::fmt::Debug + 'static,
Theme: Default + DefaultStyle,
Renderer: program::Renderer,
Executor: iced::Executor,
{
use std::marker::PhantomData;
impl<State, Message, Theme, Renderer, Update, View, Executor> Program
for Instance<State, Message, Theme, Renderer, Update, View, Executor>
where
Message: Send + std::fmt::Debug + 'static,
Theme: Default + DefaultStyle,
Renderer: program::Renderer,
Update: application::Update<State, Message>,
View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
Executor: iced::Executor,
{
type State = State;
type Message = Message;
type Theme = Theme;
type Renderer = Renderer;
type Executor = Executor;
fn update(&self, state: &mut Self::State, message: Self::Message) -> Task<Self::Message> {
self.update.update(state, message).into()
}
fn view<'a>(
&self,
state: &'a Self::State,
window: window::Id,
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.view.view(state, window).into()
}
}
MultiWindow {
raw: Instance {
update,
view,
_state: PhantomData,
_message: PhantomData,
_theme: PhantomData,
_renderer: PhantomData,
_executor: PhantomData::<Executor>,
},
settings: Settings::default(),
window: None,
}
.title(title)
}
/// The underlying definition and configuration of an iced daemon.
///
/// You can use this API to create and run iced applications
/// step by step—without coupling your logic to a trait
/// or a specific type.
///
/// You can create a [`MultiWindow`] with the [`daemon`] helper.
#[derive(Debug)]
pub struct MultiWindow<P: Program> {
raw: P,
settings: Settings,
window: Option<window::Settings>,
}
impl<P: Program> MultiWindow<P> {
#[cfg(any(feature = "winit", feature = "wayland"))]
/// Runs the [`MultiWindow`].
///
/// The state of the [`MultiWindow`] must implement [`Default`].
/// If your state does not implement [`Default`], use [`run_with`]
/// instead.
///
/// [`run_with`]: Self::run_with
pub fn run(self) -> Result
where
Self: 'static,
P::State: Default,
{
self.raw.run(self.settings, self.window)
}
#[cfg(any(feature = "winit", feature = "wayland"))]
/// Runs the [`MultiWindow`] with a closure that creates the initial state.
pub fn run_with<I>(self, initialize: I) -> Result
where
Self: 'static,
I: FnOnce() -> (P::State, Task<P::Message>) + 'static,
{
self.raw.run_with(self.settings, self.window, initialize)
}
/// Sets the [`Settings`] that will be used to run the [`MultiWindow`].
pub fn settings(self, settings: Settings) -> Self {
Self { settings, ..self }
}
/// Sets the [`Title`] of the [`MultiWindow`].
pub(crate) fn title(
self,
title: impl Title<P::State>,
) -> MultiWindow<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
MultiWindow {
raw: with_title(self.raw, move |state, window| title.title(state, window)),
settings: self.settings,
window: self.window,
}
}
/// Sets the subscription logic of the [`MultiWindow`].
pub fn subscription(
self,
f: impl Fn(&P::State) -> Subscription<P::Message>,
) -> MultiWindow<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
MultiWindow {
raw: with_subscription(self.raw, f),
settings: self.settings,
window: self.window,
}
}
/// Sets the theme logic of the [`MultiWindow`].
pub fn theme(
self,
f: impl Fn(&P::State, window::Id) -> P::Theme,
) -> MultiWindow<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
MultiWindow {
raw: with_theme(self.raw, f),
settings: self.settings,
window: self.window,
}
}
/// Sets the style logic of the [`MultiWindow`].
pub fn style(
self,
f: impl Fn(&P::State, &P::Theme) -> Appearance,
) -> MultiWindow<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
MultiWindow {
raw: with_style(self.raw, f),
settings: self.settings,
window: self.window,
}
}
/// Sets the window settings of the [`MultiWindow`].
pub fn window(self, window: window::Settings) -> Self {
Self {
raw: self.raw,
settings: self.settings,
window: Some(window),
}
}
}
/// The title logic of some [`MultiWindow`].
///
/// This trait is implemented both for `&static str` and
/// any closure `Fn(&State, window::Id) -> String`.
///
/// This trait allows the [`daemon`] builder to take any of them.
pub trait Title<State> {
/// Produces the title of the [`MultiWindow`].
fn title(&self, state: &State, window: window::Id) -> String;
}
impl<State> Title<State> for &'static str {
fn title(&self, _state: &State, _window: window::Id) -> String {
(*self).to_string()
}
}
impl<T, State> Title<State> for T
where
T: Fn(&State, window::Id) -> String,
{
fn title(&self, state: &State, window: window::Id) -> String {
self(state, window)
}
}
/// The view logic of some [`MultiWindow`].
///
/// This trait allows the [`daemon`] builder to take any closure that
/// returns any `Into<Element<'_, Message>>`.
pub trait View<'a, State, Message, Theme, Renderer> {
/// Produces the widget of the [`MultiWindow`].
fn view(
&self,
state: &'a State,
window: window::Id,
) -> impl Into<Element<'a, Message, Theme, Renderer>>;
}
impl<'a, T, State, Message, Theme, Renderer, Widget> View<'a, State, Message, Theme, Renderer> for T
where
T: Fn(&'a State, window::Id) -> Widget,
State: 'static,
Widget: Into<Element<'a, Message, Theme, Renderer>>,
{
fn view(
&self,
state: &'a State,
window: window::Id,
) -> impl Into<Element<'a, Message, Theme, Renderer>> {
self(state, window)
}
}

View file

@ -16,7 +16,7 @@ pub struct Settings {
pub(crate) antialiasing: bool, pub(crate) antialiasing: bool,
/// Autosize the window to fit its contents /// Autosize the window to fit its contents
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
pub(crate) autosize: bool, pub(crate) autosize: bool,
/// Set the application to not create a main window /// Set the application to not create a main window
@ -80,7 +80,7 @@ impl Default for Settings {
fn default() -> Self { fn default() -> Self {
Self { Self {
antialiasing: true, antialiasing: true,
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
autosize: false, autosize: false,
no_main_window: false, no_main_window: false,
client_decorations: true, client_decorations: true,

View file

@ -217,7 +217,7 @@ where
} }
fn layout( fn layout(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
renderer: &Renderer, renderer: &Renderer,
limits: &layout::Limits, limits: &layout::Limits,
@ -233,26 +233,25 @@ where
self.padding, self.padding,
self.spacing, self.spacing,
self.align, self.align,
&mut self.children, &self.children,
&mut tree.children, &mut tree.children,
) )
} }
fn operate( fn operate(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn Operation, operation: &mut dyn Operation,
) { ) {
operation.container(None, layout.bounds()); operation.container(None, layout.bounds(), &mut |operation| {
operation.traverse(&mut |operation| {
self.children self.children
.iter_mut() .iter()
.zip(&mut tree.children) .zip(&mut tree.children)
.zip(layout.children()) .zip(layout.children())
.for_each(|((child, state), c_layout)| { .for_each(|((child, state), c_layout)| {
child.as_widget_mut().operate( child.as_widget().operate(
state, state,
c_layout.with_virtual_offset(layout.virtual_offset()), c_layout.with_virtual_offset(layout.virtual_offset()),
renderer, renderer,
@ -262,17 +261,17 @@ where
}); });
} }
fn update( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &Renderer, renderer: &Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) { ) -> event::Status {
let my_state = tree.state.downcast_mut::<State>(); let my_state = tree.state.downcast_mut::<State>();
if let Some(hovered) = my_state.hovered { if let Some(hovered) = my_state.hovered {
@ -286,7 +285,7 @@ where
e, e,
mouse::Event::CursorLeft | mouse::Event::ButtonReleased { .. } mouse::Event::CursorLeft | mouse::Event::ButtonReleased { .. }
) { ) {
return self.children[hovered].as_widget_mut().update( return self.children[hovered].as_widget_mut().on_event(
&mut tree.children[hovered], &mut tree.children[hovered],
event, event,
child_layout.with_virtual_offset(layout.virtual_offset()), child_layout.with_virtual_offset(layout.virtual_offset()),
@ -303,7 +302,7 @@ where
iced::core::touch::Event::FingerLifted { .. } iced::core::touch::Event::FingerLifted { .. }
| iced::core::touch::Event::FingerLost { .. } | iced::core::touch::Event::FingerLost { .. }
) { ) {
return self.children[hovered].as_widget_mut().update( return self.children[hovered].as_widget_mut().on_event(
&mut tree.children[hovered], &mut tree.children[hovered],
event, event,
child_layout.with_virtual_offset(layout.virtual_offset()), child_layout.with_virtual_offset(layout.virtual_offset()),
@ -320,49 +319,49 @@ where
} }
} }
for (((i, child), state), c_layout) in self self.children
.children
.iter_mut() .iter_mut()
.enumerate() .enumerate()
.zip(&mut tree.children) .zip(&mut tree.children)
.zip(layout.children()) .zip(layout.children())
{ .map(|(((i, child), state), c_layout)| {
let mut cursor_virtual = cursor; let mut cursor_virtual = cursor;
if matches!( if matches!(
event, event,
Event::Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered) Event::Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered)
| Event::Touch( | Event::Touch(
iced_core::touch::Event::FingerMoved { .. } iced_core::touch::Event::FingerMoved { .. }
| iced_core::touch::Event::FingerPressed { .. } | iced_core::touch::Event::FingerPressed { .. }
) )
) && cursor.is_over(c_layout.bounds()) ) && cursor.is_over(c_layout.bounds())
{ {
my_state.hovered = Some(i); my_state.hovered = Some(i);
return child.as_widget_mut().update( return child.as_widget_mut().on_event(
state,
event.clone(),
c_layout.with_virtual_offset(layout.virtual_offset()),
cursor_virtual,
renderer,
clipboard,
shell,
viewport,
);
} else if my_state.hovered.is_some_and(|h| i != h) {
cursor_virtual = mouse::Cursor::Unavailable;
}
child.as_widget_mut().on_event(
state, state,
&event, event.clone(),
c_layout.with_virtual_offset(layout.virtual_offset()), c_layout.with_virtual_offset(layout.virtual_offset()),
cursor_virtual, cursor_virtual,
renderer, renderer,
clipboard, clipboard,
shell, shell,
viewport, viewport,
); )
} else if my_state.hovered.is_some_and(|h| i != h) { })
cursor_virtual = mouse::Cursor::Unavailable; .fold(event::Status::Ignored, event::Status::merge)
}
child.as_widget_mut().update(
state,
&event,
c_layout.with_virtual_offset(layout.virtual_offset()),
cursor_virtual,
renderer,
clipboard,
shell,
viewport,
);
}
} }
fn mouse_interaction( fn mouse_interaction(
@ -437,19 +436,11 @@ where
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'b>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
viewport: &Rectangle,
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
overlay::from_children( overlay::from_children(&mut self.children, tree, layout, renderer, translation)
&mut self.children,
tree,
layout,
renderer,
viewport,
translation,
)
} }
#[cfg(feature = "a11y")] #[cfg(feature = "a11y")]

View file

@ -1,36 +1,34 @@
#[cfg(feature = "applet-token")] #[cfg(feature = "applet-token")]
pub mod token; pub mod token;
use crate::app::{BootData, BootDataInner, cosmic}; use crate::app::cosmic;
use crate::{ use crate::{
Application, Element, Renderer, Application, Element, Renderer,
app::iced_settings, app::iced_settings,
cctk::sctk, cctk::sctk,
iced::{
self, Color, Length, Limits, Rectangle,
alignment::{Alignment, Horizontal, Vertical},
widget::Container,
window,
},
iced_widget,
theme::{self, Button, THEME, system_dark, system_light}, theme::{self, Button, THEME, system_dark, system_light},
widget::{ widget::{
self, self,
autosize::{self, Autosize, autosize}, autosize::{self, Autosize, autosize},
column::Column, column::Column,
layer_container, horizontal_space, layer_container,
row::Row, row::Row,
space::horizontal, vertical_space,
space::vertical,
}, },
}; };
pub use cosmic_panel_config; pub use cosmic_panel_config;
use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize}; use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize};
use iced::{
self, Color, Length, Limits, Rectangle,
alignment::{Alignment, Horizontal, Vertical},
widget::Container,
window,
};
use iced_core::{Padding, Shadow}; use iced_core::{Padding, Shadow};
use iced_runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner};
use iced_widget::Text; use iced_widget::Text;
use iced_widget::runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner};
use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity}; use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity};
use std::cell::RefCell;
use std::{borrow::Cow, num::NonZeroU32, rc::Rc, sync::LazyLock, time::Duration}; use std::{borrow::Cow, num::NonZeroU32, rc::Rc, sync::LazyLock, time::Duration};
use tracing::info; use tracing::info;
@ -42,7 +40,7 @@ static AUTOSIZE_ID: LazyLock<iced::id::Id> =
static AUTOSIZE_MAIN_ID: LazyLock<iced::id::Id> = static AUTOSIZE_MAIN_ID: LazyLock<iced::id::Id> =
LazyLock::new(|| iced::id::Id::new("cosmic-applet-autosize-main")); LazyLock::new(|| iced::id::Id::new("cosmic-applet-autosize-main"));
static TOOLTIP_ID: LazyLock<crate::widget::Id> = LazyLock::new(|| iced::id::Id::new("subsurface")); static TOOLTIP_ID: LazyLock<crate::widget::Id> = LazyLock::new(|| iced::id::Id::new("subsurface"));
pub(crate) static TOOLTIP_WINDOW_ID: LazyLock<window::Id> = LazyLock::new(window::Id::unique); static TOOLTIP_WINDOW_ID: LazyLock<window::Id> = LazyLock::new(window::Id::unique);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Context { pub struct Context {
@ -226,7 +224,7 @@ impl Context {
let symbolic = icon.symbolic; let symbolic = icon.symbolic;
let icon = widget::icon(icon) let icon = widget::icon(icon)
.class(if symbolic { .class(if symbolic {
theme::Svg::Custom(Rc::new(|theme| iced_widget::svg::Style { theme::Svg::Custom(Rc::new(|theme| crate::iced_widget::svg::Style {
color: Some(theme.cosmic().background.on.into()), color: Some(theme.cosmic().background.on.into()),
})) }))
} else { } else {
@ -388,10 +386,10 @@ impl Context {
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
icon_color: Some(cosmic.background.on.into()), icon_color: Some(cosmic.background.on.into()),
snap: true,
} }
}), }),
) )
.width(Length::Shrink)
.height(Length::Shrink) .height(Length::Shrink)
.align_x(horizontal_align) .align_x(horizontal_align)
.align_y(vertical_align), .align_y(vertical_align),
@ -573,33 +571,26 @@ pub fn run<App: Application>(flags: App::Flags) -> iced::Result {
// TODO make multi-window not mandatory // TODO make multi-window not mandatory
let no_main_window = core.main_window.is_none(); let mut app = super::app::multi_window::multi_window::<_, _, _, _, App::Executor>(
if no_main_window { cosmic::Cosmic::title,
// TODO still apply window settings?
// window_settings = window_settings.clone();
core.main_window = Some(iced_core::window::Id::RESERVED);
}
let mut app = iced::daemon(
BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> {
flags,
core,
settings: window_settings,
})))),
cosmic::Cosmic::update, cosmic::Cosmic::update,
cosmic::Cosmic::view, cosmic::Cosmic::view,
); );
if core.main_window.is_none() {
app = app.window(window_settings.clone());
core.main_window = Some(iced_core::window::Id::RESERVED);
}
app.subscription(cosmic::Cosmic::subscription) app.subscription(cosmic::Cosmic::subscription)
.style(cosmic::Cosmic::style) .style(cosmic::Cosmic::style)
.theme(cosmic::Cosmic::theme) .theme(cosmic::Cosmic::theme)
.settings(iced_settings) .settings(iced_settings)
.run() .run_with(move || cosmic::Cosmic::<App>::init((core, flags)))
} }
#[must_use] #[must_use]
pub fn style() -> iced::theme::Style { pub fn style() -> iced_runtime::Appearance {
let theme = crate::theme::THEME.lock().unwrap(); let theme = crate::theme::THEME.lock().unwrap();
iced::theme::Style { iced_runtime::Appearance {
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0), background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
text_color: theme.cosmic().on_bg_color().into(), text_color: theme.cosmic().on_bg_color().into(),
icon_color: theme.cosmic().on_bg_color().into(), icon_color: theme.cosmic().on_bg_color().into(),

View file

@ -208,7 +208,7 @@ where
} }
fn layout( fn layout(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
renderer: &Renderer, renderer: &Renderer,
limits: &layout::Limits, limits: &layout::Limits,
@ -222,26 +222,25 @@ where
self.padding, self.padding,
self.spacing, self.spacing,
self.align, self.align,
&mut self.children, &self.children,
&mut tree.children, &mut tree.children,
) )
} }
fn operate( fn operate(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn Operation, operation: &mut dyn Operation,
) { ) {
operation.container(None, layout.bounds()); operation.container(None, layout.bounds(), &mut |operation| {
operation.traverse(&mut |operation| {
self.children self.children
.iter_mut() .iter()
.zip(&mut tree.children) .zip(&mut tree.children)
.zip(layout.children()) .zip(layout.children())
.for_each(|((child, state), c_layout)| { .for_each(|((child, state), c_layout)| {
child.as_widget_mut().operate( child.as_widget().operate(
state, state,
c_layout.with_virtual_offset(layout.virtual_offset()), c_layout.with_virtual_offset(layout.virtual_offset()),
renderer, renderer,
@ -251,17 +250,17 @@ where
}); });
} }
fn update( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &Renderer, renderer: &Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) { ) -> event::Status {
let my_state = tree.state.downcast_mut::<State>(); let my_state = tree.state.downcast_mut::<State>();
if let Some(hovered) = my_state.hovered { if let Some(hovered) = my_state.hovered {
@ -275,7 +274,7 @@ where
e, e,
mouse::Event::CursorLeft | mouse::Event::ButtonReleased { .. } mouse::Event::CursorLeft | mouse::Event::ButtonReleased { .. }
) { ) {
return self.children[hovered].as_widget_mut().update( return self.children[hovered].as_widget_mut().on_event(
&mut tree.children[hovered], &mut tree.children[hovered],
event, event,
child_layout.with_virtual_offset(layout.virtual_offset()), child_layout.with_virtual_offset(layout.virtual_offset()),
@ -292,7 +291,7 @@ where
iced::core::touch::Event::FingerLifted { .. } iced::core::touch::Event::FingerLifted { .. }
| iced::core::touch::Event::FingerLost { .. } | iced::core::touch::Event::FingerLost { .. }
) { ) {
return self.children[hovered].as_widget_mut().update( return self.children[hovered].as_widget_mut().on_event(
&mut tree.children[hovered], &mut tree.children[hovered],
event, event,
child_layout.with_virtual_offset(layout.virtual_offset()), child_layout.with_virtual_offset(layout.virtual_offset()),
@ -309,50 +308,50 @@ where
} }
} }
for (((i, child), state), c_layout) in self self.children
.children
.iter_mut() .iter_mut()
.enumerate() .enumerate()
.zip(&mut tree.children) .zip(&mut tree.children)
.zip(layout.children()) .zip(layout.children())
{ .map(|(((i, child), state), c_layout)| {
let mut cursor_virtual = cursor; let mut cursor_virtual = cursor;
if matches!( if matches!(
event, event,
Event::Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered) Event::Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered)
| Event::Touch( | Event::Touch(
iced_core::touch::Event::FingerMoved { .. } iced_core::touch::Event::FingerMoved { .. }
| iced_core::touch::Event::FingerPressed { .. } | iced_core::touch::Event::FingerPressed { .. }
) )
) && cursor.is_over(c_layout.bounds()) ) && cursor.is_over(c_layout.bounds())
{ {
my_state.hovered = Some(i); my_state.hovered = Some(i);
return child.as_widget_mut().update( return child.as_widget_mut().on_event(
state,
event.clone(),
c_layout.with_virtual_offset(layout.virtual_offset()),
cursor_virtual,
renderer,
clipboard,
shell,
viewport,
);
} else if my_state.hovered.is_some_and(|h| i != h) {
cursor_virtual = mouse::Cursor::Unavailable;
}
child.as_widget_mut().on_event(
state, state,
&event, event.clone(),
c_layout.with_virtual_offset(layout.virtual_offset()), c_layout.with_virtual_offset(layout.virtual_offset()),
cursor_virtual, cursor_virtual,
renderer, renderer,
clipboard, clipboard,
shell, shell,
viewport, viewport,
); )
} else if my_state.hovered.is_some_and(|h| i != h) { })
cursor_virtual = mouse::Cursor::Unavailable; .fold(event::Status::Ignored, event::Status::merge)
}
child.as_widget_mut().update(
state,
&event,
c_layout.with_virtual_offset(layout.virtual_offset()),
cursor_virtual,
renderer,
clipboard,
shell,
viewport,
);
}
} }
fn mouse_interaction( fn mouse_interaction(
@ -427,19 +426,11 @@ where
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'b>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
viewport: &Rectangle,
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
overlay::from_children( overlay::from_children(&mut self.children, tree, layout, renderer, translation)
&mut self.children,
tree,
layout,
renderer,
viewport,
translation,
)
} }
#[cfg(feature = "a11y")] #[cfg(feature = "a11y")]

View file

@ -1,11 +1,11 @@
use crate::iced; use crate::iced;
use crate::iced_futures::futures;
use cctk::sctk::reexports::calloop; use cctk::sctk::reexports::calloop;
use futures::{ use futures::{
SinkExt, StreamExt, SinkExt, StreamExt,
channel::mpsc::{UnboundedReceiver, unbounded}, channel::mpsc::{UnboundedReceiver, unbounded},
}; };
use iced::Subscription; use iced::Subscription;
use iced_futures::futures;
use iced_futures::stream; use iced_futures::stream;
use std::{fmt::Debug, hash::Hash, thread::JoinHandle}; use std::{fmt::Debug, hash::Hash, thread::JoinHandle};
@ -14,15 +14,16 @@ use super::wayland_handler::wayland_handler;
pub fn activation_token_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>( pub fn activation_token_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
id: I, id: I,
) -> iced::Subscription<TokenUpdate> { ) -> iced::Subscription<TokenUpdate> {
Subscription::run_with(id, |_| { Subscription::run_with_id(
id,
stream::channel(50, move |mut output| async move { stream::channel(50, move |mut output| async move {
let mut state = State::Ready; let mut state = State::Ready;
loop { loop {
state = start_listening(state, &mut output).await; state = start_listening(state, &mut output).await;
} }
}) }),
}) )
} }
pub enum State { pub enum State {

View file

@ -39,7 +39,7 @@ pub fn set_theme<M: Send + 'static>(theme: crate::Theme) -> iced::Task<crate::Ac
/// Sets the window mode to windowed. /// Sets the window mode to windowed.
pub fn set_windowed<M>(id: window::Id) -> iced::Task<crate::Action<M>> { pub fn set_windowed<M>(id: window::Id) -> iced::Task<crate::Action<M>> {
iced_runtime::window::set_mode(id, window::Mode::Windowed) iced_runtime::window::change_mode(id, window::Mode::Windowed)
} }
/// Toggles the windows' maximize state. /// Toggles the windows' maximize state.

View file

@ -99,7 +99,7 @@ pub struct Core {
pub(crate) menu_bars: HashMap<crate::widget::Id, (Limits, Size)>, pub(crate) menu_bars: HashMap<crate::widget::Id, (Limits, Size)>,
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
pub(crate) sync_window_border_radii_to_theme: bool, pub(crate) sync_window_border_radii_to_theme: bool,
} }
@ -159,7 +159,7 @@ impl Default for Core {
main_window: None, main_window: None,
exit_on_main_window_closed: true, exit_on_main_window_closed: true,
menu_bars: HashMap::new(), menu_bars: HashMap::new(),
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
sync_window_border_radii_to_theme: true, sync_window_border_radii_to_theme: true,
} }
} }
@ -493,12 +493,12 @@ impl Core {
} }
// TODO should we emit tasks setting the corner radius or unsetting it if this is changed? // TODO should we emit tasks setting the corner radius or unsetting it if this is changed?
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
pub fn set_sync_window_border_radii_to_theme(&mut self, sync: bool) { pub fn set_sync_window_border_radii_to_theme(&mut self, sync: bool) {
self.sync_window_border_radii_to_theme = sync; self.sync_window_border_radii_to_theme = sync;
} }
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
pub fn sync_window_border_radii_to_theme(&self) -> bool { pub fn sync_window_border_radii_to_theme(&self) -> bool {
self.sync_window_border_radii_to_theme self.sync_window_border_radii_to_theme
} }

View file

@ -16,80 +16,75 @@ use {
#[cold] #[cold]
pub fn subscription<App: ApplicationExt>() -> Subscription<crate::Action<App::Message>> { pub fn subscription<App: ApplicationExt>() -> Subscription<crate::Action<App::Message>> {
use iced_futures::futures::StreamExt; use iced_futures::futures::StreamExt;
iced_futures::Subscription::run_with(TypeId::of::<DbusActivation>(), |_| { iced_futures::Subscription::run_with_id(
iced::stream::channel( TypeId::of::<DbusActivation>(),
10, iced::stream::channel(10, move |mut output| async move {
move |mut output: Sender<crate::Action<App::Message>>| async move { let mut single_instance: DbusActivation = DbusActivation::new();
let mut single_instance: DbusActivation = DbusActivation::new(); let mut rx = single_instance.rx();
let mut rx = single_instance.rx(); if let Ok(builder) = zbus::connection::Builder::session() {
if let Ok(builder) = zbus::connection::Builder::session() { let path: String = format!("/{}", App::APP_ID.replace('.', "/"));
let path: String = format!("/{}", App::APP_ID.replace('.', "/")); if let Ok(conn) = builder.build().await {
if let Ok(conn) = builder.build().await { // XXX Setup done this way seems to be more reliable.
// XXX Setup done this way seems to be more reliable. //
// // the docs for serve_at seem to imply it will replace the
// the docs for serve_at seem to imply it will replace the // existing interface at the requested path, but it doesn't
// existing interface at the requested path, but it doesn't // seem to work that way all the time. The docs for
// seem to work that way all the time. The docs for // object_server().at() imply it won't replace the existing
// object_server().at() imply it won't replace the existing // interface.
// interface. //
// // request_name is used either way, with the builder or
// request_name is used either way, with the builder or // with the connection, but it must be done after the
// with the connection, but it must be done after the // object server is setup.
// object server is setup. if conn.object_server().at(path, single_instance).await != Ok(true) {
if conn.object_server().at(path, single_instance).await != Ok(true) { tracing::error!("Failed to serve dbus");
tracing::error!("Failed to serve dbus"); std::process::exit(1);
std::process::exit(1); }
} if conn.request_name(App::APP_ID).await.is_err() {
if conn.request_name(App::APP_ID).await.is_err() { tracing::error!("Failed to serve dbus");
tracing::error!("Failed to serve dbus"); std::process::exit(1);
std::process::exit(1); }
}
output output
.send(crate::Action::Cosmic(crate::app::Action::DbusConnection( .send(crate::Action::Cosmic(crate::app::Action::DbusConnection(
conn.clone(), conn.clone(),
))) )))
.await; .await;
#[cfg(feature = "smol")] #[cfg(feature = "smol")]
let handle = { let handle = {
std::thread::spawn(move || { std::thread::spawn(move || {
let conn_clone = _conn.clone(); let conn_clone = _conn.clone();
zbus::block_on(async move { zbus::block_on(async move {
loop { loop {
conn_clone.executor().tick().await; conn_clone.executor().tick().await;
}
})
})
};
while let Some(mut msg) = rx.next().await {
if let Some(token) = msg.activation_token.take() {
if let Err(err) = output
.send(crate::Action::Cosmic(crate::app::Action::Activate(
token,
)))
.await
{
tracing::error!(?err, "Failed to send message");
} }
} })
if let Err(err) = output.send(crate::Action::DbusActivation(msg)).await })
};
while let Some(mut msg) = rx.next().await {
if let Some(token) = msg.activation_token.take() {
if let Err(err) = output
.send(crate::Action::Cosmic(crate::app::Action::Activate(token)))
.await
{ {
tracing::error!(?err, "Failed to send message"); tracing::error!(?err, "Failed to send message");
} }
} }
if let Err(err) = output.send(crate::Action::DbusActivation(msg)).await {
tracing::error!(?err, "Failed to send message");
}
} }
} else {
tracing::warn!("Failed to connect to dbus for single instance");
} }
} else {
tracing::warn!("Failed to connect to dbus for single instance");
}
loop { loop {
iced::futures::pending!(); iced::futures::pending!();
} }
}, }),
) )
})
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -416,6 +416,7 @@ fn match_exec_basename(
}; };
let basename_lower = basename.to_ascii_lowercase(); let basename_lower = basename.to_ascii_lowercase();
if normalized if normalized
.iter() .iter()
.any(|candidate| candidate == &basename_lower) .any(|candidate| candidate == &basename_lower)
@ -439,7 +440,8 @@ fn fallback_entry(context: &DesktopLookupContext<'_>) -> fde::DesktopEntry {
let name = context let name = context
.title .title
.as_ref() .as_ref()
.map_or_else(|| context.app_id.to_string(), |title| title.to_string()); .map(|title| title.to_string())
.unwrap_or_else(|| context.app_id.to_string());
entry.add_desktop_entry("Name".to_string(), name); entry.add_desktop_entry("Name".to_string(), name);
entry entry
} }
@ -456,9 +458,7 @@ fn proton_or_wine_fallback(
) -> Option<fde::DesktopEntry> { ) -> Option<fde::DesktopEntry> {
let app_id = context.app_id.as_ref(); let app_id = context.app_id.as_ref();
let is_proton_game = app_id == "steam_app_default"; let is_proton_game = app_id == "steam_app_default";
let is_wine_entry = std::path::Path::new(app_id) let is_wine_entry = app_id.ends_with(".exe");
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("exe"));
if !is_proton_game && !is_wine_entry { if !is_proton_game && !is_wine_entry {
return None; return None;
@ -487,6 +487,10 @@ fn proton_or_wine_fallback(
#[cfg(not(windows))] #[cfg(not(windows))]
fn candidate_desktop_ids(context: &DesktopLookupContext<'_>) -> Vec<String> { fn candidate_desktop_ids(context: &DesktopLookupContext<'_>) -> Vec<String> {
const SUFFIXES: &[&str] = &[".desktop", ".Desktop", ".DESKTOP"];
let mut ordered = Vec::new();
let mut seen = HashSet::new();
fn push_candidate(seen: &mut HashSet<String>, ordered: &mut Vec<String>, candidate: &str) { fn push_candidate(seen: &mut HashSet<String>, ordered: &mut Vec<String>, candidate: &str) {
let trimmed = candidate.trim(); let trimmed = candidate.trim();
if trimmed.is_empty() { if trimmed.is_empty() {
@ -527,11 +531,11 @@ fn candidate_desktop_ids(context: &DesktopLookupContext<'_>) -> Vec<String> {
} }
} }
if trimmed.contains('.') if trimmed.contains('.') {
&& let Some(last) = trimmed.rsplit('.').next() if let Some(last) = trimmed.rsplit('.').next() {
{ if last.len() >= 2 {
if last.len() >= 2 { push_candidate(seen, ordered, last);
push_candidate(seen, ordered, last); }
} }
} }
@ -542,20 +546,13 @@ fn candidate_desktop_ids(context: &DesktopLookupContext<'_>) -> Vec<String> {
push_candidate(seen, ordered, &trimmed.replace('_', "-")); push_candidate(seen, ordered, &trimmed.replace('_', "-"));
} }
for token in for token in trimmed.split(|c: char| matches!(c, '.' | '-' | '_' | '@' | ' ')) {
trimmed.split(|c: char| matches!(c, '.' | '-' | '_' | '@') || c.is_whitespace())
{
if token.len() >= 2 && token != trimmed { if token.len() >= 2 && token != trimmed {
push_candidate(seen, ordered, token); push_candidate(seen, ordered, token);
} }
} }
} }
const SUFFIXES: &[&str] = &[".desktop", ".Desktop", ".DESKTOP"];
let mut ordered = Vec::new();
let mut seen = HashSet::new();
add_variants( add_variants(
&mut seen, &mut seen,
&mut ordered, &mut ordered,
@ -789,7 +786,7 @@ pub async fn spawn_desktop_exec<S, I, K, V>(
}) })
.unwrap_or_else(|| String::from("cosmic-term")); .unwrap_or_else(|| String::from("cosmic-term"));
term_exec = format!("{term} -e {}", exec.as_ref()); term_exec = format!("{term} -- {}", exec.as_ref());
&term_exec &term_exec
} else { } else {
exec.as_ref() exec.as_ref()
@ -918,20 +915,12 @@ mod tests {
let candidates = candidate_desktop_ids(&ctx); let candidates = candidate_desktop_ids(&ctx);
assert_eq!(candidates.first().unwrap(), "com.example.App.desktop"); assert_eq!(candidates.first().unwrap(), "com.example.App.desktop");
for test in [ assert!(candidates.contains(&"com.example.App".to_string()));
"com.example.App", assert!(candidates.contains(&"com-example-App".to_string()));
"com-example-App", assert!(candidates.contains(&"com_example_App".to_string()));
"com_example_App", assert!(candidates.contains(&"Example App".to_string()));
"Example App", assert!(candidates.contains(&"Example".to_string()));
"Example", assert!(candidates.contains(&"App".to_string()));
"App",
] {
assert!(
candidates
.iter()
.any(|c| c.to_ascii_lowercase() == test.to_ascii_lowercase()),
);
}
} }
#[test] #[test]
@ -996,7 +985,7 @@ Icon=vmware-workstation\n\
let resolved = resolve_desktop_entry(&mut cache, &ctx, &DesktopResolveOptions::default()); let resolved = resolve_desktop_entry(&mut cache, &ctx, &DesktopResolveOptions::default());
assert_eq!(resolved.id(), "vmware-workstation"); assert_eq!(resolved.id(), "vmware-workstation.desktop");
} }
#[test] #[test]

View file

@ -26,8 +26,4 @@ impl iced::Executor for Executor {
let _guard = self.0.enter(); let _guard = self.0.enter();
f() f()
} }
fn block_on<T>(&self, future: impl Future<Output = T>) -> T {
self.0.block_on(future)
}
} }

View file

@ -30,8 +30,4 @@ impl iced::Executor for Executor {
let _guard = self.0.enter(); let _guard = self.0.enter();
f() f()
} }
fn block_on<T>(&self, future: impl Future<Output = T>) -> T {
self.0.block_on(future)
}
} }

View file

@ -19,6 +19,72 @@ impl<Message: 'static> ElementExt for crate::Element<'_, Message> {
} }
} }
/// Additional methods for the [`Column`] and [`Row`] widgets.
pub trait CollectionWidget<'a, Message: 'a>:
Widget<Message, crate::Theme, crate::Renderer>
where
Self: Sized,
{
/// Moves all the elements of `other` into `self`, leaving `other` empty.
#[must_use]
fn append<E>(self, other: &mut Vec<E>) -> Self
where
E: Into<crate::Element<'a, Message>>;
/// Appends all elements in an iterator to the widget.
#[must_use]
fn extend<E>(mut self, iterator: impl Iterator<Item = E>) -> Self
where
E: Into<crate::Element<'a, Message>>,
{
for item in iterator {
self = self.push(item.into());
}
self
}
/// Pushes an element into the widget.
#[must_use]
fn push(self, element: impl Into<crate::Element<'a, Message>>) -> Self;
/// Conditionally pushes an element to the widget.
#[must_use]
fn push_maybe(self, element: Option<impl Into<crate::Element<'a, Message>>>) -> Self {
if let Some(element) = element {
self.push(element.into())
} else {
self
}
}
}
impl<'a, Message: 'a> CollectionWidget<'a, Message> for crate::widget::Column<'a, Message> {
fn append<E>(self, other: &mut Vec<E>) -> Self
where
E: Into<crate::Element<'a, Message>>,
{
self.extend(other.drain(..).map(Into::into))
}
fn push(self, element: impl Into<crate::Element<'a, Message>>) -> Self {
self.push(element)
}
}
impl<'a, Message: 'a> CollectionWidget<'a, Message> for crate::widget::Row<'a, Message> {
fn append<E>(self, other: &mut Vec<E>) -> Self
where
E: Into<crate::Element<'a, Message>>,
{
self.extend(other.drain(..).map(Into::into))
}
fn push(self, element: impl Into<crate::Element<'a, Message>>) -> Self {
self.push(element)
}
}
pub trait ColorExt { pub trait ColorExt {
/// Combines color with background to create appearance of transparency. /// Combines color with background to create appearance of transparency.
#[must_use] #[must_use]

View file

@ -3,7 +3,6 @@
#![allow(clippy::module_name_repetitions)] #![allow(clippy::module_name_repetitions)]
#![cfg_attr(target_os = "redox", feature(lazy_cell))] #![cfg_attr(target_os = "redox", feature(lazy_cell))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
/// Recommended default imports. /// Recommended default imports.
pub mod prelude { pub mod prelude {
@ -19,8 +18,6 @@ pub use apply::{Also, Apply};
pub mod action; pub mod action;
pub use action::Action; pub use action::Action;
pub mod anim;
#[cfg(feature = "winit")] #[cfg(feature = "winit")]
pub mod app; pub mod app;
#[cfg(feature = "winit")] #[cfg(feature = "winit")]
@ -67,6 +64,29 @@ pub mod font;
#[doc(inline)] #[doc(inline)]
pub use iced; pub use iced;
#[doc(inline)]
pub use iced_core;
#[doc(inline)]
pub use iced_futures;
#[doc(inline)]
pub use iced_renderer;
#[doc(inline)]
pub use iced_runtime;
#[doc(inline)]
pub use iced_widget;
#[doc(inline)]
#[cfg(feature = "winit")]
pub use iced_winit;
#[doc(inline)]
#[cfg(feature = "wgpu")]
pub use iced_wgpu;
pub mod icon_theme; pub mod icon_theme;
pub mod keyboard_nav; pub mod keyboard_nav;
@ -78,8 +98,7 @@ pub(crate) mod malloc;
#[cfg(all(feature = "process", not(windows)))] #[cfg(all(feature = "process", not(windows)))]
pub mod process; pub mod process;
#[doc(inline)] #[cfg(feature = "wayland")]
#[cfg(all(feature = "wayland", target_os = "linux"))]
pub use cctk; pub use cctk;
pub mod surface; pub mod surface;

View file

@ -9,25 +9,25 @@ use iced::window;
use std::{any::Any, sync::Arc}; use std::{any::Any, sync::Arc};
/// Used to produce a destroy popup message from within a widget. /// Used to produce a destroy popup message from within a widget.
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
#[must_use] #[must_use]
pub fn destroy_popup(id: iced_core::window::Id) -> Action { pub fn destroy_popup(id: iced_core::window::Id) -> Action {
Action::DestroyPopup(id) Action::DestroyPopup(id)
} }
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
#[must_use] #[must_use]
pub fn destroy_subsurface(id: iced_core::window::Id) -> Action { pub fn destroy_subsurface(id: iced_core::window::Id) -> Action {
Action::DestroySubsurface(id) Action::DestroySubsurface(id)
} }
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
#[must_use] #[must_use]
pub fn destroy_window(id: iced_core::window::Id) -> Action { pub fn destroy_window(id: iced_core::window::Id) -> Action {
Action::DestroyWindow(id) Action::DestroyWindow(id)
} }
#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[cfg(all(feature = "wayland", feature = "winit"))]
#[must_use] #[must_use]
pub fn app_window<App: Application>( pub fn app_window<App: Application>(
settings: impl Fn(&mut App) -> window::Settings + Send + Sync + 'static, settings: impl Fn(&mut App) -> window::Settings + Send + Sync + 'static,
@ -60,7 +60,7 @@ pub fn app_window<App: Application>(
} }
/// Used to create a window message from within a widget. /// Used to create a window message from within a widget.
#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[cfg(all(feature = "wayland", feature = "winit"))]
#[must_use] #[must_use]
pub fn simple_window<Message: 'static>( pub fn simple_window<Message: 'static>(
settings: impl Fn() -> window::Settings + Send + Sync + 'static, settings: impl Fn() -> window::Settings + Send + Sync + 'static,
@ -92,7 +92,7 @@ pub fn simple_window<Message: 'static>(
) )
} }
#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[cfg(all(feature = "wayland", feature = "winit"))]
#[must_use] #[must_use]
pub fn app_popup<App: Application>( pub fn app_popup<App: Application>(
settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
@ -126,7 +126,7 @@ pub fn app_popup<App: Application>(
} }
/// Used to create a subsurface message from within a widget. /// Used to create a subsurface message from within a widget.
#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[cfg(all(feature = "wayland", feature = "winit"))]
#[must_use] #[must_use]
pub fn simple_subsurface<Message: 'static, V>( pub fn simple_subsurface<Message: 'static, V>(
settings: impl Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings settings: impl Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings
@ -155,7 +155,7 @@ pub fn simple_subsurface<Message: 'static, V>(
} }
/// Used to create a popup message from within a widget. /// Used to create a popup message from within a widget.
#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[cfg(all(feature = "wayland", feature = "winit"))]
#[must_use] #[must_use]
pub fn simple_popup<Message: 'static>( pub fn simple_popup<Message: 'static>(
settings: impl Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings settings: impl Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
@ -186,7 +186,7 @@ pub fn simple_popup<Message: 'static>(
) )
} }
#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[cfg(all(feature = "wayland", feature = "winit"))]
#[must_use] #[must_use]
pub fn subsurface<App: Application>( pub fn subsurface<App: Application>(
settings: impl Fn( settings: impl Fn(

View file

@ -36,8 +36,6 @@ pub enum Action {
), ),
/// Destroy a subsurface with a view function /// Destroy a subsurface with a view function
DestroyPopup(iced::window::Id), DestroyPopup(iced::window::Id),
/// Destroys the global tooltip popup subsurface
DestroyTooltipPopup,
/// Create a window with a view function accepting the App as a parameter /// Create a window with a view function accepting the App as a parameter
AppWindow( AppWindow(
@ -87,7 +85,6 @@ impl std::fmt::Debug for Action {
} }
Self::Popup(arg0, arg1) => f.debug_tuple("Popup").field(arg0).field(arg1).finish(), Self::Popup(arg0, arg1) => f.debug_tuple("Popup").field(arg0).field(arg1).finish(),
Self::DestroyPopup(arg0) => f.debug_tuple("DestroyPopup").field(arg0).finish(), Self::DestroyPopup(arg0) => f.debug_tuple("DestroyPopup").field(arg0).finish(),
Self::DestroyTooltipPopup => f.debug_tuple("DestroyTooltipPopup").finish(),
Self::ResponsiveMenuBar { Self::ResponsiveMenuBar {
menu_bar, menu_bar,
limits, limits,

View file

@ -307,7 +307,7 @@ impl DefaultStyle for Theme {
fn default_style(&self) -> Appearance { fn default_style(&self) -> Appearance {
let cosmic = self.cosmic(); let cosmic = self.cosmic();
Appearance { Appearance {
icon_color: cosmic.on_bg_color().into(), icon_color: cosmic.bg_color().into(),
background_color: cosmic.bg_color().into(), background_color: cosmic.bg_color().into(),
text_color: cosmic.on_bg_color().into(), text_color: cosmic.on_bg_color().into(),
} }

View file

@ -13,8 +13,9 @@ pub enum Desktop {
#[cold] #[cold]
pub fn desktop_settings() -> iced_futures::Subscription<Desktop> { pub fn desktop_settings() -> iced_futures::Subscription<Desktop> {
iced_futures::Subscription::run(|| { iced_futures::Subscription::run_with_id(
stream::channel(10, |mut tx: futures::channel::mpsc::Sender<Desktop>| { std::any::TypeId::of::<Desktop>(),
stream::channel(10, |mut tx| {
async move { async move {
let mut attempts = 0; let mut attempts = 0;
loop { loop {
@ -98,6 +99,6 @@ pub fn desktop_settings() -> iced_futures::Subscription<Desktop> {
} }
} }
} }
}) }),
}) )
} }

View file

@ -27,7 +27,7 @@ pub enum Button {
IconVertical, IconVertical,
Image, Image,
Link, Link,
ListItem([f32; 4]), ListItem,
MenuFolder, MenuFolder,
MenuItem, MenuItem,
MenuRoot, MenuRoot,
@ -148,8 +148,8 @@ pub fn appearance(
appearance.text_color = Some(component.on.into()); appearance.text_color = Some(component.on.into());
corner_radii = &cosmic.corner_radii.radius_s; corner_radii = &cosmic.corner_radii.radius_s;
} }
Button::ListItem(radii) => { Button::ListItem => {
corner_radii = radii; corner_radii = &[0.0; 4];
let (background, text, icon) = color(&cosmic.background.component); let (background, text, icon) = color(&cosmic.background.component);
if selected { if selected {
@ -197,7 +197,7 @@ impl Catalog for crate::Theme {
return active(focused, self); return active(focused, self);
} }
let mut s = appearance(self, focused, selected, false, style, move |component| { appearance(self, focused, selected, false, style, move |component| {
let text_color = if matches!( let text_color = if matches!(
style, style,
Button::Icon | Button::IconVertical | Button::HeaderBar Button::Icon | Button::IconVertical | Button::HeaderBar
@ -209,15 +209,7 @@ impl Catalog for crate::Theme {
}; };
(component.base.into(), text_color, text_color) (component.base.into(), text_color, text_color)
}); })
if let Button::ListItem(_) = style {
if !selected {
s.background = None;
}
}
s
} }
fn disabled(&self, style: &Self::Class) -> Style { fn disabled(&self, style: &Self::Class) -> Style {
@ -245,7 +237,7 @@ impl Catalog for crate::Theme {
return hovered(focused, self); return hovered(focused, self);
} }
let mut s = appearance( appearance(
self, self,
focused || matches!(style, Button::Image), focused || matches!(style, Button::Image),
selected, selected,
@ -264,15 +256,7 @@ impl Catalog for crate::Theme {
(component.hover.into(), text_color, text_color) (component.hover.into(), text_color, text_color)
}, },
); )
if let Button::ListItem(_) = style {
if !selected {
s.background = None;
}
}
s
} }
fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style { fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {

View file

@ -7,7 +7,6 @@ use crate::theme::{CosmicComponent, TRANSPARENT_COMPONENT, Theme};
use cosmic_theme::composite::over; use cosmic_theme::composite::over;
use iced::{ use iced::{
overlay::menu, overlay::menu,
theme::Base,
widget::{ widget::{
button as iced_button, checkbox as iced_checkbox, combo_box, container as iced_container, button as iced_button, checkbox as iced_checkbox, combo_box, container as iced_container,
pane_grid, pick_list, progress_bar, radio, rule, scrollable, pane_grid, pick_list, progress_bar, radio, rule, scrollable,
@ -16,7 +15,7 @@ use iced::{
}, },
}; };
use iced_core::{Background, Border, Color, Shadow, Vector}; use iced_core::{Background, Border, Color, Shadow, Vector};
use iced_widget::{pane_grid::Highlight, scrollable::AutoScroll, text_editor, text_input}; use iced_widget::{pane_grid::Highlight, text_editor, text_input};
use palette::WithAlpha; use palette::WithAlpha;
use std::rc::Rc; use std::rc::Rc;
@ -37,13 +36,13 @@ pub mod application {
} }
} }
pub fn style(theme: &Theme) -> iced::theme::Style { pub fn appearance(theme: &Theme) -> Appearance {
let cosmic = theme.cosmic(); let cosmic = theme.cosmic();
iced::theme::Style { Appearance {
icon_color: cosmic.bg_color().into(),
background_color: cosmic.bg_color().into(), background_color: cosmic.bg_color().into(),
text_color: cosmic.on_bg_color().into(), text_color: cosmic.on_bg_color().into(),
icon_color: cosmic.on_bg_color().into(),
} }
} }
} }
@ -423,7 +422,6 @@ impl<'a> Container<'a> {
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
} }
} }
@ -438,7 +436,6 @@ impl<'a> Container<'a> {
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
} }
} }
@ -453,7 +450,6 @@ impl<'a> Container<'a> {
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
} }
} }
} }
@ -497,7 +493,6 @@ impl iced_container::Catalog for Theme {
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
Container::List => { Container::List => {
@ -511,7 +506,6 @@ impl iced_container::Catalog for Theme {
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
} }
} }
@ -558,7 +552,6 @@ impl iced_container::Catalog for Theme {
.into(), .into(),
..Default::default() ..Default::default()
}, },
snap: true,
shadow: Shadow::default(), shadow: Shadow::default(),
} }
} }
@ -589,7 +582,6 @@ impl iced_container::Catalog for Theme {
radius: cosmic.corner_radii.radius_s.into(), radius: cosmic.corner_radii.radius_s.into(),
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
Container::Tooltip => iced_container::Style { Container::Tooltip => iced_container::Style {
@ -601,7 +593,6 @@ impl iced_container::Catalog for Theme {
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
Container::Card => { Container::Card => {
@ -619,7 +610,6 @@ impl iced_container::Catalog for Theme {
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
cosmic_theme::Layer::Primary => iced_container::Style { cosmic_theme::Layer::Primary => iced_container::Style {
icon_color: Some(Color::from(cosmic.primary.component.on)), icon_color: Some(Color::from(cosmic.primary.component.on)),
@ -632,7 +622,6 @@ impl iced_container::Catalog for Theme {
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
cosmic_theme::Layer::Secondary => iced_container::Style { cosmic_theme::Layer::Secondary => iced_container::Style {
icon_color: Some(Color::from(cosmic.secondary.component.on)), icon_color: Some(Color::from(cosmic.secondary.component.on)),
@ -645,7 +634,6 @@ impl iced_container::Catalog for Theme {
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
} }
} }
@ -664,7 +652,6 @@ impl iced_container::Catalog for Theme {
offset: Vector::new(0.0, 4.0), offset: Vector::new(0.0, 4.0),
blur_radius: 16.0, blur_radius: 16.0,
}, },
snap: true,
}, },
} }
} }
@ -804,7 +791,6 @@ impl menu::Catalog for Theme {
}, },
selected_text_color: cosmic.accent_text_color().into(), selected_text_color: cosmic.accent_text_color().into(),
selected_background: Background::Color(cosmic.background.component.hover.into()), selected_background: Background::Color(cosmic.background.component.hover.into()),
shadow: Default::default(),
} }
} }
} }
@ -844,7 +830,7 @@ impl pick_list::Catalog for Theme {
background: Background::Color(cosmic.background.base.into()), background: Background::Color(cosmic.background.base.into()),
..appearance ..appearance
}, },
pick_list::Status::Opened { is_hovered: _ } => appearance, pick_list::Status::Opened => appearance,
} }
} }
} }
@ -934,8 +920,6 @@ impl toggler::Catalog for Theme {
background_border_color: Color::TRANSPARENT, background_border_color: Color::TRANSPARENT,
foreground_border_width: 0.0, foreground_border_width: 0.0,
foreground_border_color: Color::TRANSPARENT, foreground_border_color: Color::TRANSPARENT,
text_color: None,
padding_ratio: 0.0,
}; };
match status { match status {
toggler::Status::Active { is_toggled } => active, toggler::Status::Active { is_toggled } => active,
@ -958,9 +942,9 @@ impl toggler::Catalog for Theme {
..active ..active
} }
} }
toggler::Status::Disabled { is_toggled } => { toggler::Status::Disabled => {
active.background = active.background.scale_alpha(0.5); active.background.a /= 2.;
active.foreground = active.foreground.scale_alpha(0.5); active.foreground.a /= 2.;
active active
} }
} }
@ -1102,21 +1086,21 @@ impl rule::Catalog for Theme {
match class { match class {
Rule::Default => rule::Style { Rule::Default => rule::Style {
color: self.current_container().divider.into(), color: self.current_container().divider.into(),
width: 1,
radius: 0.0.into(), radius: 0.0.into(),
fill_mode: rule::FillMode::Full, fill_mode: rule::FillMode::Full,
snap: true,
}, },
Rule::LightDivider => rule::Style { Rule::LightDivider => rule::Style {
color: self.current_container().divider.into(), color: self.current_container().divider.into(),
width: 1,
radius: 0.0.into(), radius: 0.0.into(),
fill_mode: rule::FillMode::Padded(8), fill_mode: rule::FillMode::Padded(8),
snap: true,
}, },
Rule::HeavyDivider => rule::Style { Rule::HeavyDivider => rule::Style {
color: self.current_container().divider.into(), color: self.current_container().divider.into(),
width: 4,
radius: 2.0.into(), radius: 2.0.into(),
fill_mode: rule::FillMode::Full, fill_mode: rule::FillMode::Full,
snap: true,
}, },
Rule::Custom(f) => f(self), Rule::Custom(f) => f(self),
} }
@ -1142,10 +1126,7 @@ impl scrollable::Catalog for Theme {
fn style(&self, class: &Self::Class<'_>, status: scrollable::Status) -> scrollable::Style { fn style(&self, class: &Self::Class<'_>, status: scrollable::Status) -> scrollable::Style {
match status { match status {
scrollable::Status::Active { scrollable::Status::Active => {
is_horizontal_scrollbar_disabled,
is_vertical_scrollbar_disabled,
} => {
let cosmic = self.cosmic(); let cosmic = self.cosmic();
let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7); let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7);
let neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7); let neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7);
@ -1158,7 +1139,7 @@ impl scrollable::Catalog for Theme {
}, },
background: None, background: None,
scroller: scrollable::Scroller { scroller: scrollable::Scroller {
background: if cosmic.is_dark { color: if cosmic.is_dark {
neutral_6.into() neutral_6.into()
} else { } else {
neutral_5.into() neutral_5.into()
@ -1176,7 +1157,7 @@ impl scrollable::Catalog for Theme {
}, },
background: None, background: None,
scroller: scrollable::Scroller { scroller: scrollable::Scroller {
background: if cosmic.is_dark { color: if cosmic.is_dark {
neutral_6.into() neutral_6.into()
} else { } else {
neutral_5.into() neutral_5.into()
@ -1188,13 +1169,6 @@ impl scrollable::Catalog for Theme {
}, },
}, },
gap: None, gap: None,
// TODO: what is auto scroll?
auto_scroll: AutoScroll {
background: Color::TRANSPARENT.into(),
border: Border::default(),
shadow: Shadow::default(),
icon: Color::TRANSPARENT.into(),
},
}; };
let small_widget_container = self.current_container().small_widget.with_alpha(0.7); let small_widget_container = self.current_container().small_widget.with_alpha(0.7);
@ -1226,7 +1200,7 @@ impl scrollable::Catalog for Theme {
}, },
background: None, background: None,
scroller: scrollable::Scroller { scroller: scrollable::Scroller {
background: if cosmic.is_dark { color: if cosmic.is_dark {
neutral_6.into() neutral_6.into()
} else { } else {
neutral_5.into() neutral_5.into()
@ -1244,7 +1218,7 @@ impl scrollable::Catalog for Theme {
}, },
background: None, background: None,
scroller: scrollable::Scroller { scroller: scrollable::Scroller {
background: if cosmic.is_dark { color: if cosmic.is_dark {
neutral_6.into() neutral_6.into()
} else { } else {
neutral_5.into() neutral_5.into()
@ -1256,13 +1230,6 @@ impl scrollable::Catalog for Theme {
}, },
}, },
gap: None, gap: None,
// TODO: what is auto scroll?
auto_scroll: AutoScroll {
background: Color::TRANSPARENT.into(),
border: Border::default(),
shadow: Shadow::default(),
icon: Color::TRANSPARENT.into(),
},
}; };
if matches!(class, Scrollable::Permanent) { if matches!(class, Scrollable::Permanent) {
@ -1433,7 +1400,7 @@ impl text_input::Catalog for Theme {
}, },
} }
} }
text_input::Status::Focused { is_hovered } => { text_input::Status::Focused => {
let bg = self.current_container().small_widget.with_alpha(0.25); let bg = self.current_container().small_widget.with_alpha(0.25);
match class { match class {
@ -1510,8 +1477,7 @@ impl iced_widget::text_editor::Catalog for Theme {
let selection = cosmic.accent.base.into(); let selection = cosmic.accent.base.into();
let value = cosmic.palette.neutral_9.into(); let value = cosmic.palette.neutral_9.into();
let placeholder = cosmic.palette.neutral_9.with_alpha(0.7).into(); let placeholder = cosmic.palette.neutral_9.with_alpha(0.7).into();
let icon: Color = cosmic.background.on.into(); let icon = cosmic.background.on.into();
// TODO do we need to add icon color back?
match status { match status {
iced_widget::text_editor::Status::Active iced_widget::text_editor::Status::Active
@ -1523,23 +1489,23 @@ impl iced_widget::text_editor::Catalog for Theme {
width: f32::from(cosmic.space_xxxs()), width: f32::from(cosmic.space_xxxs()),
color: iced::Color::from(cosmic.bg_divider()), color: iced::Color::from(cosmic.bg_divider()),
}, },
icon,
placeholder,
value,
selection,
},
iced_widget::text_editor::Status::Focused => iced_widget::text_editor::Style {
background: iced::Color::from(cosmic.bg_color()).into(),
border: Border {
radius: cosmic.corner_radii.radius_0.into(),
width: f32::from(cosmic.space_xxxs()),
color: iced::Color::from(cosmic.accent.base),
},
icon,
placeholder, placeholder,
value, value,
selection, selection,
}, },
iced_widget::text_editor::Status::Focused { is_hovered } => {
iced_widget::text_editor::Style {
background: iced::Color::from(cosmic.bg_color()).into(),
border: Border {
radius: cosmic.corner_radii.radius_0.into(),
width: f32::from(cosmic.space_xxxs()),
color: iced::Color::from(cosmic.accent.base),
},
placeholder,
value,
selection,
}
}
} }
} }
} }
@ -1556,21 +1522,6 @@ impl iced_widget::markdown::Catalog for Theme {
} }
} }
impl iced_widget::table::Catalog for Theme {
type Class<'a> = iced_widget::table::StyleFn<'a, Self>;
fn default<'a>() -> Self::Class<'a> {
Box::new(|theme| iced_widget::table::Style {
separator_x: theme.current_container().divider.into(),
separator_y: theme.current_container().divider.into(),
})
}
fn style(&self, class: &Self::Class<'_>) -> iced_widget::table::Style {
class(self)
}
}
#[cfg(feature = "qr_code")] #[cfg(feature = "qr_code")]
impl iced_widget::qr_code::Catalog for Theme { impl iced_widget::qr_code::Catalog for Theme {
type Class<'a> = iced_widget::qr_code::StyleFn<'a, Self>; type Class<'a> = iced_widget::qr_code::StyleFn<'a, Self>;
@ -1588,50 +1539,3 @@ impl iced_widget::qr_code::Catalog for Theme {
} }
impl combo_box::Catalog for Theme {} impl combo_box::Catalog for Theme {}
impl Base for Theme {
fn default(preference: iced::theme::Mode) -> Self {
match preference {
iced::theme::Mode::Light => Theme::light(),
iced::theme::Mode::Dark | iced::theme::Mode::None => Theme::dark(),
}
}
fn mode(&self) -> iced::theme::Mode {
if self.theme_type.is_dark() {
iced::theme::Mode::Dark
} else {
iced::theme::Mode::Light
}
}
fn base(&self) -> iced::theme::Style {
iced::theme::Style {
background_color: self.cosmic().bg_color().into(),
text_color: self.cosmic().on_bg_color().into(),
icon_color: self.cosmic().on_bg_color().into(),
}
}
fn palette(&self) -> Option<iced::theme::Palette> {
Some(iced::theme::Palette {
primary: self.cosmic().accent.base.into(),
success: self.cosmic().success.base.into(),
warning: self.cosmic().warning.base.into(),
danger: self.cosmic().destructive.base.into(),
background: iced::Color::from(self.cosmic().bg_color()),
text: iced::Color::from(self.cosmic().on_bg_color()),
})
}
fn name(&self) -> &str {
match &self.theme_type {
crate::theme::ThemeType::Dark => "Cosmic Dark Theme",
crate::theme::ThemeType::Light => "Cosmic Light Theme",
crate::theme::ThemeType::HighContrastDark => "Cosmic High Contrast Dark Theme",
crate::theme::ThemeType::HighContrastLight => "Cosmic High Contrast Light Theme",
crate::theme::ThemeType::Custom(theme) => "Custom Cosmic Theme",
crate::theme::ThemeType::System { prefer_dark, theme } => &theme.name,
}
}
}

View file

@ -32,7 +32,7 @@ mod text_input;
#[doc(inline)] #[doc(inline)]
pub use self::text_input::TextInput; pub use self::text_input::TextInput;
#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[cfg(all(feature = "wayland", feature = "winit"))]
pub mod tooltip; pub mod tooltip;
#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[cfg(all(feature = "wayland", feature = "winit"))]
pub use tooltip::Tooltip; pub use tooltip::Tooltip;

View file

@ -1,9 +1,8 @@
use crate::{ use crate::{
Apply, Element, fl, Apply, Element, fl,
iced::{Alignment, Length}, iced::{Alignment, Length},
widget::{self, list}, widget::{self, horizontal_space},
}; };
use std::rc::Rc;
#[derive(Debug, Default, Clone, derive_setters::Setters)] #[derive(Debug, Default, Clone, derive_setters::Setters)]
#[setters(into, strip_option)] #[setters(into, strip_option)]
@ -48,40 +47,32 @@ pub struct About {
fn add_contributors(contributors: Vec<(&str, &str)>) -> Vec<(String, String)> { fn add_contributors(contributors: Vec<(&str, &str)>) -> Vec<(String, String)> {
contributors contributors
.into_iter() .into_iter()
.map(|(name, email)| (name.into(), format!("mailto:{email}"))) .map(|(name, email)| (name.to_string(), format!("mailto:{email}")))
.collect() .collect()
} }
macro_rules! set_contributors {
($field:ident, $doc:expr) => {
#[doc = $doc]
pub fn $field(mut self, contributors: impl Into<Vec<(&'a str, &'a str)>>) -> Self {
self.$field = add_contributors(contributors.into());
self
}
};
}
impl<'a> About { impl<'a> About {
/// Artists who contributed to the application. set_contributors!(artists, "Artists who contributed to the application.");
pub fn artists(mut self, contributors: impl Into<Vec<(&'a str, &'a str)>>) -> Self { set_contributors!(designers, "Designers who contributed to the application.");
self.artists = add_contributors(contributors.into()); set_contributors!(developers, "Developers who contributed to the application.");
self set_contributors!(
} documenters,
"Documenters who contributed to the application."
/// Designers who contributed to the application. );
pub fn designers(mut self, contributors: impl Into<Vec<(&'a str, &'a str)>>) -> Self { set_contributors!(
self.designers = add_contributors(contributors.into()); translators,
self "Translators who contributed to the application."
} );
/// Developers who contributed to the application.
pub fn developers(mut self, contributors: impl Into<Vec<(&'a str, &'a str)>>) -> Self {
self.developers = add_contributors(contributors.into());
self
}
/// Documenters who contributed to the application.
pub fn documenters(mut self, contributors: impl Into<Vec<(&'a str, &'a str)>>) -> Self {
self.documenters = add_contributors(contributors.into());
self
}
/// Translators who contributed to the application.
pub fn translators(mut self, contributors: impl Into<Vec<(&'a str, &'a str)>>) -> Self {
self.translators = add_contributors(contributors.into());
self
}
/// Links associated with the application. /// Links associated with the application.
pub fn links<K: Into<String>, V: Into<String>>( pub fn links<K: Into<String>, V: Into<String>>(
@ -105,23 +96,19 @@ pub fn about<'a, Message: Clone + 'static>(
space_xxs, space_m, .. space_xxs, space_m, ..
} = crate::theme::spacing(); } = crate::theme::spacing();
let svg_accent = Rc::new(|theme: &crate::Theme| widget::svg::Style { let section_button = |name: &'a str, url: &'a str| -> Element<'a, Message> {
color: Some(theme.cosmic().accent_text_color().into()), widget::row()
}); .push(widget::text(name))
.push(horizontal_space())
let section_button = |name: &'a str, url: &'a str| -> list::ListButton<'a, Message> {
widget::row::with_capacity(2)
.push(widget::text::body(name).width(Length::Fill))
.push_maybe( .push_maybe(
(!url.is_empty()).then_some( (!url.is_empty()).then_some(crate::widget::icon::from_name("link-symbolic").icon()),
widget::icon::from_name("link-symbolic")
.icon()
.class(crate::theme::Svg::Custom(svg_accent.clone())),
),
) )
.align_y(Alignment::Center) .align_y(Alignment::Center)
.apply(list::button) .apply(widget::button::custom)
.class(crate::theme::Button::Link)
.on_press(on_url_press(url)) .on_press(on_url_press(url))
.width(Length::Fill)
.into()
}; };
let section = |list: &'a Vec<(String, String)>, title: String| { let section = |list: &'a Vec<(String, String)>, title: String| {
@ -171,7 +158,7 @@ pub fn about<'a, Message: Clone + 'static>(
let copyright = about.copyright.as_ref().map(widget::text::body); let copyright = about.copyright.as_ref().map(widget::text::body);
let comments = about.comments.as_ref().map(widget::text::body); let comments = about.comments.as_ref().map(widget::text::body);
widget::column::with_capacity(10) widget::column()
.push_maybe(header) .push_maybe(header)
.push_maybe(links_section) .push_maybe(links_section)
.push_maybe(developers_section) .push_maybe(developers_section)

View file

@ -2,7 +2,7 @@
use iced::Size; use iced::Size;
use iced::widget::Container; use iced::widget::Container;
use iced_core::event::Event; use iced_core::event::{self, Event};
use iced_core::layout; use iced_core::layout;
use iced_core::mouse; use iced_core::mouse;
use iced_core::overlay; use iced_core::overlay;
@ -172,7 +172,7 @@ where
} }
fn layout( fn layout(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
renderer: &Renderer, renderer: &Renderer,
limits: &layout::Limits, limits: &layout::Limits,
@ -186,7 +186,7 @@ where
} }
fn operate( fn operate(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
@ -195,18 +195,18 @@ where
self.container.operate(tree, layout, renderer, operation); self.container.operate(tree, layout, renderer, operation);
} }
fn update( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: mouse::Cursor, cursor_position: mouse::Cursor,
renderer: &Renderer, renderer: &Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) { ) -> event::Status {
self.container.update( self.container.on_event(
tree, tree,
event, event,
layout, layout,
@ -254,13 +254,11 @@ where
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'b>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
viewport: &Rectangle,
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
self.container self.container.overlay(tree, layout, renderer, translation)
.overlay(tree, layout, renderer, viewport, translation)
} }
#[cfg(feature = "a11y")] #[cfg(feature = "a11y")]

View file

@ -5,7 +5,7 @@ use iced_core::layout;
use iced_core::mouse; use iced_core::mouse;
use iced_core::overlay; use iced_core::overlay;
use iced_core::renderer; use iced_core::renderer;
use iced_core::widget::{Id, Operation, Tree}; use iced_core::widget::{Id, Tree};
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
pub use iced_widget::container::{Catalog, Style}; pub use iced_widget::container::{Catalog, Style};
@ -107,7 +107,7 @@ where
} }
fn diff(&mut self, tree: &mut Tree) { fn diff(&mut self, tree: &mut Tree) {
tree.diff_children(std::slice::from_mut(&mut self.content)); tree.children[0].diff(&mut self.content);
} }
fn size(&self) -> iced_core::Size<Length> { fn size(&self) -> iced_core::Size<Length> {
@ -115,7 +115,7 @@ where
} }
fn layout( fn layout(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
renderer: &Renderer, renderer: &Renderer,
limits: &layout::Limits, limits: &layout::Limits,
@ -131,22 +131,21 @@ where
} }
let node = self let node = self
.content .content
.as_widget_mut() .as_widget()
.layout(&mut tree.children[0], renderer, &my_limits); .layout(&mut tree.children[0], renderer, &my_limits);
let size = node.size(); let size = node.size();
layout::Node::with_children(size, vec![node]) layout::Node::with_children(size, vec![node])
} }
fn operate( fn operate(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn Operation, operation: &mut dyn iced_core::widget::Operation<()>,
) { ) {
operation.container(Some(&self.id), layout.bounds()); operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
operation.traverse(&mut |operation| { self.content.as_widget().operate(
self.content.as_widget_mut().operate(
&mut tree.children[0], &mut tree.children[0],
layout layout
.children() .children()
@ -159,18 +158,18 @@ where
}); });
} }
fn update( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: mouse::Cursor, cursor_position: mouse::Cursor,
renderer: &Renderer, renderer: &Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) { ) -> event::Status {
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(feature = "wayland")]
if matches!( if matches!(
event, event,
Event::PlatformSpecific(event::PlatformSpecific::Wayland( Event::PlatformSpecific(event::PlatformSpecific::Wayland(
@ -180,9 +179,9 @@ where
let bounds = layout.bounds().size(); let bounds = layout.bounds().size();
clipboard.request_logical_window_size(bounds.width.max(1.), bounds.height.max(1.)); clipboard.request_logical_window_size(bounds.width.max(1.), bounds.height.max(1.));
} }
self.content.as_widget_mut().update( self.content.as_widget_mut().on_event(
&mut tree.children[0], &mut tree.children[0],
event, event.clone(),
layout layout
.children() .children()
.next() .next()
@ -193,7 +192,7 @@ where
clipboard, clipboard,
shell, shell,
viewport, viewport,
); )
} }
fn mouse_interaction( fn mouse_interaction(
@ -239,9 +238,8 @@ where
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'b>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
viewport: &Rectangle,
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay( self.content.as_widget_mut().overlay(
@ -252,7 +250,6 @@ where
.unwrap() .unwrap()
.with_virtual_offset(layout.virtual_offset()), .with_virtual_offset(layout.virtual_offset()),
renderer, renderer,
viewport,
translation, translation,
) )
} }

View file

@ -3,7 +3,10 @@
use super::{Builder, ButtonClass}; use super::{Builder, ButtonClass};
use crate::Element; use crate::Element;
use crate::widget::{icon::Handle, tooltip}; use crate::widget::{
icon::{self, Handle},
tooltip,
};
use apply::Apply; use apply::Apply;
use iced_core::{Alignment, Length, Padding, font::Weight, text::LineHeight, widget::Id}; use iced_core::{Alignment, Length, Padding, font::Weight, text::LineHeight, widget::Id};
use std::borrow::Cow; use std::borrow::Cow;
@ -130,7 +133,7 @@ impl<Message> Button<'_, Message> {
} }
impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Message> { impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Message> {
fn from(builder: Button<'a, Message>) -> Element<'a, Message> { fn from(mut builder: Button<'a, Message>) -> Element<'a, Message> {
let mut content = Vec::with_capacity(2); let mut content = Vec::with_capacity(2);
content.push( content.push(

View file

@ -318,7 +318,7 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
} }
fn layout( fn layout(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
renderer: &crate::Renderer, renderer: &crate::Renderer,
limits: &layout::Limits, limits: &layout::Limits,
@ -331,22 +331,21 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
self.padding, self.padding,
|renderer, limits| { |renderer, limits| {
self.content self.content
.as_widget_mut() .as_widget()
.layout(&mut tree.children[0], renderer, limits) .layout(&mut tree.children[0], renderer, limits)
}, },
) )
} }
fn operate( fn operate(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
operation: &mut dyn Operation<()>, operation: &mut dyn Operation<()>,
) { ) {
operation.container(None, layout.bounds()); operation.container(None, layout.bounds(), &mut |operation| {
operation.traverse(&mut |operation| { self.content.as_widget().operate(
self.content.as_widget_mut().operate(
&mut tree.children[0], &mut tree.children[0],
layout layout
.children() .children()
@ -358,20 +357,20 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
); );
}); });
let state = tree.state.downcast_mut::<State>(); let state = tree.state.downcast_mut::<State>();
operation.focusable(Some(&self.id), layout.bounds(), state); operation.focusable(state, Some(&self.id));
} }
fn update( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &crate::Renderer, renderer: &crate::Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) { ) -> event::Status {
if let Variant::Image { if let Variant::Image {
on_remove: Some(on_remove), on_remove: Some(on_remove),
.. ..
@ -384,8 +383,7 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
if let Some(position) = cursor.position() { if let Some(position) = cursor.position() {
if removal_bounds(layout.bounds(), 4.0).contains(position) { if removal_bounds(layout.bounds(), 4.0).contains(position) {
shell.publish(on_remove.clone()); shell.publish(on_remove.clone());
shell.capture_event(); return event::Status::Captured;
return;
} }
} }
} }
@ -393,9 +391,10 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
_ => (), _ => (),
} }
} }
self.content.as_widget_mut().update(
if self.content.as_widget_mut().on_event(
&mut tree.children[0], &mut tree.children[0],
event, event.clone(),
layout layout
.children() .children()
.next() .next()
@ -406,9 +405,9 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
clipboard, clipboard,
shell, shell,
viewport, viewport,
); ) == event::Status::Captured
if shell.is_event_captured() { {
return; return event::Status::Captured;
} }
update( update(
@ -542,7 +541,6 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
selection_background, selection_background,
); );
@ -556,7 +554,7 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
y: bounds.y + (bounds.height - 18.0 - styling.border_width), y: bounds.y + (bounds.height - 18.0 - styling.border_width),
}; };
if bounds.intersects(viewport) { if bounds.intersects(viewport) {
iced_core::svg::Renderer::draw_svg(renderer, svg_handle, bounds, bounds); iced_core::svg::Renderer::draw_svg(renderer, svg_handle, bounds);
} }
} }
@ -572,7 +570,6 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
radius: c_rad.radius_m.into(), radius: c_rad.radius_m.into(),
..Default::default() ..Default::default()
}, },
snap: true,
}, },
selection_background, selection_background,
); );
@ -586,12 +583,6 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
x: bounds.x + 4.0, x: bounds.x + 4.0,
y: bounds.y + 4.0, y: bounds.y + 4.0,
}, },
Rectangle {
width: 16.0,
height: 16.0,
x: bounds.x + 4.0,
y: bounds.y + 4.0,
},
); );
} }
} }
@ -618,9 +609,8 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'b>, layout: Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
viewport: &Rectangle,
mut translation: Vector, mut translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
let position = layout.bounds().position(); let position = layout.bounds().position();
@ -634,7 +624,6 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
.unwrap() .unwrap()
.with_virtual_offset(layout.virtual_offset()), .with_virtual_offset(layout.virtual_offset()),
renderer, renderer,
viewport,
translation, translation,
) )
} }
@ -649,7 +638,7 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
) -> iced_accessibility::A11yTree { ) -> iced_accessibility::A11yTree {
use iced_accessibility::{ use iced_accessibility::{
A11yNode, A11yTree, A11yNode, A11yTree,
accesskit::{Action, Node, NodeId, Rect, Role}, accesskit::{Action, DefaultActionVerb, NodeBuilder, NodeId, Rect, Role},
}; };
// TODO why is state None sometimes? // TODO why is state None sometimes?
if matches!(state.state, iced_core::widget::tree::State::None) { if matches!(state.state, iced_core::widget::tree::State::None) {
@ -669,12 +658,12 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
let bounds = Rect::new(x as f64, y as f64, (x + width) as f64, (y + height) as f64); let bounds = Rect::new(x as f64, y as f64, (x + width) as f64, (y + height) as f64);
let is_hovered = state.state.downcast_ref::<State>().is_hovered; let is_hovered = state.state.downcast_ref::<State>().is_hovered;
let mut node = Node::new(Role::Button); let mut node = NodeBuilder::new(Role::Button);
node.add_action(Action::Focus); node.add_action(Action::Focus);
node.add_action(Action::Click); node.add_action(Action::Default);
node.set_bounds(bounds); node.set_bounds(bounds);
if let Some(name) = self.name.as_ref() { if let Some(name) = self.name.as_ref() {
node.set_label(name.clone()); node.set_name(name.clone());
} }
match self.description.as_ref() { match self.description.as_ref() {
Some(iced_accessibility::Description::Id(id)) => { Some(iced_accessibility::Description::Id(id)) => {
@ -693,10 +682,10 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
if self.on_press.is_none() { if self.on_press.is_none() {
node.set_disabled(); node.set_disabled();
} }
// TODO hover if is_hovered {
// if is_hovered { node.set_hovered();
// node.set_hovered(); }
// } node.set_default_action_verb(DefaultActionVerb::Click);
if let Some(child_tree) = child_tree.map(|child_tree| { if let Some(child_tree) = child_tree.map(|child_tree| {
self.content.as_widget().a11y_nodes( self.content.as_widget().a11y_nodes(
@ -772,14 +761,14 @@ impl State {
#[allow(clippy::needless_pass_by_value, clippy::too_many_arguments)] #[allow(clippy::needless_pass_by_value, clippy::too_many_arguments)]
pub fn update<'a, Message: Clone>( pub fn update<'a, Message: Clone>(
_id: Id, _id: Id,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
on_press: Option<&dyn Fn(Vector, Rectangle) -> Message>, on_press: Option<&dyn Fn(Vector, Rectangle) -> Message>,
on_press_down: Option<&dyn Fn(Vector, Rectangle) -> Message>, on_press_down: Option<&dyn Fn(Vector, Rectangle) -> Message>,
state: impl FnOnce() -> &'a mut State, state: impl FnOnce() -> &'a mut State,
) { ) -> event::Status {
match event { match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => { | Event::Touch(touch::Event::FingerPressed { .. }) => {
@ -798,8 +787,7 @@ pub fn update<'a, Message: Clone>(
shell.publish(msg); shell.publish(msg);
} }
shell.capture_event(); return event::Status::Captured;
return;
} }
} }
} }
@ -818,8 +806,7 @@ pub fn update<'a, Message: Clone>(
shell.publish(msg); shell.publish(msg);
} }
shell.capture_event(); return event::Status::Captured;
return;
} }
} else if on_press_down.is_some() { } else if on_press_down.is_some() {
let state = state(); let state = state();
@ -829,7 +816,7 @@ pub fn update<'a, Message: Clone>(
#[cfg(feature = "a11y")] #[cfg(feature = "a11y")]
Event::A11y(event_id, iced_accessibility::accesskit::ActionRequest { action, .. }) => { Event::A11y(event_id, iced_accessibility::accesskit::ActionRequest { action, .. }) => {
let state = state(); let state = state();
if let Some(on_press) = matches!(action, iced_accessibility::accesskit::Action::Click) if let Some(on_press) = matches!(action, iced_accessibility::accesskit::Action::Default)
.then_some(on_press) .then_some(on_press)
.flatten() .flatten()
{ {
@ -838,19 +825,17 @@ pub fn update<'a, Message: Clone>(
shell.publish(msg); shell.publish(msg);
} }
shell.capture_event(); return event::Status::Captured;
return;
} }
Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => {
if let Some(on_press) = on_press { if let Some(on_press) = on_press {
let state = state(); let state = state();
if state.is_focused && *key == keyboard::Key::Named(keyboard::key::Named::Enter) { if state.is_focused && key == keyboard::Key::Named(keyboard::key::Named::Enter) {
state.is_pressed = true; state.is_pressed = true;
let msg = (on_press)(layout.virtual_offset(), layout.bounds()); let msg = (on_press)(layout.virtual_offset(), layout.bounds());
shell.publish(msg); shell.publish(msg);
shell.capture_event(); return event::Status::Captured;
return;
} }
} }
} }
@ -861,6 +846,8 @@ pub fn update<'a, Message: Clone>(
} }
_ => {} _ => {}
} }
event::Status::Ignored
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -892,7 +879,6 @@ pub fn draw<Renderer: iced_core::Renderer, Theme>(
radius: styling.border_radius, radius: styling.border_radius,
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
Color::TRANSPARENT, Color::TRANSPARENT,
); );
@ -914,7 +900,6 @@ pub fn draw<Renderer: iced_core::Renderer, Theme>(
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
Background::Color([0.0, 0.0, 0.0, 0.5].into()), Background::Color([0.0, 0.0, 0.0, 0.5].into()),
); );
@ -930,7 +915,6 @@ pub fn draw<Renderer: iced_core::Renderer, Theme>(
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
background, background,
); );
@ -946,7 +930,6 @@ pub fn draw<Renderer: iced_core::Renderer, Theme>(
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
overlay, overlay,
); );
@ -970,7 +953,6 @@ pub fn draw<Renderer: iced_core::Renderer, Theme>(
radius: styling.border_radius, radius: styling.border_radius,
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
Color::TRANSPARENT, Color::TRANSPARENT,
); );

View file

@ -4,10 +4,10 @@
//! A widget that displays an interactive calendar. //! A widget that displays an interactive calendar.
use crate::fl; use crate::fl;
use crate::iced_core::{Alignment, Length};
use crate::widget::{button, column, grid, icon, row, text}; use crate::widget::{button, column, grid, icon, row, text};
use apply::Apply; use apply::Apply;
use iced::alignment::Vertical; use iced::alignment::Vertical;
use iced_core::{Alignment, Length};
use jiff::{ use jiff::{
ToSpan, ToSpan,
civil::{Date, Weekday}, civil::{Date, Weekday},
@ -212,10 +212,8 @@ where
let content_list = column::with_children([ let content_list = column::with_children([
row::with_children([ row::with_children([
column([date.into(), day.into()]).into(), column().push(date).push(day).into(),
crate::widget::space::horizontal() crate::widget::Space::with_width(Length::Fill).into(),
.width(Length::Fill)
.into(),
month_controls.into(), month_controls.into(),
]) ])
.align_y(Vertical::Center) .align_y(Vertical::Center)

View file

@ -1,586 +0,0 @@
//! An expandable stack of cards
use std::time::Duration;
use crate::{
anim,
widget::{
button,
card::style::Style,
column,
icon::{self, Handle},
row, text,
},
};
use float_cmp::approx_eq;
use iced::widget;
use iced_core::{
Border, Element, Event, Length, Shadow, Size, Vector, Widget, border::Radius, id::Id,
layout::Node, renderer::Quad, widget::Tree,
};
use iced_core::{widget::tree, window};
const ICON_SIZE: u16 = 16;
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
#[allow(clippy::too_many_arguments)]
pub fn cards<'a, Message, F, G>(
id: widget::Id,
card_inner_elements: Vec<Element<'a, Message, crate::Theme, crate::Renderer>>,
on_clear_all: Message,
on_show_more: Option<F>,
on_activate: Option<G>,
show_more_label: &'a str,
show_less_label: &'a str,
clear_all_label: &'a str,
show_less_icon: Option<Handle>,
expanded: bool,
) -> Cards<'a, Message, crate::Renderer>
where
Message: 'static + Clone,
F: 'a + Fn(bool) -> Message,
G: 'a + Fn(usize) -> Message,
{
Cards::new(
id,
card_inner_elements,
on_clear_all,
on_show_more,
on_activate,
show_more_label,
show_less_label,
clear_all_label,
show_less_icon,
expanded,
)
}
impl<'a, Message, Renderer> Cards<'a, Message, Renderer>
where
Renderer: iced_core::text::Renderer,
{
fn fully_expanded(&self, t: f32) -> bool {
self.expanded && self.elements.len() > 1 && self.can_show_more && approx_eq!(f32, t, 1.0)
}
fn fully_unexpanded(&self, t: f32) -> bool {
self.elements.len() == 1
|| (!self.expanded && (!self.can_show_more || approx_eq!(f32, t, 0.0)))
}
}
/// An expandable stack of cards.
#[allow(missing_debug_implementations)]
pub struct Cards<'a, Message, Renderer = crate::Renderer>
where
Renderer: iced_core::text::Renderer,
{
id: Id,
show_less_button: Element<'a, Message, crate::Theme, Renderer>,
clear_all_button: Element<'a, Message, crate::Theme, Renderer>,
elements: Vec<Element<'a, Message, crate::Theme, Renderer>>,
expanded: bool,
can_show_more: bool,
width: Length,
anim_multiplier: f32,
duration: Duration,
}
impl<'a, Message> Cards<'a, Message, crate::Renderer>
where
Message: Clone + 'static,
{
/// Get an expandable stack of cards
#[allow(clippy::too_many_arguments)]
pub fn new<F, G>(
id: widget::Id,
card_inner_elements: Vec<Element<'a, Message, crate::Theme, crate::Renderer>>,
on_clear_all: Message,
on_show_more: Option<F>,
on_activate: Option<G>,
show_more_label: &'a str,
show_less_label: &'a str,
clear_all_label: &'a str,
show_less_icon: Option<Handle>,
expanded: bool,
) -> Self
where
F: 'a + Fn(bool) -> Message,
G: 'a + Fn(usize) -> Message,
{
let can_show_more = card_inner_elements.len() > 1 && on_show_more.is_some();
Self {
can_show_more,
id: Id::unique(),
show_less_button: {
let mut show_less_children = Vec::with_capacity(3);
if let Some(source) = show_less_icon {
show_less_children.push(icon::icon(source).size(ICON_SIZE).into());
}
show_less_children.push(text::body(show_less_label).width(Length::Shrink).into());
show_less_children.push(
icon::from_name("pan-up-symbolic")
.size(ICON_SIZE)
.icon()
.into(),
);
let button_content = row::with_children(show_less_children)
.align_y(iced_core::Alignment::Center)
.spacing(TOP_SPACING)
.width(Length::Shrink);
Element::from(
button::custom(button_content)
.class(crate::theme::Button::Text)
.width(Length::Shrink)
.on_press_maybe(on_show_more.as_ref().map(|f| f(false)))
.padding([PADDING / 2, PADDING]),
)
},
clear_all_button: Element::from(
button::custom(text(clear_all_label))
.class(crate::theme::Button::Text)
.width(Length::Shrink)
.on_press(on_clear_all)
.padding([PADDING / 2, PADDING]),
),
elements: card_inner_elements
.into_iter()
.enumerate()
.map(|(i, w)| {
let custom_content = if i == 0 && !expanded && can_show_more {
column::with_capacity(2)
.push(w)
.push(text::caption(show_more_label))
.spacing(VERTICAL_SPACING)
.align_x(iced_core::Alignment::Center)
.into()
} else {
w
};
let b = crate::iced::widget::button(custom_content)
.class(crate::theme::iced::Button::Card)
.padding(PADDING);
if i == 0 && !expanded && can_show_more {
b.on_press_maybe(on_show_more.as_ref().map(|f| f(true)))
} else {
b.on_press_maybe(on_activate.as_ref().map(|f| f(i)))
}
.into()
})
// we will set the width of the container to shrink, then when laying out the top bar
// we will set the fill limit to the max of the shrink top bar width and the max shrink width of the
// cards
.collect(),
width: Length::Shrink,
anim_multiplier: 1.0,
expanded,
duration: Duration::from_millis(200),
}
}
/// Set the width of the cards stack
#[must_use]
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
#[must_use]
/// The default animation time is 100ms, to speed up the toggle
/// animation use a value less than 1.0, and to slow down the
/// animation use a value greater than 1.0.
pub fn anim_multiplier(mut self, multiplier: f32) -> Self {
self.anim_multiplier = multiplier;
self
}
pub fn duration(mut self, dur: Duration) -> Self {
self.duration = dur;
self
}
pub fn id(mut self, id: Id) -> Self {
self.id = id;
self
}
}
impl<'a, Message, Renderer> Widget<Message, crate::Theme, Renderer> for Cards<'a, Message, Renderer>
where
Message: 'a + Clone,
Renderer: 'a + iced_core::Renderer + iced_core::text::Renderer,
{
fn children(&self) -> Vec<Tree> {
[&self.show_less_button, &self.clear_all_button]
.iter()
.map(|w| Tree::new(w.as_widget()))
.chain(self.elements.iter().map(|w| Tree::new(w.as_widget())))
.collect()
}
fn diff(&mut self, tree: &mut Tree) {
let mut children: Vec<_> = vec![
self.show_less_button.as_widget_mut(),
self.clear_all_button.as_widget_mut(),
]
.into_iter()
.chain(
self.elements
.iter_mut()
.map(iced_core::Element::as_widget_mut),
)
.collect();
tree.diff_children(children.as_mut_slice());
}
#[allow(clippy::too_many_lines)]
fn layout(
&mut self,
tree: &mut Tree,
renderer: &Renderer,
limits: &iced_core::layout::Limits,
) -> iced_core::layout::Node {
let my_state = tree.state.downcast_ref::<State>();
let mut children = Vec::with_capacity(1 + self.elements.len());
let mut size = Size::new(0.0, 0.0);
let tree_children = &mut tree.children;
let count = self.elements.len();
if self.elements.is_empty() {
return Node::with_children(Size::new(1., 1.), children);
}
let s = anim::smootherstep(my_state.anim.t(self.duration, self.expanded));
let fully_expanded: bool = self.fully_expanded(s);
let fully_unexpanded: bool = self.fully_unexpanded(s);
let show_less = &mut self.show_less_button;
let clear_all = &mut self.clear_all_button;
let show_less_node = if self.can_show_more {
show_less
.as_widget_mut()
.layout(&mut tree_children[0], renderer, limits)
} else {
Node::new(Size::default())
};
let clear_all_node =
clear_all
.as_widget_mut()
.layout(&mut tree_children[1], renderer, limits);
size.width += show_less_node.size().width + clear_all_node.size().width;
let custom_limits = limits.min_width(size.width);
for (c, t) in self.elements.iter_mut().zip(tree_children[2..].iter_mut()) {
let card_node = c.as_widget_mut().layout(t, renderer, &custom_limits);
size.width = size.width.max(card_node.size().width);
}
if fully_expanded {
let show_less = &mut self.show_less_button;
let clear_all = &mut self.clear_all_button;
let show_less_node = if self.can_show_more {
show_less
.as_widget_mut()
.layout(&mut tree_children[0], renderer, limits)
} else {
Node::new(Size::default())
};
let clear_all_node = if self.can_show_more {
let mut n =
clear_all
.as_widget_mut()
.layout(&mut tree_children[1], renderer, limits);
let clear_all_node_size = n.size();
n = clear_all_node
.translate(Vector::new(size.width - clear_all_node_size.width, 0.0));
size.height += show_less_node.size().height.max(n.size().height) + VERTICAL_SPACING;
n
} else {
Node::new(Size::default())
};
children.push(show_less_node);
children.push(clear_all_node);
}
let custom_limits = limits
.min_width(size.width)
.max_width(size.width)
.width(Length::Fixed(size.width));
for (i, (c, t)) in self
.elements
.iter_mut()
.zip(tree_children[2..].iter_mut())
.enumerate()
{
let progress = s * size.height;
let card_node = c
.as_widget_mut()
.layout(t, renderer, &custom_limits)
.translate(Vector::new(0.0, progress));
size.height = size.height.max(progress + card_node.size().height);
children.push(card_node);
if fully_unexpanded {
let width = children.last().unwrap().bounds().width;
// push the background card nodes
for i in 1..self.elements.len().min(3) {
// height must be 16px for 8px padding
// but we only want 4px visible
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))
.translate(Vector::new(
margin,
size.height - BG_CARD_BORDER_RADIUS * 2.0 + BG_CARD_VISIBLE_HEIGHT,
));
size.height += BG_CARD_VISIBLE_HEIGHT;
children.push(node);
}
break;
}
if i + 1 < count {
size.height += VERTICAL_SPACING;
}
}
Node::with_children(size, children)
}
fn draw(
&self,
state: &iced_core::widget::Tree,
renderer: &mut Renderer,
theme: &crate::Theme,
style: &iced_core::renderer::Style,
layout: iced_core::Layout<'_>,
cursor: iced_core::mouse::Cursor,
viewport: &iced_core::Rectangle,
) {
let my_state = state.state.downcast_ref::<State>();
// there are 4 cases for drawing
// 1. empty entries list
// Nothing to draw
// 2. un-expanded
// go through the layout, draw the card, the inner card, and the bg cards
// 3. expanding / unexpanding
// go through the layout. draw each card and its inner card
// 4. expanded =>
// go through the layout. draw the top bar, and do all of 3
// cards may be hovered
// any buttons may have a hover state as well
if self.elements.is_empty() {
return;
}
let t = my_state.anim.t(self.duration, self.expanded);
let fully_unexpanded = self.fully_unexpanded(t);
let fully_expanded = self.fully_expanded(t);
let mut layout = layout.children();
let mut tree_children = state.children.iter();
if fully_expanded {
let show_less = &self.show_less_button;
let clear_all = &self.clear_all_button;
let show_less_layout = layout.next().unwrap();
let clear_all_layout = layout.next().unwrap();
show_less.as_widget().draw(
tree_children.next().unwrap(),
renderer,
theme,
style,
show_less_layout,
cursor,
viewport,
);
clear_all.as_widget().draw(
tree_children.next().unwrap(),
renderer,
theme,
style,
clear_all_layout,
cursor,
viewport,
);
} else {
_ = tree_children.next();
_ = tree_children.next();
}
// Draw first to appear behind
if fully_unexpanded {
let card_layout = layout.next().unwrap();
let appearance = Style::default();
let bg_layout = layout.collect::<Vec<_>>();
for (i, layout) in (0..2).zip(bg_layout.into_iter()).rev() {
renderer.fill_quad(
Quad {
bounds: layout.bounds(),
border: Border {
radius: Radius::from([
0.0,
0.0,
BG_CARD_BORDER_RADIUS,
BG_CARD_BORDER_RADIUS,
]),
..Default::default()
},
shadow: Shadow::default(),
snap: true,
},
if i == 0 {
appearance.card_1
} else {
appearance.card_2
},
);
}
self.elements[0].as_widget().draw(
tree_children.next().unwrap(),
renderer,
theme,
style,
card_layout,
cursor,
viewport,
);
} else {
let layout = layout.collect::<Vec<_>>();
// draw in reverse order so later cards appear behind earlier cards
for ((inner, layout), c_state) in self
.elements
.iter()
.rev()
.zip(layout.into_iter().rev())
.zip(tree_children.rev())
{
inner
.as_widget()
.draw(c_state, renderer, theme, style, layout, cursor, viewport);
}
}
}
fn update(
&mut self,
state: &mut Tree,
event: &iced_core::Event,
layout: iced_core::Layout<'_>,
cursor: iced_core::mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn iced_core::Clipboard,
shell: &mut iced_core::Shell<'_, Message>,
viewport: &iced_core::Rectangle,
) {
if self.elements.is_empty() {
return;
}
if let Event::Window(window::Event::RedrawRequested(_)) = event {
let state = state.state.downcast_mut::<State>();
state.anim.anim_done(self.duration);
if state.anim.last_change.is_some() {
shell.request_redraw();
shell.invalidate_layout();
}
}
let my_state = state.state.downcast_ref::<State>();
let mut layout = layout.children();
let mut tree_children = state.children.iter_mut();
let t = my_state.anim.t(self.duration, self.expanded);
let fully_expanded = self.fully_expanded(t);
let fully_unexpanded = self.fully_unexpanded(t);
let show_less_state = tree_children.next();
let clear_all_state = tree_children.next();
if fully_expanded {
let c_layout = layout.next().unwrap();
let state = show_less_state.unwrap();
self.show_less_button.as_widget_mut().update(
state, event, c_layout, cursor, renderer, clipboard, shell, viewport,
);
if shell.is_event_captured() {
return;
}
let c_layout = layout.next().unwrap();
let state = clear_all_state.unwrap();
self.clear_all_button.as_widget_mut().update(
state, &event, c_layout, cursor, renderer, clipboard, shell, viewport,
);
}
if shell.is_event_captured() {
return;
}
for ((inner, layout), c_state) in self.elements.iter_mut().zip(layout).zip(tree_children) {
inner.as_widget_mut().update(
c_state, &event, layout, cursor, renderer, clipboard, shell, viewport,
);
if shell.is_event_captured() || fully_unexpanded {
break;
}
}
}
fn size(&self) -> Size<Length> {
Size::new(self.width, Length::Shrink)
}
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
}
fn state(&self) -> tree::State {
tree::State::new(State::default())
}
fn id(&self) -> Option<Id> {
Some(self.id.clone())
}
fn set_id(&mut self, id: Id) {
self.id = id;
}
}
impl<'a, Message> From<Cards<'a, Message>> for Element<'a, Message, crate::Theme, crate::Renderer>
where
Message: Clone + 'a,
{
fn from(cards: Cards<'a, Message>) -> Self {
Self::new(cards)
}
}
#[derive(Debug, Default)]
pub struct State {
anim: anim::State,
}

View file

@ -4,6 +4,7 @@
//! Widgets for selecting colors with a color picker. //! Widgets for selecting colors with a color picker.
use std::borrow::Cow; use std::borrow::Cow;
use std::iter;
use std::rc::Rc; use std::rc::Rc;
use std::sync::LazyLock; use std::sync::LazyLock;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
@ -25,10 +26,7 @@ use iced_core::{
}; };
use iced_widget::slider::HandleShape; use iced_widget::slider::HandleShape;
use iced_widget::{ use iced_widget::{Row, canvas, column, horizontal_space, row, scrollable, vertical_space};
Row, canvas, column, row, scrollable,
space::{horizontal, vertical},
};
use palette::{FromColor, RgbHue}; use palette::{FromColor, RgbHue};
use super::divider::horizontal; use super::divider::horizontal;
@ -92,6 +90,8 @@ pub struct ColorPickerModel {
#[setters(skip)] #[setters(skip)]
active_color: palette::Hsv, active_color: palette::Hsv,
#[setters(skip)] #[setters(skip)]
save_next: Option<Color>,
#[setters(skip)]
input_color: String, input_color: String,
#[setters(skip)] #[setters(skip)]
applied_color: Option<Color>, applied_color: Option<Color>,
@ -125,6 +125,7 @@ impl ColorPickerModel {
.insert(move |b| b.text(rgb.clone())) .insert(move |b| b.text(rgb.clone()))
.build(), .build(),
active_color: hsv, active_color: hsv,
save_next: None,
input_color: color_to_string(hsv, true), input_color: color_to_string(hsv, true),
applied_color: initial, applied_color: initial,
fallback_color, fallback_color,
@ -155,26 +156,22 @@ impl ColorPickerModel {
) )
} }
fn update_recent_colors(&mut self, new_color: Color) {
if let Some(pos) = self.recent_colors.iter().position(|c| *c == new_color) {
self.recent_colors.remove(pos);
}
self.recent_colors.insert(0, new_color);
self.recent_colors.truncate(MAX_RECENT);
}
pub fn update<Message>(&mut self, update: ColorPickerUpdate) -> Task<Message> { pub fn update<Message>(&mut self, update: ColorPickerUpdate) -> Task<Message> {
match update { match update {
ColorPickerUpdate::ActiveColor(c) => { ColorPickerUpdate::ActiveColor(c) => {
self.must_clear_cache.store(true, Ordering::SeqCst); self.must_clear_cache.store(true, Ordering::SeqCst);
self.input_color = color_to_string(c, self.is_hex()); self.input_color = color_to_string(c, self.is_hex());
if let Some(to_save) = self.save_next.take() {
self.recent_colors.insert(0, to_save);
self.recent_colors.truncate(MAX_RECENT);
}
self.active_color = c; self.active_color = c;
self.copied_at = None; self.copied_at = None;
} }
ColorPickerUpdate::AppliedColor | ColorPickerUpdate::ActionFinished => { ColorPickerUpdate::AppliedColor => {
let srgb = palette::Srgb::from_color(self.active_color); let srgb = palette::Srgb::from_color(self.active_color);
if let Some(applied_color) = self.applied_color.take() { if let Some(applied_color) = self.applied_color.take() {
self.update_recent_colors(applied_color); self.recent_colors.push(applied_color);
} }
self.applied_color = Some(Color::from(srgb)); self.applied_color = Some(Color::from(srgb));
self.active = false; self.active = false;
@ -215,12 +212,21 @@ impl ColorPickerModel {
palette::Hsv::from_color(palette::Srgb::new(c.red, c.green, c.blue)); palette::Hsv::from_color(palette::Srgb::new(c.red, c.green, c.blue));
} }
} }
ColorPickerUpdate::ActionFinished => {
let srgb = palette::Srgb::from_color(self.active_color);
if let Some(applied_color) = self.applied_color.take() {
self.recent_colors.push(applied_color);
}
self.applied_color = Some(Color::from(srgb));
self.active = false;
self.save_next = Some(Color::from(srgb));
}
ColorPickerUpdate::ToggleColorPicker => { ColorPickerUpdate::ToggleColorPicker => {
self.must_clear_cache.store(true, Ordering::SeqCst); self.must_clear_cache.store(true, Ordering::SeqCst);
self.active = !self.active; self.active = !self.active;
self.copied_at = None; self.copied_at = None;
} }
} };
Task::none() Task::none()
} }
@ -328,7 +334,7 @@ where
.width(self.width), .width(self.width),
// canvas with gradient for the current color // canvas with gradient for the current color
// still needs the canvas and the handle to be drawn on it // still needs the canvas and the handle to be drawn on it
container(vertical().height(self.height)) container(vertical_space().height(self.height))
.width(self.width) .width(self.width)
.height(self.height), .height(self.height),
slider( slider(
@ -386,8 +392,7 @@ where
text_input("", self.input_color) text_input("", self.input_color)
.on_input(move |s| on_update(ColorPickerUpdate::Input(s))) .on_input(move |s| on_update(ColorPickerUpdate::Input(s)))
.on_paste(move |s| on_update(ColorPickerUpdate::Input(s))) .on_paste(move |s| on_update(ColorPickerUpdate::Input(s)))
.on_submit(move |_| on_update(ColorPickerUpdate::ActionFinished)) .on_submit(move |_| on_update(ColorPickerUpdate::AppliedColor))
// .on_unfocus(on_update(ColorPickerUpdate::ActionFinished)) Somehow this is called even when the field wasn't previously focused
.leading_icon( .leading_icon(
color_button( color_button(
None, None,
@ -543,13 +548,13 @@ where
} }
fn layout( fn layout(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
renderer: &crate::Renderer, renderer: &crate::Renderer,
limits: &layout::Limits, limits: &layout::Limits,
) -> layout::Node { ) -> layout::Node {
self.inner self.inner
.as_widget_mut() .as_widget()
.layout(&mut tree.children[0], renderer, limits) .layout(&mut tree.children[0], renderer, limits)
} }
@ -652,7 +657,6 @@ where
radius: (1.0 + handle_radius).into(), radius: (1.0 + handle_radius).into(),
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
Color::TRANSPARENT, Color::TRANSPARENT,
); );
@ -670,7 +674,6 @@ where
radius: handle_radius.into(), radius: handle_radius.into(),
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
Color::TRANSPARENT, Color::TRANSPARENT,
); );
@ -681,31 +684,26 @@ where
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
state: &'b mut Tree, state: &'b mut Tree,
layout: Layout<'b>, layout: Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
viewport: &Rectangle,
translation: Vector, translation: Vector,
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
self.inner.as_widget_mut().overlay( self.inner
&mut state.children[0], .as_widget_mut()
layout, .overlay(&mut state.children[0], layout, renderer, translation)
renderer,
viewport,
translation,
)
} }
fn update( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &crate::Renderer, renderer: &crate::Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) { ) -> event::Status {
// if the pointer is performing a drag, intercept pointer motion and button events // if the pointer is performing a drag, intercept pointer motion and button events
// else check if event is handled by child elements // else check if event is handled by child elements
// if the event is not handled by a child element, check if it is over the canvas when pressing a button // if the event is not handled by a child element, check if it is over the canvas when pressing a button
@ -734,26 +732,24 @@ where
shell.publish((self.on_update)(ColorPickerUpdate::ActionFinished)); shell.publish((self.on_update)(ColorPickerUpdate::ActionFinished));
state.dragging = false; state.dragging = false;
} }
_ => return, _ => return event::Status::Ignored,
}; };
shell.capture_event(); return event::Status::Captured;
return;
} }
let column_tree = &mut tree.children[0]; let column_tree = &mut tree.children[0];
self.inner.as_widget_mut().update( if self.inner.as_widget_mut().on_event(
column_tree, column_tree,
&event, event.clone(),
column_layout, column_layout,
cursor, cursor,
renderer, renderer,
clipboard, clipboard,
shell, shell,
viewport, viewport,
); ) == event::Status::Captured
if shell.is_event_captured() { {
shell.capture_event(); return event::Status::Captured;
return;
} }
match event { match event {
@ -768,10 +764,12 @@ where
state.dragging = true; state.dragging = true;
let hsv: palette::Hsv = palette::Hsv::new(self.active_color.hue, s, v); let hsv: palette::Hsv = palette::Hsv::new(self.active_color.hue, s, v);
shell.publish((self.on_update)(ColorPickerUpdate::ActiveColor(hsv))); shell.publish((self.on_update)(ColorPickerUpdate::ActiveColor(hsv)));
shell.capture_event(); event::Status::Captured
} else {
event::Status::Ignored
} }
} }
_ => {} _ => event::Status::Ignored,
} }
} }
@ -814,12 +812,12 @@ pub fn color_button<'a, Message: Clone + 'static>(
let spacing = THEME.lock().unwrap().cosmic().spacing; let spacing = THEME.lock().unwrap().cosmic().spacing;
button::custom(if color.is_some() { button::custom(if color.is_some() {
Element::from(vertical().height(Length::Fixed(f32::from(spacing.space_s)))) Element::from(vertical_space().height(Length::Fixed(f32::from(spacing.space_s))))
} else { } else {
Element::from(column![ Element::from(column![
vertical().height(Length::FillPortion(6)), vertical_space().height(Length::FillPortion(6)),
row![ row![
horizontal().width(Length::FillPortion(6)), horizontal_space().width(Length::FillPortion(6)),
Icon::from( Icon::from(
icon::from_name("list-add-symbolic") icon::from_name("list-add-symbolic")
.prefer_svg(true) .prefer_svg(true)
@ -829,11 +827,11 @@ pub fn color_button<'a, Message: Clone + 'static>(
.width(icon_portion) .width(icon_portion)
.height(Length::Fill) .height(Length::Fill)
.content_fit(iced_core::ContentFit::Contain), .content_fit(iced_core::ContentFit::Contain),
horizontal().width(Length::FillPortion(6)), horizontal_space().width(Length::FillPortion(6)),
] ]
.height(icon_portion) .height(icon_portion)
.width(Length::Fill), .width(Length::Fill),
vertical().height(Length::FillPortion(6)), vertical_space().height(Length::FillPortion(6)),
]) ])
}) })
.width(Length::Fixed(f32::from(spacing.space_s))) .width(Length::Fixed(f32::from(spacing.space_s)))

View file

@ -7,8 +7,8 @@ use iced::advanced::layout::{self, Layout};
use iced::advanced::widget::{self, Operation}; use iced::advanced::widget::{self, Operation};
use iced::advanced::{Clipboard, Shell}; use iced::advanced::{Clipboard, Shell};
use iced::advanced::{overlay, renderer}; use iced::advanced::{overlay, renderer};
use iced::{Event, Point, Size, mouse}; use iced::{Event, Point, Rectangle, Size, event, mouse};
use iced_core::{Renderer, touch}; use iced_core::Renderer;
pub(super) struct Overlay<'a, 'b, Message> { pub(super) struct Overlay<'a, 'b, Message> {
pub(crate) position: Point, pub(crate) position: Point,
@ -29,7 +29,7 @@ where
let node = self let node = self
.content .content
.as_widget_mut() .as_widget()
.layout(self.tree, renderer, &limits); .layout(self.tree, renderer, &limits);
let node_size = node.size(); let node_size = node.size();
@ -47,16 +47,16 @@ where
}) })
} }
fn update( fn on_event(
&mut self, &mut self,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &crate::Renderer, renderer: &crate::Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
) { ) -> event::Status {
self.content.as_widget_mut().update( self.content.as_widget_mut().on_event(
self.tree, self.tree,
event, event,
layout, layout,
@ -65,20 +65,7 @@ where
clipboard, clipboard,
shell, shell,
&layout.bounds(), &layout.bounds(),
); )
match event {
Event::Mouse(e) if !matches!(e, mouse::Event::CursorLeft) => {
if cursor.is_over(layout.bounds()) {
shell.capture_event();
}
}
Event::Touch(e) if !matches!(e, touch::Event::FingerLost { .. }) => {
if cursor.is_over(layout.bounds()) {
shell.capture_event();
}
}
_ => {}
}
} }
fn draw( fn draw(
@ -99,7 +86,7 @@ where
cursor, cursor,
&layout.bounds(), &layout.bounds(),
); );
}); })
} }
fn operate( fn operate(
@ -117,35 +104,21 @@ where
&self, &self,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &crate::Renderer, renderer: &crate::Renderer,
) -> mouse::Interaction { ) -> mouse::Interaction {
// TODO how to handle viewport here? self.content
let viewport = &layout.bounds();
let interaction = self
.content
.as_widget() .as_widget()
.mouse_interaction(self.tree, layout, cursor, viewport, renderer); .mouse_interaction(self.tree, layout, cursor, viewport, renderer)
if let mouse::Interaction::None = interaction
&& cursor.is_over(layout.bounds())
{
return mouse::Interaction::Idle;
}
interaction
} }
fn overlay<'c>( fn overlay<'c>(
&'c mut self, &'c mut self,
layout: Layout<'c>, layout: Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
) -> Option<overlay::Element<'c, Message, crate::Theme, crate::Renderer>> { ) -> Option<overlay::Element<'c, Message, crate::Theme, crate::Renderer>> {
let viewport = &layout.bounds(); self.content
.as_widget_mut()
self.content.as_widget_mut().overlay( .overlay(self.tree, layout, renderer, iced::Vector::default())
self.tree,
layout,
renderer,
viewport,
iced::Vector::default(),
)
} }
} }

View file

@ -7,7 +7,7 @@ use crate::{Apply, Element, Renderer, Theme, fl};
use std::borrow::Cow; use std::borrow::Cow;
use iced_core::Alignment; use iced_core::Alignment;
use iced_core::event::Event; use iced_core::event::{self, Event};
use iced_core::widget::{Operation, Tree}; use iced_core::widget::{Operation, Tree};
use iced_core::{ use iced_core::{
Clipboard, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, Clipboard, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse,
@ -65,7 +65,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
} else { } else {
let title = title let title = title
.map(|title| text::title4(title).width(Length::Fill).apply(Element::from)) .map(|title| text::title4(title).width(Length::Fill).apply(Element::from))
.unwrap_or_else(|| widget::space::horizontal().apply(Element::from)); .unwrap_or_else(|| widget::horizontal_space().apply(Element::from));
(title, None) (title, None)
}; };
@ -196,40 +196,40 @@ impl<Message: Clone> Widget<Message, crate::Theme, Renderer> for ContextDrawer<'
} }
fn layout( fn layout(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
renderer: &Renderer, renderer: &Renderer,
limits: &layout::Limits, limits: &layout::Limits,
) -> layout::Node { ) -> layout::Node {
self.content self.content
.as_widget_mut() .as_widget()
.layout(&mut tree.children[0], renderer, limits) .layout(&mut tree.children[0], renderer, limits)
} }
fn operate( fn operate(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn Operation<()>, operation: &mut dyn Operation<()>,
) { ) {
self.content self.content
.as_widget_mut() .as_widget()
.operate(&mut tree.children[0], layout, renderer, operation); .operate(&mut tree.children[0], layout, renderer, operation);
} }
fn update( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &Renderer, renderer: &Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) { ) -> event::Status {
self.content.as_widget_mut().update( self.content.as_widget_mut().on_event(
&mut tree.children[0], &mut tree.children[0],
event, event,
layout, layout,
@ -238,7 +238,7 @@ impl<Message: Clone> Widget<Message, crate::Theme, Renderer> for ContextDrawer<'
clipboard, clipboard,
shell, shell,
viewport, viewport,
); )
} }
fn mouse_interaction( fn mouse_interaction(
@ -282,9 +282,8 @@ impl<Message: Clone> Widget<Message, crate::Theme, Renderer> for ContextDrawer<'
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'b>, layout: Layout<'_>,
_renderer: &Renderer, _renderer: &Renderer,
_viewport: &Rectangle,
translation: Vector, translation: Vector,
) -> Option<iced_overlay::Element<'b, Message, crate::Theme, Renderer>> { ) -> Option<iced_overlay::Element<'b, Message, crate::Theme, Renderer>> {
let bounds = layout.bounds(); let bounds = layout.bounds();

View file

@ -3,12 +3,7 @@
//! A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation. //! A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation.
#[cfg(all( #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
feature = "wayland",
target_os = "linux",
feature = "winit",
feature = "surface-message"
))]
use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem};
use crate::widget::menu::{ use crate::widget::menu::{
self, CloseCondition, Direction, ItemHeight, ItemWidth, MenuBarState, PathHighlight, self, CloseCondition, Direction, ItemHeight, ItemWidth, MenuBarState, PathHighlight,
@ -18,7 +13,7 @@ use derive_setters::Setters;
use iced::touch::Finger; use iced::touch::Finger;
use iced::{Event, Vector, keyboard, window}; use iced::{Event, Vector, keyboard, window};
use iced_core::widget::{Tree, Widget, tree}; use iced_core::widget::{Tree, Widget, tree};
use iced_core::{Length, Point, Size, mouse, touch}; use iced_core::{Length, Point, Size, event, mouse, touch};
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc; use std::sync::Arc;
@ -32,7 +27,7 @@ pub fn context_menu<'a, Message: 'static + Clone>(
content: content.into(), content: content.into(),
context_menu: context_menu.map(|menus| { context_menu: context_menu.map(|menus| {
vec![menu::Tree::with_children( vec![menu::Tree::with_children(
crate::Element::from(crate::widget::Row::new()), crate::Element::from(crate::widget::row::<'static, Message>()),
menus, menus,
)] )]
}), }),
@ -64,12 +59,7 @@ pub struct ContextMenu<'a, Message> {
} }
impl<Message: Clone + 'static> ContextMenu<'_, Message> { impl<Message: Clone + 'static> ContextMenu<'_, Message> {
#[cfg(all( #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
feature = "wayland",
target_os = "linux",
feature = "winit",
feature = "surface-message"
))]
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn create_popup( fn create_popup(
&mut self, &mut self,
@ -95,7 +85,6 @@ impl<Message: Clone + 'static> ContextMenu<'_, Message> {
// close existing popups // close existing popups
state.menu_states.clear(); state.menu_states.clear();
state.active_root.clear(); state.active_root.clear();
shell.publish(self.on_surface_action.as_ref().unwrap()(destroy_popup(id))); shell.publish(self.on_surface_action.as_ref().unwrap()(destroy_popup(id)));
state.view_cursor = view_cursor; state.view_cursor = view_cursor;
( (
@ -260,7 +249,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
} }
fn diff(&mut self, tree: &mut Tree) { fn diff(&mut self, tree: &mut Tree) {
tree.diff_children(std::slice::from_mut(&mut self.content)); tree.children[0].diff(self.content.as_widget_mut());
let state = tree.state.downcast_mut::<LocalState>(); let state = tree.state.downcast_mut::<LocalState>();
state.menu_bar_state.inner.with_data_mut(|inner| { state.menu_bar_state.inner.with_data_mut(|inner| {
menu_roots_diff(self.context_menu.as_mut().unwrap(), &mut inner.tree); menu_roots_diff(self.context_menu.as_mut().unwrap(), &mut inner.tree);
@ -281,13 +270,13 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
} }
fn layout( fn layout(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
renderer: &crate::Renderer, renderer: &crate::Renderer,
limits: &iced_core::layout::Limits, limits: &iced_core::layout::Limits,
) -> iced_core::layout::Node { ) -> iced_core::layout::Node {
self.content self.content
.as_widget_mut() .as_widget()
.layout(&mut tree.children[0], renderer, limits) .layout(&mut tree.children[0], renderer, limits)
} }
@ -313,29 +302,29 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
} }
fn operate( fn operate(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
layout: iced_core::Layout<'_>, layout: iced_core::Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
operation: &mut dyn iced_core::widget::Operation<()>, operation: &mut dyn iced_core::widget::Operation<()>,
) { ) {
self.content self.content
.as_widget_mut() .as_widget()
.operate(&mut tree.children[0], layout, renderer, operation); .operate(&mut tree.children[0], layout, renderer, operation);
} }
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn update( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: &iced::Event, event: iced::Event,
layout: iced_core::Layout<'_>, layout: iced_core::Layout<'_>,
cursor: iced_core::mouse::Cursor, cursor: iced_core::mouse::Cursor,
renderer: &crate::Renderer, renderer: &crate::Renderer,
clipboard: &mut dyn iced_core::Clipboard, clipboard: &mut dyn iced_core::Clipboard,
shell: &mut iced_core::Shell<'_, Message>, shell: &mut iced_core::Shell<'_, Message>,
viewport: &iced::Rectangle, viewport: &iced::Rectangle,
) { ) -> iced_core::event::Status {
let state = tree.state.downcast_mut::<LocalState>(); let state = tree.state.downcast_mut::<LocalState>();
let bounds = layout.bounds(); let bounds = layout.bounds();
@ -347,12 +336,13 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
.with_data(|d| !d.open && !d.active_root.is_empty()); .with_data(|d| !d.open && !d.active_root.is_empty());
let open = state.menu_bar_state.inner.with_data_mut(|state| { let open = state.menu_bar_state.inner.with_data_mut(|state| {
if reset if reset {
&& let Some(popup_id) = state.popup_id.get(&self.window_id).copied() if let Some(popup_id) = state.popup_id.get(&self.window_id).copied() {
&& let Some(handler) = self.on_surface_action.as_ref() if let Some(handler) = self.on_surface_action.as_ref() {
{ shell.publish((handler)(crate::surface::Action::DestroyPopup(popup_id)));
shell.publish((handler)(crate::surface::Action::DestroyPopup(popup_id))); state.reset();
state.reset(); }
}
} }
state.open state.open
}); });
@ -366,6 +356,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
mouse::Button::Right | mouse::Button::Left, mouse::Button::Right | mouse::Button::Left,
)) ))
| Event::Touch(touch::Event::FingerPressed { .. }) | Event::Touch(touch::Event::FingerPressed { .. })
| Event::Window(window::Event::Focused)
if open ) if open )
{ {
state.menu_bar_state.inner.with_data_mut(|state| { state.menu_bar_state.inner.with_data_mut(|state| {
@ -374,20 +365,16 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
state.active_root.clear(); state.active_root.clear();
state.open = false; state.open = false;
#[cfg(all( #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
feature = "wayland", if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
target_os = "linux", if let Some(id) = state.popup_id.remove(&self.window_id) {
feature = "winit", {
feature = "surface-message" let surface_action = self.on_surface_action.as_ref().unwrap();
))] shell
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) .publish(surface_action(crate::surface::action::destroy_popup(id)));
&& let Some(id) = state.popup_id.remove(&self.window_id) }
{ state.view_cursor = cursor;
{
let surface_action = self.on_surface_action.as_ref().unwrap();
shell.publish(surface_action(crate::surface::action::destroy_popup(id)));
} }
state.view_cursor = cursor;
} }
}); });
} }
@ -397,11 +384,11 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
match event { match event {
Event::Touch(touch::Event::FingerPressed { id, .. }) => { Event::Touch(touch::Event::FingerPressed { id, .. }) => {
state.fingers_pressed.insert(*id); state.fingers_pressed.insert(id);
} }
Event::Touch(touch::Event::FingerLifted { id, .. }) => { Event::Touch(touch::Event::FingerLifted { id, .. }) => {
state.fingers_pressed.remove(id); state.fingers_pressed.remove(&id);
} }
_ => (), _ => (),
@ -410,7 +397,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
// Present a context menu on a right click event. // Present a context menu on a right click event.
if !was_open if !was_open
&& self.context_menu.is_some() && self.context_menu.is_some()
&& (right_button_released(event) || (touch_lifted(event) && fingers_pressed == 2)) && (right_button_released(&event) || (touch_lifted(&event) && fingers_pressed == 2))
{ {
state.context_cursor = cursor.position().unwrap_or_default(); state.context_cursor = cursor.position().unwrap_or_default();
let state = tree.state.downcast_mut::<LocalState>(); let state = tree.state.downcast_mut::<LocalState>();
@ -418,21 +405,15 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
state.open = true; state.open = true;
state.view_cursor = cursor; state.view_cursor = cursor;
}); });
#[cfg(all( #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
feature = "wayland",
target_os = "linux",
feature = "winit",
feature = "surface-message"
))]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
self.create_popup(layout, cursor, renderer, shell, viewport, state); self.create_popup(layout, cursor, renderer, shell, viewport, state);
} }
shell.capture_event(); return event::Status::Captured;
return; } else if !was_open && right_button_released(&event)
} else if !was_open && right_button_released(event) || (touch_lifted(&event))
|| (touch_lifted(event)) || left_button_released(&event)
|| left_button_released(event)
{ {
state.menu_bar_state.inner.with_data_mut(|state| { state.menu_bar_state.inner.with_data_mut(|state| {
was_open = true; was_open = true;
@ -442,24 +423,24 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
#[cfg(all( #[cfg(all(
feature = "wayland", feature = "wayland",
target_os = "linux",
feature = "winit", feature = "winit",
feature = "surface-message" feature = "surface-message"
))] ))]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
&& let Some(id) = state.popup_id.remove(&self.window_id) if let Some(id) = state.popup_id.remove(&self.window_id) {
{ {
{ let surface_action = self.on_surface_action.as_ref().unwrap();
let surface_action = self.on_surface_action.as_ref().unwrap(); shell.publish(surface_action(
shell crate::surface::action::destroy_popup(id),
.publish(surface_action(crate::surface::action::destroy_popup(id))); ));
}
state.view_cursor = cursor;
} }
state.view_cursor = cursor;
} }
}); });
} }
} }
self.content.as_widget_mut().update( self.content.as_widget_mut().on_event(
&mut tree.children[0], &mut tree.children[0],
event, event,
layout, layout,
@ -468,7 +449,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
clipboard, clipboard,
shell, shell,
viewport, viewport,
); )
} }
fn overlay<'b>( fn overlay<'b>(
@ -476,15 +457,9 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
tree: &'b mut Tree, tree: &'b mut Tree,
layout: iced_core::Layout<'_>, layout: iced_core::Layout<'_>,
_renderer: &crate::Renderer, _renderer: &crate::Renderer,
_viewport: &iced::Rectangle,
translation: Vector, translation: Vector,
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
#[cfg(all( #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
feature = "wayland",
target_os = "linux",
feature = "winit",
feature = "surface-message"
))]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
&& self.window_id != window::Id::NONE && self.window_id != window::Id::NONE
&& self.on_surface_action.is_some() && self.on_surface_action.is_some()

View file

@ -123,7 +123,7 @@ impl<'a, Message: Clone + 'static> From<Dialog<'a, Message>> for Element<'a, Mes
if let Some(body) = dialog.body { if let Some(body) = dialog.body {
if should_space { if should_space {
content_col = content_col content_col = content_col
.push(widget::space::vertical().height(Length::Fixed(space_xxs.into()))); .push(widget::vertical_space().height(Length::Fixed(space_xxs.into())));
} }
content_col = content_col.push( content_col = content_col.push(
widget::container(widget::scrollable(widget::text::body(body))).max_height(300.), widget::container(widget::scrollable(widget::text::body(body))).max_height(300.),
@ -133,7 +133,7 @@ impl<'a, Message: Clone + 'static> From<Dialog<'a, Message>> for Element<'a, Mes
for control in dialog.controls { for control in dialog.controls {
if should_space { if should_space {
content_col = content_col content_col = content_col
.push(widget::space::vertical().height(Length::Fixed(space_s.into()))); .push(widget::vertical_space().height(Length::Fixed(space_s.into())));
} }
content_col = content_col.push(control); content_col = content_col.push(control);
should_space = true; should_space = true;
@ -149,7 +149,7 @@ impl<'a, Message: Clone + 'static> From<Dialog<'a, Message>> for Element<'a, Mes
if let Some(button) = dialog.tertiary_action { if let Some(button) = dialog.tertiary_action {
button_row = button_row.push(button); button_row = button_row.push(button);
} }
button_row = button_row.push(widget::space::horizontal()); button_row = button_row.push(widget::horizontal_space());
if let Some(button) = dialog.secondary_action { if let Some(button) = dialog.secondary_action {
button_row = button_row.push(button); button_row = button_row.push(button);
} }

View file

@ -7,22 +7,21 @@ use iced::Vector;
use crate::{ use crate::{
Element, Element,
widget::{Id, Widget}, iced::{
}; Event, Length, Rectangle,
clipboard::{
use iced::{ dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent},
Event, Length, Rectangle, mime::AllowedMimeTypes,
clipboard::{ },
dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent}, event,
mime::AllowedMimeTypes, id::Internal,
mouse, overlay,
}, },
event, iced_core::{
id::Internal, self, Clipboard, Shell, layout,
mouse, overlay, widget::{Tree, tree},
}; },
use iced_core::{ widget::{Id, Widget},
self, Clipboard, Shell, layout,
widget::{Tree, tree},
}; };
pub fn dnd_destination<'a, Message: 'static>( pub fn dnd_destination<'a, Message: 'static>(
@ -292,7 +291,7 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
} }
fn diff(&mut self, tree: &mut Tree) { fn diff(&mut self, tree: &mut Tree) {
tree.diff_children(std::slice::from_mut(&mut self.container)); tree.children[0].diff(self.container.as_widget_mut());
} }
fn state(&self) -> iced_core::widget::tree::State { fn state(&self) -> iced_core::widget::tree::State {
@ -304,43 +303,43 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
} }
fn layout( fn layout(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
renderer: &crate::Renderer, renderer: &crate::Renderer,
limits: &layout::Limits, limits: &layout::Limits,
) -> layout::Node { ) -> layout::Node {
self.container self.container
.as_widget_mut() .as_widget()
.layout(&mut tree.children[0], renderer, limits) .layout(&mut tree.children[0], renderer, limits)
} }
fn operate( fn operate(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
layout: layout::Layout<'_>, layout: layout::Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
operation: &mut dyn iced_core::widget::Operation<()>, operation: &mut dyn iced_core::widget::Operation<()>,
) { ) {
self.container self.container
.as_widget_mut() .as_widget()
.operate(&mut tree.children[0], layout, renderer, operation); .operate(&mut tree.children[0], layout, renderer, operation);
} }
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn update( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: &Event, event: Event,
layout: layout::Layout<'_>, layout: layout::Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &crate::Renderer, renderer: &crate::Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) { ) -> event::Status {
self.container.as_widget_mut().update( let s = self.container.as_widget_mut().on_event(
&mut tree.children[0], &mut tree.children[0],
event, event.clone(),
layout, layout,
cursor, cursor,
renderer, renderer,
@ -348,8 +347,8 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
shell, shell,
viewport, viewport,
); );
if shell.is_event_captured() { if matches!(s, event::Status::Captured) {
return; return event::Status::Captured;
} }
let state = tree.state.downcast_mut::<State<()>>(); let state = tree.state.downcast_mut::<State<()>>();
@ -368,23 +367,23 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
OfferEvent::Enter { OfferEvent::Enter {
x, y, mime_types, .. x, y, mime_types, ..
}, },
)) if *id == Some(my_id) => { )) if id == Some(my_id) => {
if !self.mime_matches(&mime_types) { if !self.mime_matches(&mime_types) {
log::trace!( log::trace!(
target: DND_DEST_LOG_TARGET, target: DND_DEST_LOG_TARGET,
"offer enter id={my_id:?} ignored (mimes={mime_types:?} not in {:?})", "offer enter id={my_id:?} ignored (mimes={mime_types:?} not in {:?})",
self.mime_types self.mime_types
); );
return; return event::Status::Ignored;
} }
log::trace!( log::trace!(
target: DND_DEST_LOG_TARGET, target: DND_DEST_LOG_TARGET,
"offer enter id={my_id:?} coords=({x},{y}) mimes={mime_types:?}" "offer enter id={my_id:?} coords=({x},{y}) mimes={mime_types:?}"
); );
if let Some(msg) = state.on_enter( if let Some(msg) = state.on_enter(
*x, x,
*y, y,
mime_types.clone(), mime_types,
self.on_enter.as_ref().map(std::convert::AsRef::as_ref), self.on_enter.as_ref().map(std::convert::AsRef::as_ref),
(), (),
) { ) {
@ -392,13 +391,13 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
} }
if self.forward_drag_as_cursor { if self.forward_drag_as_cursor {
#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_truncation)]
let drag_cursor = mouse::Cursor::Available((*x as f32, *y as f32).into()); let drag_cursor = mouse::Cursor::Available((x as f32, y as f32).into());
let event = Event::Mouse(mouse::Event::CursorMoved { let event = Event::Mouse(mouse::Event::CursorMoved {
position: drag_cursor.position().unwrap(), position: drag_cursor.position().unwrap(),
}); });
self.container.as_widget_mut().update( self.container.as_widget_mut().on_event(
&mut tree.children[0], &mut tree.children[0],
&event, event,
layout, layout,
drag_cursor, drag_cursor,
renderer, renderer,
@ -407,8 +406,7 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
viewport, viewport,
); );
} }
shell.capture_event(); return event::Status::Captured;
return;
} }
Event::Dnd(DndEvent::Offer(_, OfferEvent::Leave)) => { Event::Dnd(DndEvent::Offer(_, OfferEvent::Leave)) => {
log::trace!( log::trace!(
@ -425,9 +423,9 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
if self.forward_drag_as_cursor { if self.forward_drag_as_cursor {
let drag_cursor = mouse::Cursor::Unavailable; let drag_cursor = mouse::Cursor::Unavailable;
let event = Event::Mouse(mouse::Event::CursorLeft); let event = Event::Mouse(mouse::Event::CursorLeft);
self.container.as_widget_mut().update( self.container.as_widget_mut().on_event(
&mut tree.children[0], &mut tree.children[0],
&event, event,
layout, layout,
drag_cursor, drag_cursor,
renderer, renderer,
@ -436,16 +434,16 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
viewport, viewport,
); );
} }
return; return event::Status::Ignored;
} }
Event::Dnd(DndEvent::Offer(id, OfferEvent::Motion { x, y })) if *id == Some(my_id) => { Event::Dnd(DndEvent::Offer(id, OfferEvent::Motion { x, y })) if id == Some(my_id) => {
log::trace!( log::trace!(
target: DND_DEST_LOG_TARGET, target: DND_DEST_LOG_TARGET,
"offer motion id={my_id:?} coords=({x},{y})" "offer motion id={my_id:?} coords=({x},{y})"
); );
if let Some(msg) = state.on_motion( if let Some(msg) = state.on_motion(
*x, x,
*y, y,
self.on_motion.as_ref().map(std::convert::AsRef::as_ref), self.on_motion.as_ref().map(std::convert::AsRef::as_ref),
self.on_enter.as_ref().map(std::convert::AsRef::as_ref), self.on_enter.as_ref().map(std::convert::AsRef::as_ref),
(), (),
@ -455,13 +453,13 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
if self.forward_drag_as_cursor { if self.forward_drag_as_cursor {
#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_truncation)]
let drag_cursor = mouse::Cursor::Available((*x as f32, *y as f32).into()); let drag_cursor = mouse::Cursor::Available((x as f32, y as f32).into());
let event = Event::Mouse(mouse::Event::CursorMoved { let event = Event::Mouse(mouse::Event::CursorMoved {
position: drag_cursor.position().unwrap(), position: drag_cursor.position().unwrap(),
}); });
self.container.as_widget_mut().update( self.container.as_widget_mut().on_event(
&mut tree.children[0], &mut tree.children[0],
&event, event,
layout, layout,
drag_cursor, drag_cursor,
renderer, renderer,
@ -470,8 +468,7 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
viewport, viewport,
); );
} }
shell.capture_event(); return event::Status::Captured;
return;
} }
Event::Dnd(DndEvent::Offer(_, OfferEvent::LeaveDestination)) => { Event::Dnd(DndEvent::Offer(_, OfferEvent::LeaveDestination)) => {
log::trace!( log::trace!(
@ -484,9 +481,9 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
{ {
shell.publish(msg); shell.publish(msg);
} }
return; return event::Status::Ignored;
} }
Event::Dnd(DndEvent::Offer(id, OfferEvent::Drop)) if *id == Some(my_id) => { Event::Dnd(DndEvent::Offer(id, OfferEvent::Drop)) if id == Some(my_id) => {
log::trace!( log::trace!(
target: DND_DEST_LOG_TARGET, target: DND_DEST_LOG_TARGET,
"offer drop id={my_id:?}" "offer drop id={my_id:?}"
@ -496,29 +493,27 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
{ {
shell.publish(msg); shell.publish(msg);
} }
shell.capture_event(); return event::Status::Captured;
return;
} }
Event::Dnd(DndEvent::Offer(id, OfferEvent::SelectedAction(action))) Event::Dnd(DndEvent::Offer(id, OfferEvent::SelectedAction(action)))
if *id == Some(my_id) => if id == Some(my_id) =>
{ {
log::trace!( log::trace!(
target: DND_DEST_LOG_TARGET, target: DND_DEST_LOG_TARGET,
"offer selected-action id={my_id:?} action={action:?}" "offer selected-action id={my_id:?} action={action:?}"
); );
if let Some(msg) = state.on_action_selected( if let Some(msg) = state.on_action_selected(
*action, action,
self.on_action_selected self.on_action_selected
.as_ref() .as_ref()
.map(std::convert::AsRef::as_ref), .map(std::convert::AsRef::as_ref),
) { ) {
shell.publish(msg); shell.publish(msg);
} }
shell.capture_event(); return event::Status::Captured;
return;
} }
Event::Dnd(DndEvent::Offer(id, OfferEvent::Data { data, mime_type })) Event::Dnd(DndEvent::Offer(id, OfferEvent::Data { data, mime_type }))
if *id == Some(my_id) => if id == Some(my_id) =>
{ {
log::trace!( log::trace!(
target: DND_DEST_LOG_TARGET, target: DND_DEST_LOG_TARGET,
@ -532,33 +527,25 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
&& let Ok(s) = String::from_utf8(data[..data.len() - 1].to_vec()) && let Ok(s) = String::from_utf8(data[..data.len() - 1].to_vec())
{ {
shell.publish(f(s)); shell.publish(f(s));
shell.capture_event(); return event::Status::Captured;
return;
} }
if let (Some(msg), ret) = state.on_data_received( if let (Some(msg), ret) = state.on_data_received(
mime_type.clone(), mime_type,
data.clone(), data,
self.on_data_received self.on_data_received
.as_ref() .as_ref()
.map(std::convert::AsRef::as_ref), .map(std::convert::AsRef::as_ref),
self.on_finish.as_ref().map(std::convert::AsRef::as_ref), self.on_finish.as_ref().map(std::convert::AsRef::as_ref),
) { ) {
shell.publish(msg); shell.publish(msg);
if ret == event::Status::Captured { return ret;
log::trace!(
target: DND_DEST_LOG_TARGET,
"offer data id={my_id:?} captured"
);
shell.capture_event();
}
return;
} }
shell.capture_event(); return event::Status::Captured;
return;
} }
_ => {} _ => {}
} }
event::Status::Ignored
} }
fn mouse_interaction( fn mouse_interaction(
@ -602,18 +589,13 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: layout::Layout<'b>, layout: layout::Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
viewport: &Rectangle,
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
self.container.as_widget_mut().overlay( self.container
&mut tree.children[0], .as_widget_mut()
layout, .overlay(&mut tree.children[0], layout, renderer, translation)
renderer,
viewport,
translation,
)
} }
fn drag_destinations( fn drag_destinations(

View file

@ -1,20 +1,20 @@
use std::any::Any; use std::any::Any;
use iced_core::{widget::Operation, window}; use iced_core::window;
use crate::{ use crate::{
Element, Element,
iced::{
Event, Length, Point, Rectangle, Vector,
clipboard::dnd::{DndAction, DndEvent, SourceEvent},
event, mouse, overlay,
},
iced_core::{
self, Clipboard, Shell, layout, renderer,
widget::{Tree, tree},
},
widget::{Id, Widget, container}, widget::{Id, Widget, container},
}; };
use iced::{
Event, Length, Point, Rectangle, Vector,
clipboard::dnd::{DndAction, DndEvent, SourceEvent},
event, mouse, overlay,
};
use iced_core::{
self, Clipboard, Shell, layout, renderer,
widget::{Tree, tree},
};
pub fn dnd_source< pub fn dnd_source<
'a, 'a,
@ -131,25 +131,21 @@ impl<
); );
} }
#[must_use]
pub fn on_start(mut self, on_start: Option<Message>) -> Self { pub fn on_start(mut self, on_start: Option<Message>) -> Self {
self.on_start = on_start; self.on_start = on_start;
self self
} }
#[must_use]
pub fn on_cancel(mut self, on_cancelled: Option<Message>) -> Self { pub fn on_cancel(mut self, on_cancelled: Option<Message>) -> Self {
self.on_cancelled = on_cancelled; self.on_cancelled = on_cancelled;
self self
} }
#[must_use]
pub fn on_finish(mut self, on_finish: Option<Message>) -> Self { pub fn on_finish(mut self, on_finish: Option<Message>) -> Self {
self.on_finish = on_finish; self.on_finish = on_finish;
self self
} }
#[must_use]
pub fn window(mut self, window: window::Id) -> Self { pub fn window(mut self, window: window::Id) -> Self {
self.window = Some(window); self.window = Some(window);
self self
@ -168,7 +164,7 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
} }
fn diff(&mut self, tree: &mut Tree) { fn diff(&mut self, tree: &mut Tree) {
tree.diff_children(std::slice::from_mut(&mut self.container)); tree.children[0].diff(self.container.as_widget_mut());
} }
fn state(&self) -> iced_core::widget::tree::State { fn state(&self) -> iced_core::widget::tree::State {
@ -180,7 +176,7 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
} }
fn layout( fn layout(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
renderer: &crate::Renderer, renderer: &crate::Renderer,
limits: &layout::Limits, limits: &layout::Limits,
@ -188,44 +184,41 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
let state = tree.state.downcast_mut::<State>(); let state = tree.state.downcast_mut::<State>();
let node = self let node = self
.container .container
.as_widget_mut() .as_widget()
.layout(&mut tree.children[0], renderer, limits); .layout(&mut tree.children[0], renderer, limits);
state.cached_bounds = node.bounds(); state.cached_bounds = node.bounds();
node node
} }
fn operate( fn operate(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
layout: layout::Layout<'_>, layout: layout::Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
operation: &mut dyn Operation, operation: &mut dyn iced_core::widget::Operation<()>,
) { ) {
operation.custom( operation.custom((&mut tree.state) as &mut dyn Any, Some(&self.id));
Some(&self.id), operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
layout.bounds(), self.container
(&mut tree.state) as &mut dyn Any, .as_widget()
); .operate(&mut tree.children[0], layout, renderer, operation)
});
self.container
.as_widget_mut()
.operate(&mut tree.children[0], layout, renderer, operation);
} }
fn update( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: &Event, event: Event,
layout: layout::Layout<'_>, layout: layout::Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &crate::Renderer, renderer: &crate::Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) { ) -> event::Status {
self.container.as_widget_mut().update( let ret = self.container.as_widget_mut().on_event(
&mut tree.children[0], &mut tree.children[0],
event, event.clone(),
layout, layout,
cursor, cursor,
renderer, renderer,
@ -240,48 +233,54 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
Event::Mouse(mouse_event) => match mouse_event { Event::Mouse(mouse_event) => match mouse_event {
mouse::Event::ButtonPressed(mouse::Button::Left) => { mouse::Event::ButtonPressed(mouse::Button::Left) => {
if let Some(position) = cursor.position() { if let Some(position) = cursor.position() {
if !cursor.is_over(layout.bounds()) { if !state.hovered {
return; return ret;
} }
state.left_pressed_position = Some(position); state.left_pressed_position = Some(position);
shell.capture_event(); return event::Status::Captured;
} }
} }
mouse::Event::ButtonReleased(mouse::Button::Left) mouse::Event::ButtonReleased(mouse::Button::Left)
if state.left_pressed_position.is_some() => if state.left_pressed_position.is_some() =>
{ {
state.left_pressed_position = None; state.left_pressed_position = None;
shell.capture_event(); return event::Status::Captured;
} }
mouse::Event::CursorMoved { .. } => { mouse::Event::CursorMoved { .. } => {
if let Some(position) = cursor.position() { if let Some(position) = cursor.position() {
// We ignore motion if we do not possess drag content by now. if state.hovered {
if self.drag_content.is_none() { // We ignore motion if we do not possess drag content by now.
state.left_pressed_position = None; if self.drag_content.is_none() {
return; state.left_pressed_position = None;
} return ret;
if let Some(left_pressed_position) = state.left_pressed_position
&& position.distance(left_pressed_position) > self.drag_threshold
{
if let Some(on_start) = self.on_start.as_ref() {
shell.publish(on_start.clone());
} }
let offset = Vector::new( if let Some(left_pressed_position) = state.left_pressed_position {
left_pressed_position.x - layout.bounds().x, if position.distance(left_pressed_position) > self.drag_threshold {
left_pressed_position.y - layout.bounds().y, if let Some(on_start) = self.on_start.as_ref() {
); shell.publish(on_start.clone())
self.start_dnd(clipboard, state.cached_bounds, offset); }
state.is_dragging = true; let offset = Vector::new(
state.left_pressed_position = None; left_pressed_position.x - layout.bounds().x,
left_pressed_position.y - layout.bounds().y,
);
self.start_dnd(clipboard, state.cached_bounds, offset);
state.is_dragging = true;
state.left_pressed_position = None;
}
}
if !cursor.is_over(layout.bounds()) {
state.hovered = false;
return ret;
}
} else if cursor.is_over(layout.bounds()) {
state.hovered = true;
} }
if !cursor.is_over(layout.bounds()) { return event::Status::Captured;
return;
}
shell.capture_event();
} }
} }
_ => (), _ => return ret,
}, },
Event::Dnd(DndEvent::Source(SourceEvent::Cancelled)) => { Event::Dnd(DndEvent::Source(SourceEvent::Cancelled)) => {
if state.is_dragging { if state.is_dragging {
@ -289,8 +288,9 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
shell.publish(m.clone()); shell.publish(m.clone());
} }
state.is_dragging = false; state.is_dragging = false;
shell.capture_event(); return event::Status::Captured;
} }
return ret;
} }
Event::Dnd(DndEvent::Source(SourceEvent::Finished)) => { Event::Dnd(DndEvent::Source(SourceEvent::Finished)) => {
if state.is_dragging { if state.is_dragging {
@ -298,11 +298,13 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
shell.publish(m.clone()); shell.publish(m.clone());
} }
state.is_dragging = false; state.is_dragging = false;
shell.capture_event(); return event::Status::Captured;
} }
return ret;
} }
_ => (), _ => return ret,
} }
ret
} }
fn mouse_interaction( fn mouse_interaction(
@ -350,18 +352,13 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: layout::Layout<'b>, layout: layout::Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
viewport: &Rectangle,
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
self.container.as_widget_mut().overlay( self.container
&mut tree.children[0], .as_widget_mut()
layout, .overlay(&mut tree.children[0], layout, renderer, translation)
renderer,
viewport,
translation,
)
} }
fn drag_destinations( fn drag_destinations(
@ -414,6 +411,7 @@ impl<
/// Local state of the [`MouseListener`]. /// Local state of the [`MouseListener`].
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct State { struct State {
hovered: bool,
left_pressed_position: Option<Point>, left_pressed_position: Option<Point>,
is_dragging: bool, is_dragging: bool,
cached_bounds: Rectangle, cached_bounds: Rectangle,

View file

@ -213,7 +213,7 @@ impl<'a, Message: Clone + 'a> Overlay<'a, Message> {
} }
} }
fn _layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node { fn _layout(&self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
let space_below = bounds.height - (self.position.y + self.target_height); let space_below = bounds.height - (self.position.y + self.target_height);
let space_above = self.position.y; let space_above = self.position.y;
@ -242,19 +242,19 @@ impl<'a, Message: Clone + 'a> Overlay<'a, Message> {
}) })
} }
fn _update( fn _on_event(
&mut self, &mut self,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &crate::Renderer, renderer: &crate::Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
) { ) -> event::Status {
let bounds = layout.bounds(); let bounds = layout.bounds();
self.state.with_data_mut(|tree| { self.state.with_data_mut(|tree| {
self.container.update( self.container.on_event(
tree, event, layout, cursor, renderer, clipboard, shell, &bounds, tree, event, layout, cursor, renderer, clipboard, shell, &bounds,
) )
}) })
@ -293,7 +293,6 @@ impl<'a, Message: Clone + 'a> Overlay<'a, Message> {
radius: appearance.border_radius, radius: appearance.border_radius,
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
appearance.background, appearance.background,
); );
@ -312,25 +311,26 @@ impl<'a, Message: Clone + 'a> iced_core::Overlay<Message, crate::Theme, crate::R
self._layout(renderer, bounds) self._layout(renderer, bounds)
} }
fn update( fn on_event(
&mut self, &mut self,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &crate::Renderer, renderer: &crate::Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
) { ) -> event::Status {
self._update(event, layout, cursor, renderer, clipboard, shell) self._on_event(event, layout, cursor, renderer, clipboard, shell)
} }
fn mouse_interaction( fn mouse_interaction(
&self, &self,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &crate::Renderer, renderer: &crate::Renderer,
) -> mouse::Interaction { ) -> mouse::Interaction {
self._mouse_interaction(layout, cursor, &layout.bounds(), renderer) self._mouse_interaction(layout, cursor, viewport, renderer)
} }
fn draw( fn draw(
@ -353,7 +353,7 @@ impl<'a, Message: Clone + 'a> crate::widget::Widget<Message, crate::Theme, crate
} }
fn layout( fn layout(
&mut self, &self,
_tree: &mut iced_core::widget::Tree, _tree: &mut iced_core::widget::Tree,
renderer: &crate::Renderer, renderer: &crate::Renderer,
limits: &iced::Limits, limits: &iced::Limits,
@ -375,18 +375,18 @@ impl<'a, Message: Clone + 'a> crate::widget::Widget<Message, crate::Theme, crate
self._mouse_interaction(layout, cursor, viewport, renderer) self._mouse_interaction(layout, cursor, viewport, renderer)
} }
fn update( fn on_event(
&mut self, &mut self,
_tree: &mut Tree, _tree: &mut Tree,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &crate::Renderer, renderer: &crate::Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
_viewport: &Rectangle, _viewport: &Rectangle,
) { ) -> event::Status {
self._update(event, layout, cursor, renderer, clipboard, shell) self._on_event(event, layout, cursor, renderer, clipboard, shell)
} }
fn draw( fn draw(
@ -435,7 +435,7 @@ where
} }
fn layout( fn layout(
&mut self, &self,
_tree: &mut Tree, _tree: &mut Tree,
renderer: &crate::Renderer, renderer: &crate::Renderer,
limits: &layout::Limits, limits: &layout::Limits,
@ -452,7 +452,7 @@ where
let size = { let size = {
let intrinsic = Size::new( let intrinsic = Size::new(
0.0, 0.0,
(f32::from(text_line_height) + self.padding.y()) * self.options.len() as f32, (f32::from(text_line_height) + self.padding.vertical()) * self.options.len() as f32,
); );
limits.resolve(Length::Fill, Length::Shrink, intrinsic) limits.resolve(Length::Fill, Length::Shrink, intrinsic)
@ -461,17 +461,17 @@ where
layout::Node::new(size) layout::Node::new(size)
} }
fn update( fn on_event(
&mut self, &mut self,
_state: &mut Tree, _state: &mut Tree,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &crate::Renderer, renderer: &crate::Renderer,
_clipboard: &mut dyn Clipboard, _clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
_viewport: &Rectangle, _viewport: &Rectangle,
) { ) -> event::Status {
match event { match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
let hovered_guard = self.hovered_option.lock().unwrap(); let hovered_guard = self.hovered_option.lock().unwrap();
@ -481,8 +481,7 @@ where
if let Some(close_on_selected) = self.close_on_selected.as_ref() { if let Some(close_on_selected) = self.close_on_selected.as_ref() {
shell.publish(close_on_selected.clone()); shell.publish(close_on_selected.clone());
} }
shell.capture_event(); return event::Status::Captured;
return;
} }
} }
} }
@ -494,7 +493,7 @@ where
let option_height = let option_height =
f32::from(self.text_line_height.to_absolute(Pixels(text_size))) f32::from(self.text_line_height.to_absolute(Pixels(text_size)))
+ self.padding.y(); + self.padding.vertical();
let new_hovered_option = (cursor_position.y / option_height) as usize; let new_hovered_option = (cursor_position.y / option_height) as usize;
let mut hovered_guard = self.hovered_option.lock().unwrap(); let mut hovered_guard = self.hovered_option.lock().unwrap();
@ -516,7 +515,7 @@ where
let option_height = let option_height =
f32::from(self.text_line_height.to_absolute(Pixels(text_size))) f32::from(self.text_line_height.to_absolute(Pixels(text_size)))
+ self.padding.y(); + self.padding.vertical();
let mut hovered_guard = self.hovered_option.lock().unwrap(); let mut hovered_guard = self.hovered_option.lock().unwrap();
*hovered_guard = Some((cursor_position.y / option_height) as usize); *hovered_guard = Some((cursor_position.y / option_height) as usize);
@ -526,13 +525,14 @@ where
if let Some(close_on_selected) = self.close_on_selected.as_ref() { if let Some(close_on_selected) = self.close_on_selected.as_ref() {
shell.publish(close_on_selected.clone()); shell.publish(close_on_selected.clone());
} }
shell.capture_event(); return event::Status::Captured;
return;
} }
} }
} }
_ => {} _ => {}
} }
event::Status::Ignored
} }
fn mouse_interaction( fn mouse_interaction(
@ -568,8 +568,8 @@ where
let text_size = self let text_size = self
.text_size .text_size
.unwrap_or_else(|| text::Renderer::default_size(renderer).0); .unwrap_or_else(|| text::Renderer::default_size(renderer).0);
let option_height = let option_height = f32::from(self.text_line_height.to_absolute(Pixels(text_size)))
f32::from(self.text_line_height.to_absolute(Pixels(text_size))) + self.padding.y(); + self.padding.vertical();
let offset = viewport.y - bounds.y; let offset = viewport.y - bounds.y;
let start = (offset / option_height) as usize; let start = (offset / option_height) as usize;
@ -605,7 +605,6 @@ where
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
appearance.selected_background, appearance.selected_background,
); );
@ -615,13 +614,16 @@ where
.color(appearance.selected_text_color) .color(appearance.selected_text_color)
.border_radius(appearance.border_radius); .border_radius(appearance.border_radius);
let bounds = Rectangle { svg::Renderer::draw_svg(
x: item_x + item_width - 16.0 - 8.0, renderer,
y: bounds.y + (bounds.height / 2.0 - 8.0), svg_handle,
width: 16.0, Rectangle {
height: 16.0, x: item_x + item_width - 16.0 - 8.0,
}; y: bounds.y + (bounds.height / 2.0 - 8.0),
svg::Renderer::draw_svg(renderer, svg_handle, bounds, bounds); width: 16.0,
height: 16.0,
},
);
(appearance.selected_text_color, crate::font::semibold()) (appearance.selected_text_color, crate::font::semibold())
} else if *hovered_guard == Some(i) { } else if *hovered_guard == Some(i) {
@ -640,7 +642,6 @@ where
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
appearance.hovered_background, appearance.hovered_background,
); );
@ -677,8 +678,8 @@ where
size: Pixels(text_size), size: Pixels(text_size),
line_height: self.text_line_height, line_height: self.text_line_height,
font, font,
align_x: text::Alignment::Left, horizontal_alignment: alignment::Horizontal::Left,
align_y: alignment::Vertical::Center, vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrapping: text::Wrapping::default(), wrapping: text::Wrapping::default(),
ellipsize: text::Ellipsize::default(), ellipsize: text::Ellipsize::default(),

View file

@ -50,18 +50,18 @@ pub fn popup_dropdown<
let dropdown: Dropdown<'_, S, Message, AppMessage> = let dropdown: Dropdown<'_, S, Message, AppMessage> =
Dropdown::new(selections.into(), selected, on_selected); Dropdown::new(selections.into(), selected, on_selected);
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "winit", feature = "wayland"))]
let dropdown = dropdown.with_popup(_parent_id, _on_surface_action, _map_action); let dropdown = dropdown.with_popup(_parent_id, _on_surface_action, _map_action);
dropdown dropdown
} }
// /// Produces a [`Task`] that closes the [`Dropdown`]. /// Produces a [`Task`] that closes the [`Dropdown`].
// pub fn close<Message: 'static>(id: Id) -> iced_runtime::Task<Message> { pub fn close<Message: 'static>(id: Id) -> iced_runtime::Task<Message> {
// iced_runtime::task::effect(iced_runtime::Action::Widget(Box::new(operation::close(id)))) iced_runtime::task::effect(iced_runtime::Action::Widget(Box::new(operation::close(id))))
// } }
// /// Produces a [`Task`] that opens the [`Dropdown`]. /// Produces a [`Task`] that opens the [`Dropdown`].
// pub fn open<Message: 'static>(id: Id) -> iced_runtime::Task<Message> { pub fn open<Message: 'static>(id: Id) -> iced_runtime::Task<Message> {
// iced_runtime::task::effect(iced_runtime::Action::Widget(Box::new(operation::open(id)))) iced_runtime::task::effect(iced_runtime::Action::Widget(Box::new(operation::open(id))))
// } }

View file

@ -209,18 +209,18 @@ impl<Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer> for Ove
}) })
} }
fn update( fn on_event(
&mut self, &mut self,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &crate::Renderer, renderer: &crate::Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
) { ) -> event::Status {
let bounds = layout.bounds(); let bounds = layout.bounds();
self.container.update( self.container.on_event(
self.state, event, layout, cursor, renderer, clipboard, shell, &bounds, self.state, event, layout, cursor, renderer, clipboard, shell, &bounds,
) )
} }
@ -229,10 +229,11 @@ impl<Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer> for Ove
&self, &self,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &crate::Renderer, renderer: &crate::Renderer,
) -> mouse::Interaction { ) -> mouse::Interaction {
self.container self.container
.mouse_interaction(self.state, layout, cursor, &layout.bounds(), renderer) .mouse_interaction(self.state, layout, cursor, viewport, renderer)
} }
fn draw( fn draw(
@ -255,7 +256,6 @@ impl<Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer> for Ove
radius: appearance.border_radius, radius: appearance.border_radius,
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
appearance.background, appearance.background,
); );
@ -287,7 +287,7 @@ where
} }
fn layout( fn layout(
&mut self, &self,
_tree: &mut Tree, _tree: &mut Tree,
renderer: &crate::Renderer, renderer: &crate::Renderer,
limits: &layout::Limits, limits: &layout::Limits,
@ -309,7 +309,7 @@ where
) )
}); });
let vertical_padding = self.padding.y(); let vertical_padding = self.padding.vertical();
let text_line_height = f32::from(text_line_height); let text_line_height = f32::from(text_line_height);
let size = { let size = {
@ -328,17 +328,17 @@ where
layout::Node::new(size) layout::Node::new(size)
} }
fn update( fn on_event(
&mut self, &mut self,
_state: &mut Tree, _state: &mut Tree,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &crate::Renderer, renderer: &crate::Renderer,
_clipboard: &mut dyn Clipboard, _clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
_viewport: &Rectangle, _viewport: &Rectangle,
) { ) -> event::Status {
let bounds = layout.bounds(); let bounds = layout.bounds();
match event { match event {
@ -346,8 +346,7 @@ where
if cursor.is_over(bounds) { if cursor.is_over(bounds) {
if let Some(item) = self.hovered_option.as_ref() { if let Some(item) = self.hovered_option.as_ref() {
shell.publish((self.on_selected)(item.clone())); shell.publish((self.on_selected)(item.clone()));
shell.capture_event(); return event::Status::Captured;
return;
} }
} }
} }
@ -362,7 +361,7 @@ where
let heights = self let heights = self
.options .options
.element_heights(self.padding.y(), text_line_height); .element_heights(self.padding.vertical(), text_line_height);
let mut current_offset = 0.0; let mut current_offset = 0.0;
@ -409,7 +408,7 @@ where
let heights = self let heights = self
.options .options
.element_heights(self.padding.y(), text_line_height); .element_heights(self.padding.vertical(), text_line_height);
let mut current_offset = 0.0; let mut current_offset = 0.0;
@ -447,6 +446,8 @@ where
} }
_ => {} _ => {}
} }
event::Status::Ignored
} }
fn mouse_interaction( fn mouse_interaction(
@ -489,7 +490,7 @@ where
let text_line_height = f32::from(self.text_line_height.to_absolute(Pixels(text_size))); let text_line_height = f32::from(self.text_line_height.to_absolute(Pixels(text_size)));
let visible_options = self.options.visible_options( let visible_options = self.options.visible_options(
self.padding.y(), self.padding.vertical(),
text_line_height, text_line_height,
offset, offset,
viewport.height, viewport.height,
@ -527,23 +528,24 @@ where
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
appearance.selected_background, appearance.selected_background,
); );
let svg_bounds = Rectangle {
x: item_x + item_width - 16.0 - 8.0,
y: bounds.y + (bounds.height / 2.0 - 8.0),
width: 16.0,
height: 16.0,
};
let svg_handle = let svg_handle =
svg::Svg::new(crate::widget::common::object_select().clone()) svg::Svg::new(crate::widget::common::object_select().clone())
.color(appearance.selected_text_color) .color(appearance.selected_text_color)
.border_radius(appearance.border_radius); .border_radius(appearance.border_radius);
svg::Renderer::draw_svg(renderer, svg_handle, svg_bounds, svg_bounds); svg::Renderer::draw_svg(
renderer,
svg_handle,
Rectangle {
x: item_x + item_width - 16.0 - 8.0,
y: bounds.y + (bounds.height / 2.0 - 8.0),
width: 16.0,
height: 16.0,
},
);
(appearance.selected_text_color, crate::font::semibold()) (appearance.selected_text_color, crate::font::semibold())
} else if self.hovered_option.as_ref() == Some(item) { } else if self.hovered_option.as_ref() == Some(item) {
@ -564,7 +566,6 @@ where
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
appearance.hovered_background, appearance.hovered_background,
); );
@ -589,8 +590,8 @@ where
size: iced::Pixels(text_size), size: iced::Pixels(text_size),
line_height: self.text_line_height, line_height: self.text_line_height,
font, font,
align_x: text::Alignment::Left, horizontal_alignment: alignment::Horizontal::Left,
align_y: alignment::Vertical::Center, vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrapping: text::Wrapping::default(), wrapping: text::Wrapping::default(),
ellipsize: text::Ellipsize::default(), ellipsize: text::Ellipsize::default(),
@ -610,7 +611,7 @@ where
}) })
.move_to(Point { .move_to(Point {
x: bounds.x, x: bounds.x,
y: bounds.y + (self.padding.y() / 2.0) - 4.0, y: bounds.y + (self.padding.vertical() / 2.0) - 4.0,
}); });
Widget::<Message, crate::Theme, crate::Renderer>::draw( Widget::<Message, crate::Theme, crate::Renderer>::draw(
@ -639,8 +640,8 @@ where
size: iced::Pixels(text_size), size: iced::Pixels(text_size),
line_height: text::LineHeight::Absolute(Pixels(text_line_height + 4.0)), line_height: text::LineHeight::Absolute(Pixels(text_line_height + 4.0)),
font: crate::font::default(), font: crate::font::default(),
align_x: text::Alignment::Center, horizontal_alignment: alignment::Horizontal::Center,
align_y: alignment::Vertical::Center, vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrapping: text::Wrapping::default(), wrapping: text::Wrapping::default(),
ellipsize: text::Ellipsize::default(), ellipsize: text::Ellipsize::default(),

View file

@ -78,7 +78,7 @@ impl<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>
} }
fn layout( fn layout(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
renderer: &crate::Renderer, renderer: &crate::Renderer,
limits: &layout::Limits, limits: &layout::Limits,
@ -116,17 +116,17 @@ impl<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>
) )
} }
fn update( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
_renderer: &crate::Renderer, _renderer: &crate::Renderer,
_clipboard: &mut dyn Clipboard, _clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
_viewport: &Rectangle, _viewport: &Rectangle,
) { ) -> event::Status {
update( update(
&event, &event,
layout, layout,
@ -135,7 +135,7 @@ impl<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>
self.on_selected.as_ref(), self.on_selected.as_ref(),
self.selections, self.selections,
|| tree.state.downcast_mut::<State<Item>>(), || tree.state.downcast_mut::<State<Item>>(),
); )
} }
fn mouse_interaction( fn mouse_interaction(
@ -183,9 +183,8 @@ impl<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'b>, layout: Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
_viewport: &Rectangle,
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
let state = tree.state.downcast_mut::<State<Item>>(); let state = tree.state.downcast_mut::<State<Item>>();
@ -276,8 +275,8 @@ pub fn layout(
size: iced::Pixels(text_size), size: iced::Pixels(text_size),
line_height: text_line_height, line_height: text_line_height,
font: font.unwrap_or_else(crate::font::default), font: font.unwrap_or_else(crate::font::default),
align_x: text::Alignment::Left, horizontal_alignment: alignment::Horizontal::Left,
align_y: alignment::Vertical::Top, vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrapping: text::Wrapping::default(), wrapping: text::Wrapping::default(),
ellipsize: text::Ellipsize::default(), ellipsize: text::Ellipsize::default(),
@ -315,7 +314,7 @@ pub fn update<'a, S: AsRef<str>, Message, Item: Clone + PartialEq + 'static + 'a
on_selected: &dyn Fn(Item) -> Message, on_selected: &dyn Fn(Item) -> Message,
selections: &super::Model<S, Item>, selections: &super::Model<S, Item>,
state: impl FnOnce() -> &'a mut State<Item>, state: impl FnOnce() -> &'a mut State<Item>,
) { ) -> event::Status {
match event { match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => { | Event::Touch(touch::Event::FingerPressed { .. }) => {
@ -326,12 +325,14 @@ pub fn update<'a, S: AsRef<str>, Message, Item: Clone + PartialEq + 'static + 'a
// bounds or on the drop-down, either way we close the overlay. // bounds or on the drop-down, either way we close the overlay.
state.is_open = false; state.is_open = false;
shell.capture_event(); event::Status::Captured
} else if cursor.is_over(layout.bounds()) { } else if cursor.is_over(layout.bounds()) {
state.is_open = true; state.is_open = true;
state.hovered_option = selections.selected.clone(); state.hovered_option = selections.selected.clone();
shell.capture_event(); event::Status::Captured
} else {
event::Status::Ignored
} }
} }
Event::Mouse(mouse::Event::WheelScrolled { Event::Mouse(mouse::Event::WheelScrolled {
@ -347,15 +348,19 @@ pub fn update<'a, S: AsRef<str>, Message, Item: Clone + PartialEq + 'static + 'a
shell.publish((on_selected)(option.1.clone())); shell.publish((on_selected)(option.1.clone()));
} }
shell.capture_event(); event::Status::Captured
} else {
event::Status::Ignored
} }
} }
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
let state = state(); let state = state();
state.keyboard_modifiers = *modifiers; state.keyboard_modifiers = *modifiers;
event::Status::Ignored
} }
_ => {} _ => event::Status::Ignored,
} }
} }
@ -415,8 +420,8 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static
size: iced::Pixels(text_size), size: iced::Pixels(text_size),
line_height, line_height,
font: font.unwrap_or_else(crate::font::default), font: font.unwrap_or_else(crate::font::default),
align_x: text::Alignment::Left, horizontal_alignment: alignment::Horizontal::Left,
align_y: alignment::Vertical::Top, vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrapping: text::Wrapping::default(), wrapping: text::Wrapping::default(),
ellipsize: text::Ellipsize::default(), ellipsize: text::Ellipsize::default(),
@ -425,7 +430,7 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static
}; };
let mut desc_count = 0; let mut desc_count = 0;
padding.x().mul_add( padding.horizontal().mul_add(
2.0, 2.0,
selections selections
.elements() .elements()
@ -512,20 +517,22 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>(
bounds, bounds,
border: style.border, border: style.border,
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
style.background, style.background,
); );
if let Some(handle) = state.icon.as_ref() { if let Some(handle) = state.icon.as_ref() {
let svg_handle = iced_core::Svg::new(handle.clone()).color(style.text_color); let svg_handle = iced_core::Svg::new(handle.clone()).color(style.text_color);
let svg_bounds = Rectangle { svg::Renderer::draw_svg(
x: bounds.x + bounds.width - gap - 16.0, renderer,
y: bounds.center_y() - 8.0, svg_handle,
width: 16.0, Rectangle {
height: 16.0, x: bounds.x + bounds.width - gap - 16.0,
}; y: bounds.center_y() - 8.0,
svg::Renderer::draw_svg(renderer, svg_handle, svg_bounds, svg_bounds); width: 16.0,
height: 16.0,
},
);
} }
if let Some(content) = selected.map(AsRef::as_ref) { if let Some(content) = selected.map(AsRef::as_ref) {
@ -534,7 +541,7 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>(
let bounds = Rectangle { let bounds = Rectangle {
x: bounds.x + padding.left, x: bounds.x + padding.left,
y: bounds.center_y(), y: bounds.center_y(),
width: bounds.width - padding.x(), width: bounds.width - padding.horizontal(),
height: f32::from(text_line_height.to_absolute(Pixels(text_size))), height: f32::from(text_line_height.to_absolute(Pixels(text_size))),
}; };
@ -546,8 +553,8 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>(
line_height: text_line_height, line_height: text_line_height,
font, font,
bounds: bounds.size(), bounds: bounds.size(),
align_x: text::Alignment::Left, horizontal_alignment: alignment::Horizontal::Left,
align_y: alignment::Vertical::Center, vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrapping: text::Wrapping::default(), wrapping: text::Wrapping::default(),
ellipsize: text::Ellipsize::default(), ellipsize: text::Ellipsize::default(),

View file

@ -11,62 +11,62 @@ pub trait Dropdown {
fn open(&mut self); fn open(&mut self);
} }
// /// Produces a [`Task`] that closes a [`Dropdown`] popup. /// Produces a [`Task`] that closes a [`Dropdown`] popup.
// pub fn close<T>(id: Id) -> impl Operation<T> { pub fn close<T>(id: Id) -> impl Operation<T> {
// struct Close(Id); struct Close(Id);
// impl<T> Operation<T> for Close { impl<T> Operation<T> for Close {
// fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) { fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) {
// if id.map_or(true, |id| id != &self.0) { if id.map_or(true, |id| id != &self.0) {
// return; return;
// } }
// let Some(state) = state.downcast_mut::<State>() else { let Some(state) = state.downcast_mut::<State>() else {
// return; return;
// }; };
// state.close(); state.close();
// } }
// fn container( fn container(
// &mut self, &mut self,
// _id: Option<&Id>, _id: Option<&Id>,
// _bounds: Rectangle, _bounds: Rectangle,
// operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
// ) { ) {
// operate_on_children(self) operate_on_children(self)
// } }
// } }
// Close(id) Close(id)
// } }
// /// Produces a [`Task`] that opens a [`Dropdown`] popup. /// Produces a [`Task`] that opens a [`Dropdown`] popup.
// pub fn open<T>(id: Id) -> impl Operation<T> { pub fn open<T>(id: Id) -> impl Operation<T> {
// struct Open(Id); struct Open(Id);
// impl<T> Operation<T> for Open { impl<T> Operation<T> for Open {
// fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) { fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) {
// if id.map_or(true, |id| id != &self.0) { if id.map_or(true, |id| id != &self.0) {
// return; return;
// } }
// let Some(state) = state.downcast_mut::<State>() else { let Some(state) = state.downcast_mut::<State>() else {
// return; return;
// }; };
// state.open(); state.open();
// } }
// fn container( fn container(
// &mut self, &mut self,
// _id: Option<&Id>, _id: Option<&Id>,
// _bounds: Rectangle, _bounds: Rectangle,
// operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
// ) { ) {
// operate_on_children(self) operate_on_children(self)
// } }
// } }
// Open(id) Open(id)
// } }

View file

@ -60,7 +60,7 @@ where
action_map: Option<Arc<dyn Fn(Message) -> AppMessage + 'static + Send + Sync>>, action_map: Option<Arc<dyn Fn(Message) -> AppMessage + 'static + Send + Sync>>,
#[setters(strip_option)] #[setters(strip_option)]
window_id: Option<window::Id>, window_id: Option<window::Id>,
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "winit", feature = "wayland"))]
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
} }
@ -96,14 +96,14 @@ where
text_line_height: text::LineHeight::Relative(1.2), text_line_height: text::LineHeight::Relative(1.2),
font: None, font: None,
window_id: None, window_id: None,
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "winit", feature = "wayland"))]
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(),
on_surface_action: None, on_surface_action: None,
action_map: None, action_map: None,
} }
} }
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "winit", feature = "wayland"))]
/// Handle dropdown requests for popup creation. /// Handle dropdown requests for popup creation.
/// Intended to be used with [`crate::app::message::get_popup`] /// Intended to be used with [`crate::app::message::get_popup`]
pub fn with_popup<NewAppMessage>( pub fn with_popup<NewAppMessage>(
@ -154,7 +154,7 @@ where
self self
} }
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "winit", feature = "wayland"))]
pub fn with_positioner( pub fn with_positioner(
mut self, mut self,
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
@ -203,13 +203,13 @@ where
state.hashes[i] = text_hash; state.hashes[i] = text_hash;
state.selections[i].update(Text { state.selections[i].update(Text {
content: selection.as_ref(), content: selection.as_ref(),
bounds: Size::INFINITE, bounds: Size::INFINITY,
// TODO use the renderer default size // TODO use the renderer default size
size: iced::Pixels(self.text_size.unwrap_or(14.0)), size: iced::Pixels(self.text_size.unwrap_or(14.0)),
line_height: self.text_line_height, line_height: self.text_line_height,
font: self.font.unwrap_or_else(crate::font::default), font: self.font.unwrap_or_else(crate::font::default),
align_x: text::Alignment::Left, horizontal_alignment: alignment::Horizontal::Left,
align_y: alignment::Vertical::Top, vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrapping: text::Wrapping::default(), wrapping: text::Wrapping::default(),
ellipsize: text::Ellipsize::default(), ellipsize: text::Ellipsize::default(),
@ -227,7 +227,7 @@ where
} }
fn layout( fn layout(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
renderer: &crate::Renderer, renderer: &crate::Renderer,
limits: &layout::Limits, limits: &layout::Limits,
@ -252,23 +252,23 @@ where
) )
} }
fn update( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
_renderer: &crate::Renderer, _renderer: &crate::Renderer,
_clipboard: &mut dyn Clipboard, _clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
_viewport: &Rectangle, _viewport: &Rectangle,
) { ) -> event::Status {
update::<S, Message, AppMessage>( update::<S, Message, AppMessage>(
&event, &event,
layout, layout,
cursor, cursor,
shell, shell,
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "winit", feature = "wayland"))]
self.positioner.clone(), self.positioner.clone(),
self.on_selected.clone(), self.on_selected.clone(),
self.selected, self.selected,
@ -327,26 +327,24 @@ where
} }
fn operate( fn operate(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
_layout: Layout<'_>, _layout: Layout<'_>,
_renderer: &crate::Renderer, _renderer: &crate::Renderer,
operation: &mut dyn iced_core::widget::Operation, operation: &mut dyn iced_core::widget::Operation,
) { ) {
// TODO: double check operation handling let state = tree.state.downcast_mut::<State>();
// let state = tree.state.downcast_mut::<State>(); operation.custom(state, self.id.as_ref());
// operation.custom(state, self.id.as_ref());
} }
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'b>, layout: Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
viewport: &Rectangle,
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "winit", feature = "wayland"))]
if self.window_id.is_some() || self.on_surface_action.is_some() { if self.window_id.is_some() || self.on_surface_action.is_some() {
return None; return None;
} }
@ -471,38 +469,24 @@ pub fn layout(
let max_width = match width { let max_width = match width {
Length::Shrink => { Length::Shrink => {
let measure = move |(label, paragraph): (_, Option<&mut crate::Plain>)| -> f32 { let measure = move |(label, paragraph): (_, Option<&mut crate::Plain>)| -> f32 {
let text = Text {
content: label,
bounds: Size::new(f32::MAX, f32::MAX),
size: iced::Pixels(text_size),
line_height: text_line_height,
font: font.unwrap_or_else(crate::font::default),
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced,
wrapping: text::Wrapping::default(),
ellipsize: text::Ellipsize::default(),
};
let paragraph = match paragraph { let paragraph = match paragraph {
Some(p) => { Some(p) => {
let text = Text {
content: label,
bounds: Size::new(f32::MAX, f32::MAX),
size: iced::Pixels(text_size),
line_height: text_line_height,
font: font.unwrap_or_else(crate::font::default),
align_x: text::Alignment::Left,
align_y: alignment::Vertical::Top,
shaping: text::Shaping::Advanced,
wrapping: text::Wrapping::default(),
ellipsize: text::Ellipsize::default(),
};
p.update(text); p.update(text);
p p
} }
None => { None => &mut crate::Plain::new(text),
let text = Text {
content: label.to_string(),
bounds: Size::new(f32::MAX, f32::MAX),
size: iced::Pixels(text_size),
line_height: text_line_height,
font: font.unwrap_or_else(crate::font::default),
align_x: text::Alignment::Left,
align_y: alignment::Vertical::Top,
shaping: text::Shaping::Advanced,
wrapping: text::Wrapping::default(),
ellipsize: text::Ellipsize::default(),
};
&mut crate::Plain::new(text)
}
}; };
paragraph.min_width().round() paragraph.min_width().round()
}; };
@ -545,7 +529,7 @@ pub fn update<
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "winit", feature = "wayland"))]
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
on_selected: Arc<dyn Fn(usize) -> Message + Send + Sync + 'static>, on_selected: Arc<dyn Fn(usize) -> Message + Send + Sync + 'static>,
selected: Option<usize>, selected: Option<usize>,
@ -560,7 +544,7 @@ pub fn update<
text_size: Option<f32>, text_size: Option<f32>,
font: Option<crate::font::Font>, font: Option<crate::font::Font>,
selected_option: Option<usize>, selected_option: Option<usize>,
) { ) -> event::Status {
let state = state(); let state = state();
let open = |shell: &mut Shell<'_, Message>, let open = |shell: &mut Shell<'_, Message>,
@ -571,7 +555,7 @@ pub fn update<
*hovered_guard = selected; *hovered_guard = selected;
let id = window::Id::unique(); let id = window::Id::unique();
state.popup_id = id; state.popup_id = id;
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "winit", feature = "wayland"))]
if let Some(((on_surface_action, parent), action_map)) = on_surface_action if let Some(((on_surface_action, parent), action_map)) = on_surface_action
.as_ref() .as_ref()
.zip(_window_id) .zip(_window_id)
@ -591,7 +575,7 @@ pub fn update<
let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 { let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 {
selection_paragraph.min_width().round() selection_paragraph.min_width().round()
}; };
let pad_width = padding.x().mul_add(2.0, 16.0); let pad_width = padding.horizontal().mul_add(2.0, 16.0);
let selections_width = selections let selections_width = selections
.iter() .iter()
@ -658,7 +642,7 @@ pub fn update<
state.close_operation = false; state.close_operation = false;
state.is_open.store(false, Ordering::SeqCst); state.is_open.store(false, Ordering::SeqCst);
if is_open { if is_open {
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "winit", feature = "wayland"))]
if let Some(ref on_close) = on_surface_action { if let Some(ref on_close) = on_surface_action {
shell.publish(on_close(surface::action::destroy_popup(state.popup_id))); shell.publish(on_close(surface::action::destroy_popup(state.popup_id)));
} }
@ -681,14 +665,16 @@ pub fn update<
// Event wasn't processed by overlay, so cursor was clicked either outside it's // Event wasn't processed by overlay, so cursor was clicked either outside it's
// bounds or on the drop-down, either way we close the overlay. // bounds or on the drop-down, either way we close the overlay.
state.is_open.store(false, Ordering::Relaxed); state.is_open.store(false, Ordering::Relaxed);
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "winit", feature = "wayland"))]
if let Some(on_close) = on_surface_action { if let Some(on_close) = on_surface_action {
shell.publish(on_close(surface::action::destroy_popup(state.popup_id))); shell.publish(on_close(surface::action::destroy_popup(state.popup_id)));
} }
shell.capture_event(); event::Status::Captured
} else if cursor.is_over(layout.bounds()) { } else if cursor.is_over(layout.bounds()) {
open(shell, state, on_selected); open(shell, state, on_selected);
shell.capture_event(); event::Status::Captured
} else {
event::Status::Ignored
} }
} }
Event::Mouse(mouse::Event::WheelScrolled { Event::Mouse(mouse::Event::WheelScrolled {
@ -703,13 +689,17 @@ pub fn update<
shell.publish((on_selected)(next_index)); shell.publish((on_selected)(next_index));
} }
shell.capture_event(); event::Status::Captured
} else {
event::Status::Ignored
} }
} }
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
state.keyboard_modifiers = *modifiers; state.keyboard_modifiers = *modifiers;
event::Status::Ignored
} }
_ => {} _ => event::Status::Ignored,
} }
} }
@ -726,7 +716,7 @@ pub fn mouse_interaction(layout: Layout<'_>, cursor: mouse::Cursor) -> mouse::In
} }
} }
#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "winit", feature = "wayland"))]
/// Returns the current menu widget of a [`Dropdown`]. /// Returns the current menu widget of a [`Dropdown`].
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn menu_widget< pub fn menu_widget<
@ -756,7 +746,7 @@ where
.zip(state.selections.iter()) .zip(state.selections.iter())
.map(|(label, selection)| measure(label.as_ref(), selection.raw())) .map(|(label, selection)| measure(label.as_ref(), selection.raw()))
.fold(0.0, |next, current| current.max(next)); .fold(0.0, |next, current| current.max(next));
let pad_width = padding.x().mul_add(2.0, 16.0); let pad_width = padding.horizontal().mul_add(2.0, 16.0);
let width = selections_width + gap + pad_width + icon_width; let width = selections_width + gap + pad_width + icon_width;
let is_open = state.is_open.clone(); let is_open = state.is_open.clone();
@ -832,7 +822,7 @@ where
selection_paragraph.min_width().round() selection_paragraph.min_width().round()
}; };
let pad_width = padding.x().mul_add(2.0, 16.0); let pad_width = padding.horizontal().mul_add(2.0, 16.0);
let icon_width = if icons.is_empty() { 0.0 } else { 24.0 }; let icon_width = if icons.is_empty() { 0.0 } else { 24.0 };
@ -893,20 +883,23 @@ pub fn draw<'a, S>(
bounds, bounds,
border: style.border, border: style.border,
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true,
}, },
style.background, style.background,
); );
if let Some(handle) = state.icon.clone() { if let Some(handle) = state.icon.clone() {
let svg_handle = svg::Svg::new(handle).color(style.text_color); let svg_handle = svg::Svg::new(handle).color(style.text_color);
let bounds = Rectangle {
x: bounds.x + bounds.width - gap - 16.0, svg::Renderer::draw_svg(
y: bounds.center_y() - 8.0, renderer,
width: 16.0, svg_handle,
height: 16.0, Rectangle {
}; x: bounds.x + bounds.width - gap - 16.0,
svg::Renderer::draw_svg(renderer, svg_handle, bounds, bounds); y: bounds.center_y() - 8.0,
width: 16.0,
height: 16.0,
},
);
} }
if let Some(content) = selected.map(AsRef::as_ref).or(placeholder) { if let Some(content) = selected.map(AsRef::as_ref).or(placeholder) {
@ -915,7 +908,7 @@ pub fn draw<'a, S>(
let mut bounds = Rectangle { let mut bounds = Rectangle {
x: bounds.x + padding.left, x: bounds.x + padding.left,
y: bounds.center_y(), y: bounds.center_y(),
width: bounds.width - padding.x(), width: bounds.width - padding.horizontal(),
height: f32::from(text_line_height.to_absolute(Pixels(text_size))), height: f32::from(text_line_height.to_absolute(Pixels(text_size))),
}; };
@ -939,8 +932,8 @@ pub fn draw<'a, S>(
line_height: text_line_height, line_height: text_line_height,
font, font,
bounds: bounds.size(), bounds: bounds.size(),
align_x: text::Alignment::Left, horizontal_alignment: alignment::Horizontal::Left,
align_y: alignment::Vertical::Center, vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrapping: text::Wrapping::default(), wrapping: text::Wrapping::default(),
ellipsize: text::Ellipsize::default(), ellipsize: text::Ellipsize::default(),

View file

@ -15,7 +15,7 @@ use taffy::{AlignContent, TaffyTree};
pub fn resolve<Message>( pub fn resolve<Message>(
renderer: &Renderer, renderer: &Renderer,
limits: &Limits, limits: &Limits,
items: &mut [Element<'_, Message>], items: &[Element<'_, Message>],
padding: Padding, padding: Padding,
column_spacing: f32, column_spacing: f32,
row_spacing: f32, row_spacing: f32,
@ -61,8 +61,8 @@ pub fn resolve<Message>(
..taffy::Style::default() ..taffy::Style::default()
}; };
for (child, tree) in items.iter_mut().zip(tree.iter_mut()) { for (child, tree) in items.iter().zip(tree.iter_mut()) {
let child_widget = child.as_widget_mut(); let child_widget = child.as_widget();
let child_node = child_widget.layout(tree, renderer, limits); let child_node = child_widget.layout(tree, renderer, limits);
let size = child_node.size(); let size = child_node.size();
@ -138,7 +138,7 @@ pub fn resolve<Message>(
leafs leafs
.into_iter() .into_iter()
.zip(items.iter_mut()) .zip(items.iter())
.zip(nodes.iter_mut()) .zip(nodes.iter_mut())
.zip(tree) .zip(tree)
.for_each(|(((leaf, child), node), tree)| { .for_each(|(((leaf, child), node), tree)| {
@ -146,7 +146,7 @@ pub fn resolve<Message>(
return; return;
}; };
let child_widget = child.as_widget_mut(); let child_widget = child.as_widget();
let c_size = child_widget.size(); let c_size = child_widget.size();
match c_size.width { match c_size.width {
Length::Fill | Length::FillPortion(_) => { Length::Fill | Length::FillPortion(_) => {
@ -162,14 +162,9 @@ pub fn resolve<Message>(
}); });
}); });
let actual_height = nodes
.iter()
.map(|node| node.bounds().y + node.bounds().height)
.fold(0.0f32, f32::max);
let size = Size { let size = Size {
width: flex_layout.content_size.width, width: flex_layout.content_size.width,
height: actual_height.max(flex_layout.content_size.height), height: flex_layout.content_size.height,
}; };
Node::with_children(size, nodes) Node::with_children(size, nodes)

View file

@ -100,7 +100,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for FlexR
} }
fn layout( fn layout(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
renderer: &Renderer, renderer: &Renderer,
limits: &layout::Limits, limits: &layout::Limits,
@ -114,32 +114,32 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for FlexR
super::layout::resolve( super::layout::resolve(
renderer, renderer,
&limits, &limits,
&mut self.children, &self.children,
self.padding, self.padding,
f32::from(self.column_spacing), f32::from(self.column_spacing),
f32::from(self.row_spacing), f32::from(self.row_spacing),
self.min_item_width, self.min_item_width,
self.justify_items,
self.align_items, self.align_items,
self.justify_items,
self.justify_content, self.justify_content,
&mut tree.children, &mut tree.children,
) )
} }
fn operate( fn operate(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn Operation<()>, operation: &mut dyn Operation<()>,
) { ) {
operation.traverse(&mut |operation| { operation.container(None, layout.bounds(), &mut |operation| {
self.children self.children
.iter_mut() .iter()
.zip(&mut tree.children) .zip(&mut tree.children)
.zip(layout.children()) .zip(layout.children())
.for_each(|((child, state), c_layout)| { .for_each(|((child, state), c_layout)| {
child.as_widget_mut().operate( child.as_widget().operate(
state, state,
c_layout.with_virtual_offset(layout.virtual_offset()), c_layout.with_virtual_offset(layout.virtual_offset()),
renderer, renderer,
@ -149,34 +149,34 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for FlexR
}); });
} }
fn update( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &Renderer, renderer: &Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) { ) -> event::Status {
for ((child, state), c_layout) in self self.children
.children
.iter_mut() .iter_mut()
.zip(&mut tree.children) .zip(&mut tree.children)
.zip(layout.children()) .zip(layout.children())
{ .map(|((child, state), c_layout)| {
child.as_widget_mut().update( child.as_widget_mut().on_event(
state, state,
event, event.clone(),
c_layout.with_virtual_offset(layout.virtual_offset()), c_layout.with_virtual_offset(layout.virtual_offset()),
cursor, cursor,
renderer, renderer,
clipboard, clipboard,
shell, shell,
viewport, viewport,
); )
} })
.fold(event::Status::Ignored, event::Status::merge)
} }
fn mouse_interaction( fn mouse_interaction(
@ -235,19 +235,11 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for FlexR
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'b>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
viewport: &Rectangle,
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
overlay::from_children( overlay::from_children(&mut self.children, tree, layout, renderer, translation)
&mut self.children,
tree,
layout,
renderer,
viewport,
translation,
)
} }
#[cfg(feature = "a11y")] #[cfg(feature = "a11y")]

View file

@ -8,16 +8,15 @@ use std::path::Path;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use ::image as image_rs; use ::image as image_rs;
use iced::Task;
use iced::mouse;
use iced_core::image::Renderer as ImageRenderer; use iced_core::image::Renderer as ImageRenderer;
use iced_core::mouse::Cursor; use iced_core::mouse::Cursor;
use iced_core::widget::{Tree, tree}; use iced_core::widget::{Tree, tree};
use iced_core::{ use iced_core::{
Clipboard, ContentFit, Element, Event, Layout, Length, Rectangle, Rotation, Shell, Size, Clipboard, ContentFit, Element, Event, Layout, Length, Rectangle, Shell, Size, Vector, Widget,
Widget, event, layout, renderer, window, event, layout, renderer, window,
}; };
use iced_widget::image::{self, FilterMethod, Handle}; use iced_runtime::Command;
use iced_widget::image::{self, Handle};
use image_rs::AnimationDecoder; use image_rs::AnimationDecoder;
use image_rs::codecs::gif::GifDecoder; use image_rs::codecs::gif::GifDecoder;
use image_rs::codecs::png::PngDecoder; use image_rs::codecs::png::PngDecoder;
@ -28,7 +27,7 @@ use iced_futures::futures::{AsyncRead, AsyncReadExt};
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
use tokio::io::{AsyncRead, AsyncReadExt}; use tokio::io::{AsyncRead, AsyncReadExt};
use crate::widget::icon; use super::icon::load_icon;
#[must_use] #[must_use]
/// Creates a new [`AnimatedImage`] with the given [`animated_image::Frames`] /// Creates a new [`AnimatedImage`] with the given [`animated_image::Frames`]
@ -75,13 +74,13 @@ impl Frames {
size: u16, size: u16,
theme: Option<&str>, theme: Option<&str>,
default_fallbacks: bool, default_fallbacks: bool,
) -> Task<Result<Frames, Error>> { ) -> Command<Result<Frames, Error>> {
let mut name_path_buffer = None; let mut name_path_buffer = None;
if let Some(path) = icon::Named::new(name).size(size).path() { if let Some(path) = load_icon(name, size, theme) {
name_path_buffer = Some(path); name_path_buffer = Some(path);
} else if default_fallbacks { } else if default_fallbacks {
for name in name.rmatch_indices('-').map(|(pos, _)| &name[..pos]) { for name in name.rmatch_indices('-').map(|(pos, _)| &name[..pos]) {
if let Some(path) = icon::Named::new(name).size(size).path() { if let Some(path) = load_icon(name, size, theme) {
name_path_buffer = Some(path); name_path_buffer = Some(path);
break; break;
} }
@ -91,14 +90,14 @@ impl Frames {
if let Some(name_path_buffer) = name_path_buffer { if let Some(name_path_buffer) = name_path_buffer {
Self::load_from_path(name_path_buffer) Self::load_from_path(name_path_buffer)
} else { } else {
Task::perform(async { Err(Error::Missing) }, std::convert::identity) Command::perform(async { Err(Error::Missing) }, std::convert::identity)
} }
} }
/// Load [`Frames`] from the supplied path /// Load [`Frames`] from the supplied path
pub fn load_from_path(path: impl AsRef<Path>) -> Task<Result<Frames, Error>> { pub fn load_from_path(path: impl AsRef<Path>) -> Command<Result<Frames, Error>> {
#[inline(never)] #[inline(never)]
fn inner(path: &Path) -> Task<Result<Frames, Error>> { fn inner(path: &Path) -> Command<Result<Frames, Error>> {
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
use tokio::fs::File; use tokio::fs::File;
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
@ -109,7 +108,7 @@ impl Frames {
#[cfg(not(feature = "tokio"))] #[cfg(not(feature = "tokio"))]
use iced_futures::futures::io::BufReader; use iced_futures::futures::io::BufReader;
let path = path.to_path_buf(); let path = path.as_ref().to_path_buf();
let f = async move { let f = async move {
let image_type = match &path.extension() { let image_type = match &path.extension() {
@ -120,10 +119,10 @@ impl Frames {
}; };
let reader = BufReader::new(File::open(path).await?); let reader = BufReader::new(File::open(path).await?);
Frames::from_reader(reader, image_type).await Self::from_reader(reader, image_type).await
}; };
Task::perform(f, std::convert::identity) Command::perform(f, std::convert::identity)
} }
inner(path.as_ref()) inner(path.as_ref())
@ -146,7 +145,7 @@ impl Frames {
match image_type { match image_type {
ImageType::Gif => Self::from_decoder(GifDecoder::new(io::Cursor::new(bytes))?), ImageType::Gif => Self::from_decoder(GifDecoder::new(io::Cursor::new(bytes))?),
ImageType::Apng => Self::from_decoder(PngDecoder::new(io::Cursor::new(bytes))?.apng()?), ImageType::Apng => Self::from_decoder(PngDecoder::new(io::Cursor::new(bytes))?.apng()),
ImageType::WebP => Self::from_decoder(WebPDecoder::new(io::Cursor::new(bytes))?), ImageType::WebP => Self::from_decoder(WebPDecoder::new(io::Cursor::new(bytes))?),
} }
} }
@ -168,10 +167,10 @@ impl Frames {
let first = frames.first().cloned().unwrap(); let first = frames.first().cloned().unwrap();
let total_bytes = frames let total_bytes = frames
.iter() .iter()
.map(|f| match &f.handle { .map(|f| match f.handle.data() {
Handle::Path(..) => 0, iced_core::image::Data::Path(_) => 0,
Handle::Bytes(_, b) => b.len(), iced_core::image::Data::Bytes(b) => b.len(),
Handle::Rgba { pixels, .. } => pixels.len(), iced_core::image::Data::Rgba { pixels, .. } => pixels.len(),
}) })
.sum::<usize>() .sum::<usize>()
.try_into() .try_into()
@ -196,7 +195,7 @@ impl From<image_rs::Frame> for Frame {
let delay = frame.delay().into(); let delay = frame.delay().into();
let handle = image::Handle::from_rgba(width, height, frame.into_buffer().into_vec()); let handle = image::Handle::from_pixels(width, height, frame.into_buffer().into_vec());
Self { delay, handle } Self { delay, handle }
} }
@ -279,8 +278,12 @@ impl<'a, Message, Renderer> Widget<Message, crate::Theme, Renderer> for Animated
where where
Renderer: ImageRenderer<Handle = Handle>, Renderer: ImageRenderer<Handle = Handle>,
{ {
fn size(&self) -> Size<Length> { fn width(&self) -> Length {
Size::new(self.width.into(), self.height.into()) self.width
}
fn height(&self) -> Length {
self.height
} }
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
@ -312,40 +315,30 @@ where
} }
} }
fn layout( fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
&mut self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
iced_widget::image::layout( iced_widget::image::layout(
renderer, renderer,
limits, limits,
&self.frames.first.handle, &self.frames.first.handle,
self.width, self.width,
self.height, self.height,
None,
self.content_fit, self.content_fit,
Rotation::default(),
false,
[0.0; 4],
) )
} }
fn update( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: &Event, event: Event,
layout: Layout<'_>, _layout: Layout<'_>,
cursor_position: mouse::Cursor, _cursor_position: Cursor,
renderer: &Renderer, _renderer: &Renderer,
clipboard: &mut dyn Clipboard, _clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, ) -> event::Status {
) {
let state = tree.state.downcast_mut::<State>(); let state = tree.state.downcast_mut::<State>();
if let Event::Window(window::Event::RedrawRequested(now)) = event { if let Event::Window(_, window::Event::RedrawRequested(now)) = event {
let elapsed = now.duration_since(state.current.started); let elapsed = now.duration_since(state.current.started);
if elapsed > state.current.frame.delay { if elapsed > state.current.frame.delay {
@ -353,14 +346,15 @@ where
state.current = self.frames.frames[state.index].clone().into(); state.current = self.frames.frames[state.index].clone().into();
shell shell.request_redraw(window::RedrawRequest::At(now + state.current.frame.delay));
.request_redraw_at(window::RedrawRequest::At(*now + state.current.frame.delay));
} else { } else {
let remaining = state.current.frame.delay - elapsed; let remaining = state.current.frame.delay - elapsed;
shell.request_redraw_at(window::RedrawRequest::At(*now + remaining)); shell.request_redraw(window::RedrawRequest::At(now + remaining));
} }
} }
event::Status::Ignored
} }
fn draw( fn draw(
@ -375,18 +369,37 @@ where
) { ) {
let state = tree.state.downcast_ref::<State>(); let state = tree.state.downcast_ref::<State>();
iced_widget::image::draw( // Pulled from iced_native::widget::<Image as Widget>::draw
renderer, //
layout, // TODO: export iced_native::widget::image::draw as standalone function
&state.current.frame.handle, {
None, let Size { width, height } = renderer.dimensions(&state.current.frame.handle);
iced_core::border::Radius::default(), let image_size = Size::new(width as f32, height as f32);
self.content_fit,
FilterMethod::default(), let bounds = layout.bounds();
Rotation::default(), let adjusted_fit = self.content_fit.fit(image_size, bounds.size());
1.0,
1.0, let render = |renderer: &mut Renderer| {
); let offset = Vector::new(
(bounds.width - adjusted_fit.width).max(0.0) / 2.0,
(bounds.height - adjusted_fit.height).max(0.0) / 2.0,
);
let drawing_bounds = Rectangle {
width: adjusted_fit.width,
height: adjusted_fit.height,
..bounds
};
renderer.draw(state.current.frame.handle.clone(), drawing_bounds + offset);
};
if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height {
renderer.with_layer(bounds, render);
} else {
render(renderer);
}
}
} }
} }

View file

@ -17,7 +17,7 @@ use taffy::{AlignContent, TaffyTree};
pub fn resolve<Message>( pub fn resolve<Message>(
renderer: &Renderer, renderer: &Renderer,
limits: &Limits, limits: &Limits,
items: &mut [Element<'_, Message>], items: &[Element<'_, Message>],
assignments: &[Assignment], assignments: &[Assignment],
width: Length, width: Length,
height: Length, height: Length,
@ -37,13 +37,9 @@ pub fn resolve<Message>(
let mut taffy = TaffyTree::<()>::with_capacity(items.len() + 1); let mut taffy = TaffyTree::<()>::with_capacity(items.len() + 1);
// Attach widgets as child nodes. // Attach widgets as child nodes.
for ((child, assignment), tree) in items for ((child, assignment), tree) in items.iter().zip(assignments.iter()).zip(tree.iter_mut()) {
.iter_mut()
.zip(assignments.iter())
.zip(tree.iter_mut())
{
// Calculate the dimensions of the item. // Calculate the dimensions of the item.
let child_widget = child.as_widget_mut(); let child_widget = child.as_widget();
let child_node = child_widget.layout(tree, renderer, limits); let child_node = child_widget.layout(tree, renderer, limits);
let size = child_node.size(); let size = child_node.size();
@ -176,12 +172,12 @@ pub fn resolve<Message>(
for (((leaf, child), node), tree) in leafs for (((leaf, child), node), tree) in leafs
.into_iter() .into_iter()
.zip(items.iter_mut()) .zip(items.iter())
.zip(nodes.iter_mut()) .zip(nodes.iter_mut())
.zip(tree) .zip(tree)
{ {
if let Ok(leaf_layout) = taffy.layout(leaf) { if let Ok(leaf_layout) = taffy.layout(leaf) {
let child_widget = child.as_widget_mut(); let child_widget = child.as_widget();
let c_size = child_widget.size(); let c_size = child_widget.size();
match c_size.width { match c_size.width {
Length::Fill | Length::FillPortion(_) => { Length::Fill | Length::FillPortion(_) => {

View file

@ -127,7 +127,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for Grid<
} }
fn layout( fn layout(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
renderer: &Renderer, renderer: &Renderer,
limits: &layout::Limits, limits: &layout::Limits,
@ -141,7 +141,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for Grid<
super::layout::resolve( super::layout::resolve(
renderer, renderer,
&limits, &limits,
&mut self.children, &self.children,
&self.assignments, &self.assignments,
self.width, self.width,
self.height, self.height,
@ -156,19 +156,19 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for Grid<
} }
fn operate( fn operate(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn Operation<()>, operation: &mut dyn Operation<()>,
) { ) {
operation.traverse(&mut |operation| { operation.container(None, layout.bounds(), &mut |operation| {
self.children self.children
.iter_mut() .iter()
.zip(&mut tree.children) .zip(&mut tree.children)
.zip(layout.children()) .zip(layout.children())
.for_each(|((child, state), c_layout)| { .for_each(|((child, state), c_layout)| {
child.as_widget_mut().operate( child.as_widget().operate(
state, state,
c_layout.with_virtual_offset(layout.virtual_offset()), c_layout.with_virtual_offset(layout.virtual_offset()),
renderer, renderer,
@ -178,34 +178,34 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for Grid<
}); });
} }
fn update( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &Renderer, renderer: &Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) { ) -> event::Status {
for ((child, state), c_layout) in self self.children
.children
.iter_mut() .iter_mut()
.zip(&mut tree.children) .zip(&mut tree.children)
.zip(layout.children()) .zip(layout.children())
{ .map(|((child, state), c_layout)| {
child.as_widget_mut().update( child.as_widget_mut().on_event(
state, state,
event, event.clone(),
c_layout.with_virtual_offset(layout.virtual_offset()), c_layout.with_virtual_offset(layout.virtual_offset()),
cursor, cursor,
renderer, renderer,
clipboard, clipboard,
shell, shell,
viewport, viewport,
); )
} })
.fold(event::Status::Ignored, event::Status::merge)
} }
fn mouse_interaction( fn mouse_interaction(
@ -264,19 +264,11 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for Grid<
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'b>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
viewport: &Rectangle,
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
overlay::from_children( overlay::from_children(&mut self.children, tree, layout, renderer, translation)
&mut self.children,
tree,
layout,
renderer,
viewport,
translation,
)
} }
#[cfg(feature = "a11y")] #[cfg(feature = "a11y")]

View file

@ -5,8 +5,9 @@ use crate::cosmic_theme::{Density, Spacing};
use crate::{Element, theme, widget}; use crate::{Element, theme, widget};
use apply::Apply; use apply::Apply;
use derive_setters::Setters; use derive_setters::Setters;
use iced_core::{Length, Size, Vector, Widget, layout, text, widget::tree}; use iced::Length;
use std::borrow::Cow; use iced_core::{Vector, Widget, widget::tree};
use std::{borrow::Cow, cmp};
#[must_use] #[must_use]
pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> {
@ -26,6 +27,7 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> {
sharp_corners: false, sharp_corners: false,
is_ssd: false, is_ssd: false,
on_double_click: None, on_double_click: None,
is_condensed: false,
transparent: false, transparent: false,
} }
} }
@ -89,6 +91,9 @@ pub struct HeaderBar<'a, Message> {
/// HeaderBar used for server-side decorations /// HeaderBar used for server-side decorations
is_ssd: bool, is_ssd: bool,
/// Whether the headerbar should be compact
is_condensed: bool,
/// Whether the headerbar should be transparent /// Whether the headerbar should be transparent
transparent: bool, transparent: bool,
} }
@ -121,116 +126,48 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
self.end.push(widget.into()); self.end.push(widget.into());
self self
} }
/// Build the widget
#[must_use]
#[inline]
pub fn build(self) -> HeaderBarWidget<'a, Message> {
HeaderBarWidget {
header_bar_inner: self.view(),
}
}
} }
pub struct HeaderBarWidget<'a, Message> { pub struct HeaderBarWidget<'a, Message> {
start: Element<'a, Message>, header_bar_inner: Element<'a, Message>,
center: Option<Element<'a, Message>>,
end: Element<'a, Message>,
} }
impl<'a, Message> HeaderBarWidget<'a, Message> { impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
pub fn new( for HeaderBarWidget<'_, Message>
start: Element<'a, Message>,
center: Option<Element<'a, Message>>,
end: Element<'a, Message>,
) -> Self {
Self { start, center, end }
}
fn elems(&self) -> impl Iterator<Item = &Element<'a, Message>> {
std::iter::once(&self.start)
.chain(std::iter::once(&self.end))
.chain(self.center.as_ref())
}
fn elems_mut(&mut self) -> impl Iterator<Item = &mut Element<'a, Message>> {
std::iter::once(&mut self.start)
.chain(std::iter::once(&mut self.end))
.chain(self.center.as_mut())
}
}
impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
for HeaderBarWidget<'a, Message>
{ {
fn diff(&mut self, tree: &mut tree::Tree) { fn diff(&mut self, tree: &mut tree::Tree) {
if let Some(center) = &mut self.center { tree.diff_children(&mut [&mut self.header_bar_inner]);
tree.diff_children(&mut [&mut self.start, &mut self.end, center]);
} else {
tree.diff_children(&mut [&mut self.start, &mut self.end]);
}
} }
fn children(&self) -> Vec<tree::Tree> { fn children(&self) -> Vec<tree::Tree> {
self.elems().map(tree::Tree::new).collect() vec![tree::Tree::new(&self.header_bar_inner)]
} }
fn size(&self) -> Size<Length> { fn size(&self) -> iced_core::Size<Length> {
Size { self.header_bar_inner.as_widget().size()
width: Length::Fill,
height: Length::Shrink,
}
} }
fn layout( fn layout(
&mut self, &self,
tree: &mut tree::Tree, tree: &mut tree::Tree,
renderer: &crate::Renderer, renderer: &crate::Renderer,
limits: &layout::Limits, limits: &iced_core::layout::Limits,
) -> layout::Node { ) -> iced_core::layout::Node {
let width = limits.max().width; let child_tree = &mut tree.children[0];
let height = limits.max().height; let child = self
let gap = 8.0; .header_bar_inner
.as_widget()
let end_node = .layout(child_tree, renderer, limits);
self.end iced_core::layout::Node::with_children(child.size(), vec![child])
.as_widget_mut()
.layout(&mut tree.children[1], renderer, &limits.loose());
let end_width = end_node.size().width;
let start_available = (width - end_width - gap).max(0.0);
let start_node = self.start.as_widget_mut().layout(
&mut tree.children[0],
renderer,
&layout::Limits::new(Size::ZERO, Size::new(start_available, height)),
);
let start_width = start_node.size().width;
let vcenter = |node: layout::Node, x: f32| -> layout::Node {
let dy = ((height - node.size().height) / 2.0).max(0.0);
node.translate(Vector::new(x, dy))
};
let mut child_nodes = Vec::with_capacity(3);
child_nodes.push(vcenter(start_node, 0.0));
child_nodes.push(vcenter(end_node, width - end_width));
if let Some(center) = &mut self.center {
let slot_start = start_width + gap;
let slot_end = (width - end_width - gap).max(slot_start);
let slot_width = slot_end - slot_start;
// this instead of `node.size().width` prevents center jitter as text ellipsizes
let natural_width = center
.as_widget_mut()
.layout(&mut tree.children[2], renderer, &limits.loose())
.size()
.width;
let node = center.as_widget_mut().layout(
&mut tree.children[2],
renderer,
&layout::Limits::new(Size::ZERO, Size::new(slot_width, height)),
);
let ideal_x = (width - natural_width) / 2.0;
let max_x = (width - end_width - gap - natural_width).max(slot_start);
let center_x = ideal_x.clamp(slot_start, max_x);
child_nodes.push(vcenter(node, center_x))
}
layout::Node::with_children(Size::new(width, height), child_nodes)
} }
fn draw( fn draw(
@ -243,33 +180,42 @@ impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer
cursor: iced_core::mouse::Cursor, cursor: iced_core::mouse::Cursor,
viewport: &iced_core::Rectangle, viewport: &iced_core::Rectangle,
) { ) {
self.elems() let layout_children = layout.children().next().unwrap();
.zip(&tree.children) let state_children = &tree.children[0];
.zip(layout.children()) self.header_bar_inner.as_widget().draw(
.for_each(|((e, s), l)| { state_children,
e.as_widget() renderer,
.draw(s, renderer, theme, style, l, cursor, viewport); theme,
}); style,
layout_children,
cursor,
viewport,
);
} }
fn update( fn on_event(
&mut self, &mut self,
state: &mut tree::Tree, state: &mut tree::Tree,
event: &iced_core::Event, event: iced_core::Event,
layout: iced_core::Layout<'_>, layout: iced_core::Layout<'_>,
cursor: iced_core::mouse::Cursor, cursor: iced_core::mouse::Cursor,
renderer: &crate::Renderer, renderer: &crate::Renderer,
clipboard: &mut dyn iced_core::Clipboard, clipboard: &mut dyn iced_core::Clipboard,
shell: &mut iced_core::Shell<'_, Message>, shell: &mut iced_core::Shell<'_, Message>,
viewport: &iced_core::Rectangle, viewport: &iced_core::Rectangle,
) { ) -> iced_core::event::Status {
self.elems_mut() let child_state = &mut state.children[0];
.zip(&mut state.children) let child_layout = layout.children().next().unwrap();
.zip(layout.children()) self.header_bar_inner.as_widget_mut().on_event(
.for_each(|((e, s), l)| { child_state,
e.as_widget_mut() event,
.update(s, event, l, cursor, renderer, clipboard, shell, viewport); child_layout,
}); cursor,
renderer,
clipboard,
shell,
viewport,
)
} }
fn mouse_interaction( fn mouse_interaction(
@ -280,47 +226,46 @@ impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer
viewport: &iced_core::Rectangle, viewport: &iced_core::Rectangle,
renderer: &crate::Renderer, renderer: &crate::Renderer,
) -> iced_core::mouse::Interaction { ) -> iced_core::mouse::Interaction {
self.elems() let child_tree = &state.children[0];
.zip(&state.children) let child_layout = layout.children().next().unwrap();
.zip(layout.children()) self.header_bar_inner.as_widget().mouse_interaction(
.map(|((e, s), l)| { child_tree,
e.as_widget() child_layout,
.mouse_interaction(s, l, cursor, viewport, renderer) cursor,
}) viewport,
.max() renderer,
.unwrap_or(iced_core::mouse::Interaction::None) )
} }
fn operate( fn operate(
&mut self, &self,
state: &mut tree::Tree, state: &mut tree::Tree,
layout: iced_core::Layout<'_>, layout: iced_core::Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
operation: &mut dyn iced_core::widget::Operation<()>, operation: &mut dyn iced_core::widget::Operation<()>,
) { ) {
self.elems_mut() let child_tree = &mut state.children[0];
.zip(&mut state.children) let child_layout = layout.children().next().unwrap();
.zip(layout.children()) self.header_bar_inner
.for_each(|((e, s), l)| { .as_widget()
e.as_widget_mut().operate(s, l, renderer, operation); .operate(child_tree, child_layout, renderer, operation);
});
} }
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
state: &'b mut tree::Tree, state: &'b mut tree::Tree,
layout: iced_core::Layout<'b>, layout: iced_core::Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
viewport: &iced_core::Rectangle,
translation: Vector, translation: Vector,
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
self.elems_mut() let child_tree = &mut state.children[0];
.zip(&mut state.children) let child_layout = layout.children().next().unwrap();
.zip(layout.children()) self.header_bar_inner.as_widget_mut().overlay(
.find_map(|((e, s), l)| { child_tree,
e.as_widget_mut() child_layout,
.overlay(s, l, renderer, viewport, translation) renderer,
}) translation,
)
} }
fn drag_destinations( fn drag_destinations(
@ -330,13 +275,16 @@ impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer
renderer: &crate::Renderer, renderer: &crate::Renderer,
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) { ) {
self.elems() if let Some((child_tree, child_layout)) =
.zip(&state.children) state.children.iter().zip(layout.children()).next()
.zip(layout.children()) {
.for_each(|((e, s), l)| { self.header_bar_inner.as_widget().drag_destinations(
e.as_widget() child_tree,
.drag_destinations(s, l, renderer, dnd_rectangles); child_layout,
}); renderer,
dnd_rectangles,
);
}
} }
#[cfg(feature = "a11y")] #[cfg(feature = "a11y")]
@ -347,22 +295,16 @@ impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer
state: &tree::Tree, state: &tree::Tree,
p: iced::mouse::Cursor, p: iced::mouse::Cursor,
) -> iced_accessibility::A11yTree { ) -> iced_accessibility::A11yTree {
iced_accessibility::A11yTree::join( let c_layout = layout.children().next().unwrap();
self.elems() let c_state = &state.children[0];
.zip(&state.children) self.header_bar_inner
.zip(layout.children()) .as_widget()
.map(|((e, s), l)| e.as_widget().a11y_nodes(l, s, p)), .a11y_nodes(c_layout, c_state, p)
)
}
}
impl<'a, Message: Clone + 'static> From<HeaderBarWidget<'a, Message>> for Element<'a, Message> {
fn from(w: HeaderBarWidget<'a, Message>) -> Self {
Element::new(w)
} }
} }
impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
#[allow(clippy::too_many_lines)]
/// Converts the headerbar builder into an Iced element. /// Converts the headerbar builder into an Iced element.
pub fn view(mut self) -> Element<'a, Message> { pub fn view(mut self) -> Element<'a, Message> {
let Spacing { let Spacing {
@ -376,84 +318,153 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
let center = std::mem::take(&mut self.center); let center = std::mem::take(&mut self.center);
let mut end = std::mem::take(&mut self.end); let mut end = std::mem::take(&mut self.end);
let window_control_cnt = self.on_close.is_some() as usize
+ self.on_maximize.is_some() as usize
+ self.on_minimize.is_some() as usize;
// Also packs the window controls at the very end. // Also packs the window controls at the very end.
end.push(self.window_controls(space_xxs)); end.push(self.window_controls());
let padding = if self.is_ssd { // Center content depending on window border
[2, 8, 2, 8] let padding = match self.density.unwrap_or_else(crate::config::header_size) {
} else { Density::Compact => {
match ( if self.maximized {
self.density.unwrap_or_else(crate::config::header_size), [4, 8, 4, 8]
self.maximized, // window border handling } else {
) { [3, 7, 4, 7]
(Density::Compact, true) => [4, 8, 4, 8], }
(Density::Compact, false) => [3, 7, 4, 7], }
(_, true) => [8, 8, 8, 8], _ => {
(_, false) => [7, 7, 8, 7], if self.maximized {
[8, 8, 8, 8]
} else {
[7, 7, 8, 7]
}
} }
}; };
let start = widget::row::with_children(start) let acc_count = |v: &[Element<'a, Message>]| {
.spacing(space_xxxs) v.iter().fold(0, |acc, e| {
.align_y(iced::Alignment::Center) acc + match e.as_widget().size().width {
.into(); Length::Fixed(w) if w > 30. => (w / 30.0).ceil() as usize,
let center = if !center.is_empty() { _ => 1,
Some( }
widget::row::with_children(center) })
};
let left_len = acc_count(&start);
let right_len = acc_count(&end);
let portion = ((left_len.max(right_len + window_control_cnt) as f32
/ center.len().max(1) as f32)
.round() as u16)
.max(1);
let (left_portion, right_portion) =
if center.is_empty() && (self.title.is_empty() || self.is_condensed) {
let left_to_right_ratio = left_len as f32 / right_len.max(1) as f32;
let right_to_left_ratio = right_len as f32 / left_len.max(1) as f32;
if right_to_left_ratio > 2. || left_len < 1 {
(1, 2)
} else if left_to_right_ratio > 2. || right_len < 1 {
(2, 1)
} else {
(left_len as u16, (right_len + window_control_cnt) as u16)
}
} else {
(portion, portion)
};
let title_portion = cmp::max(left_portion, right_portion) * 2;
// Creates the headerbar widget.
let mut widget = widget::row::with_capacity(3)
// If elements exist in the start region, append them here.
.push(
widget::row::with_children(start)
.spacing(space_xxxs) .spacing(space_xxxs)
.align_y(iced::Alignment::Center) .align_y(iced::Alignment::Center)
.into(), .apply(widget::container)
.align_x(iced::Alignment::Start)
.width(Length::FillPortion(left_portion)),
) )
} else if !self.title.is_empty() { // If elements exist in the center region, use them here.
Some( // This will otherwise use the title as a widget if a title was defined.
widget::text::heading(self.title) .push_maybe(if !center.is_empty() {
.wrapping(text::Wrapping::None) Some(
.ellipsize(text::Ellipsize::End(text::EllipsizeHeightLimit::Lines(1))) widget::row::with_children(center)
.into(), .spacing(space_xxxs)
.align_y(iced::Alignment::Center)
.apply(widget::container)
.center_x(Length::Fill)
.into(),
)
} else if !self.title.is_empty() && !self.is_condensed {
Some(self.title_widget(title_portion))
} else {
None
})
.push(
widget::row::with_children(end)
.spacing(space_xxs)
.align_y(iced::Alignment::Center)
.apply(widget::container)
.align_x(iced::Alignment::End)
.width(Length::FillPortion(right_portion)),
) )
} else {
None
};
let end = widget::row::with_children(end)
.spacing(space_xxs)
.align_y(iced::Alignment::Center) .align_y(iced::Alignment::Center)
.into(); .height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32))
.padding(if self.is_ssd { [0, 8, 0, 8] } else { padding })
let mut widget = HeaderBarWidget::new(start, center, end) .spacing(8)
.apply(widget::container) .apply(widget::container)
.class(theme::Container::HeaderBar { .class(crate::theme::Container::HeaderBar {
focused: self.focused, focused: self.focused,
sharp_corners: self.sharp_corners, sharp_corners: self.sharp_corners,
transparent: self.transparent, transparent: self.transparent,
}) })
.height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32)) .center_y(Length::Shrink)
.padding(padding)
.apply(widget::mouse_area); .apply(widget::mouse_area);
if let Some(message) = self.on_drag { // Assigns a message to emit when the headerbar is dragged.
if let Some(message) = self.on_drag.clone() {
widget = widget.on_drag(message); widget = widget.on_drag(message);
} }
if let Some(message) = self.on_maximize {
// Assigns a message to emit when the headerbar is double-clicked.
if let Some(message) = self.on_maximize.clone() {
widget = widget.on_release(message); widget = widget.on_release(message);
} }
if let Some(message) = self.on_double_click { if let Some(message) = self.on_double_click.clone() {
widget = widget.on_double_press(message); widget = widget.on_double_press(message);
} }
if let Some(message) = self.on_right_click { if let Some(message) = self.on_right_click.clone() {
widget = widget.on_right_press(message); widget = widget.on_right_press(message);
} }
widget.into() widget.into()
} }
fn title_widget(&mut self, title_portion: u16) -> Element<'a, Message> {
let mut title = Cow::default();
std::mem::swap(&mut title, &mut self.title);
widget::text::heading(title)
.wrapping(iced_core::text::Wrapping::None)
.ellipsize(iced_core::text::Ellipsize::End(
iced_core::text::EllipsizeHeightLimit::Lines(1),
))
.apply(widget::container)
.center(Length::FillPortion(title_portion))
.into()
}
/// Creates the widget for window controls. /// Creates the widget for window controls.
fn window_controls(&mut self, spacing: u16) -> Element<'a, Message> { fn window_controls(&mut self) -> Element<'a, Message> {
macro_rules! icon { macro_rules! icon {
($name:expr, $size:expr, $on_press:expr) => {{ ($name:expr, $size:expr, $on_press:expr) => {{
widget::icon::from_name($name) let icon = {
.apply(widget::button::icon) widget::icon::from_name($name)
.padding(8) .apply(widget::button::icon)
.class(theme::Button::HeaderBar) .padding(8)
};
icon.class(crate::theme::Button::HeaderBar)
.selected(self.focused) .selected(self.focused)
.icon_size($size) .icon_size($size)
.on_press($on_press) .on_press($on_press)
@ -464,7 +475,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
.push_maybe( .push_maybe(
self.on_minimize self.on_minimize
.take() .take()
.map(|m| icon!("window-minimize-symbolic", 16, m)), .map(|m: Message| icon!("window-minimize-symbolic", 16, m)),
) )
.push_maybe(self.on_maximize.take().map(|m| { .push_maybe(self.on_maximize.take().map(|m| {
if self.maximized { if self.maximized {
@ -478,14 +489,21 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
.take() .take()
.map(|m| icon!("window-close-symbolic", 16, m)), .map(|m| icon!("window-close-symbolic", 16, m)),
) )
.spacing(spacing) .spacing(theme::spacing().space_xxs)
.align_y(iced::Alignment::Center) .apply(widget::container)
.center_y(Length::Fill)
.into() .into()
} }
} }
impl<'a, Message: Clone + 'static> From<HeaderBar<'a, Message>> for Element<'a, Message> { impl<'a, Message: Clone + 'static> From<HeaderBar<'a, Message>> for Element<'a, Message> {
fn from(headerbar: HeaderBar<'a, Message>) -> Self { fn from(headerbar: HeaderBar<'a, Message>) -> Self {
headerbar.view() Element::new(headerbar.build())
}
}
impl<'a, Message: Clone + 'static> From<HeaderBarWidget<'a, Message>> for Element<'a, Message> {
fn from(headerbar: HeaderBarWidget<'a, Message>) -> Self {
Element::new(headerbar)
} }
} }

View file

@ -4,12 +4,12 @@
//! Embedded icons for platforms which do not support icon themes yet. //! Embedded icons for platforms which do not support icon themes yet.
/// Icon bundling is not enabled on unix platforms. /// Icon bundling is not enabled on unix platforms.
#[cfg(all(unix, not(target_os = "macos")))] #[cfg(unix)]
pub fn get(icon_name: &str) -> Option<super::Data> { pub fn get(icon_name: &str) -> Option<super::Data> {
None None
} }
#[cfg(any(not(unix), target_os = "macos"))] #[cfg(not(unix))]
/// Get a bundled icon on non-unix platforms. /// Get a bundled icon on non-unix platforms.
pub fn get(icon_name: &str) -> Option<super::Data> { pub fn get(icon_name: &str) -> Option<super::Data> {
ICONS ICONS
@ -17,5 +17,5 @@ pub fn get(icon_name: &str) -> Option<super::Data> {
.map(|bytes| super::Data::Svg(crate::iced::widget::svg::Handle::from_memory(*bytes))) .map(|bytes| super::Data::Svg(crate::iced::widget::svg::Handle::from_memory(*bytes)))
} }
#[cfg(any(not(unix), target_os = "macos"))] #[cfg(not(unix))]
include!(concat!(env!("OUT_DIR"), "/bundled_icons.rs")); include!(concat!(env!("OUT_DIR"), "/bundled_icons.rs"));

View file

@ -15,7 +15,7 @@ pub use handle::{Data, Handle, from_path, from_raster_bytes, from_raster_pixels,
use crate::Element; use crate::Element;
use derive_setters::Setters; use derive_setters::Setters;
use iced::widget::{Image, Svg}; use iced::widget::{Image, Svg};
use iced::{ContentFit, Length, Radians, Rectangle}; use iced::{ContentFit, Length, Rectangle};
use iced_core::Rotation; use iced_core::Rotation;
/// Create an [`Icon`] from a pre-existing [`Handle`] /// Create an [`Icon`] from a pre-existing [`Handle`]
@ -125,22 +125,17 @@ pub fn draw(renderer: &mut crate::Renderer, handle: &Handle, icon_bounds: Rectan
renderer, renderer,
iced_core::svg::Svg::new(handle), iced_core::svg::Svg::new(handle),
icon_bounds, icon_bounds,
icon_bounds,
), ),
Data::Image(handle) => { Data::Image(handle) => {
iced_core::image::Renderer::draw_image( iced_core::image::Renderer::draw_image(
renderer, renderer,
iced_core::Image { handle,
handle, iced_core::image::FilterMethod::Linear,
filter_method: iced_core::image::FilterMethod::Linear,
rotation: Radians(0.),
border_radius: [0.0; 4].into(),
opacity: 1.0,
snap: true,
},
icon_bounds,
icon_bounds, icon_bounds,
iced_core::Radians::from(0),
1.0,
[0.0; 4],
); );
} }
} }

View file

@ -52,7 +52,7 @@ impl Named {
} }
} }
#[cfg(all(unix, not(target_os = "macos")))] #[cfg(not(windows))]
#[must_use] #[must_use]
pub fn path(self) -> Option<PathBuf> { pub fn path(self) -> Option<PathBuf> {
let name = &*self.name; let name = &*self.name;
@ -107,7 +107,7 @@ impl Named {
result result
} }
#[cfg(any(not(unix), target_os = "macos"))] #[cfg(windows)]
#[must_use] #[must_use]
pub fn path(self) -> Option<PathBuf> { pub fn path(self) -> Option<PathBuf> {
//TODO: implement icon lookup for Windows //TODO: implement icon lookup for Windows

View file

@ -3,7 +3,7 @@ use iced_core::layout;
use iced_core::mouse; use iced_core::mouse;
use iced_core::overlay; use iced_core::overlay;
use iced_core::renderer; use iced_core::renderer;
use iced_core::widget::{Id, Operation, Tree}; use iced_core::widget::{Id, Tree};
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
pub use iced_widget::container::{Catalog, Style}; pub use iced_widget::container::{Catalog, Style};
@ -57,7 +57,7 @@ where
} }
fn diff(&mut self, tree: &mut Tree) { fn diff(&mut self, tree: &mut Tree) {
tree.diff_children(std::slice::from_mut(&mut self.content)); tree.children[0].diff(&mut self.content);
} }
fn size(&self) -> iced_core::Size<Length> { fn size(&self) -> iced_core::Size<Length> {
@ -65,29 +65,28 @@ where
} }
fn layout( fn layout(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
renderer: &Renderer, renderer: &Renderer,
limits: &layout::Limits, limits: &layout::Limits,
) -> layout::Node { ) -> layout::Node {
let node = self let node = self
.content .content
.as_widget_mut() .as_widget()
.layout(&mut tree.children[0], renderer, limits); .layout(&mut tree.children[0], renderer, limits);
let size = node.size(); let size = node.size();
layout::Node::with_children(size, vec![node]) layout::Node::with_children(size, vec![node])
} }
fn operate( fn operate(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn Operation, operation: &mut dyn iced_core::widget::Operation<()>,
) { ) {
operation.container(Some(&self.id), layout.bounds()); operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
operation.traverse(&mut |operation| { self.content.as_widget().operate(
self.content.as_widget_mut().operate(
&mut tree.children[0], &mut tree.children[0],
layout layout
.children() .children()
@ -100,18 +99,18 @@ where
}); });
} }
fn update( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: mouse::Cursor, cursor_position: mouse::Cursor,
renderer: &Renderer, renderer: &Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) { ) -> event::Status {
self.content.as_widget_mut().update( self.content.as_widget_mut().on_event(
&mut tree.children[0], &mut tree.children[0],
event, event,
layout layout
@ -124,7 +123,7 @@ where
clipboard, clipboard,
shell, shell,
viewport, viewport,
); )
} }
fn mouse_interaction( fn mouse_interaction(
@ -170,9 +169,8 @@ where
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'b>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
viewport: &Rectangle,
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay( self.content.as_widget_mut().overlay(
@ -183,7 +181,6 @@ where
.unwrap() .unwrap()
.with_virtual_offset(layout.virtual_offset()), .with_virtual_offset(layout.virtual_offset()),
renderer, renderer,
viewport,
translation, translation,
) )
} }

View file

@ -172,7 +172,7 @@ where
} }
fn layout( fn layout(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
renderer: &Renderer, renderer: &Renderer,
limits: &layout::Limits, limits: &layout::Limits,
@ -181,7 +181,7 @@ where
} }
fn operate( fn operate(
&mut self, &self,
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
@ -190,18 +190,18 @@ where
self.container.operate(tree, layout, renderer, operation); self.container.operate(tree, layout, renderer, operation);
} }
fn update( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: &Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: mouse::Cursor, cursor_position: mouse::Cursor,
renderer: &Renderer, renderer: &Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) { ) -> event::Status {
self.container.update( self.container.on_event(
tree, tree,
event, event,
layout, layout,
@ -257,13 +257,11 @@ where
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'b>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
viewport: &Rectangle,
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.container self.container.overlay(tree, layout, renderer, translation)
.overlay(tree, layout, renderer, viewport, translation)
} }
fn drag_destinations( fn drag_destinations(

Some files were not shown because too many files have changed in this diff Show more