Merge remote-tracking branch 'upstream/master' into fix/switch-qt-view-and-window
This commit is contained in:
commit
db35a10c64
89 changed files with 3948 additions and 2475 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -66,7 +66,7 @@ jobs:
|
|||
- name: Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Test features
|
||||
run: cargo test --no-default-features --features "${{ matrix.features }}"
|
||||
run: cargo test --no-default-features --features "${{ matrix.features }}" -- --test-threads=1
|
||||
env:
|
||||
RUST_BACKTRACE: full
|
||||
|
||||
|
|
|
|||
44
Cargo.toml
44
Cargo.toml
|
|
@ -8,13 +8,28 @@ rust-version = "1.90"
|
|||
name = "cosmic"
|
||||
|
||||
[features]
|
||||
default = ["dbus-config", "multi-window", "a11y"]
|
||||
default = [
|
||||
"winit",
|
||||
"tokio",
|
||||
"a11y",
|
||||
"dbus-config",
|
||||
"x11",
|
||||
"wayland",
|
||||
"multi-window",
|
||||
] # default = ["dbus-config", "multi-window", "a11y"]
|
||||
# Accessibility support
|
||||
a11y = ["iced/a11y", "iced_accessibility"]
|
||||
# Enable about widget
|
||||
about = []
|
||||
# Builds support for animated images
|
||||
animated-image = ["dep:async-fs", "image/gif", "tokio?/io-util", "tokio?/fs"]
|
||||
animated-image = [
|
||||
"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
|
||||
autosize = []
|
||||
applet = [
|
||||
|
|
@ -76,7 +91,7 @@ wayland = [
|
|||
]
|
||||
surface-message = []
|
||||
# multi-window support
|
||||
multi-window = ["iced/multi-window"]
|
||||
multi-window = []
|
||||
# Render with wgpu
|
||||
wgpu = ["iced/wgpu", "iced_wgpu"]
|
||||
# X11 window support via winit
|
||||
|
|
@ -96,14 +111,15 @@ async-std = [
|
|||
"zbus?/async-io",
|
||||
"iced/async-std",
|
||||
]
|
||||
x11 = ["iced/x11", "iced_winit/x11"]
|
||||
|
||||
[dependencies]
|
||||
apply = "0.3.0"
|
||||
ashpd = { version = "0.12.1", default-features = false, optional = true }
|
||||
ashpd = { version = "0.12.3", default-features = false, optional = true }
|
||||
async-fs = { version = "2.2", optional = true }
|
||||
async-std = { version = "1.13", optional = true }
|
||||
auto_enums = "0.8.7"
|
||||
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d0e95be", optional = true }
|
||||
auto_enums = "0.8.8"
|
||||
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true }
|
||||
jiff = "0.2"
|
||||
cosmic-config = { path = "cosmic-config" }
|
||||
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true }
|
||||
|
|
@ -115,17 +131,16 @@ i18n-embed = { version = "0.16.0", features = [
|
|||
i18n-embed-fl = "0.10"
|
||||
rust-embed = "8.11.0"
|
||||
css-color = "0.2.8"
|
||||
derive_setters = "0.1.8"
|
||||
derive_setters = "0.1.9"
|
||||
futures = "0.3"
|
||||
image = { version = "0.25.9", default-features = false, features = [
|
||||
image = { version = "0.25.10", default-features = false, features = [
|
||||
"jpeg",
|
||||
"png",
|
||||
] }
|
||||
libc = { version = "0.2.180", optional = true }
|
||||
libc = { version = "0.2.183", optional = true }
|
||||
log = "0.4"
|
||||
mime = { version = "0.3.17", optional = true }
|
||||
palette = "0.7.6"
|
||||
raw-window-handle = "0.6"
|
||||
rfd = { version = "0.16.0", default-features = false, features = [
|
||||
"xdg-portal",
|
||||
], optional = true }
|
||||
|
|
@ -135,17 +150,18 @@ slotmap = "1.1.1"
|
|||
smol = { version = "2.0.2", optional = true }
|
||||
thiserror = "2.0.18"
|
||||
taffy = { version = "0.9.2", features = ["grid"] }
|
||||
tokio = { version = "1.49.0", optional = true }
|
||||
tokio = { version = "1.50.0", optional = true }
|
||||
tracing = "0.1.44"
|
||||
unicode-segmentation = "1.12"
|
||||
url = "2.5.8"
|
||||
zbus = { version = "5.13.2", default-features = false, optional = true }
|
||||
zbus = { version = "5.14.0", default-features = false, optional = true }
|
||||
float-cmp = "0.10.0"
|
||||
|
||||
# Enable DBus feature on Linux targets
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
cosmic-config = { path = "cosmic-config", features = ["dbus"] }
|
||||
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" }
|
||||
zbus = { version = "5.13.2", default-features = false }
|
||||
zbus = { version = "5.14.0", default-features = false }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" }
|
||||
|
|
@ -225,4 +241,4 @@ exclude = ["iced"]
|
|||
dirs = "6.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.24.0"
|
||||
tempfile = "3.27.0"
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ subscription = ["iced_futures"]
|
|||
|
||||
[dependencies]
|
||||
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
||||
zbus = { version = "5.13.2", default-features = false, optional = true }
|
||||
zbus = { version = "5.14.0", default-features = false, optional = true }
|
||||
atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" }
|
||||
calloop = { version = "0.14.3", optional = true }
|
||||
calloop = { version = "0.14.4", optional = true }
|
||||
notify = "8.2.0"
|
||||
ron = "0.12.0"
|
||||
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 }
|
||||
futures-util = { version = "0.3", optional = true }
|
||||
dirs.workspace = true
|
||||
tokio = { version = "1.49", optional = true, features = ["time"] }
|
||||
tokio = { version = "1.50", optional = true, features = ["time"] }
|
||||
async-std = { version = "1.13", optional = true }
|
||||
tracing = "0.1"
|
||||
|
||||
|
|
@ -30,4 +30,4 @@ tracing = "0.1"
|
|||
xdg = "3.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
known-folders = "1.4.0"
|
||||
known-folders = "1.4.2"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use std::ops::Deref;
|
||||
use std::{any::TypeId, ops::Deref};
|
||||
|
||||
use crate::{CosmicConfigEntry, Update};
|
||||
use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy};
|
||||
use futures_util::SinkExt;
|
||||
use iced_futures::{
|
||||
Subscription,
|
||||
futures::{self, Stream, StreamExt, future::pending},
|
||||
futures::{self, StreamExt, future::pending},
|
||||
stream,
|
||||
};
|
||||
|
||||
|
|
@ -57,6 +57,20 @@ 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)]
|
||||
pub fn watcher_subscription<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone>(
|
||||
settings_daemon: CosmicSettingsDaemonProxy<'static>,
|
||||
|
|
@ -64,166 +78,185 @@ pub fn watcher_subscription<T: CosmicConfigEntry + Send + Sync + Default + 'stat
|
|||
is_state: bool,
|
||||
) -> iced_futures::Subscription<Update<T>> {
|
||||
let id = std::any::TypeId::of::<T>();
|
||||
Subscription::run_with_id(
|
||||
(id, config_id),
|
||||
watcher_stream(settings_daemon, config_id, is_state),
|
||||
)
|
||||
}
|
||||
Subscription::run_with(
|
||||
Wrapper(id, settings_daemon, config_id, is_state),
|
||||
|&Wrapper(_, ref settings_daemon, ref config_id, ref 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;
|
||||
|
||||
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 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;
|
||||
|
||||
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}");
|
||||
|
||||
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 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;
|
||||
};
|
||||
|
||||
#[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 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();
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
// 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;
|
||||
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}");
|
||||
}
|
||||
}
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,24 @@ pub fn config_subscription<
|
|||
config_id: Cow<'static, str>,
|
||||
config_version: u64,
|
||||
) -> iced_futures::Subscription<crate::Update<T>> {
|
||||
iced_futures::Subscription::run_with_id(id, watcher_stream(config_id, config_version, false))
|
||||
iced_futures::Subscription::run_with(
|
||||
(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]
|
||||
|
|
@ -37,25 +54,23 @@ pub fn config_state_subscription<
|
|||
config_id: Cow<'static, str>,
|
||||
config_version: u64,
|
||||
) -> iced_futures::Subscription<crate::Update<T>> {
|
||||
iced_futures::Subscription::run_with_id(id, watcher_stream(config_id, config_version, true))
|
||||
}
|
||||
|
||||
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 {
|
||||
iced_futures::Subscription::run_with(
|
||||
(id, config_id, config_version, true),
|
||||
|(_, config_id, config_version, is_state)| {
|
||||
let config_id = config_id.clone();
|
||||
let mut state = ConfigState::Init(config_id, config_version, is_state);
|
||||
let config_version = *config_version;
|
||||
let is_state = *is_state;
|
||||
|
||||
loop {
|
||||
state = start_listening::<T>(state, &mut output).await;
|
||||
}
|
||||
}
|
||||
})
|
||||
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;
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
async fn start_listening<T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry>(
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ serde_json = { version = "1.0.149", optional = true, features = [
|
|||
"preserve_order",
|
||||
] }
|
||||
ron = "0.12.0"
|
||||
csscolorparser = { version = "0.8.1", features = ["serde"] }
|
||||
csscolorparser = { version = "0.8.3", features = ["serde"] }
|
||||
cosmic-config = { path = "../cosmic-config/", default-features = false, features = [
|
||||
"subscription",
|
||||
"macro",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ pub struct Window {
|
|||
core: Core,
|
||||
popup: Option<Id>,
|
||||
example_row: bool,
|
||||
toggle: bool,
|
||||
selected: Option<usize>,
|
||||
}
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ impl Default for Window {
|
|||
core: Core::default(),
|
||||
popup: None,
|
||||
example_row: false,
|
||||
toggle: false,
|
||||
selected: None,
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +35,7 @@ pub enum Message {
|
|||
ToggleExampleRow(bool),
|
||||
Selected(usize),
|
||||
Surface(cosmic::surface::Action),
|
||||
Toggle(bool),
|
||||
}
|
||||
|
||||
impl cosmic::Application for Window {
|
||||
|
|
@ -71,7 +74,6 @@ impl cosmic::Application for Window {
|
|||
Message::ToggleExampleRow(toggled) => {
|
||||
self.example_row = toggled;
|
||||
}
|
||||
|
||||
Message::Surface(a) => {
|
||||
return cosmic::task::message(cosmic::Action::Cosmic(
|
||||
cosmic::app::Action::Surface(a),
|
||||
|
|
@ -80,6 +82,9 @@ impl cosmic::Application for Window {
|
|||
Message::Selected(i) => {
|
||||
self.selected = Some(i);
|
||||
}
|
||||
Message::Toggle(v) => {
|
||||
self.toggle = v;
|
||||
}
|
||||
};
|
||||
Task::none()
|
||||
}
|
||||
|
|
@ -123,9 +128,8 @@ impl cosmic::Application for Window {
|
|||
"Example row",
|
||||
cosmic::widget::container(
|
||||
toggler(state.example_row)
|
||||
.on_toggle(|value| Message::ToggleExampleRow(value)),
|
||||
)
|
||||
.height(Length::Fixed(50.)),
|
||||
.on_toggle(Message::ToggleExampleRow),
|
||||
),
|
||||
))
|
||||
.add(popup_dropdown(
|
||||
&["1", "asdf", "hello", "test"],
|
||||
|
|
@ -155,7 +159,7 @@ impl cosmic::Application for Window {
|
|||
"oops".into()
|
||||
}
|
||||
|
||||
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> {
|
||||
fn style(&self) -> Option<cosmic::iced_core::theme::Style> {
|
||||
Some(cosmic::applet::style())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,10 @@ default = ["wayland"]
|
|||
wayland = ["libcosmic/wayland"]
|
||||
|
||||
[dependencies]
|
||||
tracing = "0.1.44"
|
||||
tracing-subscriber = "0.3.22"
|
||||
tracing-log = "0.2.0"
|
||||
env_logger = "0.11"
|
||||
|
||||
[dependencies.libcosmic]
|
||||
path = "../../"
|
||||
git = "https://github.com/pop-os/libcosmic"
|
||||
features = [
|
||||
"debug",
|
||||
"winit",
|
||||
|
|
@ -23,4 +21,5 @@ features = [
|
|||
"wgpu",
|
||||
"single-instance",
|
||||
"surface-message",
|
||||
"multi-window",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -54,8 +54,9 @@ impl widget::menu::Action for Action {
|
|||
/// Runs application with these settings
|
||||
#[rustfmt::skip]
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// tracing_subscriber::fmt::init();
|
||||
// let _ = tracing_log::LogTracer::init();
|
||||
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
||||
|
||||
|
||||
let input = vec![
|
||||
(Page::Page1, "🖖 Hello from libcosmic.".into()),
|
||||
|
|
@ -66,9 +67,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
let settings = Settings::default()
|
||||
.size(Size::new(1024., 768.));
|
||||
|
||||
cosmic::app::run::<App>(settings, input)?;
|
||||
|
||||
cosmic::app::run::<App>(settings, input).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,13 +28,14 @@ impl State {
|
|||
column!(
|
||||
list_column().add(settings::item(
|
||||
"Bluetooth",
|
||||
toggler(None, self.enabled, Message::Enable)
|
||||
toggler(self.enabled).on_toggle(Message::Enable)
|
||||
)),
|
||||
text("Now visible as \"TODO\", just kidding")
|
||||
)
|
||||
.spacing(8)
|
||||
.into(),
|
||||
settings::view_section("Devices")
|
||||
settings::section()
|
||||
.title("Devices")
|
||||
.add(settings::item("No devices found", text("")))
|
||||
.into(),
|
||||
])
|
||||
|
|
|
|||
|
|
@ -258,12 +258,13 @@ impl State {
|
|||
match self.tab_bar.active_data() {
|
||||
None => panic!("no tab is active"),
|
||||
Some(DemoView::TabA) => settings::view_column(vec![
|
||||
settings::view_section("Debug")
|
||||
settings::section()
|
||||
.title("Debug")
|
||||
.add(settings::item("Debug theme", choose_theme))
|
||||
.add(settings::item("Debug icon theme", choose_icon_theme))
|
||||
.add(settings::item(
|
||||
"Debug layout",
|
||||
toggler(None, window.debug, Message::Debug),
|
||||
toggler(window.debug).on_toggle(Message::Debug),
|
||||
))
|
||||
.add(settings::item(
|
||||
"Scaling Factor",
|
||||
|
|
@ -276,10 +277,11 @@ impl State {
|
|||
.into(),
|
||||
]))
|
||||
.into(),
|
||||
settings::view_section("Controls")
|
||||
settings::section()
|
||||
.title("Controls")
|
||||
.add(settings::item(
|
||||
"Toggler",
|
||||
toggler(None, self.toggler_value, Message::TogglerToggled),
|
||||
toggler(self.toggler_value).on_toggle(Message::TogglerToggled),
|
||||
))
|
||||
.add(settings::item(
|
||||
"Pick List (TODO)",
|
||||
|
|
@ -299,15 +301,13 @@ impl State {
|
|||
.add(settings::item(
|
||||
"Progress",
|
||||
progress_bar(0.0..=100.0, self.slider_value)
|
||||
.width(Length::Fixed(250.0))
|
||||
.height(Length::Fixed(4.0)),
|
||||
.length(Length::Fixed(250.0))
|
||||
.girth(Length::Fixed(4.0)),
|
||||
))
|
||||
.add(settings::item_row(vec![checkbox(
|
||||
"Checkbox",
|
||||
self.checkbox_value,
|
||||
Message::CheckboxToggled,
|
||||
)
|
||||
.into()]))
|
||||
.add(settings::item_row(vec![checkbox(self.checkbox_value)
|
||||
.label("Checkbox")
|
||||
.on_toggle(Message::CheckboxToggled)
|
||||
.into()]))
|
||||
.add(settings::item(
|
||||
format!(
|
||||
"Spin Button (Range {}:{})",
|
||||
|
|
@ -354,8 +354,7 @@ impl State {
|
|||
.width(Length::Shrink)
|
||||
.on_activate(Message::MultiSelection)
|
||||
.apply(container)
|
||||
.center_x()
|
||||
.width(Length::Fill)
|
||||
.center_x(Length::Fill)
|
||||
.into(),
|
||||
text("Vertical With Spacing").into(),
|
||||
cosmic::iced::widget::row(vec![
|
||||
|
|
@ -424,13 +423,12 @@ impl State {
|
|||
])
|
||||
.padding(0)
|
||||
.into(),
|
||||
Some(DemoView::TabC) => {
|
||||
settings::view_column(vec![settings::view_section("Tab C")
|
||||
.add(text("Nothing here yet").width(Length::Fill))
|
||||
.into()])
|
||||
.padding(0)
|
||||
.into()
|
||||
}
|
||||
Some(DemoView::TabC) => settings::view_column(vec![settings::section()
|
||||
.title("Tab C")
|
||||
.add(text("Nothing here yet").width(Length::Fill))
|
||||
.into()])
|
||||
.padding(0)
|
||||
.into(),
|
||||
},
|
||||
container(text("Background container with some text").size(24))
|
||||
.layer(cosmic_theme::Layer::Background)
|
||||
|
|
|
|||
|
|
@ -147,7 +147,8 @@ impl State {
|
|||
fn view_desktop_options<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
||||
settings::view_column(vec![
|
||||
window.parent_page_button(DesktopPage::DesktopOptions),
|
||||
settings::view_section("Super Key Action")
|
||||
settings::section()
|
||||
.title("Super Key Action")
|
||||
.add(settings::item("Launcher", horizontal_space(Length::Fill)))
|
||||
.add(settings::item("Workspaces", horizontal_space(Length::Fill)))
|
||||
.add(settings::item(
|
||||
|
|
@ -155,38 +156,34 @@ impl State {
|
|||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.into(),
|
||||
settings::view_section("Hot Corner")
|
||||
settings::section()
|
||||
.title("Hot Corner")
|
||||
.add(settings::item(
|
||||
"Enable top-left hot corner for Workspaces",
|
||||
toggler(None, self.top_left_hot_corner, Message::TopLeftHotCorner),
|
||||
toggler(self.top_left_hot_corner).on_toggle(Message::TopLeftHotCorner),
|
||||
))
|
||||
.into(),
|
||||
settings::view_section("Top Panel")
|
||||
settings::section()
|
||||
.title("Top Panel")
|
||||
.add(settings::item(
|
||||
"Show Workspaces Button",
|
||||
toggler(
|
||||
None,
|
||||
self.show_workspaces_button,
|
||||
Message::ShowWorkspacesButton,
|
||||
),
|
||||
toggler(self.show_workspaces_button).on_toggle(Message::ShowWorkspacesButton),
|
||||
))
|
||||
.add(settings::item(
|
||||
"Show Applications Button",
|
||||
toggler(
|
||||
None,
|
||||
self.show_applications_button,
|
||||
Message::ShowApplicationsButton,
|
||||
),
|
||||
toggler(self.show_applications_button)
|
||||
.on_toggle(Message::ShowApplicationsButton),
|
||||
))
|
||||
.into(),
|
||||
settings::view_section("Window Controls")
|
||||
settings::section()
|
||||
.title("Window Controls")
|
||||
.add(settings::item(
|
||||
"Show Minimize Button",
|
||||
toggler(None, self.show_minimize_button, Message::ShowMinimizeButton),
|
||||
toggler(self.show_minimize_button).on_toggle(Message::ShowMinimizeButton),
|
||||
))
|
||||
.add(settings::item(
|
||||
"Show Maximize Button",
|
||||
toggler(None, self.show_maximize_button, Message::ShowMaximizeButton),
|
||||
toggler(self.show_maximize_button).on_toggle(Message::ShowMaximizeButton),
|
||||
))
|
||||
.into(),
|
||||
])
|
||||
|
|
@ -245,12 +242,12 @@ impl State {
|
|||
list_column()
|
||||
.add(settings::item(
|
||||
"Same background on all displays",
|
||||
toggler(None, self.same_background, Message::SameBackground),
|
||||
toggler(self.same_background).on_toggle(Message::SameBackground),
|
||||
))
|
||||
.add(settings::item("Background fit", text("TODO")))
|
||||
.add(settings::item(
|
||||
"Slideshow",
|
||||
toggler(None, self.slideshow, Message::Slideshow),
|
||||
toggler(self.slideshow).on_toggle(Message::Slideshow),
|
||||
))
|
||||
.into(),
|
||||
column(image_column).spacing(16).into(),
|
||||
|
|
@ -261,7 +258,8 @@ impl State {
|
|||
fn view_desktop_workspaces<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
||||
settings::view_column(vec![
|
||||
window.parent_page_button(DesktopPage::Wallpaper),
|
||||
settings::view_section("Workspace Behavior")
|
||||
settings::section()
|
||||
.title("Workspace Behavior")
|
||||
.add(settings::item(
|
||||
"Dynamic workspaces",
|
||||
horizontal_space(Length::Fill),
|
||||
|
|
@ -271,7 +269,8 @@ impl State {
|
|||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.into(),
|
||||
settings::view_section("Multi-monitor Behavior")
|
||||
settings::section()
|
||||
.title("Multi-monitor Behavior")
|
||||
.add(settings::item(
|
||||
"Workspaces Span Displays",
|
||||
horizontal_space(Length::Fill),
|
||||
|
|
|
|||
|
|
@ -69,14 +69,16 @@ impl State {
|
|||
list_column()
|
||||
.add(settings::item("Device name", text("TODO")))
|
||||
.into(),
|
||||
settings::view_section("Hardware")
|
||||
settings::section()
|
||||
.title("Hardware")
|
||||
.add(settings::item("Hardware model", text("TODO")))
|
||||
.add(settings::item("Memory", text("TODO")))
|
||||
.add(settings::item("Processor", text("TODO")))
|
||||
.add(settings::item("Graphics", text("TODO")))
|
||||
.add(settings::item("Disk Capacity", text("TODO")))
|
||||
.into(),
|
||||
settings::view_section("Operating System")
|
||||
settings::section()
|
||||
.title("Operating System")
|
||||
.add(settings::item("Operating system", text("TODO")))
|
||||
.add(settings::item(
|
||||
"Operating system architecture",
|
||||
|
|
@ -85,7 +87,8 @@ impl State {
|
|||
.add(settings::item("Desktop environment", text("TODO")))
|
||||
.add(settings::item("Windowing system", text("TODO")))
|
||||
.into(),
|
||||
settings::view_section("Related settings")
|
||||
settings::section()
|
||||
.title("Related settings")
|
||||
.add(settings::item("Get support", text("TODO")))
|
||||
.into(),
|
||||
])
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ impl cosmic::Application for App {
|
|||
);
|
||||
|
||||
content.push(
|
||||
iced::widget::vertical_space()
|
||||
iced::widget::space::vertical()
|
||||
.height(Length::Fixed(12.0))
|
||||
.into(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ september = September { $year }
|
|||
october = Oktober { $year }
|
||||
november = November { $year }
|
||||
december = Dezember { $year }
|
||||
monday = Mo
|
||||
tuesday = Di
|
||||
monday = Montag
|
||||
tuesday = Dienstag
|
||||
wednesday = Mittwoch
|
||||
thursday = Donnerstag
|
||||
friday = Freitag
|
||||
|
|
@ -33,3 +33,5 @@ thu = Do
|
|||
fri = Fr
|
||||
sat = Sa
|
||||
sun = So
|
||||
tue = Di
|
||||
mon = Mo
|
||||
|
|
|
|||
|
|
@ -2,26 +2,33 @@ february = Vasaris { $year }
|
|||
close = Uždaryti
|
||||
documenters = Dokumentuotojai
|
||||
november = Lapkritis { $year }
|
||||
friday = Penk
|
||||
tuesday = Antr
|
||||
friday = Penktadienis
|
||||
tuesday = Antradienis
|
||||
may = Gegužė { $year }
|
||||
wednesday = Treč
|
||||
wednesday = Trečiadienis
|
||||
april = Balandis { $year }
|
||||
monday = Pirm
|
||||
monday = Pirmadienis
|
||||
translators = Vertėjai
|
||||
artists = Menininkai
|
||||
license = Licencija
|
||||
december = Gruodis { $year }
|
||||
sunday = Sekm
|
||||
sunday = Sekmadienis
|
||||
links = Nuorodos
|
||||
march = Kovas { $year }
|
||||
june = Birželis { $year }
|
||||
saturday = Šešt
|
||||
saturday = Šeštadienis
|
||||
august = Rugpjūtis { $year }
|
||||
developers = Kūrėjai
|
||||
july = Liepa { $year }
|
||||
thursday = Ketv
|
||||
thursday = Ketvirtadienis
|
||||
september = Rugsėjis { $year }
|
||||
designers = Dizaineriai
|
||||
october = Spalis { $year }
|
||||
january = Sausis { $year }
|
||||
mon = Pirm
|
||||
tue = Antr
|
||||
wed = Treč
|
||||
thu = Ketv
|
||||
fri = Penkt
|
||||
sat = Šešt
|
||||
sun = Sekm
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
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 = ਐਤ
|
||||
2
iced
2
iced
|
|
@ -1 +1 @@
|
|||
Subproject commit d36e4df47f2e277fafcd3505229d53438c7f128d
|
||||
Subproject commit f59d5354bfc433d636c6987a60b61bc8f7a25d68
|
||||
51
src/anim.rs
Normal file
51
src/anim.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -8,8 +8,6 @@ use crate::{config::CosmicTk, keyboard_nav};
|
|||
#[cfg(feature = "wayland")]
|
||||
use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState};
|
||||
use cosmic_theme::ThemeMode;
|
||||
#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
|
||||
use iced::Application as IcedApplication;
|
||||
|
||||
/// A message managed internally by COSMIC.
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use cosmic_theme::ThemeMode;
|
|||
use iced::Application as IcedApplication;
|
||||
#[cfg(feature = "wayland")]
|
||||
use iced::event::wayland;
|
||||
use iced::{Task, window};
|
||||
use iced::{Task, theme, window};
|
||||
use iced_futures::event::listen_with;
|
||||
#[cfg(feature = "wayland")]
|
||||
use iced_winit::SurfaceIdWrapper;
|
||||
|
|
@ -49,8 +49,8 @@ pub fn windowing_system() -> Option<WindowingSystem> {
|
|||
WINDOWING_SYSTEM.get().copied()
|
||||
}
|
||||
|
||||
fn init_windowing_system<M>(handle: raw_window_handle::WindowHandle) -> crate::Action<M> {
|
||||
let raw: &raw_window_handle::RawWindowHandle = handle.as_ref();
|
||||
fn init_windowing_system<M>(handle: window::raw_window_handle::WindowHandle) -> crate::Action<M> {
|
||||
let raw = handle.as_ref();
|
||||
let system = match raw {
|
||||
window::raw_window_handle::RawWindowHandle::UiKit(_) => WindowingSystem::UiKit,
|
||||
window::raw_window_handle::RawWindowHandle::AppKit(_) => WindowingSystem::AppKit,
|
||||
|
|
@ -397,15 +397,16 @@ where
|
|||
f64::from(self.app.core().scale_factor())
|
||||
}
|
||||
|
||||
pub fn style(&self, theme: &Theme) -> iced_runtime::Appearance {
|
||||
pub fn style(&self, theme: &Theme) -> theme::Style {
|
||||
if let Some(style) = self.app.style() {
|
||||
style
|
||||
} else if self.app.core().window.is_maximized {
|
||||
let theme = THEME.lock().unwrap();
|
||||
crate::style::iced::application::appearance(theme.borrow())
|
||||
crate::style::iced::application::style(theme.borrow())
|
||||
} else {
|
||||
let theme = THEME.lock().unwrap();
|
||||
iced_runtime::Appearance {
|
||||
|
||||
theme::Style {
|
||||
background_color: iced_core::Color::TRANSPARENT,
|
||||
icon_color: theme.cosmic().on_bg_color().into(),
|
||||
text_color: theme.cosmic().on_bg_color().into(),
|
||||
|
|
@ -635,7 +636,7 @@ impl<T: Application> Cosmic<T> {
|
|||
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)
|
||||
return iced::window::get_maximized(id).map(move |maximized| {
|
||||
return iced::window::is_maximized(id).map(move |maximized| {
|
||||
crate::Action::Cosmic(Action::WindowMaximized(id, maximized))
|
||||
});
|
||||
}
|
||||
|
|
@ -711,10 +712,10 @@ impl<T: Application> Cosmic<T> {
|
|||
|
||||
Action::KeyboardNav(message) => match message {
|
||||
keyboard_nav::Action::FocusNext => {
|
||||
return iced::widget::focus_next().map(crate::Action::Cosmic);
|
||||
return iced::widget::operation::focus_next().map(crate::Action::Cosmic);
|
||||
}
|
||||
keyboard_nav::Action::FocusPrevious => {
|
||||
return iced::widget::focus_previous().map(crate::Action::Cosmic);
|
||||
return iced::widget::operation::focus_previous().map(crate::Action::Cosmic);
|
||||
}
|
||||
keyboard_nav::Action::Escape => return self.app.on_escape(),
|
||||
keyboard_nav::Action::Search => return self.app.on_search(),
|
||||
|
|
|
|||
124
src/app/mod.rs
124
src/app/mod.rs
|
|
@ -11,9 +11,8 @@ pub use action::Action;
|
|||
use cosmic_config::CosmicConfigEntry;
|
||||
pub mod context_drawer;
|
||||
pub use context_drawer::{ContextDrawer, context_drawer};
|
||||
use iced::application::BootFn;
|
||||
pub mod cosmic;
|
||||
#[cfg(all(feature = "winit", feature = "multi-window"))]
|
||||
pub(crate) mod multi_window;
|
||||
pub mod settings;
|
||||
|
||||
pub type Task<M> = iced::Task<crate::Action<M>>;
|
||||
|
|
@ -21,12 +20,13 @@ pub type Task<M> = iced::Task<crate::Action<M>>;
|
|||
pub use crate::Core;
|
||||
use crate::prelude::*;
|
||||
use crate::theme::THEME;
|
||||
use crate::widget::{container, horizontal_space, id_container, menu, nav_bar, popover};
|
||||
use crate::widget::{container, id_container, menu, nav_bar, popover, space};
|
||||
use apply::Apply;
|
||||
use iced::window;
|
||||
use iced::{Length, Subscription};
|
||||
use iced::{theme, window};
|
||||
pub use settings::Settings;
|
||||
use std::borrow::Cow;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
#[cold]
|
||||
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);
|
||||
}
|
||||
let max_size = settings.size_limits.max();
|
||||
if max_size != iced::Size::INFINITY {
|
||||
if max_size != iced::Size::INFINITE {
|
||||
window_settings.max_size = Some(max_size);
|
||||
}
|
||||
|
||||
|
|
@ -90,6 +90,38 @@ pub(crate) fn iced_settings<App: Application>(
|
|||
(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`].
|
||||
///
|
||||
/// # Errors
|
||||
|
|
@ -102,39 +134,52 @@ pub fn run<App: Application>(settings: Settings, flags: App::Flags) -> iced::Res
|
|||
}
|
||||
|
||||
let default_font = settings.default_font;
|
||||
let (settings, mut flags, window_settings) = iced_settings::<App>(settings, flags);
|
||||
let (settings, (mut core, flags), window_settings) = iced_settings::<App>(settings, flags);
|
||||
#[cfg(not(feature = "multi-window"))]
|
||||
{
|
||||
flags.0.main_window = Some(iced::window::Id::RESERVED);
|
||||
core.main_window = Some(iced::window::Id::RESERVED);
|
||||
|
||||
iced::application(
|
||||
cosmic::Cosmic::title,
|
||||
BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> {
|
||||
flags,
|
||||
core,
|
||||
settings: window_settings.clone(),
|
||||
})))),
|
||||
cosmic::Cosmic::update,
|
||||
cosmic::Cosmic::view,
|
||||
)
|
||||
.subscription(cosmic::Cosmic::subscription)
|
||||
.title(cosmic::Cosmic::title)
|
||||
.style(cosmic::Cosmic::style)
|
||||
.theme(cosmic::Cosmic::theme)
|
||||
.window_size((500.0, 800.0))
|
||||
.settings(settings)
|
||||
.window(window_settings)
|
||||
.run_with(move || cosmic::Cosmic::<App>::init(flags))
|
||||
.run()
|
||||
}
|
||||
#[cfg(feature = "multi-window")]
|
||||
{
|
||||
let mut app = multi_window::multi_window::<_, _, _, _, App::Executor>(
|
||||
cosmic::Cosmic::title,
|
||||
let no_main_window = core.main_window.is_none();
|
||||
if no_main_window {
|
||||
// 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::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)
|
||||
.title(cosmic::Cosmic::title)
|
||||
.style(cosmic::Cosmic::style)
|
||||
.theme(cosmic::Cosmic::theme)
|
||||
.settings(settings)
|
||||
.run_with(move || cosmic::Cosmic::<App>::init(flags))
|
||||
.run()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -204,13 +249,17 @@ where
|
|||
tracing::info!("Another instance is running");
|
||||
Ok(())
|
||||
} else {
|
||||
let (settings, mut flags, window_settings) = iced_settings::<App>(settings, flags);
|
||||
flags.0.single_instance = true;
|
||||
let (settings, (mut core, flags), window_settings) = iced_settings::<App>(settings, flags);
|
||||
core.single_instance = true;
|
||||
|
||||
#[cfg(not(feature = "multi-window"))]
|
||||
{
|
||||
iced::application(
|
||||
cosmic::Cosmic::title,
|
||||
BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> {
|
||||
flags,
|
||||
core,
|
||||
settings: window_settings.clone(),
|
||||
})))),
|
||||
cosmic::Cosmic::update,
|
||||
cosmic::Cosmic::view,
|
||||
)
|
||||
|
|
@ -220,24 +269,31 @@ where
|
|||
.window_size((500.0, 800.0))
|
||||
.settings(settings)
|
||||
.window(window_settings)
|
||||
.run_with(move || cosmic::Cosmic::<App>::init(flags))
|
||||
.run()
|
||||
}
|
||||
#[cfg(feature = "multi-window")]
|
||||
{
|
||||
let mut app = multi_window::multi_window::<_, _, _, _, App::Executor>(
|
||||
cosmic::Cosmic::title,
|
||||
let no_main_window = core.main_window.is_none();
|
||||
if no_main_window {
|
||||
// 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::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)
|
||||
.style(cosmic::Cosmic::style)
|
||||
.title(cosmic::Cosmic::title)
|
||||
.theme(cosmic::Cosmic::theme)
|
||||
.settings(settings)
|
||||
.run_with(move || cosmic::Cosmic::<App>::init(flags))
|
||||
.run()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -329,9 +385,8 @@ where
|
|||
.on_context(|id| crate::Action::Cosmic(Action::NavBarContext(id)))
|
||||
.context_menu(self.nav_context_menu(self.core().nav_bar_context()))
|
||||
.into_container()
|
||||
// XXX both must be shrink to avoid flex layout from ignoring it
|
||||
.width(iced::Length::Shrink)
|
||||
.height(iced::Length::Shrink);
|
||||
.height(iced::Length::Fill);
|
||||
|
||||
if !self.core().is_condensed() {
|
||||
nav = nav.max_width(280);
|
||||
|
|
@ -428,7 +483,7 @@ where
|
|||
}
|
||||
|
||||
/// Overrides the default style for applications
|
||||
fn style(&self) -> Option<iced_runtime::Appearance> {
|
||||
fn style(&self) -> Option<theme::Style> {
|
||||
None
|
||||
}
|
||||
|
||||
|
|
@ -664,16 +719,17 @@ impl<App: Application> ApplicationExt for App {
|
|||
[0, 0, 0, 0]
|
||||
})
|
||||
.into(),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
//TODO: this element is added to workaround state issues
|
||||
widgets.push(horizontal_space().width(Length::Shrink).into());
|
||||
widgets.push(space::horizontal().width(Length::Shrink).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
widgets
|
||||
});
|
||||
|
||||
let content_col = crate::widget::column::with_capacity(2)
|
||||
.push(content_row)
|
||||
.push_maybe(self.footer().map(|footer| {
|
||||
|
|
@ -686,7 +742,6 @@ impl<App: Application> ApplicationExt for App {
|
|||
}));
|
||||
let content: Element<_> = if content_container {
|
||||
content_col
|
||||
.apply(container)
|
||||
.width(iced::Length::Fill)
|
||||
.height(iced::Length::Fill)
|
||||
.apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_content_container")))
|
||||
|
|
@ -716,8 +771,7 @@ impl<App: Application> ApplicationExt for App {
|
|||
.title(&core.window.header_title)
|
||||
.on_drag(crate::Action::Cosmic(Action::Drag))
|
||||
.on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu))
|
||||
.on_double_click(crate::Action::Cosmic(Action::Maximize))
|
||||
.is_condensed(is_condensed);
|
||||
.on_double_click(crate::Action::Cosmic(Action::Maximize));
|
||||
|
||||
if self.nav_model().is_some() {
|
||||
let toggle = crate::widget::nav_bar_toggle()
|
||||
|
|
|
|||
|
|
@ -1,244 +0,0 @@
|
|||
// 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -217,7 +217,7 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -233,25 +233,26 @@ where
|
|||
self.padding,
|
||||
self.spacing,
|
||||
self.align,
|
||||
&self.children,
|
||||
&mut self.children,
|
||||
&mut tree.children,
|
||||
)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation,
|
||||
) {
|
||||
operation.container(None, layout.bounds(), &mut |operation| {
|
||||
operation.container(None, layout.bounds());
|
||||
operation.traverse(&mut |operation| {
|
||||
self.children
|
||||
.iter()
|
||||
.iter_mut()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.for_each(|((child, state), c_layout)| {
|
||||
child.as_widget().operate(
|
||||
child.as_widget_mut().operate(
|
||||
state,
|
||||
c_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
renderer,
|
||||
|
|
@ -261,17 +262,17 @@ where
|
|||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
) {
|
||||
let my_state = tree.state.downcast_mut::<State>();
|
||||
|
||||
if let Some(hovered) = my_state.hovered {
|
||||
|
|
@ -285,7 +286,7 @@ where
|
|||
e,
|
||||
mouse::Event::CursorLeft | mouse::Event::ButtonReleased { .. }
|
||||
) {
|
||||
return self.children[hovered].as_widget_mut().on_event(
|
||||
return self.children[hovered].as_widget_mut().update(
|
||||
&mut tree.children[hovered],
|
||||
event,
|
||||
child_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
|
|
@ -302,7 +303,7 @@ where
|
|||
iced::core::touch::Event::FingerLifted { .. }
|
||||
| iced::core::touch::Event::FingerLost { .. }
|
||||
) {
|
||||
return self.children[hovered].as_widget_mut().on_event(
|
||||
return self.children[hovered].as_widget_mut().update(
|
||||
&mut tree.children[hovered],
|
||||
event,
|
||||
child_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
|
|
@ -319,49 +320,49 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
self.children
|
||||
for (((i, child), state), c_layout) in self
|
||||
.children
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.map(|(((i, child), state), c_layout)| {
|
||||
let mut cursor_virtual = cursor;
|
||||
if matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered)
|
||||
| Event::Touch(
|
||||
iced_core::touch::Event::FingerMoved { .. }
|
||||
| iced_core::touch::Event::FingerPressed { .. }
|
||||
)
|
||||
) && cursor.is_over(c_layout.bounds())
|
||||
{
|
||||
my_state.hovered = Some(i);
|
||||
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(
|
||||
{
|
||||
let mut cursor_virtual = cursor;
|
||||
if matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered)
|
||||
| Event::Touch(
|
||||
iced_core::touch::Event::FingerMoved { .. }
|
||||
| iced_core::touch::Event::FingerPressed { .. }
|
||||
)
|
||||
) && cursor.is_over(c_layout.bounds())
|
||||
{
|
||||
my_state.hovered = Some(i);
|
||||
return child.as_widget_mut().update(
|
||||
state,
|
||||
event.clone(),
|
||||
&event,
|
||||
c_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
cursor_virtual,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
)
|
||||
})
|
||||
.fold(event::Status::Ignored, event::Status::merge)
|
||||
);
|
||||
} else if my_state.hovered.is_some_and(|h| i != h) {
|
||||
cursor_virtual = mouse::Cursor::Unavailable;
|
||||
}
|
||||
|
||||
child.as_widget_mut().update(
|
||||
state,
|
||||
&event,
|
||||
c_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
cursor_virtual,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -436,11 +437,19 @@ where
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
overlay::from_children(&mut self.children, tree, layout, renderer, translation)
|
||||
overlay::from_children(
|
||||
&mut self.children,
|
||||
tree,
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "a11y")]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#[cfg(feature = "applet-token")]
|
||||
pub mod token;
|
||||
|
||||
use crate::app::cosmic;
|
||||
use crate::app::{BootData, BootDataInner, cosmic};
|
||||
use crate::{
|
||||
Application, Element, Renderer,
|
||||
app::iced_settings,
|
||||
|
|
@ -18,17 +18,19 @@ use crate::{
|
|||
self,
|
||||
autosize::{self, Autosize, autosize},
|
||||
column::Column,
|
||||
horizontal_space, layer_container,
|
||||
layer_container,
|
||||
row::Row,
|
||||
vertical_space,
|
||||
space::horizontal,
|
||||
space::vertical,
|
||||
},
|
||||
};
|
||||
pub use cosmic_panel_config;
|
||||
use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize};
|
||||
use iced_core::{Padding, Shadow};
|
||||
use iced_runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner};
|
||||
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 std::cell::RefCell;
|
||||
use std::{borrow::Cow, num::NonZeroU32, rc::Rc, sync::LazyLock, time::Duration};
|
||||
use tracing::info;
|
||||
|
||||
|
|
@ -386,10 +388,10 @@ impl Context {
|
|||
},
|
||||
shadow: Shadow::default(),
|
||||
icon_color: Some(cosmic.background.on.into()),
|
||||
snap: true,
|
||||
}
|
||||
}),
|
||||
)
|
||||
.width(Length::Shrink)
|
||||
.height(Length::Shrink)
|
||||
.align_x(horizontal_align)
|
||||
.align_y(vertical_align),
|
||||
|
|
@ -571,26 +573,33 @@ pub fn run<App: Application>(flags: App::Flags) -> iced::Result {
|
|||
|
||||
// TODO make multi-window not mandatory
|
||||
|
||||
let mut app = super::app::multi_window::multi_window::<_, _, _, _, App::Executor>(
|
||||
cosmic::Cosmic::title,
|
||||
let no_main_window = core.main_window.is_none();
|
||||
if no_main_window {
|
||||
// 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::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)
|
||||
.style(cosmic::Cosmic::style)
|
||||
.theme(cosmic::Cosmic::theme)
|
||||
.settings(iced_settings)
|
||||
.run_with(move || cosmic::Cosmic::<App>::init((core, flags)))
|
||||
.run()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn style() -> iced_runtime::Appearance {
|
||||
pub fn style() -> iced::theme::Style {
|
||||
let theme = crate::theme::THEME.lock().unwrap();
|
||||
iced_runtime::Appearance {
|
||||
iced::theme::Style {
|
||||
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
||||
text_color: theme.cosmic().on_bg_color().into(),
|
||||
icon_color: theme.cosmic().on_bg_color().into(),
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -222,25 +222,26 @@ where
|
|||
self.padding,
|
||||
self.spacing,
|
||||
self.align,
|
||||
&self.children,
|
||||
&mut self.children,
|
||||
&mut tree.children,
|
||||
)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation,
|
||||
) {
|
||||
operation.container(None, layout.bounds(), &mut |operation| {
|
||||
operation.container(None, layout.bounds());
|
||||
operation.traverse(&mut |operation| {
|
||||
self.children
|
||||
.iter()
|
||||
.iter_mut()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.for_each(|((child, state), c_layout)| {
|
||||
child.as_widget().operate(
|
||||
child.as_widget_mut().operate(
|
||||
state,
|
||||
c_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
renderer,
|
||||
|
|
@ -250,17 +251,17 @@ where
|
|||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
) {
|
||||
let my_state = tree.state.downcast_mut::<State>();
|
||||
|
||||
if let Some(hovered) = my_state.hovered {
|
||||
|
|
@ -274,7 +275,7 @@ where
|
|||
e,
|
||||
mouse::Event::CursorLeft | mouse::Event::ButtonReleased { .. }
|
||||
) {
|
||||
return self.children[hovered].as_widget_mut().on_event(
|
||||
return self.children[hovered].as_widget_mut().update(
|
||||
&mut tree.children[hovered],
|
||||
event,
|
||||
child_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
|
|
@ -291,7 +292,7 @@ where
|
|||
iced::core::touch::Event::FingerLifted { .. }
|
||||
| iced::core::touch::Event::FingerLost { .. }
|
||||
) {
|
||||
return self.children[hovered].as_widget_mut().on_event(
|
||||
return self.children[hovered].as_widget_mut().update(
|
||||
&mut tree.children[hovered],
|
||||
event,
|
||||
child_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
|
|
@ -308,50 +309,50 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
self.children
|
||||
for (((i, child), state), c_layout) in self
|
||||
.children
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.map(|(((i, child), state), c_layout)| {
|
||||
let mut cursor_virtual = cursor;
|
||||
{
|
||||
let mut cursor_virtual = cursor;
|
||||
|
||||
if matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered)
|
||||
| Event::Touch(
|
||||
iced_core::touch::Event::FingerMoved { .. }
|
||||
| iced_core::touch::Event::FingerPressed { .. }
|
||||
)
|
||||
) && cursor.is_over(c_layout.bounds())
|
||||
{
|
||||
my_state.hovered = Some(i);
|
||||
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(
|
||||
if matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered)
|
||||
| Event::Touch(
|
||||
iced_core::touch::Event::FingerMoved { .. }
|
||||
| iced_core::touch::Event::FingerPressed { .. }
|
||||
)
|
||||
) && cursor.is_over(c_layout.bounds())
|
||||
{
|
||||
my_state.hovered = Some(i);
|
||||
return child.as_widget_mut().update(
|
||||
state,
|
||||
event.clone(),
|
||||
&event,
|
||||
c_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
cursor_virtual,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
)
|
||||
})
|
||||
.fold(event::Status::Ignored, event::Status::merge)
|
||||
);
|
||||
} else if my_state.hovered.is_some_and(|h| i != h) {
|
||||
cursor_virtual = mouse::Cursor::Unavailable;
|
||||
}
|
||||
|
||||
child.as_widget_mut().update(
|
||||
state,
|
||||
&event,
|
||||
c_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
cursor_virtual,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -426,11 +427,19 @@ where
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
overlay::from_children(&mut self.children, tree, layout, renderer, translation)
|
||||
overlay::from_children(
|
||||
&mut self.children,
|
||||
tree,
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "a11y")]
|
||||
|
|
|
|||
|
|
@ -14,16 +14,15 @@ use super::wayland_handler::wayland_handler;
|
|||
pub fn activation_token_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
||||
id: I,
|
||||
) -> iced::Subscription<TokenUpdate> {
|
||||
Subscription::run_with_id(
|
||||
id,
|
||||
Subscription::run_with(id, |_| {
|
||||
stream::channel(50, move |mut output| async move {
|
||||
let mut state = State::Ready;
|
||||
|
||||
loop {
|
||||
state = start_listening(state, &mut output).await;
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub enum State {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ pub fn set_theme<M: Send + 'static>(theme: crate::Theme) -> iced::Task<crate::Ac
|
|||
|
||||
/// Sets the window mode to windowed.
|
||||
pub fn set_windowed<M>(id: window::Id) -> iced::Task<crate::Action<M>> {
|
||||
iced_runtime::window::change_mode(id, window::Mode::Windowed)
|
||||
iced_runtime::window::set_mode(id, window::Mode::Windowed)
|
||||
}
|
||||
|
||||
/// Toggles the windows' maximize state.
|
||||
|
|
|
|||
|
|
@ -16,75 +16,80 @@ use {
|
|||
#[cold]
|
||||
pub fn subscription<App: ApplicationExt>() -> Subscription<crate::Action<App::Message>> {
|
||||
use iced_futures::futures::StreamExt;
|
||||
iced_futures::Subscription::run_with_id(
|
||||
TypeId::of::<DbusActivation>(),
|
||||
iced::stream::channel(10, move |mut output| async move {
|
||||
let mut single_instance: DbusActivation = DbusActivation::new();
|
||||
let mut rx = single_instance.rx();
|
||||
if let Ok(builder) = zbus::connection::Builder::session() {
|
||||
let path: String = format!("/{}", App::APP_ID.replace('.', "/"));
|
||||
if let Ok(conn) = builder.build().await {
|
||||
// XXX Setup done this way seems to be more reliable.
|
||||
//
|
||||
// the docs for serve_at seem to imply it will replace the
|
||||
// existing interface at the requested path, but it doesn't
|
||||
// seem to work that way all the time. The docs for
|
||||
// object_server().at() imply it won't replace the existing
|
||||
// interface.
|
||||
//
|
||||
// request_name is used either way, with the builder or
|
||||
// with the connection, but it must be done after the
|
||||
// object server is setup.
|
||||
if conn.object_server().at(path, single_instance).await != Ok(true) {
|
||||
tracing::error!("Failed to serve dbus");
|
||||
std::process::exit(1);
|
||||
}
|
||||
if conn.request_name(App::APP_ID).await.is_err() {
|
||||
tracing::error!("Failed to serve dbus");
|
||||
std::process::exit(1);
|
||||
}
|
||||
iced_futures::Subscription::run_with(TypeId::of::<DbusActivation>(), |_| {
|
||||
iced::stream::channel(
|
||||
10,
|
||||
move |mut output: Sender<crate::Action<App::Message>>| async move {
|
||||
let mut single_instance: DbusActivation = DbusActivation::new();
|
||||
let mut rx = single_instance.rx();
|
||||
if let Ok(builder) = zbus::connection::Builder::session() {
|
||||
let path: String = format!("/{}", App::APP_ID.replace('.', "/"));
|
||||
if let Ok(conn) = builder.build().await {
|
||||
// XXX Setup done this way seems to be more reliable.
|
||||
//
|
||||
// the docs for serve_at seem to imply it will replace the
|
||||
// existing interface at the requested path, but it doesn't
|
||||
// seem to work that way all the time. The docs for
|
||||
// object_server().at() imply it won't replace the existing
|
||||
// interface.
|
||||
//
|
||||
// request_name is used either way, with the builder or
|
||||
// with the connection, but it must be done after the
|
||||
// object server is setup.
|
||||
if conn.object_server().at(path, single_instance).await != Ok(true) {
|
||||
tracing::error!("Failed to serve dbus");
|
||||
std::process::exit(1);
|
||||
}
|
||||
if conn.request_name(App::APP_ID).await.is_err() {
|
||||
tracing::error!("Failed to serve dbus");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
output
|
||||
.send(crate::Action::Cosmic(crate::app::Action::DbusConnection(
|
||||
conn.clone(),
|
||||
)))
|
||||
.await;
|
||||
output
|
||||
.send(crate::Action::Cosmic(crate::app::Action::DbusConnection(
|
||||
conn.clone(),
|
||||
)))
|
||||
.await;
|
||||
|
||||
#[cfg(feature = "smol")]
|
||||
let handle = {
|
||||
std::thread::spawn(move || {
|
||||
let conn_clone = _conn.clone();
|
||||
#[cfg(feature = "smol")]
|
||||
let handle = {
|
||||
std::thread::spawn(move || {
|
||||
let conn_clone = _conn.clone();
|
||||
|
||||
zbus::block_on(async move {
|
||||
loop {
|
||||
conn_clone.executor().tick().await;
|
||||
}
|
||||
zbus::block_on(async move {
|
||||
loop {
|
||||
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
|
||||
};
|
||||
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
|
||||
{
|
||||
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 {
|
||||
iced::futures::pending!();
|
||||
}
|
||||
}),
|
||||
)
|
||||
loop {
|
||||
iced::futures::pending!();
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
|||
|
|
@ -416,7 +416,6 @@ fn match_exec_basename(
|
|||
};
|
||||
|
||||
let basename_lower = basename.to_ascii_lowercase();
|
||||
|
||||
if normalized
|
||||
.iter()
|
||||
.any(|candidate| candidate == &basename_lower)
|
||||
|
|
@ -440,8 +439,7 @@ fn fallback_entry(context: &DesktopLookupContext<'_>) -> fde::DesktopEntry {
|
|||
let name = context
|
||||
.title
|
||||
.as_ref()
|
||||
.map(|title| title.to_string())
|
||||
.unwrap_or_else(|| context.app_id.to_string());
|
||||
.map_or_else(|| context.app_id.to_string(), |title| title.to_string());
|
||||
entry.add_desktop_entry("Name".to_string(), name);
|
||||
entry
|
||||
}
|
||||
|
|
@ -458,7 +456,9 @@ fn proton_or_wine_fallback(
|
|||
) -> Option<fde::DesktopEntry> {
|
||||
let app_id = context.app_id.as_ref();
|
||||
let is_proton_game = app_id == "steam_app_default";
|
||||
let is_wine_entry = app_id.ends_with(".exe");
|
||||
let is_wine_entry = std::path::Path::new(app_id)
|
||||
.extension()
|
||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("exe"));
|
||||
|
||||
if !is_proton_game && !is_wine_entry {
|
||||
return None;
|
||||
|
|
@ -487,10 +487,6 @@ fn proton_or_wine_fallback(
|
|||
|
||||
#[cfg(not(windows))]
|
||||
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) {
|
||||
let trimmed = candidate.trim();
|
||||
if trimmed.is_empty() {
|
||||
|
|
@ -531,11 +527,11 @@ fn candidate_desktop_ids(context: &DesktopLookupContext<'_>) -> Vec<String> {
|
|||
}
|
||||
}
|
||||
|
||||
if trimmed.contains('.') {
|
||||
if let Some(last) = trimmed.rsplit('.').next() {
|
||||
if last.len() >= 2 {
|
||||
push_candidate(seen, ordered, last);
|
||||
}
|
||||
if trimmed.contains('.')
|
||||
&& let Some(last) = trimmed.rsplit('.').next()
|
||||
{
|
||||
if last.len() >= 2 {
|
||||
push_candidate(seen, ordered, last);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -546,13 +542,20 @@ fn candidate_desktop_ids(context: &DesktopLookupContext<'_>) -> Vec<String> {
|
|||
push_candidate(seen, ordered, &trimmed.replace('_', "-"));
|
||||
}
|
||||
|
||||
for token in trimmed.split(|c: char| matches!(c, '.' | '-' | '_' | '@' | ' ')) {
|
||||
for token in
|
||||
trimmed.split(|c: char| matches!(c, '.' | '-' | '_' | '@') || c.is_whitespace())
|
||||
{
|
||||
if token.len() >= 2 && token != trimmed {
|
||||
push_candidate(seen, ordered, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const SUFFIXES: &[&str] = &[".desktop", ".Desktop", ".DESKTOP"];
|
||||
|
||||
let mut ordered = Vec::new();
|
||||
let mut seen = HashSet::new();
|
||||
|
||||
add_variants(
|
||||
&mut seen,
|
||||
&mut ordered,
|
||||
|
|
@ -915,12 +918,20 @@ mod tests {
|
|||
let candidates = candidate_desktop_ids(&ctx);
|
||||
|
||||
assert_eq!(candidates.first().unwrap(), "com.example.App.desktop");
|
||||
assert!(candidates.contains(&"com.example.App".to_string()));
|
||||
assert!(candidates.contains(&"com-example-App".to_string()));
|
||||
assert!(candidates.contains(&"com_example_App".to_string()));
|
||||
assert!(candidates.contains(&"Example App".to_string()));
|
||||
assert!(candidates.contains(&"Example".to_string()));
|
||||
assert!(candidates.contains(&"App".to_string()));
|
||||
for test in [
|
||||
"com.example.App",
|
||||
"com-example-App",
|
||||
"com_example_App",
|
||||
"Example App",
|
||||
"Example",
|
||||
"App",
|
||||
] {
|
||||
assert!(
|
||||
candidates
|
||||
.iter()
|
||||
.any(|c| c.to_ascii_lowercase() == test.to_ascii_lowercase()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -985,7 +996,7 @@ Icon=vmware-workstation\n\
|
|||
|
||||
let resolved = resolve_desktop_entry(&mut cache, &ctx, &DesktopResolveOptions::default());
|
||||
|
||||
assert_eq!(resolved.id(), "vmware-workstation.desktop");
|
||||
assert_eq!(resolved.id(), "vmware-workstation");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -26,4 +26,8 @@ impl iced::Executor for Executor {
|
|||
let _guard = self.0.enter();
|
||||
f()
|
||||
}
|
||||
|
||||
fn block_on<T>(&self, future: impl Future<Output = T>) -> T {
|
||||
self.0.block_on(future)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,4 +30,8 @@ impl iced::Executor for Executor {
|
|||
let _guard = self.0.enter();
|
||||
f()
|
||||
}
|
||||
|
||||
fn block_on<T>(&self, future: impl Future<Output = T>) -> T {
|
||||
self.0.block_on(future)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ pub use apply::{Also, Apply};
|
|||
pub mod action;
|
||||
pub use action::Action;
|
||||
|
||||
pub mod anim;
|
||||
|
||||
#[cfg(feature = "winit")]
|
||||
pub mod app;
|
||||
#[cfg(feature = "winit")]
|
||||
|
|
|
|||
|
|
@ -13,9 +13,8 @@ pub enum Desktop {
|
|||
|
||||
#[cold]
|
||||
pub fn desktop_settings() -> iced_futures::Subscription<Desktop> {
|
||||
iced_futures::Subscription::run_with_id(
|
||||
std::any::TypeId::of::<Desktop>(),
|
||||
stream::channel(10, |mut tx| {
|
||||
iced_futures::Subscription::run(|| {
|
||||
stream::channel(10, |mut tx: futures::channel::mpsc::Sender<Desktop>| {
|
||||
async move {
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
|
|
@ -99,6 +98,6 @@ pub fn desktop_settings() -> iced_futures::Subscription<Desktop> {
|
|||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use crate::theme::{CosmicComponent, TRANSPARENT_COMPONENT, Theme};
|
|||
use cosmic_theme::composite::over;
|
||||
use iced::{
|
||||
overlay::menu,
|
||||
theme::Base,
|
||||
widget::{
|
||||
button as iced_button, checkbox as iced_checkbox, combo_box, container as iced_container,
|
||||
pane_grid, pick_list, progress_bar, radio, rule, scrollable,
|
||||
|
|
@ -15,7 +16,7 @@ use iced::{
|
|||
},
|
||||
};
|
||||
use iced_core::{Background, Border, Color, Shadow, Vector};
|
||||
use iced_widget::{pane_grid::Highlight, text_editor, text_input};
|
||||
use iced_widget::{pane_grid::Highlight, scrollable::AutoScroll, text_editor, text_input};
|
||||
use palette::WithAlpha;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
|
@ -36,13 +37,13 @@ pub mod application {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn appearance(theme: &Theme) -> Appearance {
|
||||
pub fn style(theme: &Theme) -> iced::theme::Style {
|
||||
let cosmic = theme.cosmic();
|
||||
|
||||
Appearance {
|
||||
icon_color: cosmic.bg_color().into(),
|
||||
iced::theme::Style {
|
||||
background_color: cosmic.bg_color().into(),
|
||||
text_color: cosmic.on_bg_color().into(),
|
||||
icon_color: cosmic.bg_color().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -422,6 +423,7 @@ impl<'a> Container<'a> {
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -436,6 +438,7 @@ impl<'a> Container<'a> {
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -450,6 +453,7 @@ impl<'a> Container<'a> {
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -493,6 +497,7 @@ impl iced_container::Catalog for Theme {
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
|
||||
Container::List => {
|
||||
|
|
@ -506,6 +511,7 @@ impl iced_container::Catalog for Theme {
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -552,6 +558,7 @@ impl iced_container::Catalog for Theme {
|
|||
.into(),
|
||||
..Default::default()
|
||||
},
|
||||
snap: true,
|
||||
shadow: Shadow::default(),
|
||||
}
|
||||
}
|
||||
|
|
@ -582,6 +589,7 @@ impl iced_container::Catalog for Theme {
|
|||
radius: cosmic.corner_radii.radius_s.into(),
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
|
||||
Container::Tooltip => iced_container::Style {
|
||||
|
|
@ -593,6 +601,7 @@ impl iced_container::Catalog for Theme {
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
|
||||
Container::Card => {
|
||||
|
|
@ -610,6 +619,7 @@ impl iced_container::Catalog for Theme {
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
cosmic_theme::Layer::Primary => iced_container::Style {
|
||||
icon_color: Some(Color::from(cosmic.primary.component.on)),
|
||||
|
|
@ -622,6 +632,7 @@ impl iced_container::Catalog for Theme {
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
cosmic_theme::Layer::Secondary => iced_container::Style {
|
||||
icon_color: Some(Color::from(cosmic.secondary.component.on)),
|
||||
|
|
@ -634,6 +645,7 @@ impl iced_container::Catalog for Theme {
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -652,6 +664,7 @@ impl iced_container::Catalog for Theme {
|
|||
offset: Vector::new(0.0, 4.0),
|
||||
blur_radius: 16.0,
|
||||
},
|
||||
snap: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -791,6 +804,7 @@ impl menu::Catalog for Theme {
|
|||
},
|
||||
selected_text_color: cosmic.accent_text_color().into(),
|
||||
selected_background: Background::Color(cosmic.background.component.hover.into()),
|
||||
shadow: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -830,7 +844,7 @@ impl pick_list::Catalog for Theme {
|
|||
background: Background::Color(cosmic.background.base.into()),
|
||||
..appearance
|
||||
},
|
||||
pick_list::Status::Opened => appearance,
|
||||
pick_list::Status::Opened { is_hovered: _ } => appearance,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -920,6 +934,8 @@ impl toggler::Catalog for Theme {
|
|||
background_border_color: Color::TRANSPARENT,
|
||||
foreground_border_width: 0.0,
|
||||
foreground_border_color: Color::TRANSPARENT,
|
||||
text_color: None,
|
||||
padding_ratio: 0.0,
|
||||
};
|
||||
match status {
|
||||
toggler::Status::Active { is_toggled } => active,
|
||||
|
|
@ -942,9 +958,9 @@ impl toggler::Catalog for Theme {
|
|||
..active
|
||||
}
|
||||
}
|
||||
toggler::Status::Disabled => {
|
||||
active.background.a /= 2.;
|
||||
active.foreground.a /= 2.;
|
||||
toggler::Status::Disabled { is_toggled } => {
|
||||
active.background = active.background.scale_alpha(0.5);
|
||||
active.foreground = active.foreground.scale_alpha(0.5);
|
||||
active
|
||||
}
|
||||
}
|
||||
|
|
@ -1086,21 +1102,21 @@ impl rule::Catalog for Theme {
|
|||
match class {
|
||||
Rule::Default => rule::Style {
|
||||
color: self.current_container().divider.into(),
|
||||
width: 1,
|
||||
radius: 0.0.into(),
|
||||
fill_mode: rule::FillMode::Full,
|
||||
snap: true,
|
||||
},
|
||||
Rule::LightDivider => rule::Style {
|
||||
color: self.current_container().divider.into(),
|
||||
width: 1,
|
||||
radius: 0.0.into(),
|
||||
fill_mode: rule::FillMode::Padded(8),
|
||||
snap: true,
|
||||
},
|
||||
Rule::HeavyDivider => rule::Style {
|
||||
color: self.current_container().divider.into(),
|
||||
width: 4,
|
||||
radius: 2.0.into(),
|
||||
fill_mode: rule::FillMode::Full,
|
||||
snap: true,
|
||||
},
|
||||
Rule::Custom(f) => f(self),
|
||||
}
|
||||
|
|
@ -1126,7 +1142,10 @@ impl scrollable::Catalog for Theme {
|
|||
|
||||
fn style(&self, class: &Self::Class<'_>, status: scrollable::Status) -> scrollable::Style {
|
||||
match status {
|
||||
scrollable::Status::Active => {
|
||||
scrollable::Status::Active {
|
||||
is_horizontal_scrollbar_disabled,
|
||||
is_vertical_scrollbar_disabled,
|
||||
} => {
|
||||
let cosmic = self.cosmic();
|
||||
let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7);
|
||||
let neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7);
|
||||
|
|
@ -1139,7 +1158,7 @@ impl scrollable::Catalog for Theme {
|
|||
},
|
||||
background: None,
|
||||
scroller: scrollable::Scroller {
|
||||
color: if cosmic.is_dark {
|
||||
background: if cosmic.is_dark {
|
||||
neutral_6.into()
|
||||
} else {
|
||||
neutral_5.into()
|
||||
|
|
@ -1157,7 +1176,7 @@ impl scrollable::Catalog for Theme {
|
|||
},
|
||||
background: None,
|
||||
scroller: scrollable::Scroller {
|
||||
color: if cosmic.is_dark {
|
||||
background: if cosmic.is_dark {
|
||||
neutral_6.into()
|
||||
} else {
|
||||
neutral_5.into()
|
||||
|
|
@ -1169,6 +1188,13 @@ impl scrollable::Catalog for Theme {
|
|||
},
|
||||
},
|
||||
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);
|
||||
|
||||
|
|
@ -1200,7 +1226,7 @@ impl scrollable::Catalog for Theme {
|
|||
},
|
||||
background: None,
|
||||
scroller: scrollable::Scroller {
|
||||
color: if cosmic.is_dark {
|
||||
background: if cosmic.is_dark {
|
||||
neutral_6.into()
|
||||
} else {
|
||||
neutral_5.into()
|
||||
|
|
@ -1218,7 +1244,7 @@ impl scrollable::Catalog for Theme {
|
|||
},
|
||||
background: None,
|
||||
scroller: scrollable::Scroller {
|
||||
color: if cosmic.is_dark {
|
||||
background: if cosmic.is_dark {
|
||||
neutral_6.into()
|
||||
} else {
|
||||
neutral_5.into()
|
||||
|
|
@ -1230,6 +1256,13 @@ impl scrollable::Catalog for Theme {
|
|||
},
|
||||
},
|
||||
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) {
|
||||
|
|
@ -1400,7 +1433,7 @@ impl text_input::Catalog for Theme {
|
|||
},
|
||||
}
|
||||
}
|
||||
text_input::Status::Focused => {
|
||||
text_input::Status::Focused { is_hovered } => {
|
||||
let bg = self.current_container().small_widget.with_alpha(0.25);
|
||||
|
||||
match class {
|
||||
|
|
@ -1477,7 +1510,8 @@ impl iced_widget::text_editor::Catalog for Theme {
|
|||
let selection = cosmic.accent.base.into();
|
||||
let value = cosmic.palette.neutral_9.into();
|
||||
let placeholder = cosmic.palette.neutral_9.with_alpha(0.7).into();
|
||||
let icon = cosmic.background.on.into();
|
||||
let icon: Color = cosmic.background.on.into();
|
||||
// TODO do we need to add icon color back?
|
||||
|
||||
match status {
|
||||
iced_widget::text_editor::Status::Active
|
||||
|
|
@ -1489,23 +1523,23 @@ impl iced_widget::text_editor::Catalog for Theme {
|
|||
width: f32::from(cosmic.space_xxxs()),
|
||||
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,
|
||||
value,
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1522,6 +1556,21 @@ 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")]
|
||||
impl iced_widget::qr_code::Catalog for Theme {
|
||||
type Class<'a> = iced_widget::qr_code::StyleFn<'a, Self>;
|
||||
|
|
@ -1539,3 +1588,50 @@ impl iced_widget::qr_code::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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
Apply, Element, fl,
|
||||
iced::{Alignment, Length},
|
||||
widget::{self, horizontal_space},
|
||||
widget::{self, space},
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Clone, derive_setters::Setters)]
|
||||
|
|
@ -99,7 +99,7 @@ pub fn about<'a, Message: Clone + 'static>(
|
|||
let section_button = |name: &'a str, url: &'a str| -> Element<'a, Message> {
|
||||
widget::row()
|
||||
.push(widget::text(name))
|
||||
.push(horizontal_space())
|
||||
.push(space::horizontal())
|
||||
.push_maybe(
|
||||
(!url.is_empty()).then_some(crate::widget::icon::from_name("link-symbolic").icon()),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use iced::Size;
|
||||
use iced::widget::Container;
|
||||
use iced_core::event::{self, Event};
|
||||
use iced_core::event::Event;
|
||||
use iced_core::layout;
|
||||
use iced_core::mouse;
|
||||
use iced_core::overlay;
|
||||
|
|
@ -172,7 +172,7 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -186,7 +186,7 @@ where
|
|||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
|
|
@ -195,18 +195,18 @@ where
|
|||
self.container.operate(tree, layout, renderer, operation);
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
self.container.on_event(
|
||||
) {
|
||||
self.container.update(
|
||||
tree,
|
||||
event,
|
||||
layout,
|
||||
|
|
@ -254,11 +254,13 @@ where
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||
self.container.overlay(tree, layout, renderer, translation)
|
||||
self.container
|
||||
.overlay(tree, layout, renderer, viewport, translation)
|
||||
}
|
||||
|
||||
#[cfg(feature = "a11y")]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use iced_core::layout;
|
|||
use iced_core::mouse;
|
||||
use iced_core::overlay;
|
||||
use iced_core::renderer;
|
||||
use iced_core::widget::{Id, Tree};
|
||||
use iced_core::widget::{Id, Operation, Tree};
|
||||
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
|
||||
pub use iced_widget::container::{Catalog, Style};
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ where
|
|||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.children[0].diff(&mut self.content);
|
||||
tree.diff_children(std::slice::from_mut(&mut self.content));
|
||||
}
|
||||
|
||||
fn size(&self) -> iced_core::Size<Length> {
|
||||
|
|
@ -115,7 +115,7 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -131,21 +131,22 @@ where
|
|||
}
|
||||
let node = self
|
||||
.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, &my_limits);
|
||||
let size = node.size();
|
||||
layout::Node::with_children(size, vec![node])
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
operation: &mut dyn Operation,
|
||||
) {
|
||||
operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
|
||||
self.content.as_widget().operate(
|
||||
operation.container(Some(&self.id), layout.bounds());
|
||||
operation.traverse(&mut |operation| {
|
||||
self.content.as_widget_mut().operate(
|
||||
&mut tree.children[0],
|
||||
layout
|
||||
.children()
|
||||
|
|
@ -158,17 +159,17 @@ where
|
|||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
) {
|
||||
#[cfg(feature = "wayland")]
|
||||
if matches!(
|
||||
event,
|
||||
|
|
@ -179,9 +180,9 @@ where
|
|||
let bounds = layout.bounds().size();
|
||||
clipboard.request_logical_window_size(bounds.width.max(1.), bounds.height.max(1.));
|
||||
}
|
||||
self.content.as_widget_mut().on_event(
|
||||
self.content.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event.clone(),
|
||||
event,
|
||||
layout
|
||||
.children()
|
||||
.next()
|
||||
|
|
@ -192,7 +193,7 @@ where
|
|||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -238,8 +239,9 @@ where
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
self.content.as_widget_mut().overlay(
|
||||
|
|
@ -250,6 +252,7 @@ where
|
|||
.unwrap()
|
||||
.with_virtual_offset(layout.virtual_offset()),
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -318,7 +318,7 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -331,21 +331,22 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
self.padding,
|
||||
|renderer, limits| {
|
||||
self.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, limits)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
operation.container(None, layout.bounds(), &mut |operation| {
|
||||
self.content.as_widget().operate(
|
||||
operation.container(None, layout.bounds());
|
||||
operation.traverse(&mut |operation| {
|
||||
self.content.as_widget_mut().operate(
|
||||
&mut tree.children[0],
|
||||
layout
|
||||
.children()
|
||||
|
|
@ -356,21 +357,19 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
operation,
|
||||
);
|
||||
});
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
operation.focusable(state, Some(&self.id));
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
) {
|
||||
if let Variant::Image {
|
||||
on_remove: Some(on_remove),
|
||||
..
|
||||
|
|
@ -383,7 +382,8 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
if let Some(position) = cursor.position() {
|
||||
if removal_bounds(layout.bounds(), 4.0).contains(position) {
|
||||
shell.publish(on_remove.clone());
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -391,10 +391,9 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
if self.content.as_widget_mut().on_event(
|
||||
self.content.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event.clone(),
|
||||
event,
|
||||
layout
|
||||
.children()
|
||||
.next()
|
||||
|
|
@ -405,9 +404,9 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
) == event::Status::Captured
|
||||
{
|
||||
return event::Status::Captured;
|
||||
);
|
||||
if shell.is_event_captured() {
|
||||
return;
|
||||
}
|
||||
|
||||
update(
|
||||
|
|
@ -541,6 +540,7 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
selection_background,
|
||||
);
|
||||
|
|
@ -554,7 +554,7 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
y: bounds.y + (bounds.height - 18.0 - styling.border_width),
|
||||
};
|
||||
if bounds.intersects(viewport) {
|
||||
iced_core::svg::Renderer::draw_svg(renderer, svg_handle, bounds);
|
||||
iced_core::svg::Renderer::draw_svg(renderer, svg_handle, bounds, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -570,6 +570,7 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
radius: c_rad.radius_m.into(),
|
||||
..Default::default()
|
||||
},
|
||||
snap: true,
|
||||
},
|
||||
selection_background,
|
||||
);
|
||||
|
|
@ -583,6 +584,12 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
x: bounds.x + 4.0,
|
||||
y: bounds.y + 4.0,
|
||||
},
|
||||
Rectangle {
|
||||
width: 16.0,
|
||||
height: 16.0,
|
||||
x: bounds.x + 4.0,
|
||||
y: bounds.y + 4.0,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -609,8 +616,9 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &crate::Renderer,
|
||||
viewport: &Rectangle,
|
||||
mut translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
let position = layout.bounds().position();
|
||||
|
|
@ -624,6 +632,7 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
.unwrap()
|
||||
.with_virtual_offset(layout.virtual_offset()),
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
|
@ -638,7 +647,7 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
) -> iced_accessibility::A11yTree {
|
||||
use iced_accessibility::{
|
||||
A11yNode, A11yTree,
|
||||
accesskit::{Action, DefaultActionVerb, NodeBuilder, NodeId, Rect, Role},
|
||||
accesskit::{Action, Node, NodeId, Rect, Role},
|
||||
};
|
||||
// TODO why is state None sometimes?
|
||||
if matches!(state.state, iced_core::widget::tree::State::None) {
|
||||
|
|
@ -658,12 +667,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 is_hovered = state.state.downcast_ref::<State>().is_hovered;
|
||||
|
||||
let mut node = NodeBuilder::new(Role::Button);
|
||||
let mut node = Node::new(Role::Button);
|
||||
node.add_action(Action::Focus);
|
||||
node.add_action(Action::Default);
|
||||
node.add_action(Action::Click);
|
||||
node.set_bounds(bounds);
|
||||
if let Some(name) = self.name.as_ref() {
|
||||
node.set_name(name.clone());
|
||||
node.set_label(name.clone());
|
||||
}
|
||||
match self.description.as_ref() {
|
||||
Some(iced_accessibility::Description::Id(id)) => {
|
||||
|
|
@ -682,10 +691,10 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
if self.on_press.is_none() {
|
||||
node.set_disabled();
|
||||
}
|
||||
if is_hovered {
|
||||
node.set_hovered();
|
||||
}
|
||||
node.set_default_action_verb(DefaultActionVerb::Click);
|
||||
// TODO hover
|
||||
// if is_hovered {
|
||||
// node.set_hovered();
|
||||
// }
|
||||
|
||||
if let Some(child_tree) = child_tree.map(|child_tree| {
|
||||
self.content.as_widget().a11y_nodes(
|
||||
|
|
@ -761,14 +770,14 @@ impl State {
|
|||
#[allow(clippy::needless_pass_by_value, clippy::too_many_arguments)]
|
||||
pub fn update<'a, Message: Clone>(
|
||||
_id: Id,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
on_press: Option<&dyn Fn(Vector, Rectangle) -> Message>,
|
||||
on_press_down: Option<&dyn Fn(Vector, Rectangle) -> Message>,
|
||||
state: impl FnOnce() -> &'a mut State,
|
||||
) -> event::Status {
|
||||
) {
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||
|
|
@ -787,7 +796,8 @@ pub fn update<'a, Message: Clone>(
|
|||
shell.publish(msg);
|
||||
}
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -806,7 +816,8 @@ pub fn update<'a, Message: Clone>(
|
|||
shell.publish(msg);
|
||||
}
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
} else if on_press_down.is_some() {
|
||||
let state = state();
|
||||
|
|
@ -816,7 +827,7 @@ pub fn update<'a, Message: Clone>(
|
|||
#[cfg(feature = "a11y")]
|
||||
Event::A11y(event_id, iced_accessibility::accesskit::ActionRequest { action, .. }) => {
|
||||
let state = state();
|
||||
if let Some(on_press) = matches!(action, iced_accessibility::accesskit::Action::Default)
|
||||
if let Some(on_press) = matches!(action, iced_accessibility::accesskit::Action::Click)
|
||||
.then_some(on_press)
|
||||
.flatten()
|
||||
{
|
||||
|
|
@ -825,17 +836,19 @@ pub fn update<'a, Message: Clone>(
|
|||
|
||||
shell.publish(msg);
|
||||
}
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => {
|
||||
if let Some(on_press) = on_press {
|
||||
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;
|
||||
let msg = (on_press)(layout.virtual_offset(), layout.bounds());
|
||||
|
||||
shell.publish(msg);
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -846,8 +859,6 @@ pub fn update<'a, Message: Clone>(
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
|
@ -879,6 +890,7 @@ pub fn draw<Renderer: iced_core::Renderer, Theme>(
|
|||
radius: styling.border_radius,
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
Color::TRANSPARENT,
|
||||
);
|
||||
|
|
@ -900,6 +912,7 @@ pub fn draw<Renderer: iced_core::Renderer, Theme>(
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
Background::Color([0.0, 0.0, 0.0, 0.5].into()),
|
||||
);
|
||||
|
|
@ -915,6 +928,7 @@ pub fn draw<Renderer: iced_core::Renderer, Theme>(
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
background,
|
||||
);
|
||||
|
|
@ -930,6 +944,7 @@ pub fn draw<Renderer: iced_core::Renderer, Theme>(
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
overlay,
|
||||
);
|
||||
|
|
@ -953,6 +968,7 @@ pub fn draw<Renderer: iced_core::Renderer, Theme>(
|
|||
radius: styling.border_radius,
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
Color::TRANSPARENT,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -213,7 +213,9 @@ where
|
|||
let content_list = column::with_children([
|
||||
row::with_children([
|
||||
column().push(date).push(day).into(),
|
||||
crate::widget::Space::with_width(Length::Fill).into(),
|
||||
crate::widget::space::horizontal()
|
||||
.width(Length::Fill)
|
||||
.into(),
|
||||
month_controls.into(),
|
||||
])
|
||||
.align_y(Vertical::Center)
|
||||
|
|
|
|||
587
src/widget/cards.rs
Normal file
587
src/widget/cards.rs
Normal file
|
|
@ -0,0 +1,587 @@
|
|||
//! An expandable stack of cards
|
||||
use std::time::Duration;
|
||||
|
||||
use self::iced_core::{
|
||||
Element, Event, Length, Size, Vector, Widget, border::Radius, id::Id, layout::Node,
|
||||
renderer::Quad, widget::Tree,
|
||||
};
|
||||
use crate::{
|
||||
anim,
|
||||
iced_core::{self, Border, Shadow},
|
||||
widget::{
|
||||
button,
|
||||
card::style::Style,
|
||||
column,
|
||||
icon::{self, Handle},
|
||||
row, text,
|
||||
},
|
||||
};
|
||||
use float_cmp::approx_eq;
|
||||
use iced::widget;
|
||||
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,
|
||||
}
|
||||
|
|
@ -26,7 +26,10 @@ use iced_core::{
|
|||
};
|
||||
|
||||
use iced_widget::slider::HandleShape;
|
||||
use iced_widget::{Row, canvas, column, horizontal_space, row, scrollable, vertical_space};
|
||||
use iced_widget::{
|
||||
Row, canvas, column, row, scrollable,
|
||||
space::{horizontal, vertical},
|
||||
};
|
||||
use palette::{FromColor, RgbHue};
|
||||
|
||||
use super::divider::horizontal;
|
||||
|
|
@ -334,7 +337,7 @@ where
|
|||
.width(self.width),
|
||||
// canvas with gradient for the current color
|
||||
// still needs the canvas and the handle to be drawn on it
|
||||
container(vertical_space().height(self.height))
|
||||
container(vertical().height(self.height))
|
||||
.width(self.width)
|
||||
.height(self.height),
|
||||
slider(
|
||||
|
|
@ -548,13 +551,13 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.inner
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, limits)
|
||||
}
|
||||
|
||||
|
|
@ -657,6 +660,7 @@ where
|
|||
radius: (1.0 + handle_radius).into(),
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
Color::TRANSPARENT,
|
||||
);
|
||||
|
|
@ -674,6 +678,7 @@ where
|
|||
radius: handle_radius.into(),
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
Color::TRANSPARENT,
|
||||
);
|
||||
|
|
@ -684,26 +689,31 @@ where
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
state: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &crate::Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
self.inner
|
||||
.as_widget_mut()
|
||||
.overlay(&mut state.children[0], layout, renderer, translation)
|
||||
self.inner.as_widget_mut().overlay(
|
||||
&mut state.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
) {
|
||||
// if the pointer is performing a drag, intercept pointer motion and button events
|
||||
// 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
|
||||
|
|
@ -732,24 +742,26 @@ where
|
|||
shell.publish((self.on_update)(ColorPickerUpdate::ActionFinished));
|
||||
state.dragging = false;
|
||||
}
|
||||
_ => return event::Status::Ignored,
|
||||
_ => return,
|
||||
};
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
let column_tree = &mut tree.children[0];
|
||||
if self.inner.as_widget_mut().on_event(
|
||||
self.inner.as_widget_mut().update(
|
||||
column_tree,
|
||||
event.clone(),
|
||||
&event,
|
||||
column_layout,
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
) == event::Status::Captured
|
||||
{
|
||||
return event::Status::Captured;
|
||||
);
|
||||
if shell.is_event_captured() {
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
match event {
|
||||
|
|
@ -764,12 +776,10 @@ where
|
|||
state.dragging = true;
|
||||
let hsv: palette::Hsv = palette::Hsv::new(self.active_color.hue, s, v);
|
||||
shell.publish((self.on_update)(ColorPickerUpdate::ActiveColor(hsv)));
|
||||
event::Status::Captured
|
||||
} else {
|
||||
event::Status::Ignored
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
_ => event::Status::Ignored,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -812,12 +822,12 @@ pub fn color_button<'a, Message: Clone + 'static>(
|
|||
let spacing = THEME.lock().unwrap().cosmic().spacing;
|
||||
|
||||
button::custom(if color.is_some() {
|
||||
Element::from(vertical_space().height(Length::Fixed(f32::from(spacing.space_s))))
|
||||
Element::from(vertical().height(Length::Fixed(f32::from(spacing.space_s))))
|
||||
} else {
|
||||
Element::from(column![
|
||||
vertical_space().height(Length::FillPortion(6)),
|
||||
vertical().height(Length::FillPortion(6)),
|
||||
row![
|
||||
horizontal_space().width(Length::FillPortion(6)),
|
||||
horizontal().width(Length::FillPortion(6)),
|
||||
Icon::from(
|
||||
icon::from_name("list-add-symbolic")
|
||||
.prefer_svg(true)
|
||||
|
|
@ -827,11 +837,11 @@ pub fn color_button<'a, Message: Clone + 'static>(
|
|||
.width(icon_portion)
|
||||
.height(Length::Fill)
|
||||
.content_fit(iced_core::ContentFit::Contain),
|
||||
horizontal_space().width(Length::FillPortion(6)),
|
||||
horizontal().width(Length::FillPortion(6)),
|
||||
]
|
||||
.height(icon_portion)
|
||||
.width(Length::Fill),
|
||||
vertical_space().height(Length::FillPortion(6)),
|
||||
vertical().height(Length::FillPortion(6)),
|
||||
])
|
||||
})
|
||||
.width(Length::Fixed(f32::from(spacing.space_s)))
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ use iced::advanced::layout::{self, Layout};
|
|||
use iced::advanced::widget::{self, Operation};
|
||||
use iced::advanced::{Clipboard, Shell};
|
||||
use iced::advanced::{overlay, renderer};
|
||||
use iced::{Event, Point, Rectangle, Size, event, mouse};
|
||||
use iced_core::Renderer;
|
||||
use iced::{Event, Point, Size, mouse};
|
||||
use iced_core::{Renderer, touch};
|
||||
|
||||
pub(super) struct Overlay<'a, 'b, Message> {
|
||||
pub(crate) position: Point,
|
||||
|
|
@ -29,7 +29,7 @@ where
|
|||
|
||||
let node = self
|
||||
.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.layout(self.tree, renderer, &limits);
|
||||
let node_size = node.size();
|
||||
|
||||
|
|
@ -47,16 +47,16 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
self.content.as_widget_mut().on_event(
|
||||
) {
|
||||
self.content.as_widget_mut().update(
|
||||
self.tree,
|
||||
event,
|
||||
layout,
|
||||
|
|
@ -65,7 +65,20 @@ where
|
|||
clipboard,
|
||||
shell,
|
||||
&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(
|
||||
|
|
@ -86,7 +99,7 @@ where
|
|||
cursor,
|
||||
&layout.bounds(),
|
||||
);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn operate(
|
||||
|
|
@ -104,21 +117,35 @@ where
|
|||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &crate::Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content
|
||||
// TODO how to handle viewport here?
|
||||
let viewport = &layout.bounds();
|
||||
let interaction = self
|
||||
.content
|
||||
.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>(
|
||||
&'c mut self,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'c>,
|
||||
renderer: &crate::Renderer,
|
||||
) -> Option<overlay::Element<'c, Message, crate::Theme, crate::Renderer>> {
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.overlay(self.tree, layout, renderer, iced::Vector::default())
|
||||
let viewport = &layout.bounds();
|
||||
|
||||
self.content.as_widget_mut().overlay(
|
||||
self.tree,
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
iced::Vector::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::{Apply, Element, Renderer, Theme, fl};
|
|||
use std::borrow::Cow;
|
||||
|
||||
use iced_core::Alignment;
|
||||
use iced_core::event::{self, Event};
|
||||
use iced_core::event::Event;
|
||||
use iced_core::widget::{Operation, Tree};
|
||||
use iced_core::{
|
||||
Clipboard, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse,
|
||||
|
|
@ -65,7 +65,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
|
|||
} else {
|
||||
let title = title
|
||||
.map(|title| text::title4(title).width(Length::Fill).apply(Element::from))
|
||||
.unwrap_or_else(|| widget::horizontal_space().apply(Element::from));
|
||||
.unwrap_or_else(|| widget::space::horizontal().apply(Element::from));
|
||||
(title, None)
|
||||
};
|
||||
|
||||
|
|
@ -196,40 +196,40 @@ impl<Message: Clone> Widget<Message, crate::Theme, Renderer> for ContextDrawer<'
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, limits)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
self.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.operate(&mut tree.children[0], layout, renderer, operation);
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
self.content.as_widget_mut().on_event(
|
||||
) {
|
||||
self.content.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout,
|
||||
|
|
@ -238,7 +238,7 @@ impl<Message: Clone> Widget<Message, crate::Theme, Renderer> for ContextDrawer<'
|
|||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -282,8 +282,9 @@ impl<Message: Clone> Widget<Message, crate::Theme, Renderer> for ContextDrawer<'
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
_renderer: &Renderer,
|
||||
_viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<iced_overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||
let bounds = layout.bounds();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use derive_setters::Setters;
|
|||
use iced::touch::Finger;
|
||||
use iced::{Event, Vector, keyboard, window};
|
||||
use iced_core::widget::{Tree, Widget, tree};
|
||||
use iced_core::{Length, Point, Size, event, mouse, touch};
|
||||
use iced_core::{Length, Point, Size, mouse, touch};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -85,6 +85,7 @@ impl<Message: Clone + 'static> ContextMenu<'_, Message> {
|
|||
// close existing popups
|
||||
state.menu_states.clear();
|
||||
state.active_root.clear();
|
||||
|
||||
shell.publish(self.on_surface_action.as_ref().unwrap()(destroy_popup(id)));
|
||||
state.view_cursor = view_cursor;
|
||||
(
|
||||
|
|
@ -249,7 +250,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.children[0].diff(self.content.as_widget_mut());
|
||||
tree.diff_children(std::slice::from_mut(&mut self.content));
|
||||
let state = tree.state.downcast_mut::<LocalState>();
|
||||
state.menu_bar_state.inner.with_data_mut(|inner| {
|
||||
menu_roots_diff(self.context_menu.as_mut().unwrap(), &mut inner.tree);
|
||||
|
|
@ -270,13 +271,13 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &iced_core::layout::Limits,
|
||||
) -> iced_core::layout::Node {
|
||||
self.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, limits)
|
||||
}
|
||||
|
||||
|
|
@ -302,29 +303,29 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: iced_core::Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
) {
|
||||
self.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.operate(&mut tree.children[0], layout, renderer, operation);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: iced::Event,
|
||||
event: &iced::Event,
|
||||
layout: iced_core::Layout<'_>,
|
||||
cursor: iced_core::mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn iced_core::Clipboard,
|
||||
shell: &mut iced_core::Shell<'_, Message>,
|
||||
viewport: &iced::Rectangle,
|
||||
) -> iced_core::event::Status {
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<LocalState>();
|
||||
let bounds = layout.bounds();
|
||||
|
||||
|
|
@ -336,13 +337,12 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
.with_data(|d| !d.open && !d.active_root.is_empty());
|
||||
|
||||
let open = state.menu_bar_state.inner.with_data_mut(|state| {
|
||||
if reset {
|
||||
if let Some(popup_id) = state.popup_id.get(&self.window_id).copied() {
|
||||
if let Some(handler) = self.on_surface_action.as_ref() {
|
||||
shell.publish((handler)(crate::surface::Action::DestroyPopup(popup_id)));
|
||||
state.reset();
|
||||
}
|
||||
}
|
||||
if reset
|
||||
&& let Some(popup_id) = state.popup_id.get(&self.window_id).copied()
|
||||
&& let Some(handler) = self.on_surface_action.as_ref()
|
||||
{
|
||||
shell.publish((handler)(crate::surface::Action::DestroyPopup(popup_id)));
|
||||
state.reset();
|
||||
}
|
||||
state.open
|
||||
});
|
||||
|
|
@ -356,7 +356,6 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
mouse::Button::Right | mouse::Button::Left,
|
||||
))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. })
|
||||
| Event::Window(window::Event::Focused)
|
||||
if open )
|
||||
{
|
||||
state.menu_bar_state.inner.with_data_mut(|state| {
|
||||
|
|
@ -366,15 +365,14 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
state.open = false;
|
||||
|
||||
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
|
||||
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
|
||||
if let Some(id) = state.popup_id.remove(&self.window_id) {
|
||||
{
|
||||
let surface_action = self.on_surface_action.as_ref().unwrap();
|
||||
shell
|
||||
.publish(surface_action(crate::surface::action::destroy_popup(id)));
|
||||
}
|
||||
state.view_cursor = cursor;
|
||||
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
|
||||
&& let Some(id) = state.popup_id.remove(&self.window_id)
|
||||
{
|
||||
{
|
||||
let surface_action = self.on_surface_action.as_ref().unwrap();
|
||||
shell.publish(surface_action(crate::surface::action::destroy_popup(id)));
|
||||
}
|
||||
state.view_cursor = cursor;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -384,11 +382,11 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
|
||||
match event {
|
||||
Event::Touch(touch::Event::FingerPressed { id, .. }) => {
|
||||
state.fingers_pressed.insert(id);
|
||||
state.fingers_pressed.insert(*id);
|
||||
}
|
||||
|
||||
Event::Touch(touch::Event::FingerLifted { id, .. }) => {
|
||||
state.fingers_pressed.remove(&id);
|
||||
state.fingers_pressed.remove(id);
|
||||
}
|
||||
|
||||
_ => (),
|
||||
|
|
@ -397,7 +395,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
// Present a context menu on a right click event.
|
||||
if !was_open
|
||||
&& 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();
|
||||
let state = tree.state.downcast_mut::<LocalState>();
|
||||
|
|
@ -410,10 +408,11 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
self.create_popup(layout, cursor, renderer, shell, viewport, state);
|
||||
}
|
||||
|
||||
return event::Status::Captured;
|
||||
} else if !was_open && right_button_released(&event)
|
||||
|| (touch_lifted(&event))
|
||||
|| left_button_released(&event)
|
||||
shell.capture_event();
|
||||
return;
|
||||
} else if !was_open && right_button_released(event)
|
||||
|| (touch_lifted(event))
|
||||
|| left_button_released(event)
|
||||
{
|
||||
state.menu_bar_state.inner.with_data_mut(|state| {
|
||||
was_open = true;
|
||||
|
|
@ -426,21 +425,20 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
feature = "winit",
|
||||
feature = "surface-message"
|
||||
))]
|
||||
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
|
||||
if let Some(id) = state.popup_id.remove(&self.window_id) {
|
||||
{
|
||||
let surface_action = self.on_surface_action.as_ref().unwrap();
|
||||
shell.publish(surface_action(
|
||||
crate::surface::action::destroy_popup(id),
|
||||
));
|
||||
}
|
||||
state.view_cursor = cursor;
|
||||
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
|
||||
&& let Some(id) = state.popup_id.remove(&self.window_id)
|
||||
{
|
||||
{
|
||||
let surface_action = self.on_surface_action.as_ref().unwrap();
|
||||
shell
|
||||
.publish(surface_action(crate::surface::action::destroy_popup(id)));
|
||||
}
|
||||
state.view_cursor = cursor;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
self.content.as_widget_mut().on_event(
|
||||
self.content.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout,
|
||||
|
|
@ -449,7 +447,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
|
|
@ -457,6 +455,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
tree: &'b mut Tree,
|
||||
layout: iced_core::Layout<'_>,
|
||||
_renderer: &crate::Renderer,
|
||||
_viewport: &iced::Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ impl<'a, Message: Clone + 'static> From<Dialog<'a, Message>> for Element<'a, Mes
|
|||
if let Some(body) = dialog.body {
|
||||
if should_space {
|
||||
content_col = content_col
|
||||
.push(widget::vertical_space().height(Length::Fixed(space_xxs.into())));
|
||||
.push(widget::space::vertical().height(Length::Fixed(space_xxs.into())));
|
||||
}
|
||||
content_col = content_col.push(
|
||||
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 {
|
||||
if should_space {
|
||||
content_col = content_col
|
||||
.push(widget::vertical_space().height(Length::Fixed(space_s.into())));
|
||||
.push(widget::space::vertical().height(Length::Fixed(space_s.into())));
|
||||
}
|
||||
content_col = content_col.push(control);
|
||||
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 {
|
||||
button_row = button_row.push(button);
|
||||
}
|
||||
button_row = button_row.push(widget::horizontal_space());
|
||||
button_row = button_row.push(widget::space::horizontal());
|
||||
if let Some(button) = dialog.secondary_action {
|
||||
button_row = button_row.push(button);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.children[0].diff(self.container.as_widget_mut());
|
||||
tree.diff_children(std::slice::from_mut(&mut self.container));
|
||||
}
|
||||
|
||||
fn state(&self) -> iced_core::widget::tree::State {
|
||||
|
|
@ -303,43 +303,43 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.container
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, limits)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: layout::Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
) {
|
||||
self.container
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.operate(&mut tree.children[0], layout, renderer, operation);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: layout::Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
let s = self.container.as_widget_mut().on_event(
|
||||
) {
|
||||
self.container.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event.clone(),
|
||||
event,
|
||||
layout,
|
||||
cursor,
|
||||
renderer,
|
||||
|
|
@ -347,8 +347,8 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
shell,
|
||||
viewport,
|
||||
);
|
||||
if matches!(s, event::Status::Captured) {
|
||||
return event::Status::Captured;
|
||||
if shell.is_event_captured() {
|
||||
return;
|
||||
}
|
||||
|
||||
let state = tree.state.downcast_mut::<State<()>>();
|
||||
|
|
@ -367,23 +367,23 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
OfferEvent::Enter {
|
||||
x, y, mime_types, ..
|
||||
},
|
||||
)) if id == Some(my_id) => {
|
||||
)) if *id == Some(my_id) => {
|
||||
if !self.mime_matches(&mime_types) {
|
||||
log::trace!(
|
||||
target: DND_DEST_LOG_TARGET,
|
||||
"offer enter id={my_id:?} ignored (mimes={mime_types:?} not in {:?})",
|
||||
self.mime_types
|
||||
);
|
||||
return event::Status::Ignored;
|
||||
return;
|
||||
}
|
||||
log::trace!(
|
||||
target: DND_DEST_LOG_TARGET,
|
||||
"offer enter id={my_id:?} coords=({x},{y}) mimes={mime_types:?}"
|
||||
);
|
||||
if let Some(msg) = state.on_enter(
|
||||
x,
|
||||
y,
|
||||
mime_types,
|
||||
*x,
|
||||
*y,
|
||||
mime_types.clone(),
|
||||
self.on_enter.as_ref().map(std::convert::AsRef::as_ref),
|
||||
(),
|
||||
) {
|
||||
|
|
@ -391,13 +391,13 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
}
|
||||
if self.forward_drag_as_cursor {
|
||||
#[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 {
|
||||
position: drag_cursor.position().unwrap(),
|
||||
});
|
||||
self.container.as_widget_mut().on_event(
|
||||
self.container.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
&event,
|
||||
layout,
|
||||
drag_cursor,
|
||||
renderer,
|
||||
|
|
@ -406,7 +406,8 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
viewport,
|
||||
);
|
||||
}
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
Event::Dnd(DndEvent::Offer(_, OfferEvent::Leave)) => {
|
||||
log::trace!(
|
||||
|
|
@ -423,9 +424,9 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
if self.forward_drag_as_cursor {
|
||||
let drag_cursor = mouse::Cursor::Unavailable;
|
||||
let event = Event::Mouse(mouse::Event::CursorLeft);
|
||||
self.container.as_widget_mut().on_event(
|
||||
self.container.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
&event,
|
||||
layout,
|
||||
drag_cursor,
|
||||
renderer,
|
||||
|
|
@ -434,16 +435,16 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
viewport,
|
||||
);
|
||||
}
|
||||
return event::Status::Ignored;
|
||||
return;
|
||||
}
|
||||
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!(
|
||||
target: DND_DEST_LOG_TARGET,
|
||||
"offer motion id={my_id:?} coords=({x},{y})"
|
||||
);
|
||||
if let Some(msg) = state.on_motion(
|
||||
x,
|
||||
y,
|
||||
*x,
|
||||
*y,
|
||||
self.on_motion.as_ref().map(std::convert::AsRef::as_ref),
|
||||
self.on_enter.as_ref().map(std::convert::AsRef::as_ref),
|
||||
(),
|
||||
|
|
@ -453,13 +454,13 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
|
||||
if self.forward_drag_as_cursor {
|
||||
#[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 {
|
||||
position: drag_cursor.position().unwrap(),
|
||||
});
|
||||
self.container.as_widget_mut().on_event(
|
||||
self.container.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
&event,
|
||||
layout,
|
||||
drag_cursor,
|
||||
renderer,
|
||||
|
|
@ -468,7 +469,8 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
viewport,
|
||||
);
|
||||
}
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
Event::Dnd(DndEvent::Offer(_, OfferEvent::LeaveDestination)) => {
|
||||
log::trace!(
|
||||
|
|
@ -481,9 +483,9 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
{
|
||||
shell.publish(msg);
|
||||
}
|
||||
return event::Status::Ignored;
|
||||
return;
|
||||
}
|
||||
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!(
|
||||
target: DND_DEST_LOG_TARGET,
|
||||
"offer drop id={my_id:?}"
|
||||
|
|
@ -493,27 +495,29 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
{
|
||||
shell.publish(msg);
|
||||
}
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
Event::Dnd(DndEvent::Offer(id, OfferEvent::SelectedAction(action)))
|
||||
if id == Some(my_id) =>
|
||||
if *id == Some(my_id) =>
|
||||
{
|
||||
log::trace!(
|
||||
target: DND_DEST_LOG_TARGET,
|
||||
"offer selected-action id={my_id:?} action={action:?}"
|
||||
);
|
||||
if let Some(msg) = state.on_action_selected(
|
||||
action,
|
||||
*action,
|
||||
self.on_action_selected
|
||||
.as_ref()
|
||||
.map(std::convert::AsRef::as_ref),
|
||||
) {
|
||||
shell.publish(msg);
|
||||
}
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
Event::Dnd(DndEvent::Offer(id, OfferEvent::Data { data, mime_type }))
|
||||
if id == Some(my_id) =>
|
||||
if *id == Some(my_id) =>
|
||||
{
|
||||
log::trace!(
|
||||
target: DND_DEST_LOG_TARGET,
|
||||
|
|
@ -527,25 +531,33 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
&& let Ok(s) = String::from_utf8(data[..data.len() - 1].to_vec())
|
||||
{
|
||||
shell.publish(f(s));
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
if let (Some(msg), ret) = state.on_data_received(
|
||||
mime_type,
|
||||
data,
|
||||
mime_type.clone(),
|
||||
data.clone(),
|
||||
self.on_data_received
|
||||
.as_ref()
|
||||
.map(std::convert::AsRef::as_ref),
|
||||
self.on_finish.as_ref().map(std::convert::AsRef::as_ref),
|
||||
) {
|
||||
shell.publish(msg);
|
||||
return ret;
|
||||
if ret == event::Status::Captured {
|
||||
log::trace!(
|
||||
target: DND_DEST_LOG_TARGET,
|
||||
"offer data id={my_id:?} captured"
|
||||
);
|
||||
shell.capture_event();
|
||||
}
|
||||
return;
|
||||
}
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -589,13 +601,18 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: layout::Layout<'_>,
|
||||
layout: layout::Layout<'b>,
|
||||
renderer: &crate::Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
self.container
|
||||
.as_widget_mut()
|
||||
.overlay(&mut tree.children[0], layout, renderer, translation)
|
||||
self.container.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
||||
fn drag_destinations(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::any::Any;
|
||||
|
||||
use iced_core::window;
|
||||
use iced_core::{widget::Operation, window};
|
||||
|
||||
use crate::{
|
||||
Element,
|
||||
|
|
@ -131,21 +131,25 @@ impl<
|
|||
);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn on_start(mut self, on_start: Option<Message>) -> Self {
|
||||
self.on_start = on_start;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn on_cancel(mut self, on_cancelled: Option<Message>) -> Self {
|
||||
self.on_cancelled = on_cancelled;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn on_finish(mut self, on_finish: Option<Message>) -> Self {
|
||||
self.on_finish = on_finish;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn window(mut self, window: window::Id) -> Self {
|
||||
self.window = Some(window);
|
||||
self
|
||||
|
|
@ -164,7 +168,7 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
|
|||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.children[0].diff(self.container.as_widget_mut());
|
||||
tree.diff_children(std::slice::from_mut(&mut self.container));
|
||||
}
|
||||
|
||||
fn state(&self) -> iced_core::widget::tree::State {
|
||||
|
|
@ -176,7 +180,7 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -184,41 +188,44 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
|
|||
let state = tree.state.downcast_mut::<State>();
|
||||
let node = self
|
||||
.container
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, limits);
|
||||
state.cached_bounds = node.bounds();
|
||||
node
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: layout::Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
operation: &mut dyn Operation,
|
||||
) {
|
||||
operation.custom((&mut tree.state) as &mut dyn Any, Some(&self.id));
|
||||
operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
|
||||
self.container
|
||||
.as_widget()
|
||||
.operate(&mut tree.children[0], layout, renderer, operation)
|
||||
});
|
||||
operation.custom(
|
||||
Some(&self.id),
|
||||
layout.bounds(),
|
||||
(&mut tree.state) as &mut dyn Any,
|
||||
);
|
||||
|
||||
self.container
|
||||
.as_widget_mut()
|
||||
.operate(&mut tree.children[0], layout, renderer, operation);
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: layout::Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
let ret = self.container.as_widget_mut().on_event(
|
||||
) {
|
||||
self.container.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event.clone(),
|
||||
event,
|
||||
layout,
|
||||
cursor,
|
||||
renderer,
|
||||
|
|
@ -233,54 +240,48 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
|
|||
Event::Mouse(mouse_event) => match mouse_event {
|
||||
mouse::Event::ButtonPressed(mouse::Button::Left) => {
|
||||
if let Some(position) = cursor.position() {
|
||||
if !state.hovered {
|
||||
return ret;
|
||||
if !cursor.is_over(layout.bounds()) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.left_pressed_position = Some(position);
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
mouse::Event::ButtonReleased(mouse::Button::Left)
|
||||
if state.left_pressed_position.is_some() =>
|
||||
{
|
||||
state.left_pressed_position = None;
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
}
|
||||
mouse::Event::CursorMoved { .. } => {
|
||||
if let Some(position) = cursor.position() {
|
||||
if state.hovered {
|
||||
// We ignore motion if we do not possess drag content by now.
|
||||
if self.drag_content.is_none() {
|
||||
state.left_pressed_position = None;
|
||||
return ret;
|
||||
}
|
||||
if let Some(left_pressed_position) = state.left_pressed_position {
|
||||
if 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(
|
||||
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;
|
||||
// We ignore motion if we do not possess drag content by now.
|
||||
if self.drag_content.is_none() {
|
||||
state.left_pressed_position = None;
|
||||
return;
|
||||
}
|
||||
return event::Status::Captured;
|
||||
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(
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
_ => return ret,
|
||||
_ => (),
|
||||
},
|
||||
Event::Dnd(DndEvent::Source(SourceEvent::Cancelled)) => {
|
||||
if state.is_dragging {
|
||||
|
|
@ -288,9 +289,8 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
|
|||
shell.publish(m.clone());
|
||||
}
|
||||
state.is_dragging = false;
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
Event::Dnd(DndEvent::Source(SourceEvent::Finished)) => {
|
||||
if state.is_dragging {
|
||||
|
|
@ -298,13 +298,11 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
|
|||
shell.publish(m.clone());
|
||||
}
|
||||
state.is_dragging = false;
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
_ => return ret,
|
||||
_ => (),
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -352,13 +350,18 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: layout::Layout<'_>,
|
||||
layout: layout::Layout<'b>,
|
||||
renderer: &crate::Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
self.container
|
||||
.as_widget_mut()
|
||||
.overlay(&mut tree.children[0], layout, renderer, translation)
|
||||
self.container.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
||||
fn drag_destinations(
|
||||
|
|
@ -411,7 +414,6 @@ impl<
|
|||
/// Local state of the [`MouseListener`].
|
||||
#[derive(Debug, Default)]
|
||||
struct State {
|
||||
hovered: bool,
|
||||
left_pressed_position: Option<Point>,
|
||||
is_dragging: bool,
|
||||
cached_bounds: Rectangle,
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ impl<'a, Message: Clone + 'a> Overlay<'a, Message> {
|
|||
}
|
||||
}
|
||||
|
||||
fn _layout(&self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
|
||||
fn _layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
|
||||
let space_below = bounds.height - (self.position.y + self.target_height);
|
||||
let space_above = self.position.y;
|
||||
|
||||
|
|
@ -242,19 +242,19 @@ impl<'a, Message: Clone + 'a> Overlay<'a, Message> {
|
|||
})
|
||||
}
|
||||
|
||||
fn _on_event(
|
||||
fn _update(
|
||||
&mut self,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
self.state.with_data_mut(|tree| {
|
||||
self.container.on_event(
|
||||
self.container.update(
|
||||
tree, event, layout, cursor, renderer, clipboard, shell, &bounds,
|
||||
)
|
||||
})
|
||||
|
|
@ -293,6 +293,7 @@ impl<'a, Message: Clone + 'a> Overlay<'a, Message> {
|
|||
radius: appearance.border_radius,
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
appearance.background,
|
||||
);
|
||||
|
|
@ -311,26 +312,25 @@ impl<'a, Message: Clone + 'a> iced_core::Overlay<Message, crate::Theme, crate::R
|
|||
self._layout(renderer, bounds)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
self._on_event(event, layout, cursor, renderer, clipboard, shell)
|
||||
) {
|
||||
self._update(event, layout, cursor, renderer, clipboard, shell)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &crate::Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self._mouse_interaction(layout, cursor, viewport, renderer)
|
||||
self._mouse_interaction(layout, cursor, &layout.bounds(), renderer)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
|
|
@ -353,7 +353,7 @@ impl<'a, Message: Clone + 'a> crate::widget::Widget<Message, crate::Theme, crate
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
_tree: &mut iced_core::widget::Tree,
|
||||
renderer: &crate::Renderer,
|
||||
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)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
_tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
self._on_event(event, layout, cursor, renderer, clipboard, shell)
|
||||
) {
|
||||
self._update(event, layout, cursor, renderer, clipboard, shell)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
|
|
@ -435,7 +435,7 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
_tree: &mut Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -452,7 +452,7 @@ where
|
|||
let size = {
|
||||
let intrinsic = Size::new(
|
||||
0.0,
|
||||
(f32::from(text_line_height) + self.padding.vertical()) * self.options.len() as f32,
|
||||
(f32::from(text_line_height) + self.padding.y()) * self.options.len() as f32,
|
||||
);
|
||||
|
||||
limits.resolve(Length::Fill, Length::Shrink, intrinsic)
|
||||
|
|
@ -461,17 +461,17 @@ where
|
|||
layout::Node::new(size)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
_state: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
) {
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
||||
let hovered_guard = self.hovered_option.lock().unwrap();
|
||||
|
|
@ -481,7 +481,8 @@ where
|
|||
if let Some(close_on_selected) = self.close_on_selected.as_ref() {
|
||||
shell.publish(close_on_selected.clone());
|
||||
}
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -493,7 +494,7 @@ where
|
|||
|
||||
let option_height =
|
||||
f32::from(self.text_line_height.to_absolute(Pixels(text_size)))
|
||||
+ self.padding.vertical();
|
||||
+ self.padding.y();
|
||||
|
||||
let new_hovered_option = (cursor_position.y / option_height) as usize;
|
||||
let mut hovered_guard = self.hovered_option.lock().unwrap();
|
||||
|
|
@ -515,7 +516,7 @@ where
|
|||
|
||||
let option_height =
|
||||
f32::from(self.text_line_height.to_absolute(Pixels(text_size)))
|
||||
+ self.padding.vertical();
|
||||
+ self.padding.y();
|
||||
let mut hovered_guard = self.hovered_option.lock().unwrap();
|
||||
|
||||
*hovered_guard = Some((cursor_position.y / option_height) as usize);
|
||||
|
|
@ -525,14 +526,13 @@ where
|
|||
if let Some(close_on_selected) = self.close_on_selected.as_ref() {
|
||||
shell.publish(close_on_selected.clone());
|
||||
}
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -568,8 +568,8 @@ where
|
|||
let text_size = self
|
||||
.text_size
|
||||
.unwrap_or_else(|| text::Renderer::default_size(renderer).0);
|
||||
let option_height = f32::from(self.text_line_height.to_absolute(Pixels(text_size)))
|
||||
+ self.padding.vertical();
|
||||
let option_height =
|
||||
f32::from(self.text_line_height.to_absolute(Pixels(text_size))) + self.padding.y();
|
||||
|
||||
let offset = viewport.y - bounds.y;
|
||||
let start = (offset / option_height) as usize;
|
||||
|
|
@ -605,6 +605,7 @@ where
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
appearance.selected_background,
|
||||
);
|
||||
|
|
@ -614,16 +615,13 @@ where
|
|||
.color(appearance.selected_text_color)
|
||||
.border_radius(appearance.border_radius);
|
||||
|
||||
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,
|
||||
},
|
||||
);
|
||||
let 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,
|
||||
};
|
||||
svg::Renderer::draw_svg(renderer, svg_handle, bounds, bounds);
|
||||
|
||||
(appearance.selected_text_color, crate::font::semibold())
|
||||
} else if *hovered_guard == Some(i) {
|
||||
|
|
@ -642,6 +640,7 @@ where
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
appearance.hovered_background,
|
||||
);
|
||||
|
|
@ -678,8 +677,8 @@ where
|
|||
size: Pixels(text_size),
|
||||
line_height: self.text_line_height,
|
||||
font,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
align_x: text::Alignment::Left,
|
||||
align_y: alignment::Vertical::Center,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::default(),
|
||||
ellipsize: text::Ellipsize::default(),
|
||||
|
|
|
|||
|
|
@ -56,12 +56,12 @@ pub fn popup_dropdown<
|
|||
dropdown
|
||||
}
|
||||
|
||||
/// Produces a [`Task`] that closes the [`Dropdown`].
|
||||
pub fn close<Message: 'static>(id: Id) -> iced_runtime::Task<Message> {
|
||||
iced_runtime::task::effect(iced_runtime::Action::Widget(Box::new(operation::close(id))))
|
||||
}
|
||||
// /// Produces a [`Task`] that closes the [`Dropdown`].
|
||||
// pub fn close<Message: 'static>(id: Id) -> iced_runtime::Task<Message> {
|
||||
// iced_runtime::task::effect(iced_runtime::Action::Widget(Box::new(operation::close(id))))
|
||||
// }
|
||||
|
||||
/// Produces a [`Task`] that opens the [`Dropdown`].
|
||||
pub fn open<Message: 'static>(id: Id) -> iced_runtime::Task<Message> {
|
||||
iced_runtime::task::effect(iced_runtime::Action::Widget(Box::new(operation::open(id))))
|
||||
}
|
||||
// /// Produces a [`Task`] that opens the [`Dropdown`].
|
||||
// pub fn open<Message: 'static>(id: Id) -> iced_runtime::Task<Message> {
|
||||
// iced_runtime::task::effect(iced_runtime::Action::Widget(Box::new(operation::open(id))))
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -209,18 +209,18 @@ impl<Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer> for Ove
|
|||
})
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
self.container.on_event(
|
||||
self.container.update(
|
||||
self.state, event, layout, cursor, renderer, clipboard, shell, &bounds,
|
||||
)
|
||||
}
|
||||
|
|
@ -229,11 +229,10 @@ impl<Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer> for Ove
|
|||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &crate::Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.container
|
||||
.mouse_interaction(self.state, layout, cursor, viewport, renderer)
|
||||
.mouse_interaction(self.state, layout, cursor, &layout.bounds(), renderer)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
|
|
@ -256,6 +255,7 @@ impl<Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer> for Ove
|
|||
radius: appearance.border_radius,
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
appearance.background,
|
||||
);
|
||||
|
|
@ -287,7 +287,7 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
_tree: &mut Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -309,7 +309,7 @@ where
|
|||
)
|
||||
});
|
||||
|
||||
let vertical_padding = self.padding.vertical();
|
||||
let vertical_padding = self.padding.y();
|
||||
let text_line_height = f32::from(text_line_height);
|
||||
|
||||
let size = {
|
||||
|
|
@ -328,17 +328,17 @@ where
|
|||
layout::Node::new(size)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
_state: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
match event {
|
||||
|
|
@ -346,7 +346,8 @@ where
|
|||
if cursor.is_over(bounds) {
|
||||
if let Some(item) = self.hovered_option.as_ref() {
|
||||
shell.publish((self.on_selected)(item.clone()));
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -361,7 +362,7 @@ where
|
|||
|
||||
let heights = self
|
||||
.options
|
||||
.element_heights(self.padding.vertical(), text_line_height);
|
||||
.element_heights(self.padding.y(), text_line_height);
|
||||
|
||||
let mut current_offset = 0.0;
|
||||
|
||||
|
|
@ -408,7 +409,7 @@ where
|
|||
|
||||
let heights = self
|
||||
.options
|
||||
.element_heights(self.padding.vertical(), text_line_height);
|
||||
.element_heights(self.padding.y(), text_line_height);
|
||||
|
||||
let mut current_offset = 0.0;
|
||||
|
||||
|
|
@ -446,8 +447,6 @@ where
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -490,7 +489,7 @@ where
|
|||
let text_line_height = f32::from(self.text_line_height.to_absolute(Pixels(text_size)));
|
||||
|
||||
let visible_options = self.options.visible_options(
|
||||
self.padding.vertical(),
|
||||
self.padding.y(),
|
||||
text_line_height,
|
||||
offset,
|
||||
viewport.height,
|
||||
|
|
@ -528,24 +527,23 @@ where
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
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 =
|
||||
svg::Svg::new(crate::widget::common::object_select().clone())
|
||||
.color(appearance.selected_text_color)
|
||||
.border_radius(appearance.border_radius);
|
||||
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,
|
||||
},
|
||||
);
|
||||
svg::Renderer::draw_svg(renderer, svg_handle, svg_bounds, svg_bounds);
|
||||
|
||||
(appearance.selected_text_color, crate::font::semibold())
|
||||
} else if self.hovered_option.as_ref() == Some(item) {
|
||||
|
|
@ -566,6 +564,7 @@ where
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
appearance.hovered_background,
|
||||
);
|
||||
|
|
@ -590,8 +589,8 @@ where
|
|||
size: iced::Pixels(text_size),
|
||||
line_height: self.text_line_height,
|
||||
font,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
align_x: text::Alignment::Left,
|
||||
align_y: alignment::Vertical::Center,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::default(),
|
||||
ellipsize: text::Ellipsize::default(),
|
||||
|
|
@ -611,7 +610,7 @@ where
|
|||
})
|
||||
.move_to(Point {
|
||||
x: bounds.x,
|
||||
y: bounds.y + (self.padding.vertical() / 2.0) - 4.0,
|
||||
y: bounds.y + (self.padding.y() / 2.0) - 4.0,
|
||||
});
|
||||
|
||||
Widget::<Message, crate::Theme, crate::Renderer>::draw(
|
||||
|
|
@ -640,8 +639,8 @@ where
|
|||
size: iced::Pixels(text_size),
|
||||
line_height: text::LineHeight::Absolute(Pixels(text_line_height + 4.0)),
|
||||
font: crate::font::default(),
|
||||
horizontal_alignment: alignment::Horizontal::Center,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
align_x: text::Alignment::Center,
|
||||
align_y: alignment::Vertical::Center,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::default(),
|
||||
ellipsize: text::Ellipsize::default(),
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ impl<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -116,17 +116,17 @@ impl<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>
|
|||
)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
_renderer: &crate::Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
) {
|
||||
update(
|
||||
&event,
|
||||
layout,
|
||||
|
|
@ -135,7 +135,7 @@ impl<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>
|
|||
self.on_selected.as_ref(),
|
||||
self.selections,
|
||||
|| tree.state.downcast_mut::<State<Item>>(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -183,8 +183,9 @@ impl<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &crate::Renderer,
|
||||
_viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
let state = tree.state.downcast_mut::<State<Item>>();
|
||||
|
|
@ -275,8 +276,8 @@ pub fn layout(
|
|||
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,
|
||||
align_x: text::Alignment::Left,
|
||||
align_y: alignment::Vertical::Top,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::default(),
|
||||
ellipsize: text::Ellipsize::default(),
|
||||
|
|
@ -314,7 +315,7 @@ pub fn update<'a, S: AsRef<str>, Message, Item: Clone + PartialEq + 'static + 'a
|
|||
on_selected: &dyn Fn(Item) -> Message,
|
||||
selections: &super::Model<S, Item>,
|
||||
state: impl FnOnce() -> &'a mut State<Item>,
|
||||
) -> event::Status {
|
||||
) {
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||
|
|
@ -325,14 +326,12 @@ 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.
|
||||
state.is_open = false;
|
||||
|
||||
event::Status::Captured
|
||||
shell.capture_event();
|
||||
} else if cursor.is_over(layout.bounds()) {
|
||||
state.is_open = true;
|
||||
state.hovered_option = selections.selected.clone();
|
||||
|
||||
event::Status::Captured
|
||||
} else {
|
||||
event::Status::Ignored
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::WheelScrolled {
|
||||
|
|
@ -348,19 +347,15 @@ pub fn update<'a, S: AsRef<str>, Message, Item: Clone + PartialEq + 'static + 'a
|
|||
shell.publish((on_selected)(option.1.clone()));
|
||||
}
|
||||
|
||||
event::Status::Captured
|
||||
} else {
|
||||
event::Status::Ignored
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
|
||||
let state = state();
|
||||
|
||||
state.keyboard_modifiers = *modifiers;
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
_ => event::Status::Ignored,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -420,8 +415,8 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static
|
|||
size: iced::Pixels(text_size),
|
||||
line_height,
|
||||
font: font.unwrap_or_else(crate::font::default),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
align_x: text::Alignment::Left,
|
||||
align_y: alignment::Vertical::Top,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::default(),
|
||||
ellipsize: text::Ellipsize::default(),
|
||||
|
|
@ -430,7 +425,7 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static
|
|||
};
|
||||
|
||||
let mut desc_count = 0;
|
||||
padding.horizontal().mul_add(
|
||||
padding.x().mul_add(
|
||||
2.0,
|
||||
selections
|
||||
.elements()
|
||||
|
|
@ -517,22 +512,20 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>(
|
|||
bounds,
|
||||
border: style.border,
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
style.background,
|
||||
);
|
||||
|
||||
if let Some(handle) = state.icon.as_ref() {
|
||||
let svg_handle = iced_core::Svg::new(handle.clone()).color(style.text_color);
|
||||
svg::Renderer::draw_svg(
|
||||
renderer,
|
||||
svg_handle,
|
||||
Rectangle {
|
||||
x: bounds.x + bounds.width - gap - 16.0,
|
||||
y: bounds.center_y() - 8.0,
|
||||
width: 16.0,
|
||||
height: 16.0,
|
||||
},
|
||||
);
|
||||
let svg_bounds = Rectangle {
|
||||
x: bounds.x + bounds.width - gap - 16.0,
|
||||
y: bounds.center_y() - 8.0,
|
||||
width: 16.0,
|
||||
height: 16.0,
|
||||
};
|
||||
svg::Renderer::draw_svg(renderer, svg_handle, svg_bounds, svg_bounds);
|
||||
}
|
||||
|
||||
if let Some(content) = selected.map(AsRef::as_ref) {
|
||||
|
|
@ -541,7 +534,7 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>(
|
|||
let bounds = Rectangle {
|
||||
x: bounds.x + padding.left,
|
||||
y: bounds.center_y(),
|
||||
width: bounds.width - padding.horizontal(),
|
||||
width: bounds.width - padding.x(),
|
||||
height: f32::from(text_line_height.to_absolute(Pixels(text_size))),
|
||||
};
|
||||
|
||||
|
|
@ -553,8 +546,8 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>(
|
|||
line_height: text_line_height,
|
||||
font,
|
||||
bounds: bounds.size(),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
align_x: text::Alignment::Left,
|
||||
align_y: alignment::Vertical::Center,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::default(),
|
||||
ellipsize: text::Ellipsize::default(),
|
||||
|
|
|
|||
|
|
@ -11,62 +11,62 @@ pub trait Dropdown {
|
|||
fn open(&mut self);
|
||||
}
|
||||
|
||||
/// Produces a [`Task`] that closes a [`Dropdown`] popup.
|
||||
pub fn close<T>(id: Id) -> impl Operation<T> {
|
||||
struct Close(Id);
|
||||
// /// Produces a [`Task`] that closes a [`Dropdown`] popup.
|
||||
// pub fn close<T>(id: Id) -> impl Operation<T> {
|
||||
// struct Close(Id);
|
||||
|
||||
impl<T> Operation<T> for Close {
|
||||
fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) {
|
||||
if id.map_or(true, |id| id != &self.0) {
|
||||
return;
|
||||
}
|
||||
// impl<T> Operation<T> for Close {
|
||||
// fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) {
|
||||
// if id.map_or(true, |id| id != &self.0) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
let Some(state) = state.downcast_mut::<State>() else {
|
||||
return;
|
||||
};
|
||||
// let Some(state) = state.downcast_mut::<State>() else {
|
||||
// return;
|
||||
// };
|
||||
|
||||
state.close();
|
||||
}
|
||||
// state.close();
|
||||
// }
|
||||
|
||||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
_bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
}
|
||||
}
|
||||
// fn container(
|
||||
// &mut self,
|
||||
// _id: Option<&Id>,
|
||||
// _bounds: Rectangle,
|
||||
// operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
// ) {
|
||||
// operate_on_children(self)
|
||||
// }
|
||||
// }
|
||||
|
||||
Close(id)
|
||||
}
|
||||
// Close(id)
|
||||
// }
|
||||
|
||||
/// Produces a [`Task`] that opens a [`Dropdown`] popup.
|
||||
pub fn open<T>(id: Id) -> impl Operation<T> {
|
||||
struct Open(Id);
|
||||
// /// Produces a [`Task`] that opens a [`Dropdown`] popup.
|
||||
// pub fn open<T>(id: Id) -> impl Operation<T> {
|
||||
// struct Open(Id);
|
||||
|
||||
impl<T> Operation<T> for Open {
|
||||
fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) {
|
||||
if id.map_or(true, |id| id != &self.0) {
|
||||
return;
|
||||
}
|
||||
// impl<T> Operation<T> for Open {
|
||||
// fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) {
|
||||
// if id.map_or(true, |id| id != &self.0) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
let Some(state) = state.downcast_mut::<State>() else {
|
||||
return;
|
||||
};
|
||||
// let Some(state) = state.downcast_mut::<State>() else {
|
||||
// return;
|
||||
// };
|
||||
|
||||
state.open();
|
||||
}
|
||||
// state.open();
|
||||
// }
|
||||
|
||||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
_bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
}
|
||||
}
|
||||
// fn container(
|
||||
// &mut self,
|
||||
// _id: Option<&Id>,
|
||||
// _bounds: Rectangle,
|
||||
// operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
// ) {
|
||||
// operate_on_children(self)
|
||||
// }
|
||||
// }
|
||||
|
||||
Open(id)
|
||||
}
|
||||
// Open(id)
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -203,13 +203,13 @@ where
|
|||
state.hashes[i] = text_hash;
|
||||
state.selections[i].update(Text {
|
||||
content: selection.as_ref(),
|
||||
bounds: Size::INFINITY,
|
||||
bounds: Size::INFINITE,
|
||||
// TODO use the renderer default size
|
||||
size: iced::Pixels(self.text_size.unwrap_or(14.0)),
|
||||
line_height: self.text_line_height,
|
||||
font: self.font.unwrap_or_else(crate::font::default),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
align_x: text::Alignment::Left,
|
||||
align_y: alignment::Vertical::Top,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::default(),
|
||||
ellipsize: text::Ellipsize::default(),
|
||||
|
|
@ -227,7 +227,7 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -252,17 +252,17 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
_renderer: &crate::Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
) {
|
||||
update::<S, Message, AppMessage>(
|
||||
&event,
|
||||
layout,
|
||||
|
|
@ -327,21 +327,23 @@ where
|
|||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
_layout: Layout<'_>,
|
||||
_renderer: &crate::Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation,
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
operation.custom(state, self.id.as_ref());
|
||||
// TODO: double check operation handling
|
||||
// let state = tree.state.downcast_mut::<State>();
|
||||
// operation.custom(state, self.id.as_ref());
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &crate::Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
#[cfg(all(feature = "winit", feature = "wayland"))]
|
||||
|
|
@ -469,24 +471,38 @@ pub fn layout(
|
|||
let max_width = match width {
|
||||
Length::Shrink => {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
None => &mut crate::Plain::new(text),
|
||||
None => {
|
||||
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()
|
||||
};
|
||||
|
|
@ -544,7 +560,7 @@ pub fn update<
|
|||
text_size: Option<f32>,
|
||||
font: Option<crate::font::Font>,
|
||||
selected_option: Option<usize>,
|
||||
) -> event::Status {
|
||||
) {
|
||||
let state = state();
|
||||
|
||||
let open = |shell: &mut Shell<'_, Message>,
|
||||
|
|
@ -575,7 +591,7 @@ pub fn update<
|
|||
let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 {
|
||||
selection_paragraph.min_width().round()
|
||||
};
|
||||
let pad_width = padding.horizontal().mul_add(2.0, 16.0);
|
||||
let pad_width = padding.x().mul_add(2.0, 16.0);
|
||||
|
||||
let selections_width = selections
|
||||
.iter()
|
||||
|
|
@ -669,12 +685,10 @@ pub fn update<
|
|||
if let Some(on_close) = on_surface_action {
|
||||
shell.publish(on_close(surface::action::destroy_popup(state.popup_id)));
|
||||
}
|
||||
event::Status::Captured
|
||||
shell.capture_event();
|
||||
} else if cursor.is_over(layout.bounds()) {
|
||||
open(shell, state, on_selected);
|
||||
event::Status::Captured
|
||||
} else {
|
||||
event::Status::Ignored
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::WheelScrolled {
|
||||
|
|
@ -689,17 +703,13 @@ pub fn update<
|
|||
shell.publish((on_selected)(next_index));
|
||||
}
|
||||
|
||||
event::Status::Captured
|
||||
} else {
|
||||
event::Status::Ignored
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
|
||||
state.keyboard_modifiers = *modifiers;
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
_ => event::Status::Ignored,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -746,7 +756,7 @@ where
|
|||
.zip(state.selections.iter())
|
||||
.map(|(label, selection)| measure(label.as_ref(), selection.raw()))
|
||||
.fold(0.0, |next, current| current.max(next));
|
||||
let pad_width = padding.horizontal().mul_add(2.0, 16.0);
|
||||
let pad_width = padding.x().mul_add(2.0, 16.0);
|
||||
|
||||
let width = selections_width + gap + pad_width + icon_width;
|
||||
let is_open = state.is_open.clone();
|
||||
|
|
@ -822,7 +832,7 @@ where
|
|||
selection_paragraph.min_width().round()
|
||||
};
|
||||
|
||||
let pad_width = padding.horizontal().mul_add(2.0, 16.0);
|
||||
let pad_width = padding.x().mul_add(2.0, 16.0);
|
||||
|
||||
let icon_width = if icons.is_empty() { 0.0 } else { 24.0 };
|
||||
|
||||
|
|
@ -883,23 +893,20 @@ pub fn draw<'a, S>(
|
|||
bounds,
|
||||
border: style.border,
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
style.background,
|
||||
);
|
||||
|
||||
if let Some(handle) = state.icon.clone() {
|
||||
let svg_handle = svg::Svg::new(handle).color(style.text_color);
|
||||
|
||||
svg::Renderer::draw_svg(
|
||||
renderer,
|
||||
svg_handle,
|
||||
Rectangle {
|
||||
x: bounds.x + bounds.width - gap - 16.0,
|
||||
y: bounds.center_y() - 8.0,
|
||||
width: 16.0,
|
||||
height: 16.0,
|
||||
},
|
||||
);
|
||||
let bounds = Rectangle {
|
||||
x: bounds.x + bounds.width - gap - 16.0,
|
||||
y: bounds.center_y() - 8.0,
|
||||
width: 16.0,
|
||||
height: 16.0,
|
||||
};
|
||||
svg::Renderer::draw_svg(renderer, svg_handle, bounds, bounds);
|
||||
}
|
||||
|
||||
if let Some(content) = selected.map(AsRef::as_ref).or(placeholder) {
|
||||
|
|
@ -908,7 +915,7 @@ pub fn draw<'a, S>(
|
|||
let mut bounds = Rectangle {
|
||||
x: bounds.x + padding.left,
|
||||
y: bounds.center_y(),
|
||||
width: bounds.width - padding.horizontal(),
|
||||
width: bounds.width - padding.x(),
|
||||
height: f32::from(text_line_height.to_absolute(Pixels(text_size))),
|
||||
};
|
||||
|
||||
|
|
@ -932,8 +939,8 @@ pub fn draw<'a, S>(
|
|||
line_height: text_line_height,
|
||||
font,
|
||||
bounds: bounds.size(),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
align_x: text::Alignment::Left,
|
||||
align_y: alignment::Vertical::Center,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::default(),
|
||||
ellipsize: text::Ellipsize::default(),
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use taffy::{AlignContent, TaffyTree};
|
|||
pub fn resolve<Message>(
|
||||
renderer: &Renderer,
|
||||
limits: &Limits,
|
||||
items: &[Element<'_, Message>],
|
||||
items: &mut [Element<'_, Message>],
|
||||
padding: Padding,
|
||||
column_spacing: f32,
|
||||
row_spacing: f32,
|
||||
|
|
@ -61,8 +61,8 @@ pub fn resolve<Message>(
|
|||
..taffy::Style::default()
|
||||
};
|
||||
|
||||
for (child, tree) in items.iter().zip(tree.iter_mut()) {
|
||||
let child_widget = child.as_widget();
|
||||
for (child, tree) in items.iter_mut().zip(tree.iter_mut()) {
|
||||
let child_widget = child.as_widget_mut();
|
||||
let child_node = child_widget.layout(tree, renderer, limits);
|
||||
let size = child_node.size();
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ pub fn resolve<Message>(
|
|||
|
||||
leafs
|
||||
.into_iter()
|
||||
.zip(items.iter())
|
||||
.zip(items.iter_mut())
|
||||
.zip(nodes.iter_mut())
|
||||
.zip(tree)
|
||||
.for_each(|(((leaf, child), node), tree)| {
|
||||
|
|
@ -146,7 +146,7 @@ pub fn resolve<Message>(
|
|||
return;
|
||||
};
|
||||
|
||||
let child_widget = child.as_widget();
|
||||
let child_widget = child.as_widget_mut();
|
||||
let c_size = child_widget.size();
|
||||
match c_size.width {
|
||||
Length::Fill | Length::FillPortion(_) => {
|
||||
|
|
@ -162,9 +162,14 @@ pub fn resolve<Message>(
|
|||
});
|
||||
});
|
||||
|
||||
let actual_height = nodes
|
||||
.iter()
|
||||
.map(|node| node.bounds().y + node.bounds().height)
|
||||
.fold(0.0f32, f32::max);
|
||||
|
||||
let size = Size {
|
||||
width: flex_layout.content_size.width,
|
||||
height: flex_layout.content_size.height,
|
||||
height: actual_height.max(flex_layout.content_size.height),
|
||||
};
|
||||
|
||||
Node::with_children(size, nodes)
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for FlexR
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -114,32 +114,32 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for FlexR
|
|||
super::layout::resolve(
|
||||
renderer,
|
||||
&limits,
|
||||
&self.children,
|
||||
&mut self.children,
|
||||
self.padding,
|
||||
f32::from(self.column_spacing),
|
||||
f32::from(self.row_spacing),
|
||||
self.min_item_width,
|
||||
self.align_items,
|
||||
self.justify_items,
|
||||
self.align_items,
|
||||
self.justify_content,
|
||||
&mut tree.children,
|
||||
)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
operation.container(None, layout.bounds(), &mut |operation| {
|
||||
operation.traverse(&mut |operation| {
|
||||
self.children
|
||||
.iter()
|
||||
.iter_mut()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.for_each(|((child, state), c_layout)| {
|
||||
child.as_widget().operate(
|
||||
child.as_widget_mut().operate(
|
||||
state,
|
||||
c_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
renderer,
|
||||
|
|
@ -149,34 +149,34 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for FlexR
|
|||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
self.children
|
||||
) {
|
||||
for ((child, state), c_layout) in self
|
||||
.children
|
||||
.iter_mut()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.map(|((child, state), c_layout)| {
|
||||
child.as_widget_mut().on_event(
|
||||
state,
|
||||
event.clone(),
|
||||
c_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
)
|
||||
})
|
||||
.fold(event::Status::Ignored, event::Status::merge)
|
||||
{
|
||||
child.as_widget_mut().update(
|
||||
state,
|
||||
event,
|
||||
c_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -235,11 +235,19 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for FlexR
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||
overlay::from_children(&mut self.children, tree, layout, renderer, translation)
|
||||
overlay::from_children(
|
||||
&mut self.children,
|
||||
tree,
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "a11y")]
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ use std::path::Path;
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
use ::image as image_rs;
|
||||
use iced::Task;
|
||||
use iced::mouse;
|
||||
use iced_core::image::Renderer as ImageRenderer;
|
||||
use iced_core::mouse::Cursor;
|
||||
use iced_core::widget::{Tree, tree};
|
||||
|
|
@ -15,7 +17,6 @@ use iced_core::{
|
|||
Clipboard, ContentFit, Element, Event, Layout, Length, Rectangle, Shell, Size, Vector, Widget,
|
||||
event, layout, renderer, window,
|
||||
};
|
||||
use iced_runtime::Command;
|
||||
use iced_widget::image::{self, Handle};
|
||||
use image_rs::AnimationDecoder;
|
||||
use image_rs::codecs::gif::GifDecoder;
|
||||
|
|
@ -27,7 +28,7 @@ use iced_futures::futures::{AsyncRead, AsyncReadExt};
|
|||
#[cfg(feature = "tokio")]
|
||||
use tokio::io::{AsyncRead, AsyncReadExt};
|
||||
|
||||
use super::icon::load_icon;
|
||||
use crate::widget::icon;
|
||||
|
||||
#[must_use]
|
||||
/// Creates a new [`AnimatedImage`] with the given [`animated_image::Frames`]
|
||||
|
|
@ -74,13 +75,13 @@ impl Frames {
|
|||
size: u16,
|
||||
theme: Option<&str>,
|
||||
default_fallbacks: bool,
|
||||
) -> Command<Result<Frames, Error>> {
|
||||
) -> Task<Result<Frames, Error>> {
|
||||
let mut name_path_buffer = None;
|
||||
if let Some(path) = load_icon(name, size, theme) {
|
||||
if let Some(path) = icon::Named::new(name).size(size).path() {
|
||||
name_path_buffer = Some(path);
|
||||
} else if default_fallbacks {
|
||||
for name in name.rmatch_indices('-').map(|(pos, _)| &name[..pos]) {
|
||||
if let Some(path) = load_icon(name, size, theme) {
|
||||
if let Some(path) = icon::Named::new(name).size(size).path() {
|
||||
name_path_buffer = Some(path);
|
||||
break;
|
||||
}
|
||||
|
|
@ -90,14 +91,14 @@ impl Frames {
|
|||
if let Some(name_path_buffer) = name_path_buffer {
|
||||
Self::load_from_path(name_path_buffer)
|
||||
} else {
|
||||
Command::perform(async { Err(Error::Missing) }, std::convert::identity)
|
||||
Task::perform(async { Err(Error::Missing) }, std::convert::identity)
|
||||
}
|
||||
}
|
||||
|
||||
/// Load [`Frames`] from the supplied path
|
||||
pub fn load_from_path(path: impl AsRef<Path>) -> Command<Result<Frames, Error>> {
|
||||
pub fn load_from_path(path: impl AsRef<Path>) -> Task<Result<Frames, Error>> {
|
||||
#[inline(never)]
|
||||
fn inner(path: &Path) -> Command<Result<Frames, Error>> {
|
||||
fn inner(path: &Path) -> Task<Result<Frames, Error>> {
|
||||
#[cfg(feature = "tokio")]
|
||||
use tokio::fs::File;
|
||||
#[cfg(feature = "tokio")]
|
||||
|
|
@ -108,7 +109,7 @@ impl Frames {
|
|||
#[cfg(not(feature = "tokio"))]
|
||||
use iced_futures::futures::io::BufReader;
|
||||
|
||||
let path = path.as_ref().to_path_buf();
|
||||
let path = path.to_path_buf();
|
||||
|
||||
let f = async move {
|
||||
let image_type = match &path.extension() {
|
||||
|
|
@ -119,10 +120,10 @@ impl Frames {
|
|||
};
|
||||
let reader = BufReader::new(File::open(path).await?);
|
||||
|
||||
Self::from_reader(reader, image_type).await
|
||||
Frames::from_reader(reader, image_type).await
|
||||
};
|
||||
|
||||
Command::perform(f, std::convert::identity)
|
||||
Task::perform(f, std::convert::identity)
|
||||
}
|
||||
|
||||
inner(path.as_ref())
|
||||
|
|
@ -168,9 +169,9 @@ impl Frames {
|
|||
let total_bytes = frames
|
||||
.iter()
|
||||
.map(|f| match f.handle.data() {
|
||||
iced_core::image::Data::Path(_) => 0,
|
||||
iced_core::image::Data::Bytes(b) => b.len(),
|
||||
iced_core::image::Data::Rgba { pixels, .. } => pixels.len(),
|
||||
iced_core::image::Handle::Path(..) => 0,
|
||||
iced_core::image::Handle::Bytes(_, b) => b.len(),
|
||||
iced_core::image::Handle::Rgba { pixels, .. } => pixels.len(),
|
||||
})
|
||||
.sum::<usize>()
|
||||
.try_into()
|
||||
|
|
@ -195,7 +196,7 @@ impl From<image_rs::Frame> for Frame {
|
|||
|
||||
let delay = frame.delay().into();
|
||||
|
||||
let handle = image::Handle::from_pixels(width, height, frame.into_buffer().into_vec());
|
||||
let handle = image::Handle::from_rgba(width, height, frame.into_buffer().into_vec());
|
||||
|
||||
Self { delay, handle }
|
||||
}
|
||||
|
|
@ -278,12 +279,8 @@ impl<'a, Message, Renderer> Widget<Message, crate::Theme, Renderer> for Animated
|
|||
where
|
||||
Renderer: ImageRenderer<Handle = Handle>,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size::new(self.width.into(), self.height.into())
|
||||
}
|
||||
|
||||
fn tag(&self) -> tree::Tag {
|
||||
|
|
@ -315,7 +312,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
|
||||
fn layout(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
iced_widget::image::layout(
|
||||
renderer,
|
||||
limits,
|
||||
|
|
@ -326,19 +328,20 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
_layout: Layout<'_>,
|
||||
_cursor_position: Cursor,
|
||||
_renderer: &Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
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);
|
||||
|
||||
if elapsed > state.current.frame.delay {
|
||||
|
|
@ -346,15 +349,14 @@ where
|
|||
|
||||
state.current = self.frames.frames[state.index].clone().into();
|
||||
|
||||
shell.request_redraw(window::RedrawRequest::At(now + state.current.frame.delay));
|
||||
shell
|
||||
.request_redraw_at(window::RedrawRequest::At(*now + state.current.frame.delay));
|
||||
} else {
|
||||
let remaining = state.current.frame.delay - elapsed;
|
||||
|
||||
shell.request_redraw(window::RedrawRequest::At(now + remaining));
|
||||
shell.request_redraw_at(window::RedrawRequest::At(*now + remaining));
|
||||
}
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn draw(
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use taffy::{AlignContent, TaffyTree};
|
|||
pub fn resolve<Message>(
|
||||
renderer: &Renderer,
|
||||
limits: &Limits,
|
||||
items: &[Element<'_, Message>],
|
||||
items: &mut [Element<'_, Message>],
|
||||
assignments: &[Assignment],
|
||||
width: Length,
|
||||
height: Length,
|
||||
|
|
@ -37,9 +37,13 @@ pub fn resolve<Message>(
|
|||
let mut taffy = TaffyTree::<()>::with_capacity(items.len() + 1);
|
||||
|
||||
// Attach widgets as child nodes.
|
||||
for ((child, assignment), tree) in items.iter().zip(assignments.iter()).zip(tree.iter_mut()) {
|
||||
for ((child, assignment), tree) in items
|
||||
.iter_mut()
|
||||
.zip(assignments.iter())
|
||||
.zip(tree.iter_mut())
|
||||
{
|
||||
// Calculate the dimensions of the item.
|
||||
let child_widget = child.as_widget();
|
||||
let child_widget = child.as_widget_mut();
|
||||
let child_node = child_widget.layout(tree, renderer, limits);
|
||||
let size = child_node.size();
|
||||
|
||||
|
|
@ -172,12 +176,12 @@ pub fn resolve<Message>(
|
|||
|
||||
for (((leaf, child), node), tree) in leafs
|
||||
.into_iter()
|
||||
.zip(items.iter())
|
||||
.zip(items.iter_mut())
|
||||
.zip(nodes.iter_mut())
|
||||
.zip(tree)
|
||||
{
|
||||
if let Ok(leaf_layout) = taffy.layout(leaf) {
|
||||
let child_widget = child.as_widget();
|
||||
let child_widget = child.as_widget_mut();
|
||||
let c_size = child_widget.size();
|
||||
match c_size.width {
|
||||
Length::Fill | Length::FillPortion(_) => {
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for Grid<
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -141,7 +141,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for Grid<
|
|||
super::layout::resolve(
|
||||
renderer,
|
||||
&limits,
|
||||
&self.children,
|
||||
&mut self.children,
|
||||
&self.assignments,
|
||||
self.width,
|
||||
self.height,
|
||||
|
|
@ -156,19 +156,19 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for Grid<
|
|||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
operation.container(None, layout.bounds(), &mut |operation| {
|
||||
operation.traverse(&mut |operation| {
|
||||
self.children
|
||||
.iter()
|
||||
.iter_mut()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.for_each(|((child, state), c_layout)| {
|
||||
child.as_widget().operate(
|
||||
child.as_widget_mut().operate(
|
||||
state,
|
||||
c_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
renderer,
|
||||
|
|
@ -178,34 +178,34 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for Grid<
|
|||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
self.children
|
||||
) {
|
||||
for ((child, state), c_layout) in self
|
||||
.children
|
||||
.iter_mut()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.map(|((child, state), c_layout)| {
|
||||
child.as_widget_mut().on_event(
|
||||
state,
|
||||
event.clone(),
|
||||
c_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
)
|
||||
})
|
||||
.fold(event::Status::Ignored, event::Status::merge)
|
||||
{
|
||||
child.as_widget_mut().update(
|
||||
state,
|
||||
event,
|
||||
c_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -264,11 +264,19 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for Grid<
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||
overlay::from_children(&mut self.children, tree, layout, renderer, translation)
|
||||
overlay::from_children(
|
||||
&mut self.children,
|
||||
tree,
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "a11y")]
|
||||
|
|
|
|||
|
|
@ -5,9 +5,8 @@ use crate::cosmic_theme::{Density, Spacing};
|
|||
use crate::{Element, theme, widget};
|
||||
use apply::Apply;
|
||||
use derive_setters::Setters;
|
||||
use iced::Length;
|
||||
use iced_core::{Vector, Widget, widget::tree};
|
||||
use std::{borrow::Cow, cmp};
|
||||
use iced_core::{Length, Size, Vector, Widget, layout, text, widget::tree};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[must_use]
|
||||
pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> {
|
||||
|
|
@ -27,7 +26,6 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> {
|
|||
sharp_corners: false,
|
||||
is_ssd: false,
|
||||
on_double_click: None,
|
||||
is_condensed: false,
|
||||
transparent: false,
|
||||
}
|
||||
}
|
||||
|
|
@ -91,9 +89,6 @@ pub struct HeaderBar<'a, Message> {
|
|||
/// HeaderBar used for server-side decorations
|
||||
is_ssd: bool,
|
||||
|
||||
/// Whether the headerbar should be compact
|
||||
is_condensed: bool,
|
||||
|
||||
/// Whether the headerbar should be transparent
|
||||
transparent: bool,
|
||||
}
|
||||
|
|
@ -126,48 +121,116 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
|||
self.end.push(widget.into());
|
||||
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> {
|
||||
header_bar_inner: Element<'a, Message>,
|
||||
start: Element<'a, Message>,
|
||||
center: Option<Element<'a, Message>>,
|
||||
end: Element<'a, Message>,
|
||||
}
|
||||
|
||||
impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||
for HeaderBarWidget<'_, Message>
|
||||
impl<'a, Message> HeaderBarWidget<'a, Message> {
|
||||
pub fn new(
|
||||
start: Element<'a, Message>,
|
||||
center: Option<Element<'a, Message>>,
|
||||
end: Element<'a, Message>,
|
||||
) -> Self {
|
||||
Self { start, center, end }
|
||||
}
|
||||
|
||||
fn elems(&self) -> impl Iterator<Item = &Element<'a, Message>> {
|
||||
std::iter::once(&self.start)
|
||||
.chain(std::iter::once(&self.end))
|
||||
.chain(self.center.as_ref())
|
||||
}
|
||||
|
||||
fn elems_mut(&mut self) -> impl Iterator<Item = &mut Element<'a, Message>> {
|
||||
std::iter::once(&mut self.start)
|
||||
.chain(std::iter::once(&mut self.end))
|
||||
.chain(self.center.as_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||
for HeaderBarWidget<'a, Message>
|
||||
{
|
||||
fn diff(&mut self, tree: &mut tree::Tree) {
|
||||
tree.diff_children(&mut [&mut self.header_bar_inner]);
|
||||
if let Some(center) = &mut self.center {
|
||||
tree.diff_children(&mut [&mut self.start, &mut self.end, center]);
|
||||
} else {
|
||||
tree.diff_children(&mut [&mut self.start, &mut self.end]);
|
||||
}
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<tree::Tree> {
|
||||
vec![tree::Tree::new(&self.header_bar_inner)]
|
||||
self.elems().map(tree::Tree::new).collect()
|
||||
}
|
||||
|
||||
fn size(&self) -> iced_core::Size<Length> {
|
||||
self.header_bar_inner.as_widget().size()
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size {
|
||||
width: Length::Fill,
|
||||
height: Length::Shrink,
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut tree::Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &iced_core::layout::Limits,
|
||||
) -> iced_core::layout::Node {
|
||||
let child_tree = &mut tree.children[0];
|
||||
let child = self
|
||||
.header_bar_inner
|
||||
.as_widget()
|
||||
.layout(child_tree, renderer, limits);
|
||||
iced_core::layout::Node::with_children(child.size(), vec![child])
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let width = limits.max().width;
|
||||
let height = limits.max().height;
|
||||
let gap = 8.0;
|
||||
|
||||
let end_node =
|
||||
self.end
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[1], renderer, &limits.loose());
|
||||
let end_width = end_node.size().width;
|
||||
|
||||
let start_available = (width - end_width - gap).max(0.0);
|
||||
let start_node = self.start.as_widget_mut().layout(
|
||||
&mut tree.children[0],
|
||||
renderer,
|
||||
&layout::Limits::new(Size::ZERO, Size::new(start_available, height)),
|
||||
);
|
||||
let start_width = start_node.size().width;
|
||||
|
||||
let vcenter = |node: layout::Node, x: f32| -> layout::Node {
|
||||
let dy = ((height - node.size().height) / 2.0).max(0.0);
|
||||
node.translate(Vector::new(x, dy))
|
||||
};
|
||||
|
||||
let mut child_nodes = Vec::with_capacity(3);
|
||||
child_nodes.push(vcenter(start_node, 0.0));
|
||||
child_nodes.push(vcenter(end_node, width - end_width));
|
||||
|
||||
if let Some(center) = &mut self.center {
|
||||
let slot_start = start_width + gap;
|
||||
let slot_end = (width - end_width - gap).max(slot_start);
|
||||
let slot_width = slot_end - slot_start;
|
||||
// this instead of `node.size().width` prevents center jitter as text ellipsizes
|
||||
let natural_width = center
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[2], renderer, &limits.loose())
|
||||
.size()
|
||||
.width;
|
||||
|
||||
let node = center.as_widget_mut().layout(
|
||||
&mut tree.children[2],
|
||||
renderer,
|
||||
&layout::Limits::new(Size::ZERO, Size::new(slot_width, height)),
|
||||
);
|
||||
|
||||
let ideal_x = (width - natural_width) / 2.0;
|
||||
let max_x = (width - end_width - gap - natural_width).max(slot_start);
|
||||
let center_x = ideal_x.clamp(slot_start, max_x);
|
||||
|
||||
child_nodes.push(vcenter(node, center_x))
|
||||
}
|
||||
|
||||
layout::Node::with_children(Size::new(width, height), child_nodes)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
|
|
@ -180,42 +243,31 @@ impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
cursor: iced_core::mouse::Cursor,
|
||||
viewport: &iced_core::Rectangle,
|
||||
) {
|
||||
let layout_children = layout.children().next().unwrap();
|
||||
let state_children = &tree.children[0];
|
||||
self.header_bar_inner.as_widget().draw(
|
||||
state_children,
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout_children,
|
||||
cursor,
|
||||
viewport,
|
||||
);
|
||||
for ((e, s), l) in self.elems().zip(&tree.children).zip(layout.children()) {
|
||||
e.as_widget()
|
||||
.draw(s, renderer, theme, style, l, cursor, viewport);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
state: &mut tree::Tree,
|
||||
event: iced_core::Event,
|
||||
event: &iced_core::Event,
|
||||
layout: iced_core::Layout<'_>,
|
||||
cursor: iced_core::mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn iced_core::Clipboard,
|
||||
shell: &mut iced_core::Shell<'_, Message>,
|
||||
viewport: &iced_core::Rectangle,
|
||||
) -> iced_core::event::Status {
|
||||
let child_state = &mut state.children[0];
|
||||
let child_layout = layout.children().next().unwrap();
|
||||
self.header_bar_inner.as_widget_mut().on_event(
|
||||
child_state,
|
||||
event,
|
||||
child_layout,
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
)
|
||||
) {
|
||||
for ((e, s), l) in self
|
||||
.elems_mut()
|
||||
.zip(&mut state.children)
|
||||
.zip(layout.children())
|
||||
{
|
||||
e.as_widget_mut()
|
||||
.update(s, event, l, cursor, renderer, clipboard, shell, viewport);
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -226,46 +278,62 @@ impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
viewport: &iced_core::Rectangle,
|
||||
renderer: &crate::Renderer,
|
||||
) -> iced_core::mouse::Interaction {
|
||||
let child_tree = &state.children[0];
|
||||
let child_layout = layout.children().next().unwrap();
|
||||
self.header_bar_inner.as_widget().mouse_interaction(
|
||||
child_tree,
|
||||
child_layout,
|
||||
cursor,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
self.elems()
|
||||
.zip(&state.children)
|
||||
.zip(layout.children())
|
||||
.map(|((e, s), l)| {
|
||||
e.as_widget()
|
||||
.mouse_interaction(s, l, cursor, viewport, renderer)
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(iced_core::mouse::Interaction::None)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
state: &mut tree::Tree,
|
||||
layout: iced_core::Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
) {
|
||||
let child_tree = &mut state.children[0];
|
||||
let child_layout = layout.children().next().unwrap();
|
||||
self.header_bar_inner
|
||||
.as_widget()
|
||||
.operate(child_tree, child_layout, renderer, operation);
|
||||
for ((e, s), l) in self
|
||||
.elems_mut()
|
||||
.zip(&mut state.children)
|
||||
.zip(layout.children())
|
||||
{
|
||||
e.as_widget_mut().operate(s, l, renderer, operation);
|
||||
}
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
state: &'b mut tree::Tree,
|
||||
layout: iced_core::Layout<'_>,
|
||||
layout: iced_core::Layout<'b>,
|
||||
renderer: &crate::Renderer,
|
||||
viewport: &iced_core::Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
let child_tree = &mut state.children[0];
|
||||
let child_layout = layout.children().next().unwrap();
|
||||
self.header_bar_inner.as_widget_mut().overlay(
|
||||
child_tree,
|
||||
child_layout,
|
||||
renderer,
|
||||
translation,
|
||||
)
|
||||
let mut layouts = layout.children();
|
||||
let mut try_overlay = |elem: &'b mut Element<'a, Message>,
|
||||
state: &'b mut tree::Tree|
|
||||
-> Option<
|
||||
iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>,
|
||||
> {
|
||||
elem.as_widget_mut()
|
||||
.overlay(state, layouts.next()?, renderer, viewport, translation)
|
||||
};
|
||||
|
||||
if let Some(center) = &mut self.center {
|
||||
let (start_slice, end_center) = state.children.split_at_mut(1);
|
||||
let (end_slice, center_slice) = end_center.split_at_mut(1);
|
||||
try_overlay(&mut self.start, &mut start_slice[0])
|
||||
.or_else(|| try_overlay(&mut self.end, &mut end_slice[0]))
|
||||
.or_else(|| try_overlay(center, &mut center_slice[0]))
|
||||
} else {
|
||||
let (start_slice, end_slice) = state.children.split_at_mut(1);
|
||||
try_overlay(&mut self.start, &mut start_slice[0])
|
||||
.or_else(|| try_overlay(&mut self.end, &mut end_slice[0]))
|
||||
}
|
||||
}
|
||||
|
||||
fn drag_destinations(
|
||||
|
|
@ -275,15 +343,9 @@ impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
renderer: &crate::Renderer,
|
||||
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||
) {
|
||||
if let Some((child_tree, child_layout)) =
|
||||
state.children.iter().zip(layout.children()).next()
|
||||
{
|
||||
self.header_bar_inner.as_widget().drag_destinations(
|
||||
child_tree,
|
||||
child_layout,
|
||||
renderer,
|
||||
dnd_rectangles,
|
||||
);
|
||||
for ((e, s), l) in self.elems().zip(&state.children).zip(layout.children()) {
|
||||
e.as_widget()
|
||||
.drag_destinations(s, l, renderer, dnd_rectangles);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -295,16 +357,22 @@ impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
state: &tree::Tree,
|
||||
p: iced::mouse::Cursor,
|
||||
) -> iced_accessibility::A11yTree {
|
||||
let c_layout = layout.children().next().unwrap();
|
||||
let c_state = &state.children[0];
|
||||
self.header_bar_inner
|
||||
.as_widget()
|
||||
.a11y_nodes(c_layout, c_state, p)
|
||||
iced_accessibility::A11yTree::join(
|
||||
self.elems()
|
||||
.zip(&state.children)
|
||||
.zip(layout.children())
|
||||
.map(|((e, s), l)| e.as_widget().a11y_nodes(l, s, p)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: Clone + 'static> From<HeaderBarWidget<'a, Message>> for Element<'a, Message> {
|
||||
fn from(w: HeaderBarWidget<'a, Message>) -> Self {
|
||||
Element::new(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
/// Converts the headerbar builder into an Iced element.
|
||||
pub fn view(mut self) -> Element<'a, Message> {
|
||||
let Spacing {
|
||||
|
|
@ -318,153 +386,84 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
|||
let center = std::mem::take(&mut self.center);
|
||||
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.
|
||||
end.push(self.window_controls());
|
||||
end.push(self.window_controls(space_xxs));
|
||||
|
||||
// Center content depending on window border
|
||||
let padding = match self.density.unwrap_or_else(crate::config::header_size) {
|
||||
Density::Compact => {
|
||||
if self.maximized {
|
||||
[4, 8, 4, 8]
|
||||
} else {
|
||||
[3, 7, 4, 7]
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if self.maximized {
|
||||
[8, 8, 8, 8]
|
||||
} else {
|
||||
[7, 7, 8, 7]
|
||||
}
|
||||
let padding = if self.is_ssd {
|
||||
[2, 8, 2, 8]
|
||||
} else {
|
||||
match (
|
||||
self.density.unwrap_or_else(crate::config::header_size),
|
||||
self.maximized, // window border handling
|
||||
) {
|
||||
(Density::Compact, true) => [4, 8, 4, 8],
|
||||
(Density::Compact, false) => [3, 7, 4, 7],
|
||||
(_, true) => [8, 8, 8, 8],
|
||||
(_, false) => [7, 7, 8, 7],
|
||||
}
|
||||
};
|
||||
|
||||
let acc_count = |v: &[Element<'a, Message>]| {
|
||||
v.iter().fold(0, |acc, e| {
|
||||
acc + match e.as_widget().size().width {
|
||||
Length::Fixed(w) if w > 30. => (w / 30.0).ceil() as usize,
|
||||
_ => 1,
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
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)
|
||||
let start = widget::row::with_children(start)
|
||||
.spacing(space_xxxs)
|
||||
.align_y(iced::Alignment::Center)
|
||||
.into();
|
||||
let center = if !center.is_empty() {
|
||||
Some(
|
||||
widget::row::with_children(center)
|
||||
.spacing(space_xxxs)
|
||||
.align_y(iced::Alignment::Center)
|
||||
.apply(widget::container)
|
||||
.align_x(iced::Alignment::Start)
|
||||
.width(Length::FillPortion(left_portion)),
|
||||
.into(),
|
||||
)
|
||||
// If elements exist in the center region, use them here.
|
||||
// This will otherwise use the title as a widget if a title was defined.
|
||||
.push_maybe(if !center.is_empty() {
|
||||
Some(
|
||||
widget::row::with_children(center)
|
||||
.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 if !self.title.is_empty() {
|
||||
Some(
|
||||
widget::text::heading(self.title)
|
||||
.wrapping(text::Wrapping::None)
|
||||
.ellipsize(text::Ellipsize::End(text::EllipsizeHeightLimit::Lines(1)))
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let end = widget::row::with_children(end)
|
||||
.spacing(space_xxs)
|
||||
.align_y(iced::Alignment::Center)
|
||||
.height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32))
|
||||
.padding(if self.is_ssd { [0, 8, 0, 8] } else { padding })
|
||||
.spacing(8)
|
||||
.into();
|
||||
|
||||
let mut widget = HeaderBarWidget::new(start, center, end)
|
||||
.apply(widget::container)
|
||||
.class(crate::theme::Container::HeaderBar {
|
||||
focused: self.focused,
|
||||
sharp_corners: self.sharp_corners,
|
||||
transparent: self.transparent,
|
||||
})
|
||||
.center_y(Length::Shrink)
|
||||
.height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32))
|
||||
.padding(padding)
|
||||
.apply(widget::mouse_area);
|
||||
|
||||
// Assigns a message to emit when the headerbar is dragged.
|
||||
if let Some(message) = self.on_drag.clone() {
|
||||
if let Some(message) = self.on_drag {
|
||||
widget = widget.on_drag(message);
|
||||
}
|
||||
|
||||
// Assigns a message to emit when the headerbar is double-clicked.
|
||||
if let Some(message) = self.on_maximize.clone() {
|
||||
if let Some(message) = self.on_maximize {
|
||||
widget = widget.on_release(message);
|
||||
}
|
||||
if let Some(message) = self.on_double_click.clone() {
|
||||
if let Some(message) = self.on_double_click {
|
||||
widget = widget.on_double_press(message);
|
||||
}
|
||||
if let Some(message) = self.on_right_click.clone() {
|
||||
if let Some(message) = self.on_right_click {
|
||||
widget = widget.on_right_press(message);
|
||||
}
|
||||
|
||||
widget.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.
|
||||
fn window_controls(&mut self) -> Element<'a, Message> {
|
||||
fn window_controls(&mut self, spacing: u16) -> Element<'a, Message> {
|
||||
macro_rules! icon {
|
||||
($name:expr, $size:expr, $on_press:expr) => {{
|
||||
let icon = {
|
||||
widget::icon::from_name($name)
|
||||
.apply(widget::button::icon)
|
||||
.padding(8)
|
||||
};
|
||||
|
||||
icon.class(crate::theme::Button::HeaderBar)
|
||||
widget::icon::from_name($name)
|
||||
.apply(widget::button::icon)
|
||||
.padding(8)
|
||||
.class(crate::theme::Button::HeaderBar)
|
||||
.selected(self.focused)
|
||||
.icon_size($size)
|
||||
.on_press($on_press)
|
||||
|
|
@ -475,7 +474,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
|||
.push_maybe(
|
||||
self.on_minimize
|
||||
.take()
|
||||
.map(|m: Message| icon!("window-minimize-symbolic", 16, m)),
|
||||
.map(|m| icon!("window-minimize-symbolic", 16, m)),
|
||||
)
|
||||
.push_maybe(self.on_maximize.take().map(|m| {
|
||||
if self.maximized {
|
||||
|
|
@ -489,21 +488,14 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
|||
.take()
|
||||
.map(|m| icon!("window-close-symbolic", 16, m)),
|
||||
)
|
||||
.spacing(theme::spacing().space_xxs)
|
||||
.apply(widget::container)
|
||||
.center_y(Length::Fill)
|
||||
.spacing(spacing)
|
||||
.align_y(iced::Alignment::Center)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: Clone + 'static> From<HeaderBar<'a, Message>> for Element<'a, Message> {
|
||||
fn from(headerbar: HeaderBar<'a, Message>) -> Self {
|
||||
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)
|
||||
headerbar.view()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ pub use handle::{Data, Handle, from_path, from_raster_bytes, from_raster_pixels,
|
|||
use crate::Element;
|
||||
use derive_setters::Setters;
|
||||
use iced::widget::{Image, Svg};
|
||||
use iced::{ContentFit, Length, Rectangle};
|
||||
use iced::{ContentFit, Length, Radians, Rectangle};
|
||||
use iced_core::Rotation;
|
||||
|
||||
/// Create an [`Icon`] from a pre-existing [`Handle`]
|
||||
|
|
@ -125,17 +125,22 @@ pub fn draw(renderer: &mut crate::Renderer, handle: &Handle, icon_bounds: Rectan
|
|||
renderer,
|
||||
iced_core::svg::Svg::new(handle),
|
||||
icon_bounds,
|
||||
icon_bounds,
|
||||
),
|
||||
|
||||
Data::Image(handle) => {
|
||||
iced_core::image::Renderer::draw_image(
|
||||
renderer,
|
||||
handle,
|
||||
iced_core::image::FilterMethod::Linear,
|
||||
iced_core::Image {
|
||||
handle,
|
||||
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,
|
||||
iced_core::Radians::from(0),
|
||||
1.0,
|
||||
[0.0; 4],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use iced_core::layout;
|
|||
use iced_core::mouse;
|
||||
use iced_core::overlay;
|
||||
use iced_core::renderer;
|
||||
use iced_core::widget::{Id, Tree};
|
||||
use iced_core::widget::{Id, Operation, Tree};
|
||||
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
|
||||
pub use iced_widget::container::{Catalog, Style};
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ where
|
|||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.children[0].diff(&mut self.content);
|
||||
tree.diff_children(std::slice::from_mut(&mut self.content));
|
||||
}
|
||||
|
||||
fn size(&self) -> iced_core::Size<Length> {
|
||||
|
|
@ -65,28 +65,29 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let node = self
|
||||
.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, limits);
|
||||
let size = node.size();
|
||||
layout::Node::with_children(size, vec![node])
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
operation: &mut dyn Operation,
|
||||
) {
|
||||
operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
|
||||
self.content.as_widget().operate(
|
||||
operation.container(Some(&self.id), layout.bounds());
|
||||
operation.traverse(&mut |operation| {
|
||||
self.content.as_widget_mut().operate(
|
||||
&mut tree.children[0],
|
||||
layout
|
||||
.children()
|
||||
|
|
@ -99,18 +100,18 @@ where
|
|||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
self.content.as_widget_mut().on_event(
|
||||
) {
|
||||
self.content.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout
|
||||
|
|
@ -123,7 +124,7 @@ where
|
|||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -169,8 +170,9 @@ where
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
self.content.as_widget_mut().overlay(
|
||||
|
|
@ -181,6 +183,7 @@ where
|
|||
.unwrap()
|
||||
.with_virtual_offset(layout.virtual_offset()),
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -181,7 +181,7 @@ where
|
|||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
|
|
@ -190,18 +190,18 @@ where
|
|||
self.container.operate(tree, layout, renderer, operation);
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
self.container.on_event(
|
||||
) {
|
||||
self.container.update(
|
||||
tree,
|
||||
event,
|
||||
layout,
|
||||
|
|
@ -257,11 +257,13 @@ where
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
self.container.overlay(tree, layout, renderer, translation)
|
||||
self.container
|
||||
.overlay(tree, layout, renderer, viewport, translation)
|
||||
}
|
||||
|
||||
fn drag_destinations(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use iced_widget::container::Catalog;
|
|||
|
||||
use crate::{
|
||||
Apply, Element, theme,
|
||||
widget::{container, divider, vertical_space},
|
||||
widget::{container, divider, space::vertical},
|
||||
};
|
||||
|
||||
#[inline]
|
||||
|
|
@ -65,7 +65,7 @@ impl<'a, Message: 'static> ListColumn<'a, Message> {
|
|||
// Ensure a minimum height of 32.
|
||||
let list_item = iced::widget::row![
|
||||
container(item).align_y(iced::Alignment::Center),
|
||||
vertical_space().height(iced::Length::Fixed(32.))
|
||||
vertical().height(iced::Length::Fixed(32.))
|
||||
]
|
||||
.padding(this.list_item_padding)
|
||||
.align_y(iced::Alignment::Center);
|
||||
|
|
@ -112,6 +112,7 @@ impl<'a, Message: 'static> ListColumn<'a, Message> {
|
|||
crate::widget::column::with_children(self.children)
|
||||
.spacing(self.spacing)
|
||||
.padding(self.padding)
|
||||
.width(iced::Length::Fill)
|
||||
.apply(container)
|
||||
.padding([self.spacing, 0])
|
||||
.class(self.style)
|
||||
|
|
|
|||
|
|
@ -57,11 +57,11 @@ pub fn resolve<'a, E, Message, Renderer>(
|
|||
padding: Padding,
|
||||
spacing: f32,
|
||||
align_items: Alignment,
|
||||
items: &[E],
|
||||
items: &mut [E],
|
||||
tree: &mut [&mut Tree],
|
||||
) -> Node
|
||||
where
|
||||
E: std::borrow::Borrow<Element<'a, Message, crate::Theme, Renderer>>,
|
||||
E: std::borrow::BorrowMut<Element<'a, Message, crate::Theme, Renderer>>,
|
||||
Renderer: renderer::Renderer,
|
||||
{
|
||||
let limits = limits.shrink(padding);
|
||||
|
|
@ -69,7 +69,7 @@ where
|
|||
let max_cross = axis.cross(limits.max());
|
||||
|
||||
let mut fill_sum = 0;
|
||||
let mut cross = axis.cross(limits.min()).max(axis.cross(Size::INFINITY));
|
||||
let mut cross = axis.cross(limits.min()).max(axis.cross(Size::INFINITE));
|
||||
let mut available = axis.main(limits.max()) - total_spacing;
|
||||
|
||||
let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
|
||||
|
|
@ -78,8 +78,8 @@ where
|
|||
if align_items == Alignment::Center {
|
||||
let mut fill_cross = axis.cross(limits.min());
|
||||
|
||||
for (child, tree) in items.iter().zip(tree.iter_mut()) {
|
||||
let child = child.borrow();
|
||||
for (child, tree) in items.iter_mut().zip(tree.iter_mut()) {
|
||||
let child = child.borrow_mut();
|
||||
let c_size = child.as_widget().size();
|
||||
let cross_fill_factor = match axis {
|
||||
Axis::Horizontal => c_size.height,
|
||||
|
|
@ -92,7 +92,7 @@ where
|
|||
|
||||
let child_limits = Limits::new(Size::ZERO, Size::new(max_width, max_height));
|
||||
|
||||
let layout = child.as_widget().layout(tree, renderer, &child_limits);
|
||||
let layout = child.as_widget_mut().layout(tree, renderer, &child_limits);
|
||||
let size = layout.size();
|
||||
|
||||
fill_cross = fill_cross.max(axis.cross(size));
|
||||
|
|
@ -102,8 +102,8 @@ where
|
|||
cross = fill_cross;
|
||||
}
|
||||
|
||||
for (i, (child, tree)) in items.iter().zip(tree.iter_mut()).enumerate() {
|
||||
let child = child.borrow();
|
||||
for (i, (child, tree)) in items.iter_mut().zip(tree.iter_mut()).enumerate() {
|
||||
let child = child.borrow_mut();
|
||||
let c_size = child.as_widget().size();
|
||||
let fill_factor = match axis {
|
||||
Axis::Horizontal => c_size.width,
|
||||
|
|
@ -129,7 +129,7 @@ where
|
|||
Size::new(max_width, max_height),
|
||||
);
|
||||
|
||||
let layout = child.as_widget().layout(tree, renderer, &child_limits);
|
||||
let layout = child.as_widget_mut().layout(tree, renderer, &child_limits);
|
||||
let size = layout.size();
|
||||
|
||||
available -= axis.main(size);
|
||||
|
|
@ -146,8 +146,8 @@ where
|
|||
|
||||
let remaining = available.max(0.0);
|
||||
|
||||
for (i, (child, tree)) in items.iter().zip(tree.iter_mut()).enumerate() {
|
||||
let child = child.borrow();
|
||||
for (i, (child, tree)) in items.iter_mut().zip(tree.iter_mut()).enumerate() {
|
||||
let child = child.borrow_mut();
|
||||
let c_size = child.as_widget().size();
|
||||
let fill_factor = match axis {
|
||||
Axis::Horizontal => c_size.width,
|
||||
|
|
@ -180,7 +180,7 @@ where
|
|||
Size::new(max_width, max_height),
|
||||
);
|
||||
|
||||
let layout = child.as_widget().layout(tree, renderer, &child_limits);
|
||||
let layout = child.as_widget_mut().layout(tree, renderer, &child_limits);
|
||||
|
||||
if align_items != Alignment::Center {
|
||||
cross = cross.max(axis.cross(layout.size()));
|
||||
|
|
@ -231,7 +231,7 @@ pub fn resolve_wrapper<'a, Message>(
|
|||
padding: Padding,
|
||||
spacing: f32,
|
||||
align_items: Alignment,
|
||||
items: &[&RcElementWrapper<Message>],
|
||||
items: &mut [&mut RcElementWrapper<Message>],
|
||||
tree: &mut [&mut Tree],
|
||||
) -> Node {
|
||||
let limits = limits.shrink(padding);
|
||||
|
|
@ -239,7 +239,7 @@ pub fn resolve_wrapper<'a, Message>(
|
|||
let max_cross = axis.cross(limits.max());
|
||||
|
||||
let mut fill_sum = 0;
|
||||
let mut cross = axis.cross(limits.min()).max(axis.cross(Size::INFINITY));
|
||||
let mut cross = axis.cross(limits.min()).max(axis.cross(Size::INFINITE));
|
||||
let mut available = axis.main(limits.max()) - total_spacing;
|
||||
|
||||
let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
|
||||
|
|
@ -248,7 +248,7 @@ pub fn resolve_wrapper<'a, Message>(
|
|||
if align_items == Alignment::Center {
|
||||
let mut fill_cross = axis.cross(limits.min());
|
||||
|
||||
for (child, tree) in items.iter().zip(tree.iter_mut()) {
|
||||
for (child, tree) in items.into_iter().zip(tree.iter_mut()) {
|
||||
let c_size = child.size();
|
||||
let cross_fill_factor = match axis {
|
||||
Axis::Horizontal => c_size.height,
|
||||
|
|
@ -271,7 +271,7 @@ pub fn resolve_wrapper<'a, Message>(
|
|||
cross = fill_cross;
|
||||
}
|
||||
|
||||
for (i, (child, tree)) in items.iter().zip(tree.iter_mut()).enumerate() {
|
||||
for (i, (child, tree)) in items.into_iter().zip(tree.iter_mut()).enumerate() {
|
||||
let c_size = child.size();
|
||||
let fill_factor = match axis {
|
||||
Axis::Horizontal => c_size.width,
|
||||
|
|
@ -314,7 +314,7 @@ pub fn resolve_wrapper<'a, Message>(
|
|||
|
||||
let remaining = available.max(0.0);
|
||||
|
||||
for (i, (child, tree)) in items.iter().zip(tree.iter_mut()).enumerate() {
|
||||
for (i, (child, tree)) in items.into_iter().zip(tree.iter_mut()).enumerate() {
|
||||
let c_size = child.size();
|
||||
let fill_factor = match axis {
|
||||
Axis::Horizontal => c_size.width,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ use crate::{
|
|||
},
|
||||
};
|
||||
|
||||
use iced::{Point, Shadow, Vector, window};
|
||||
use iced::{Point, Shadow, Vector, event::Status, window};
|
||||
use iced_core::Border;
|
||||
use iced_widget::core::{
|
||||
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, event,
|
||||
|
|
@ -533,14 +533,14 @@ where
|
|||
menu_roots_children(&self.menu_roots)
|
||||
}
|
||||
|
||||
fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
|
||||
fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
|
||||
use super::flex;
|
||||
|
||||
let limits = limits.width(self.width).height(self.height);
|
||||
let children = self
|
||||
let mut children = self
|
||||
.menu_roots
|
||||
.iter()
|
||||
.map(|root| &root.item)
|
||||
.iter_mut()
|
||||
.map(|root| &mut root.item)
|
||||
.collect::<Vec<_>>();
|
||||
// the first children of the tree are the menu roots items
|
||||
let mut tree_children = tree
|
||||
|
|
@ -555,32 +555,32 @@ where
|
|||
self.padding,
|
||||
self.spacing,
|
||||
Alignment::Center,
|
||||
&children,
|
||||
&mut children,
|
||||
&mut tree_children,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: event::Event,
|
||||
event: &event::Event,
|
||||
layout: Layout<'_>,
|
||||
view_cursor: Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
) {
|
||||
use event::Event::{Mouse, Touch};
|
||||
use mouse::{Button::Left, Event::ButtonReleased};
|
||||
use touch::Event::{FingerLifted, FingerLost};
|
||||
|
||||
let root_status = process_root_events(
|
||||
process_root_events(
|
||||
&mut self.menu_roots,
|
||||
view_cursor,
|
||||
tree,
|
||||
&event,
|
||||
event,
|
||||
layout,
|
||||
renderer,
|
||||
clipboard,
|
||||
|
|
@ -609,6 +609,13 @@ where
|
|||
});
|
||||
|
||||
match event {
|
||||
Mouse(mouse::Event::ButtonPressed(Left))
|
||||
| Touch(touch::Event::FingerPressed { .. })
|
||||
if view_cursor.is_over(layout.bounds()) =>
|
||||
{
|
||||
// TODO should we track that it has been pressed?
|
||||
shell.capture_event();
|
||||
}
|
||||
Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. } | FingerLost { .. }) => {
|
||||
let create_popup = my_state.inner.with_data_mut(|state| {
|
||||
let mut create_popup = false;
|
||||
|
|
@ -627,6 +634,7 @@ where
|
|||
))]
|
||||
{
|
||||
let surface_action = self.on_surface_action.as_ref().unwrap();
|
||||
shell.capture_event();
|
||||
|
||||
shell.publish(surface_action(crate::surface::action::destroy_popup(
|
||||
_id,
|
||||
|
|
@ -638,8 +646,9 @@ where
|
|||
});
|
||||
|
||||
if !create_popup {
|
||||
return event::Status::Ignored;
|
||||
return;
|
||||
}
|
||||
shell.capture_event();
|
||||
#[cfg(all(
|
||||
feature = "multi-window",
|
||||
feature = "wayland",
|
||||
|
|
@ -653,6 +662,7 @@ where
|
|||
Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered)
|
||||
if open && view_cursor.is_over(layout.bounds()) =>
|
||||
{
|
||||
shell.capture_event();
|
||||
#[cfg(all(
|
||||
feature = "multi-window",
|
||||
feature = "wayland",
|
||||
|
|
@ -665,8 +675,6 @@ where
|
|||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
root_status
|
||||
}
|
||||
|
||||
fn draw(
|
||||
|
|
@ -704,6 +712,7 @@ where
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
};
|
||||
|
||||
renderer.fill_quad(path_quad, styling.path);
|
||||
|
|
@ -731,8 +740,9 @@ where
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
_renderer: &Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||
#[cfg(all(
|
||||
|
|
@ -799,25 +809,22 @@ fn process_root_events<Message>(
|
|||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status
|
||||
where
|
||||
{
|
||||
menu_roots
|
||||
) {
|
||||
for ((root, t), lo) in menu_roots
|
||||
.iter_mut()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.map(|((root, t), lo)| {
|
||||
// assert!(t.tag == tree::Tag::stateless());
|
||||
root.item.on_event(
|
||||
&mut t.children[root.index],
|
||||
event.clone(),
|
||||
lo,
|
||||
view_cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
)
|
||||
})
|
||||
.fold(event::Status::Ignored, event::Status::merge)
|
||||
{
|
||||
// assert!(t.tag == tree::Tag::stateless());
|
||||
root.item.update(
|
||||
&mut t.children[root.index],
|
||||
event,
|
||||
lo,
|
||||
view_cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ pub(crate) struct MenuState {
|
|||
}
|
||||
impl MenuState {
|
||||
pub(super) fn layout<Message>(
|
||||
&self,
|
||||
&mut self,
|
||||
overlay_offset: Vector,
|
||||
slice: MenuSlice,
|
||||
renderer: &crate::Renderer,
|
||||
|
|
@ -329,8 +329,8 @@ impl MenuState {
|
|||
// viewport space children bounds
|
||||
let children_bounds = self.menu_bounds.children_bounds + overlay_offset;
|
||||
let child_nodes = self.menu_bounds.child_positions[start_index..=end_index]
|
||||
.iter()
|
||||
.zip(self.menu_bounds.child_sizes[start_index..=end_index].iter())
|
||||
.iter_mut()
|
||||
.zip(self.menu_bounds.child_sizes[start_index..=end_index].iter_mut())
|
||||
.zip(menu_tree[start_index..=end_index].iter())
|
||||
.map(|((cp, size), mt)| {
|
||||
let mut position = *cp;
|
||||
|
|
@ -347,7 +347,11 @@ impl MenuState {
|
|||
let limits = Limits::new(size, size);
|
||||
|
||||
mt.item
|
||||
.layout(&mut tree[mt.index], renderer, &limits)
|
||||
.element
|
||||
.with_data_mut(|e| {
|
||||
e.as_widget_mut()
|
||||
.layout(&mut tree[mt.index], renderer, &limits)
|
||||
})
|
||||
.move_to(Point::new(0.0, position + self.scroll_offset))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
|
@ -360,7 +364,7 @@ impl MenuState {
|
|||
overlay_offset: Vector,
|
||||
index: usize,
|
||||
renderer: &crate::Renderer,
|
||||
menu_tree: &MenuTree<Message>,
|
||||
menu_tree: &mut MenuTree<Message>,
|
||||
tree: &mut Tree,
|
||||
) -> Node {
|
||||
// viewport space children bounds
|
||||
|
|
@ -499,7 +503,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
|||
} else {
|
||||
self.depth
|
||||
}]
|
||||
.iter()
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.filter(|ms| self.is_overlay || ms.0 < 1)
|
||||
.fold(
|
||||
|
|
@ -545,15 +549,15 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
|||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
event: event::Event,
|
||||
event: &event::Event,
|
||||
layout: Layout<'_>,
|
||||
view_cursor: Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> (Option<(usize, MenuState)>, event::Status) {
|
||||
) -> Option<(usize, MenuState)> {
|
||||
use event::{
|
||||
Event::{Mouse, Touch},
|
||||
Status::{Captured, Ignored},
|
||||
|
|
@ -569,7 +573,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
|||
.inner
|
||||
.with_data(|data| data.open || data.active_root.len() <= self.depth)
|
||||
{
|
||||
return (None, Ignored);
|
||||
return None;
|
||||
}
|
||||
|
||||
let viewport = layout.bounds();
|
||||
|
|
@ -581,9 +585,9 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
|||
Cow::Borrowed(_) => panic!(),
|
||||
Cow::Owned(o) => o.as_mut_slice(),
|
||||
};
|
||||
let menu_status = process_menu_events(
|
||||
process_menu_events(
|
||||
self,
|
||||
event.clone(),
|
||||
event,
|
||||
view_cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
|
|
@ -602,28 +606,30 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
|||
self.main_offset as f32,
|
||||
);
|
||||
|
||||
let ret = match event {
|
||||
Mouse(WheelScrolled { delta }) => {
|
||||
process_scroll_events(self, delta, overlay_cursor, viewport_size, overlay_offset)
|
||||
.merge(menu_status)
|
||||
}
|
||||
match event {
|
||||
Mouse(WheelScrolled { delta }) => process_scroll_events(
|
||||
self,
|
||||
shell,
|
||||
*delta,
|
||||
overlay_cursor,
|
||||
viewport_size,
|
||||
overlay_offset,
|
||||
),
|
||||
|
||||
Mouse(ButtonPressed(Left)) | Touch(FingerPressed { .. }) => {
|
||||
self.tree.inner.with_data_mut(|data| {
|
||||
data.pressed = true;
|
||||
data.view_cursor = view_cursor;
|
||||
});
|
||||
Captured
|
||||
}
|
||||
|
||||
Mouse(CursorMoved { position }) | Touch(FingerMoved { position, .. }) => {
|
||||
let view_cursor = Cursor::Available(position);
|
||||
let view_cursor = Cursor::Available(*position);
|
||||
let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset;
|
||||
if !self.is_overlay && !view_cursor.is_over(viewport) {
|
||||
return (None, menu_status);
|
||||
return None;
|
||||
}
|
||||
|
||||
let (new_root, status) = process_overlay_events(
|
||||
let new_root = process_overlay_events(
|
||||
self,
|
||||
renderer,
|
||||
viewport_size,
|
||||
|
|
@ -634,7 +640,11 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
|||
shell,
|
||||
);
|
||||
|
||||
return (new_root, status.merge(menu_status));
|
||||
if self.is_overlay && view_cursor.is_over(viewport) {
|
||||
shell.capture_event();
|
||||
}
|
||||
|
||||
return new_root;
|
||||
}
|
||||
|
||||
Mouse(ButtonReleased(_)) | Touch(FingerLifted { .. }) => {
|
||||
|
|
@ -673,44 +683,39 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
|||
feature = "winit",
|
||||
feature = "surface-message"
|
||||
))]
|
||||
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
|
||||
if let Some(handler) = self.on_surface_action.as_ref() {
|
||||
let mut root = self.window_id;
|
||||
let mut depth = self.depth;
|
||||
while let Some(parent) =
|
||||
state.popup_id.iter().find(|(_, v)| **v == root)
|
||||
{
|
||||
// parent of root popup is the window, so we stop.
|
||||
if depth == 0 {
|
||||
break;
|
||||
}
|
||||
root = *parent.0;
|
||||
depth = depth.saturating_sub(1);
|
||||
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
|
||||
&& let Some(handler) = self.on_surface_action.as_ref()
|
||||
{
|
||||
let mut root = self.window_id;
|
||||
let mut depth = self.depth;
|
||||
while let Some(parent) =
|
||||
state.popup_id.iter().find(|(_, v)| **v == root)
|
||||
{
|
||||
// parent of root popup is the window, so we stop.
|
||||
if depth == 0 {
|
||||
break;
|
||||
}
|
||||
shell.publish((handler)(crate::surface::Action::DestroyPopup(
|
||||
root,
|
||||
)));
|
||||
root = *parent.0;
|
||||
depth = depth.saturating_sub(1);
|
||||
}
|
||||
shell
|
||||
.publish((handler)(crate::surface::Action::DestroyPopup(root)));
|
||||
}
|
||||
|
||||
state.reset();
|
||||
return Captured;
|
||||
}
|
||||
}
|
||||
|
||||
// close all menus when clicking inside the menu bar
|
||||
if self.bar_bounds.contains(overlay_cursor) {
|
||||
state.reset();
|
||||
Captured
|
||||
} else {
|
||||
menu_status
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
_ => menu_status,
|
||||
_ => {}
|
||||
};
|
||||
(None, ret)
|
||||
None
|
||||
}
|
||||
|
||||
#[allow(unused_results, clippy::too_many_lines)]
|
||||
|
|
@ -734,7 +739,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
|||
let render_bounds = if self.is_overlay {
|
||||
Rectangle::new(Point::ORIGIN, viewport.size())
|
||||
} else {
|
||||
Rectangle::new(Point::ORIGIN, Size::INFINITY)
|
||||
Rectangle::new(Point::ORIGIN, Size::INFINITE)
|
||||
};
|
||||
|
||||
let styling = theme.appearance(&self.style);
|
||||
|
|
@ -796,29 +801,30 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
|||
color: styling.border_color,
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
};
|
||||
let menu_color = styling.background;
|
||||
r.fill_quad(menu_quad, menu_color);
|
||||
// draw path hightlight
|
||||
if let (true, Some(active)) = (draw_path, ms.index) {
|
||||
if let Some(active_layout) = children_layout
|
||||
if let (true, Some(active)) = (draw_path, ms.index)
|
||||
&& let Some(active_layout) = children_layout
|
||||
.children()
|
||||
.nth(active.saturating_sub(start_index))
|
||||
{
|
||||
let path_quad = renderer::Quad {
|
||||
bounds: active_layout
|
||||
.bounds()
|
||||
.intersection(&viewport)
|
||||
.unwrap_or_default(),
|
||||
border: Border {
|
||||
radius: styling.menu_border_radius.into(),
|
||||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
};
|
||||
{
|
||||
let path_quad = renderer::Quad {
|
||||
bounds: active_layout
|
||||
.bounds()
|
||||
.intersection(&viewport)
|
||||
.unwrap_or_default(),
|
||||
border: Border {
|
||||
radius: styling.menu_border_radius.into(),
|
||||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
};
|
||||
|
||||
r.fill_quad(path_quad, styling.path);
|
||||
}
|
||||
r.fill_quad(path_quad, styling.path);
|
||||
}
|
||||
if start_index < menu_roots.len() {
|
||||
// draw item
|
||||
|
|
@ -867,17 +873,16 @@ impl<Message: Clone + 'static> overlay::Overlay<Message, crate::Theme, crate::Re
|
|||
)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
event: iced::Event,
|
||||
event: &iced::Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
self.on_event(event, layout, cursor, renderer, clipboard, shell)
|
||||
.1
|
||||
) {
|
||||
self.update(event, layout, cursor, renderer, clipboard, shell);
|
||||
}
|
||||
|
||||
fn draw(
|
||||
|
|
@ -890,6 +895,19 @@ impl<Message: Clone + 'static> overlay::Overlay<Message, crate::Theme, crate::Re
|
|||
) {
|
||||
self.draw(renderer, theme, style, layout, cursor);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
_renderer: &crate::Renderer,
|
||||
) -> mouse::Interaction {
|
||||
if cursor.is_over(layout.bounds()) {
|
||||
mouse::Interaction::Idle
|
||||
} else {
|
||||
mouse::Interaction::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||
|
|
@ -903,7 +921,7 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
_tree: &mut Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &iced_core::layout::Limits,
|
||||
|
|
@ -925,18 +943,18 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
|
|||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: iced::Event,
|
||||
event: &iced::Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
let (new_root, status) = self.on_event(event, layout, cursor, renderer, clipboard, shell);
|
||||
) {
|
||||
let new_root = self.update(event, layout, cursor, renderer, clipboard, shell);
|
||||
|
||||
#[cfg(all(
|
||||
feature = "multi-window",
|
||||
|
|
@ -944,73 +962,74 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
|
|||
feature = "winit",
|
||||
feature = "surface-message"
|
||||
))]
|
||||
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
|
||||
if let Some((new_root, new_ms)) = new_root {
|
||||
use iced_runtime::platform_specific::wayland::popup::{
|
||||
SctkPopupSettings, SctkPositioner,
|
||||
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
|
||||
&& let Some((new_root, new_ms)) = new_root
|
||||
{
|
||||
use iced_runtime::platform_specific::wayland::popup::{
|
||||
SctkPopupSettings, SctkPositioner,
|
||||
};
|
||||
let overlay_offset = Point::ORIGIN - viewport.position();
|
||||
|
||||
let overlay_cursor = cursor.position().unwrap_or_default() - overlay_offset;
|
||||
|
||||
let Some((mut menu, popup_id)) = self.tree.inner.with_data_mut(|state| {
|
||||
let popup_id = *state
|
||||
.popup_id
|
||||
.entry(self.window_id)
|
||||
.or_insert_with(window::Id::unique);
|
||||
let active_roots = state
|
||||
.active_root
|
||||
.get(self.depth)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
let root_bounds_list = layout
|
||||
.children()
|
||||
.next()
|
||||
.unwrap()
|
||||
.children()
|
||||
.map(|lo| lo.bounds())
|
||||
.collect();
|
||||
|
||||
let mut popup_menu = Menu {
|
||||
tree: self.tree.clone(),
|
||||
menu_roots: Cow::Owned(Cow::into_owned(self.menu_roots.clone())),
|
||||
bounds_expand: self.bounds_expand,
|
||||
menu_overlays_parent: false,
|
||||
close_condition: self.close_condition,
|
||||
item_width: self.item_width,
|
||||
item_height: self.item_height,
|
||||
bar_bounds: layout.bounds(),
|
||||
main_offset: self.main_offset,
|
||||
cross_offset: self.cross_offset,
|
||||
root_bounds_list,
|
||||
path_highlight: self.path_highlight,
|
||||
style: Cow::Owned(Cow::into_owned(self.style.clone())),
|
||||
position: Point::new(0., 0.),
|
||||
is_overlay: false,
|
||||
window_id: popup_id,
|
||||
depth: self.depth + 1,
|
||||
on_surface_action: self.on_surface_action.clone(),
|
||||
};
|
||||
let overlay_offset = Point::ORIGIN - viewport.position();
|
||||
|
||||
let overlay_cursor = cursor.position().unwrap_or_default() - overlay_offset;
|
||||
state.active_root.push(new_root);
|
||||
|
||||
let Some((mut menu, popup_id)) = self.tree.inner.with_data_mut(|state| {
|
||||
let popup_id = *state
|
||||
.popup_id
|
||||
.entry(self.window_id)
|
||||
.or_insert_with(window::Id::unique);
|
||||
let active_roots = state
|
||||
.active_root
|
||||
.get(self.depth)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
let root_bounds_list = layout
|
||||
.children()
|
||||
.next()
|
||||
.unwrap()
|
||||
.children()
|
||||
.map(|lo| lo.bounds())
|
||||
.collect();
|
||||
|
||||
let mut popup_menu = Menu {
|
||||
tree: self.tree.clone(),
|
||||
menu_roots: Cow::Owned(Cow::into_owned(self.menu_roots.clone())),
|
||||
bounds_expand: self.bounds_expand,
|
||||
menu_overlays_parent: false,
|
||||
close_condition: self.close_condition,
|
||||
item_width: self.item_width,
|
||||
item_height: self.item_height,
|
||||
bar_bounds: layout.bounds(),
|
||||
main_offset: self.main_offset,
|
||||
cross_offset: self.cross_offset,
|
||||
root_bounds_list,
|
||||
path_highlight: self.path_highlight,
|
||||
style: Cow::Owned(Cow::into_owned(self.style.clone())),
|
||||
position: Point::new(0., 0.),
|
||||
is_overlay: false,
|
||||
window_id: popup_id,
|
||||
depth: self.depth + 1,
|
||||
on_surface_action: self.on_surface_action.clone(),
|
||||
};
|
||||
|
||||
state.active_root.push(new_root);
|
||||
|
||||
Some((popup_menu, popup_id))
|
||||
}) else {
|
||||
return status;
|
||||
};
|
||||
// XXX we push a new active root manually instead
|
||||
init_root_popup_menu(
|
||||
&mut menu,
|
||||
renderer,
|
||||
shell,
|
||||
cursor.position().unwrap(),
|
||||
layout.bounds().size(),
|
||||
Vector::new(0., 0.),
|
||||
layout.bounds(),
|
||||
self.main_offset as f32,
|
||||
);
|
||||
let (anchor_rect, gravity) = self.tree.inner.with_data_mut(|state| {
|
||||
Some((popup_menu, popup_id))
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
// XXX we push a new active root manually instead
|
||||
init_root_popup_menu(
|
||||
&mut menu,
|
||||
renderer,
|
||||
shell,
|
||||
cursor.position().unwrap_or_default(),
|
||||
layout.bounds().size(),
|
||||
Vector::new(0., 0.),
|
||||
layout.bounds(),
|
||||
self.main_offset as f32,
|
||||
);
|
||||
let (anchor_rect, gravity) = self.tree.inner.with_data_mut(|state| {
|
||||
(state
|
||||
.menu_states
|
||||
.get(self.depth + 1)
|
||||
|
|
@ -1039,53 +1058,64 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
|
|||
})
|
||||
});
|
||||
|
||||
let menu_node = Widget::layout(
|
||||
&menu,
|
||||
&mut Tree::empty(),
|
||||
renderer,
|
||||
&Limits::NONE.min_width(1.).min_height(1.),
|
||||
);
|
||||
let menu_node = Widget::layout(
|
||||
&mut menu,
|
||||
&mut Tree::empty(),
|
||||
renderer,
|
||||
&Limits::NONE.min_width(1.).min_height(1.),
|
||||
);
|
||||
|
||||
let popup_size = menu_node.size();
|
||||
let mut positioner = SctkPositioner {
|
||||
size: Some((
|
||||
popup_size.width.ceil() as u32 + 2,
|
||||
popup_size.height.ceil() as u32 + 2,
|
||||
)),
|
||||
anchor_rect,
|
||||
anchor:
|
||||
cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::TopRight,
|
||||
gravity,
|
||||
reactive: true,
|
||||
..Default::default()
|
||||
};
|
||||
// disable slide_x if it is set in the default
|
||||
positioner.constraint_adjustment &= !(1 << 0);
|
||||
let parent = self.window_id;
|
||||
shell.publish((self.on_surface_action.as_ref().unwrap())(
|
||||
crate::surface::action::simple_popup(
|
||||
move || SctkPopupSettings {
|
||||
parent,
|
||||
id: popup_id,
|
||||
positioner: positioner.clone(),
|
||||
parent_size: None,
|
||||
grab: true,
|
||||
close_with_children: false,
|
||||
input_zone: None,
|
||||
},
|
||||
Some(move || {
|
||||
crate::Element::from(
|
||||
crate::widget::container(menu.clone()).center(Length::Fill),
|
||||
)
|
||||
.map(crate::action::app)
|
||||
}),
|
||||
),
|
||||
));
|
||||
|
||||
return status;
|
||||
}
|
||||
let popup_size = menu_node.size();
|
||||
let mut positioner = SctkPositioner {
|
||||
size: Some((
|
||||
popup_size.width.ceil() as u32 + 2,
|
||||
popup_size.height.ceil() as u32 + 2,
|
||||
)),
|
||||
anchor_rect,
|
||||
anchor:
|
||||
cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::TopRight,
|
||||
gravity,
|
||||
reactive: true,
|
||||
..Default::default()
|
||||
};
|
||||
// disable slide_x if it is set in the default
|
||||
positioner.constraint_adjustment &= !(1 << 0);
|
||||
let parent = self.window_id;
|
||||
shell.publish((self.on_surface_action.as_ref().unwrap())(
|
||||
crate::surface::action::simple_popup(
|
||||
move || SctkPopupSettings {
|
||||
parent,
|
||||
id: popup_id,
|
||||
positioner: positioner.clone(),
|
||||
parent_size: None,
|
||||
grab: true,
|
||||
close_with_children: false,
|
||||
input_zone: None,
|
||||
},
|
||||
Some(move || {
|
||||
crate::Element::from(
|
||||
crate::widget::container(menu.clone()).center(Length::Fill),
|
||||
)
|
||||
.map(crate::action::app)
|
||||
}),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &crate::Renderer,
|
||||
) -> mouse::Interaction {
|
||||
if cursor.is_over(layout.bounds()) {
|
||||
mouse::Interaction::Idle
|
||||
} else {
|
||||
mouse::Interaction::None
|
||||
}
|
||||
status
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1103,8 +1133,8 @@ fn pad_rectangle(rect: Rectangle, padding: Padding) -> Rectangle {
|
|||
Rectangle {
|
||||
x: rect.x - padding.left,
|
||||
y: rect.y - padding.top,
|
||||
width: rect.width + padding.horizontal(),
|
||||
height: rect.height + padding.vertical(),
|
||||
width: rect.width + padding.x(),
|
||||
height: rect.height + padding.y(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1178,7 +1208,6 @@ pub(crate) fn init_root_menu<Message: Clone>(
|
|||
menu_bounds,
|
||||
};
|
||||
state.menu_states.push(ms);
|
||||
|
||||
// Hack to ensure menu opens properly
|
||||
shell.invalidate_layout();
|
||||
|
||||
|
|
@ -1274,15 +1303,13 @@ pub(super) fn init_root_popup_menu<Message>(
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
fn process_menu_events<Message: std::clone::Clone>(
|
||||
menu: &mut Menu<Message>,
|
||||
event: event::Event,
|
||||
event: &event::Event,
|
||||
view_cursor: Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
overlay_offset: Vector,
|
||||
) -> event::Status {
|
||||
use event::Status;
|
||||
|
||||
) {
|
||||
let my_state = &mut menu.tree;
|
||||
let menu_roots = match &mut menu.menu_roots {
|
||||
Cow::Borrowed(_) => panic!(),
|
||||
|
|
@ -1290,15 +1317,15 @@ fn process_menu_events<Message: std::clone::Clone>(
|
|||
};
|
||||
my_state.inner.with_data_mut(|state| {
|
||||
if state.active_root.len() <= menu.depth {
|
||||
return event::Status::Ignored;
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(hover) = state.menu_states.last_mut() else {
|
||||
return Status::Ignored;
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(hover_index) = hover.index else {
|
||||
return Status::Ignored;
|
||||
return;
|
||||
};
|
||||
|
||||
let mt = state.active_root.iter().skip(1).fold(
|
||||
|
|
@ -1321,7 +1348,7 @@ fn process_menu_events<Message: std::clone::Clone>(
|
|||
let child_layout = Layout::new(&child_node);
|
||||
|
||||
// process only the last widget
|
||||
mt.item.on_event(
|
||||
mt.item.update(
|
||||
tree,
|
||||
event,
|
||||
child_layout,
|
||||
|
|
@ -1330,8 +1357,8 @@ fn process_menu_events<Message: std::clone::Clone>(
|
|||
clipboard,
|
||||
shell,
|
||||
&Rectangle::default(),
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(unused_results, clippy::too_many_lines, clippy::too_many_arguments)]
|
||||
|
|
@ -1343,12 +1370,11 @@ fn process_overlay_events<Message>(
|
|||
view_cursor: Cursor,
|
||||
overlay_cursor: Point,
|
||||
cross_offset: f32,
|
||||
_shell: &mut Shell<'_, Message>,
|
||||
) -> (Option<(usize, MenuState)>, event::Status)
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> Option<(usize, MenuState)>
|
||||
where
|
||||
Message: std::clone::Clone,
|
||||
{
|
||||
use event::Status::{Captured, Ignored};
|
||||
/*
|
||||
if no active root || pressed:
|
||||
return
|
||||
|
|
@ -1431,8 +1457,8 @@ where
|
|||
state.open = false;
|
||||
}
|
||||
}
|
||||
|
||||
return (new_menu_root, Captured);
|
||||
shell.capture_event();
|
||||
return new_menu_root;
|
||||
};
|
||||
|
||||
let last_menu_bounds = &last_menu_state.menu_bounds;
|
||||
|
|
@ -1446,7 +1472,8 @@ where
|
|||
{
|
||||
|
||||
last_menu_state.index = None;
|
||||
return (new_menu_root, Captured);
|
||||
shell.capture_event();
|
||||
return new_menu_root;
|
||||
}
|
||||
|
||||
// calc new index
|
||||
|
|
@ -1461,7 +1488,7 @@ where
|
|||
};
|
||||
|
||||
if state.pressed {
|
||||
return (new_menu_root, Ignored);
|
||||
return new_menu_root;
|
||||
}
|
||||
let roots = active_root.iter().skip(1).fold(
|
||||
&menu.menu_roots[active_root[0]].children,
|
||||
|
|
@ -1494,7 +1521,7 @@ where
|
|||
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove {
|
||||
if let Some(id) = state.popup_id.remove(&menu.window_id) {
|
||||
state.active_root.truncate(menu.depth + 1);
|
||||
_shell.publish((menu.on_surface_action.as_ref().unwrap())({
|
||||
shell.publish((menu.on_surface_action.as_ref().unwrap())({
|
||||
crate::surface::action::destroy_popup(id)
|
||||
}));
|
||||
}
|
||||
|
|
@ -1555,18 +1582,19 @@ where
|
|||
state.menu_states.truncate(menu.depth + 1);
|
||||
}
|
||||
|
||||
(new_menu_root, Captured)
|
||||
shell.capture_event();
|
||||
new_menu_root
|
||||
})
|
||||
}
|
||||
|
||||
fn process_scroll_events<Message>(
|
||||
menu: &mut Menu<'_, Message>,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
delta: mouse::ScrollDelta,
|
||||
overlay_cursor: Point,
|
||||
viewport_size: Size,
|
||||
overlay_offset: Vector,
|
||||
) -> event::Status
|
||||
where
|
||||
) where
|
||||
Message: Clone,
|
||||
{
|
||||
use event::Status::{Captured, Ignored};
|
||||
|
|
@ -1590,12 +1618,12 @@ where
|
|||
|
||||
// update
|
||||
if state.menu_states.is_empty() {
|
||||
return Ignored;
|
||||
return;
|
||||
} else if state.menu_states.len() == 1 {
|
||||
let last_ms = &mut state.menu_states[0];
|
||||
|
||||
if last_ms.index.is_none() {
|
||||
return Captured;
|
||||
return;
|
||||
}
|
||||
|
||||
let (max_offset, min_offset) = calc_offset_bounds(last_ms, viewport_size);
|
||||
|
|
@ -1616,7 +1644,8 @@ where
|
|||
.children_bounds
|
||||
.contains(overlay_cursor)
|
||||
{
|
||||
return Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
// scroll the second last one
|
||||
|
|
@ -1632,8 +1661,8 @@ where
|
|||
last_two[1].menu_bounds.check_bounds.y += clamped_delta_y;
|
||||
}
|
||||
}
|
||||
Captured
|
||||
})
|
||||
shell.capture_event();
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(clippy::pedantic)]
|
||||
|
|
@ -1666,11 +1695,11 @@ fn get_children_layout<Message>(
|
|||
.map(|mt| {
|
||||
mt.item
|
||||
.element
|
||||
.with_data(|w| match w.as_widget().size().height {
|
||||
.with_data_mut(|w| match w.as_widget_mut().size().height {
|
||||
Length::Fixed(f) => Size::new(width, f),
|
||||
Length::Shrink => {
|
||||
let l_height = w
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.layout(
|
||||
&mut tree[mt.index],
|
||||
renderer,
|
||||
|
|
|
|||
|
|
@ -253,13 +253,16 @@ pub fn menu_items<
|
|||
let key = find_key(&action, key_binds);
|
||||
let mut items = vec![
|
||||
widget::text(l).into(),
|
||||
widget::horizontal_space().into(),
|
||||
widget::space::horizontal().into(),
|
||||
widget::text(key).class(key_class).into(),
|
||||
];
|
||||
|
||||
if let Some(icon) = icon {
|
||||
items.insert(0, widget::icon::icon(icon).size(14).into());
|
||||
items.insert(1, widget::Space::with_width(spacing.space_xxs).into());
|
||||
items.insert(
|
||||
1,
|
||||
widget::space::horizontal().width(spacing.space_xxs).into(),
|
||||
);
|
||||
}
|
||||
|
||||
let menu_button = menu_button(items).on_press(action.message());
|
||||
|
|
@ -273,13 +276,16 @@ pub fn menu_items<
|
|||
|
||||
let mut items = vec![
|
||||
widget::text(l).into(),
|
||||
widget::horizontal_space().into(),
|
||||
widget::space::horizontal().into(),
|
||||
widget::text(key).class(key_class).into(),
|
||||
];
|
||||
|
||||
if let Some(icon) = icon {
|
||||
items.insert(0, widget::icon::icon(icon).size(14).into());
|
||||
items.insert(1, widget::Space::with_width(spacing.space_xxs).into());
|
||||
items.insert(
|
||||
1,
|
||||
widget::space::horizontal().width(spacing.space_xxs).into(),
|
||||
);
|
||||
}
|
||||
|
||||
let menu_button = menu_button(items);
|
||||
|
|
@ -301,16 +307,21 @@ pub fn menu_items<
|
|||
.width(Length::Fixed(16.0))
|
||||
.into()
|
||||
} else {
|
||||
widget::Space::with_width(Length::Fixed(16.0)).into()
|
||||
widget::space::horizontal()
|
||||
.width(Length::Fixed(16.0))
|
||||
.into()
|
||||
},
|
||||
widget::Space::with_width(spacing.space_xxs).into(),
|
||||
widget::space::horizontal().width(spacing.space_xxs).into(),
|
||||
widget::text(label).align_x(iced::Alignment::Start).into(),
|
||||
widget::horizontal_space().into(),
|
||||
widget::space::horizontal().into(),
|
||||
widget::text(key).class(key_class).into(),
|
||||
];
|
||||
|
||||
if let Some(icon) = icon {
|
||||
items.insert(1, widget::Space::with_width(spacing.space_xxs).into());
|
||||
items.insert(
|
||||
1,
|
||||
widget::space::horizontal().width(spacing.space_xxs).into(),
|
||||
);
|
||||
items.insert(2, widget::icon::icon(icon).size(14).into());
|
||||
}
|
||||
|
||||
|
|
@ -325,7 +336,7 @@ pub fn menu_items<
|
|||
RcElementWrapper::new(crate::Element::from(
|
||||
menu_button::<'static, _>(vec![
|
||||
widget::text(l.clone()).into(),
|
||||
widget::horizontal_space().into(),
|
||||
widget::space::horizontal().into(),
|
||||
widget::icon::from_name("pan-end-symbolic")
|
||||
.size(16)
|
||||
.icon()
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ pub use iced::widget::{ComboBox, combo_box};
|
|||
pub use iced::widget::{Container, container};
|
||||
|
||||
#[doc(inline)]
|
||||
pub use iced::widget::{Space, horizontal_space, vertical_space};
|
||||
pub use iced::widget::{Space, space};
|
||||
|
||||
#[doc(inline)]
|
||||
pub use iced::widget::{Image, image};
|
||||
|
|
@ -127,6 +127,10 @@ pub use color_picker::{ColorPicker, ColorPickerModel};
|
|||
#[doc(inline)]
|
||||
pub use iced::widget::qr_code;
|
||||
|
||||
mod cards;
|
||||
#[doc(inline)]
|
||||
pub use cards::cards;
|
||||
|
||||
pub mod context_drawer;
|
||||
#[doc(inline)]
|
||||
pub use context_drawer::{ContextDrawer, context_drawer};
|
||||
|
|
@ -175,47 +179,47 @@ pub use dialog::{Dialog, dialog};
|
|||
pub mod divider {
|
||||
/// Horizontal variant of a divider.
|
||||
pub mod horizontal {
|
||||
use iced::widget::{Rule, horizontal_rule};
|
||||
use iced::{widget::Rule, widget::rule};
|
||||
|
||||
/// Horizontal divider with default thickness
|
||||
#[must_use]
|
||||
pub fn default<'a>() -> Rule<'a, crate::Theme> {
|
||||
horizontal_rule(1).class(crate::theme::Rule::Default)
|
||||
rule::horizontal(1).class(crate::theme::Rule::Default)
|
||||
}
|
||||
|
||||
/// Horizontal divider with light thickness
|
||||
#[must_use]
|
||||
pub fn light<'a>() -> Rule<'a, crate::Theme> {
|
||||
horizontal_rule(1).class(crate::theme::Rule::LightDivider)
|
||||
rule::horizontal(1).class(crate::theme::Rule::LightDivider)
|
||||
}
|
||||
|
||||
/// Horizontal divider with heavy thickness.
|
||||
#[must_use]
|
||||
pub fn heavy<'a>() -> Rule<'a, crate::Theme> {
|
||||
horizontal_rule(4).class(crate::theme::Rule::HeavyDivider)
|
||||
rule::horizontal(4).class(crate::theme::Rule::HeavyDivider)
|
||||
}
|
||||
}
|
||||
|
||||
/// Vertical variant of a divider.
|
||||
pub mod vertical {
|
||||
use iced::widget::{Rule, vertical_rule};
|
||||
use iced::widget::{Rule, rule};
|
||||
|
||||
/// Vertical divider with default thickness
|
||||
#[must_use]
|
||||
pub fn default<'a>() -> Rule<'a, crate::Theme> {
|
||||
vertical_rule(1).class(crate::theme::Rule::Default)
|
||||
rule::vertical(1).class(crate::theme::Rule::Default)
|
||||
}
|
||||
|
||||
/// Vertical divider with light thickness
|
||||
#[must_use]
|
||||
pub fn light<'a>() -> Rule<'a, crate::Theme> {
|
||||
vertical_rule(4).class(crate::theme::Rule::LightDivider)
|
||||
rule::vertical(4).class(crate::theme::Rule::LightDivider)
|
||||
}
|
||||
|
||||
/// Vertical divider with heavy thickness.
|
||||
#[must_use]
|
||||
pub fn heavy<'a>() -> Rule<'a, crate::Theme> {
|
||||
vertical_rule(10).class(crate::theme::Rule::HeavyDivider)
|
||||
rule::vertical(10).class(crate::theme::Rule::HeavyDivider)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -255,7 +259,7 @@ pub use id_container::{IdContainer, id_container};
|
|||
#[cfg(feature = "animated-image")]
|
||||
pub mod frames;
|
||||
|
||||
pub use taffy::JustifyContent;
|
||||
pub use taffy::{JustifyContent, JustifyItems};
|
||||
|
||||
pub mod list;
|
||||
#[doc(inline)]
|
||||
|
|
@ -346,7 +350,7 @@ pub use toaster::{Toast, ToastId, Toasts, toaster};
|
|||
|
||||
mod toggler;
|
||||
#[doc(inline)]
|
||||
pub use toggler::toggler;
|
||||
pub use toggler::{Toggler, toggler};
|
||||
|
||||
#[doc(inline)]
|
||||
pub use tooltip::{Tooltip, tooltip};
|
||||
|
|
|
|||
|
|
@ -180,5 +180,6 @@ pub fn nav_bar_style(theme: &Theme) -> iced_widget::container::Style {
|
|||
radius: cosmic.corner_radii.radius_s.into(),
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
//! A container which displays an overlay when a popup widget is attached.
|
||||
|
||||
use iced::widget;
|
||||
use iced_core::event::{self, Event};
|
||||
use iced_core::layout;
|
||||
use iced_core::mouse;
|
||||
|
|
@ -33,6 +34,7 @@ pub enum Position {
|
|||
/// A container which displays overlays when a popup widget is assigned.
|
||||
#[must_use]
|
||||
pub struct Popover<'a, Message, Renderer> {
|
||||
id: widget::Id,
|
||||
content: Element<'a, Message, crate::Theme, Renderer>,
|
||||
modal: bool,
|
||||
popup: Option<Element<'a, Message, crate::Theme, Renderer>>,
|
||||
|
|
@ -43,6 +45,7 @@ pub struct Popover<'a, Message, Renderer> {
|
|||
impl<'a, Message, Renderer> Popover<'a, Message, Renderer> {
|
||||
pub fn new(content: impl Into<Element<'a, Message, crate::Theme, Renderer>>) -> Self {
|
||||
Self {
|
||||
id: widget::Id::unique(),
|
||||
content: content.into(),
|
||||
modal: false,
|
||||
popup: None,
|
||||
|
|
@ -51,6 +54,13 @@ impl<'a, Message, Renderer> Popover<'a, Message, Renderer> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set the Id
|
||||
#[inline]
|
||||
pub fn id(mut self, id: widget::Id) -> Self {
|
||||
self.id = id;
|
||||
self
|
||||
}
|
||||
|
||||
/// A modal popup intercepts user inputs while a popup is active.
|
||||
#[inline]
|
||||
pub fn modal(mut self, modal: bool) -> Self {
|
||||
|
|
@ -83,6 +93,14 @@ impl<Message: Clone, Renderer> Widget<Message, crate::Theme, Renderer>
|
|||
where
|
||||
Renderer: iced_core::Renderer,
|
||||
{
|
||||
fn id(&self) -> Option<widget::Id> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
fn set_id(&mut self, id: widget::Id) {
|
||||
self.id = id;
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
if let Some(popup) = &self.popup {
|
||||
vec![Tree::new(&self.content), Tree::new(popup)]
|
||||
|
|
@ -104,42 +122,43 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let tree = content_tree_mut(tree);
|
||||
self.content.as_widget().layout(tree, renderer, limits)
|
||||
let tree = &mut tree.children[0];
|
||||
self.content.as_widget_mut().layout(tree, renderer, limits)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation<()>,
|
||||
operation: &mut dyn Operation,
|
||||
) {
|
||||
self.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.operate(content_tree_mut(tree), layout, renderer, operation);
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
) {
|
||||
if self.popup.is_some() {
|
||||
if self.modal {
|
||||
if matches!(event, Event::Mouse(_) | Event::Touch(_)) {
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
} else if let Some(on_close) = self.on_close.as_ref() {
|
||||
if matches!(
|
||||
|
|
@ -153,8 +172,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
self.content.as_widget_mut().on_event(
|
||||
content_tree_mut(tree),
|
||||
self.content.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
|
|
@ -209,8 +228,9 @@ where
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &Renderer,
|
||||
viewport: &Rectangle,
|
||||
mut translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||
if let Some(popup) = &mut self.popup {
|
||||
|
|
@ -235,7 +255,6 @@ where
|
|||
overlay_position.y = overlay_position.y.round();
|
||||
translation.x += overlay_position.x;
|
||||
translation.y += overlay_position.y;
|
||||
|
||||
Some(overlay::Element::new(Box::new(Overlay {
|
||||
tree: &mut tree.children[1],
|
||||
content: popup,
|
||||
|
|
@ -245,9 +264,10 @@ where
|
|||
})))
|
||||
} else {
|
||||
self.content.as_widget_mut().overlay(
|
||||
content_tree_mut(tree),
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
|
@ -312,7 +332,7 @@ where
|
|||
let limits = layout::Limits::new(Size::UNIT, bounds);
|
||||
let node = self
|
||||
.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.layout(self.tree, renderer, &limits);
|
||||
match self.position {
|
||||
Position::Center => {
|
||||
|
|
@ -353,27 +373,28 @@ where
|
|||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
self.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.operate(self.tree, layout, renderer, operation);
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
) {
|
||||
if self.modal
|
||||
&& matches!(event, Event::Mouse(_) | Event::Touch(_))
|
||||
&& !cursor_position.is_over(layout.bounds())
|
||||
{
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
self.content.as_widget_mut().on_event(
|
||||
self.content.as_widget_mut().update(
|
||||
self.tree,
|
||||
event,
|
||||
layout,
|
||||
|
|
@ -389,7 +410,6 @@ where
|
|||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
if self.modal && !cursor_position.is_over(layout.bounds()) {
|
||||
|
|
@ -400,7 +420,7 @@ where
|
|||
self.tree,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
&layout.bounds(),
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
|
@ -427,12 +447,16 @@ where
|
|||
|
||||
fn overlay<'c>(
|
||||
&'c mut self,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'c>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'c, Message, crate::Theme, Renderer>> {
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.overlay(self.tree, layout, renderer, Default::default())
|
||||
self.content.as_widget_mut().overlay(
|
||||
self.tree,
|
||||
layout,
|
||||
renderer,
|
||||
&layout.bounds(),
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ where
|
|||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.children[0].diff(&mut self.label);
|
||||
tree.diff_children(std::slice::from_mut(&mut self.label));
|
||||
}
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size {
|
||||
|
|
@ -175,7 +175,7 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -186,20 +186,20 @@ where
|
|||
|_| layout::Node::new(Size::new(self.size, self.size)),
|
||||
|limits| {
|
||||
self.label
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, limits)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
) {
|
||||
self.label.as_widget().operate(
|
||||
self.label.as_widget_mut().operate(
|
||||
&mut tree.children[0],
|
||||
layout.children().nth(1).unwrap(),
|
||||
renderer,
|
||||
|
|
@ -207,20 +207,20 @@ where
|
|||
);
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
let status = self.label.as_widget_mut().on_event(
|
||||
) {
|
||||
self.label.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event.clone(),
|
||||
event,
|
||||
layout.children().nth(1).unwrap(),
|
||||
cursor,
|
||||
renderer,
|
||||
|
|
@ -229,22 +229,19 @@ where
|
|||
viewport,
|
||||
);
|
||||
|
||||
if status == event::Status::Ignored {
|
||||
if !shell.is_event_captured() {
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||
if cursor.is_over(layout.bounds()) {
|
||||
shell.publish(self.on_click.clone());
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
} else {
|
||||
status
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -359,14 +356,16 @@ where
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
self.label.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout.children().nth(1).unwrap(),
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -221,7 +221,7 @@ where
|
|||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
|
|
@ -230,18 +230,18 @@ where
|
|||
self.container.operate(tree, layout, renderer, operation);
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &iced_core::Rectangle,
|
||||
) -> event::Status {
|
||||
self.container.on_event(
|
||||
) {
|
||||
self.container.update(
|
||||
tree,
|
||||
event,
|
||||
layout,
|
||||
|
|
@ -290,11 +290,13 @@ where
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||
self.container.overlay(tree, layout, renderer, translation)
|
||||
self.container
|
||||
.overlay(tree, layout, renderer, viewport, translation)
|
||||
}
|
||||
|
||||
fn drag_destinations(
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ pub fn rectangle_tracker_subscription<
|
|||
>(
|
||||
id: I,
|
||||
) -> Subscription<(I, RectangleUpdate<R>)> {
|
||||
Subscription::run_with_id(
|
||||
id,
|
||||
stream::unfold(State::Ready, move |state| start_listening(id, state)),
|
||||
)
|
||||
Subscription::run_with(id, |id| {
|
||||
let id = *id;
|
||||
stream::unfold(State::Ready, move |state| start_listening(id, state))
|
||||
})
|
||||
}
|
||||
|
||||
pub enum State<I> {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use iced_core::layout;
|
|||
use iced_core::mouse;
|
||||
use iced_core::overlay;
|
||||
use iced_core::renderer;
|
||||
use iced_core::widget::{Id, Tree, tree};
|
||||
use iced_core::widget::{Id, Operation, Tree, tree};
|
||||
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
|
||||
|
||||
pub(crate) fn responsive_container<'a, Message: 'static, Theme, E>(
|
||||
|
|
@ -81,7 +81,7 @@ where
|
|||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.children[0].diff(&mut self.content);
|
||||
tree.diff_children(std::slice::from_mut(&mut self.content));
|
||||
}
|
||||
|
||||
fn size(&self) -> iced_core::Size<Length> {
|
||||
|
|
@ -89,47 +89,72 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
let unrestricted_size = self.size.unwrap_or_else(|| {
|
||||
let mut unrestricted_size = self.size.unwrap_or_else(|| {
|
||||
let node =
|
||||
self.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, &Limits::NONE);
|
||||
node.size()
|
||||
});
|
||||
|
||||
let cur_unrestricted_size = {
|
||||
let node =
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, &Limits::NONE);
|
||||
node.size()
|
||||
};
|
||||
|
||||
let max_size = limits.max();
|
||||
|
||||
let old_max = state.limits.max();
|
||||
state.needs_update = (unrestricted_size.width > max_size.width)
|
||||
^ (state.size.width > old_max.width)
|
||||
|| (unrestricted_size.height > max_size.height) ^ (state.size.height > old_max.height);
|
||||
|
||||
state.needs_update = (cur_unrestricted_size.width > max_size.width)
|
||||
|| (cur_unrestricted_size.width > old_max.width)
|
||||
|| (cur_unrestricted_size.height > max_size.height)
|
||||
|| (cur_unrestricted_size.height > old_max.height)
|
||||
|| ((unrestricted_size.width <= max_size.width)
|
||||
&& (unrestricted_size.height <= max_size.height)
|
||||
&& (unrestricted_size.width - cur_unrestricted_size.width > 1.
|
||||
|| unrestricted_size.height - cur_unrestricted_size.height > 1.));
|
||||
|
||||
if unrestricted_size.width < cur_unrestricted_size.width {
|
||||
state.needs_update = true;
|
||||
unrestricted_size.width = cur_unrestricted_size.width;
|
||||
} else if unrestricted_size.height < cur_unrestricted_size.height {
|
||||
state.needs_update = true;
|
||||
unrestricted_size.height = cur_unrestricted_size.height;
|
||||
}
|
||||
let node = self
|
||||
.content
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, limits);
|
||||
let size = node.size();
|
||||
|
||||
if state.needs_update {
|
||||
state.limits = *limits;
|
||||
state.size = unrestricted_size;
|
||||
}
|
||||
|
||||
let node = self
|
||||
.content
|
||||
.as_widget()
|
||||
.layout(&mut tree.children[0], renderer, limits);
|
||||
let size = node.size();
|
||||
layout::Node::with_children(size, vec![node])
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
operation: &mut dyn Operation,
|
||||
) {
|
||||
operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
|
||||
self.content.as_widget().operate(
|
||||
operation.container(Some(&self.id), layout.bounds());
|
||||
operation.traverse(&mut |operation| {
|
||||
self.content.as_widget_mut().operate(
|
||||
&mut tree.children[0],
|
||||
layout
|
||||
.children()
|
||||
|
|
@ -142,17 +167,17 @@ where
|
|||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
if state.needs_update {
|
||||
|
|
@ -166,7 +191,7 @@ where
|
|||
state.needs_update = false;
|
||||
}
|
||||
|
||||
self.content.as_widget_mut().on_event(
|
||||
self.content.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout
|
||||
|
|
@ -225,8 +250,9 @@ where
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
self.content.as_widget_mut().overlay(
|
||||
|
|
@ -237,6 +263,7 @@ where
|
|||
.unwrap()
|
||||
.with_virtual_offset(layout.virtual_offset()),
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -213,6 +213,18 @@ where
|
|||
state.buttons_offset = num - state.buttons_visible;
|
||||
}
|
||||
|
||||
// Resize paragraph bounds so that text ellipsis can take effect.
|
||||
if !matches!(self.width, Length::Shrink) || state.collapsed {
|
||||
let num = state.buttons_visible.max(1) as f32;
|
||||
let spacing = f32::from(self.spacing);
|
||||
let mut width_offset = 0.0;
|
||||
if state.collapsed {
|
||||
width_offset = f32::from(self.button_height) * 2.0;
|
||||
}
|
||||
let button_width = ((num).mul_add(-spacing, size.width - width_offset) + spacing) / num;
|
||||
self.resize_paragraphs(state, button_width);
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,10 +117,15 @@ where
|
|||
height += item_height;
|
||||
}
|
||||
|
||||
limits.height(Length::Fixed(height)).resolve(
|
||||
let size = limits.height(Length::Fixed(height)).resolve(
|
||||
self.width,
|
||||
self.height,
|
||||
Size::new(width, height),
|
||||
)
|
||||
);
|
||||
|
||||
// Resize paragraph bounds so that text ellipsis can take effect.
|
||||
self.resize_paragraphs(state, size.width);
|
||||
|
||||
size
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -5,10 +5,11 @@ use std::borrow::Cow;
|
|||
|
||||
use crate::{
|
||||
Element, theme,
|
||||
widget::{FlexRow, Row, column, container, flex_row, horizontal_space, row, text},
|
||||
widget::{FlexRow, Row, column, container, flex_row, row, text},
|
||||
};
|
||||
use derive_setters::Setters;
|
||||
use iced_core::{Length, text::Wrapping};
|
||||
use iced_widget::space;
|
||||
use taffy::AlignContent;
|
||||
|
||||
/// A settings item aligned in a row
|
||||
|
|
@ -25,7 +26,7 @@ pub fn item<'a, Message: 'static>(
|
|||
) -> Row<'a, Message> {
|
||||
item_row(vec![
|
||||
text(title).wrapping(Wrapping::Word).into(),
|
||||
horizontal_space().into(),
|
||||
space::horizontal().into(),
|
||||
widget,
|
||||
])
|
||||
}
|
||||
|
|
@ -40,6 +41,7 @@ pub fn item_row<Message>(children: Vec<Element<Message>>) -> Row<Message> {
|
|||
row::with_children(children)
|
||||
.spacing(theme::spacing().space_xs)
|
||||
.align_y(iced::Alignment::Center)
|
||||
.width(Length::Fill)
|
||||
}
|
||||
|
||||
/// A settings item aligned in a flex row
|
||||
|
|
@ -58,8 +60,9 @@ pub fn flex_item<'a, Message: 'static>(
|
|||
.wrapping(Wrapping::Word)
|
||||
.width(Length::Fill)
|
||||
.into(),
|
||||
container(widget).into(),
|
||||
container(widget).width(Length::Shrink).into(),
|
||||
])
|
||||
.width(Length::Fill)
|
||||
}
|
||||
|
||||
inner(title.into(), widget.into())
|
||||
|
|
@ -140,6 +143,10 @@ impl<'a, Message: 'static> Item<'a, Message> {
|
|||
is_checked: bool,
|
||||
message: impl Fn(bool) -> Message + 'static,
|
||||
) -> Row<'a, Message> {
|
||||
self.control(crate::widget::toggler(is_checked).on_toggle(message))
|
||||
self.control(
|
||||
crate::widget::toggler(is_checked)
|
||||
.width(Length::Shrink)
|
||||
.on_toggle(message),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,6 @@ use crate::Element;
|
|||
use crate::widget::{ListColumn, column, text};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// A section within a settings view column.
|
||||
#[deprecated(note = "use `settings::section().title()` instead")]
|
||||
pub fn view_section<'a, Message: 'static>(title: impl Into<Cow<'a, str>>) -> Section<'a, Message> {
|
||||
section().title(title)
|
||||
}
|
||||
|
||||
/// A section within a settings view column.
|
||||
pub fn section<'a, Message: 'static>() -> Section<'a, Message> {
|
||||
with_column(ListColumn::default())
|
||||
|
|
|
|||
|
|
@ -313,6 +313,7 @@ fn container_style(theme: &crate::Theme) -> iced_widget::container::Style {
|
|||
background: None,
|
||||
border,
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ where
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Default::default(),
|
||||
snap: true,
|
||||
}
|
||||
}))
|
||||
.apply(widget::mouse_area)
|
||||
|
|
@ -144,7 +145,7 @@ where
|
|||
})
|
||||
// Double click
|
||||
.apply(|mouse_area| {
|
||||
if let Some(ref on_item_mb) = val.on_item_mb_left {
|
||||
if let Some(ref on_item_mb) = val.on_item_mb_double {
|
||||
mouse_area.on_double_click((on_item_mb)(entity))
|
||||
} else {
|
||||
mouse_area
|
||||
|
|
|
|||
|
|
@ -192,6 +192,7 @@ where
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Default::default(),
|
||||
snap: true,
|
||||
}
|
||||
}))
|
||||
.apply(widget::mouse_area)
|
||||
|
|
@ -205,7 +206,7 @@ where
|
|||
})
|
||||
// Double click
|
||||
.apply(|mouse_area| {
|
||||
if let Some(ref on_item_mb) = val.on_item_mb_left {
|
||||
if let Some(ref on_item_mb) = val.on_item_mb_double {
|
||||
mouse_area.on_double_click((on_item_mb)(entity))
|
||||
} else {
|
||||
mouse_area
|
||||
|
|
|
|||
|
|
@ -651,11 +651,11 @@ where
|
|||
|
||||
// if the previous state was at the end of the text, keep it there
|
||||
let old_value = Value::new(&old_value);
|
||||
if state.is_focused() {
|
||||
if let cursor::State::Index(index) = state.cursor.state(&old_value) {
|
||||
if index == old_value.len() {
|
||||
state.cursor.move_to(self.value.len());
|
||||
}
|
||||
if state.is_focused()
|
||||
&& let cursor::State::Index(index) = state.cursor.state(&old_value)
|
||||
{
|
||||
if index == old_value.len() {
|
||||
state.cursor.move_to(self.value.len());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -699,7 +699,7 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -711,7 +711,7 @@ where
|
|||
|
||||
let size = self.size.unwrap_or_else(|| renderer.default_size().0);
|
||||
|
||||
let bounds = limits.resolve(Length::Shrink, Length::Fill, Size::INFINITY);
|
||||
let bounds = limits.resolve(Length::Shrink, Length::Fill, Size::INFINITE);
|
||||
let value_paragraph = &mut state.value;
|
||||
let v = self.value.to_string();
|
||||
value_paragraph.update(Text {
|
||||
|
|
@ -723,8 +723,8 @@ where
|
|||
font,
|
||||
bounds,
|
||||
size: iced::Pixels(size),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
align_x: text::Alignment::Left,
|
||||
align_y: alignment::Vertical::Center,
|
||||
line_height: text::LineHeight::default(),
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::None,
|
||||
|
|
@ -743,8 +743,8 @@ where
|
|||
self.width,
|
||||
self.padding,
|
||||
self.size,
|
||||
self.leading_icon.as_ref(),
|
||||
self.trailing_icon.as_ref(),
|
||||
self.leading_icon.as_mut(),
|
||||
self.trailing_icon.as_mut(),
|
||||
self.line_height,
|
||||
self.label.as_deref(),
|
||||
self.helper_text.as_deref(),
|
||||
|
|
@ -780,24 +780,25 @@ where
|
|||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
_layout: Layout<'_>,
|
||||
_renderer: &crate::Renderer,
|
||||
operation: &mut dyn Operation<()>,
|
||||
layout: Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
operation: &mut dyn Operation,
|
||||
) {
|
||||
operation.container(Some(&self.id), layout.bounds());
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
operation.custom(state, Some(&self.id));
|
||||
operation.focusable(state, Some(&self.id));
|
||||
operation.text_input(state, Some(&self.id));
|
||||
operation.focusable(Some(&self.id), layout.bounds(), state);
|
||||
operation.text_input(Some(&self.id), layout.bounds(), state);
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &crate::Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
let mut layout_ = Vec::with_capacity(2);
|
||||
|
|
@ -823,24 +824,24 @@ where
|
|||
.filter_map(|((child, state), layout)| {
|
||||
child
|
||||
.as_widget_mut()
|
||||
.overlay(state, layout, renderer, translation)
|
||||
.overlay(state, layout, renderer, viewport, translation)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
(!children.is_empty()).then(|| Group::with_children(children).overlay())
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
) {
|
||||
let text_layout = self.text_layout(layout);
|
||||
let mut trailing_icon_layout = None;
|
||||
let font = self.font.unwrap_or_else(|| renderer.default_font());
|
||||
|
|
@ -877,9 +878,9 @@ where
|
|||
// Enable custom buttons defined on the trailing icon position to be handled.
|
||||
if !self.is_editable_variant {
|
||||
if let Some(trailing_layout) = trailing_icon_layout {
|
||||
let res = trailing_icon.as_widget_mut().on_event(
|
||||
let res = trailing_icon.as_widget_mut().update(
|
||||
tree,
|
||||
event.clone(),
|
||||
event,
|
||||
trailing_layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
|
|
@ -888,8 +889,8 @@ where
|
|||
viewport,
|
||||
);
|
||||
|
||||
if res == event::Status::Captured {
|
||||
return res;
|
||||
if shell.is_event_captured() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -934,7 +935,8 @@ where
|
|||
layout,
|
||||
self.manage_value,
|
||||
self.drag_threshold,
|
||||
)
|
||||
self.always_active,
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -1133,8 +1135,8 @@ pub fn layout<Message>(
|
|||
width: Length,
|
||||
padding: Padding,
|
||||
size: Option<f32>,
|
||||
leading_icon: Option<&Element<'_, Message, crate::Theme, crate::Renderer>>,
|
||||
trailing_icon: Option<&Element<'_, Message, crate::Theme, crate::Renderer>>,
|
||||
leading_icon: Option<&mut Element<'_, Message, crate::Theme, crate::Renderer>>,
|
||||
trailing_icon: Option<&mut Element<'_, Message, crate::Theme, crate::Renderer>>,
|
||||
line_height: text::LineHeight,
|
||||
label: Option<&str>,
|
||||
helper_text: Option<&str>,
|
||||
|
|
@ -1148,7 +1150,7 @@ pub fn layout<Message>(
|
|||
let mut nodes = Vec::with_capacity(3);
|
||||
|
||||
let text_pos = if let Some(label) = label {
|
||||
let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITY);
|
||||
let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITE);
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
let label_paragraph = &mut state.label;
|
||||
label_paragraph.update(Text {
|
||||
|
|
@ -1156,8 +1158,8 @@ pub fn layout<Message>(
|
|||
font,
|
||||
bounds: text_bounds,
|
||||
size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
align_x: text::Alignment::Left,
|
||||
align_y: alignment::Vertical::Center,
|
||||
line_height,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::None,
|
||||
|
|
@ -1186,7 +1188,7 @@ pub fn layout<Message>(
|
|||
let (leading_icon_width, mut leading_icon) =
|
||||
if let Some((icon, tree)) = leading_icon.zip(children.get_mut(c_i)) {
|
||||
let size = icon.as_widget().size();
|
||||
let icon_node = icon.as_widget().layout(
|
||||
let icon_node = icon.as_widget_mut().layout(
|
||||
tree,
|
||||
renderer,
|
||||
&Limits::NONE.width(size.width).height(size.height),
|
||||
|
|
@ -1201,7 +1203,7 @@ pub fn layout<Message>(
|
|||
let (trailing_icon_width, mut trailing_icon) =
|
||||
if let Some((icon, tree)) = trailing_icon.zip(children.get_mut(c_i)) {
|
||||
let size = icon.as_widget().size();
|
||||
let icon_node = icon.as_widget().layout(
|
||||
let icon_node = icon.as_widget_mut().layout(
|
||||
tree,
|
||||
renderer,
|
||||
&Limits::NONE.width(size.width).height(size.height),
|
||||
|
|
@ -1214,7 +1216,7 @@ pub fn layout<Message>(
|
|||
let text_limits = limits
|
||||
.width(width)
|
||||
.height(line_height.to_absolute(text_size.into()));
|
||||
let text_bounds = text_limits.resolve(Length::Shrink, Length::Shrink, Size::INFINITY);
|
||||
let text_bounds = text_limits.resolve(Length::Shrink, Length::Shrink, Size::INFINITE);
|
||||
let text_node = layout::Node::new(
|
||||
text_bounds - Size::new(leading_icon_width + trailing_icon_width, 0.0),
|
||||
)
|
||||
|
|
@ -1266,9 +1268,9 @@ pub fn layout<Message>(
|
|||
} else {
|
||||
let limits = limits
|
||||
.width(width)
|
||||
.height(text_input_height + padding.vertical())
|
||||
.height(text_input_height + padding.y())
|
||||
.shrink(padding);
|
||||
let text_bounds = limits.resolve(Length::Shrink, Length::Shrink, Size::INFINITY);
|
||||
let text_bounds = limits.resolve(Length::Shrink, Length::Shrink, Size::INFINITE);
|
||||
|
||||
let text = layout::Node::new(text_bounds).move_to(Point::new(padding.left, padding.top));
|
||||
|
||||
|
|
@ -1286,7 +1288,7 @@ pub fn layout<Message>(
|
|||
.width(width)
|
||||
.shrink(padding)
|
||||
.height(helper_text_line_height.to_absolute(helper_text_size.into()));
|
||||
let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITY);
|
||||
let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITE);
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
let helper_text_paragraph = &mut state.helper_text;
|
||||
helper_text_paragraph.update(Text {
|
||||
|
|
@ -1294,8 +1296,8 @@ pub fn layout<Message>(
|
|||
font,
|
||||
bounds: text_bounds,
|
||||
size: iced::Pixels(helper_text_size),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
align_x: text::Alignment::Left,
|
||||
align_y: alignment::Vertical::Center,
|
||||
line_height: helper_text_line_height,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::None,
|
||||
|
|
@ -1332,7 +1334,7 @@ pub fn layout<Message>(
|
|||
#[allow(clippy::cast_possible_truncation)]
|
||||
pub fn update<'a, Message: Clone + 'static>(
|
||||
id: Option<Id>,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
text_layout: Layout<'_>,
|
||||
edit_button_layout: Option<Layout<'_>>,
|
||||
cursor: mouse::Cursor,
|
||||
|
|
@ -1357,7 +1359,8 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
layout: Layout<'_>,
|
||||
manage_value: bool,
|
||||
drag_threshold: f32,
|
||||
) -> event::Status {
|
||||
always_active: bool,
|
||||
) {
|
||||
let update_cache = |state, value| {
|
||||
replace_paragraph(
|
||||
state,
|
||||
|
|
@ -1420,7 +1423,8 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
});
|
||||
}
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
let target = cursor_position.x - text_layout.bounds().x;
|
||||
|
|
@ -1461,13 +1465,15 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
if cursor.is_over(selection_bounds) && (on_input.is_some() || manage_value)
|
||||
{
|
||||
state.dragging_state = Some(DraggingState::PrepareDnd(cursor_position));
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
// clear selection and place cursor at click position
|
||||
update_cache(state, value);
|
||||
state.setting_selection(value, text_layout.bounds(), target);
|
||||
state.dragging_state = None;
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
(None, click::Kind::Single, _) => {
|
||||
state.setting_selection(value, text_layout.bounds(), target);
|
||||
|
|
@ -1528,7 +1534,8 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
|
||||
state.last_click = Some(click);
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
} else {
|
||||
state.unfocus();
|
||||
|
||||
|
|
@ -1551,12 +1558,10 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
}
|
||||
}
|
||||
state.dragging_state = None;
|
||||
|
||||
return if cursor.is_over(layout.bounds()) {
|
||||
event::Status::Captured
|
||||
} else {
|
||||
event::Status::Ignored
|
||||
};
|
||||
if cursor.is_over(layout.bounds()) {
|
||||
shell.capture_event();
|
||||
}
|
||||
return;
|
||||
}
|
||||
Event::Mouse(mouse::Event::CursorMoved { position })
|
||||
| Event::Touch(touch::Event::FingerMoved { position, .. }) => {
|
||||
|
|
@ -1573,7 +1578,8 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
.cursor
|
||||
.select_range(state.cursor.start(value), position);
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
#[cfg(feature = "wayland")]
|
||||
if let Some(DraggingState::PrepareDnd(start_position)) = state.dragging_state {
|
||||
|
|
@ -1583,7 +1589,7 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
|
||||
if distance >= drag_threshold {
|
||||
if is_secure {
|
||||
return event::Status::Ignored;
|
||||
return;
|
||||
}
|
||||
|
||||
let input_text = state.selected_text(&value.to_string()).unwrap_or_default();
|
||||
|
|
@ -1625,7 +1631,8 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
state.dragging_state = Some(DraggingState::PrepareDnd(start_position));
|
||||
}
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
Event::Keyboard(keyboard::Event::KeyPressed {
|
||||
|
|
@ -1636,11 +1643,11 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
..
|
||||
}) => {
|
||||
let state = state();
|
||||
state.keyboard_modifiers = modifiers;
|
||||
state.keyboard_modifiers = *modifiers;
|
||||
|
||||
if let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) {
|
||||
if state.is_read_only || (!manage_value && on_input.is_none()) {
|
||||
return event::Status::Ignored;
|
||||
return;
|
||||
};
|
||||
let modifiers = state.keyboard_modifiers;
|
||||
focus.updated_at = Instant::now();
|
||||
|
|
@ -1724,12 +1731,14 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
};
|
||||
|
||||
update_cache(state, &value);
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
keyboard::Key::Character("a") | keyboard::Key::Character("A") => {
|
||||
state.cursor.select_all(value);
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
|
|
@ -1737,9 +1746,12 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
}
|
||||
|
||||
// Capture keyboard inputs that should be submitted.
|
||||
if let Some(c) = text.and_then(|t| t.chars().next().filter(|c| !c.is_control())) {
|
||||
if let Some(c) = text
|
||||
.as_ref()
|
||||
.and_then(|t| t.chars().next().filter(|c| !c.is_control()))
|
||||
{
|
||||
if state.is_read_only || (!manage_value && on_input.is_none()) {
|
||||
return event::Status::Ignored;
|
||||
return;
|
||||
};
|
||||
|
||||
state.is_pasting = None;
|
||||
|
|
@ -1769,7 +1781,8 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
|
||||
update_cache(state, &value);
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1902,19 +1915,20 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
shell.publish(on_unfocus.clone());
|
||||
}
|
||||
|
||||
return event::Status::Ignored;
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
keyboard::Key::Named(
|
||||
keyboard::key::Named::ArrowUp | keyboard::key::Named::ArrowDown,
|
||||
) => {
|
||||
return event::Status::Ignored;
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
|
||||
|
|
@ -1928,31 +1942,34 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
keyboard::Key::Named(keyboard::key::Named::Tab)
|
||||
| keyboard::Key::Named(keyboard::key::Named::ArrowUp)
|
||||
| keyboard::Key::Named(keyboard::key::Named::ArrowDown) => {
|
||||
return event::Status::Ignored;
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
|
||||
let state = state();
|
||||
|
||||
state.keyboard_modifiers = modifiers;
|
||||
state.keyboard_modifiers = *modifiers;
|
||||
}
|
||||
Event::Window(window::Event::RedrawRequested(now)) => {
|
||||
let state = state();
|
||||
|
||||
if let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) {
|
||||
focus.now = now;
|
||||
focus.now = *now;
|
||||
|
||||
let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
|
||||
- (now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS;
|
||||
|
||||
shell.request_redraw(window::RedrawRequest::At(
|
||||
now + Duration::from_millis(u64::try_from(millis_until_redraw).unwrap()),
|
||||
- (*now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS;
|
||||
shell.request_redraw_at(window::RedrawRequest::At(
|
||||
now.checked_add(Duration::from_millis(millis_until_redraw as u64))
|
||||
.unwrap_or(*now),
|
||||
));
|
||||
} else if always_active {
|
||||
shell.request_redraw();
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "wayland")]
|
||||
|
|
@ -1962,7 +1979,8 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) {
|
||||
// TODO: restore value in text input
|
||||
state.dragging_state = None;
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "wayland")]
|
||||
|
|
@ -1974,23 +1992,23 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
mime_types,
|
||||
surface,
|
||||
},
|
||||
)) if rectangle == Some(dnd_id) => {
|
||||
)) if *rectangle == Some(dnd_id) => {
|
||||
cold();
|
||||
let state = state();
|
||||
let is_clicked = text_layout.bounds().contains(Point {
|
||||
x: x as f32,
|
||||
y: y as f32,
|
||||
x: *x as f32,
|
||||
y: *y as f32,
|
||||
});
|
||||
|
||||
let mut accepted = false;
|
||||
for m in &mime_types {
|
||||
for m in mime_types {
|
||||
if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) {
|
||||
let clone = m.clone();
|
||||
accepted = true;
|
||||
}
|
||||
}
|
||||
if accepted {
|
||||
let target = x as f32 - text_layout.bounds().x;
|
||||
let target = *x as f32 - text_layout.bounds().x;
|
||||
state.dnd_offer =
|
||||
DndOfferState::HandlingOffer(mime_types.clone(), DndAction::empty());
|
||||
// existing logic for setting the selection
|
||||
|
|
@ -2002,16 +2020,17 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
};
|
||||
|
||||
state.cursor.move_to(position.unwrap_or(0));
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "wayland")]
|
||||
Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y }))
|
||||
if rectangle == Some(dnd_id) =>
|
||||
if *rectangle == Some(dnd_id) =>
|
||||
{
|
||||
let state = state();
|
||||
|
||||
let target = x as f32 - text_layout.bounds().x;
|
||||
let target = *x as f32 - text_layout.bounds().x;
|
||||
// existing logic for setting the selection
|
||||
let position = if target > 0.0 {
|
||||
update_cache(state, value);
|
||||
|
|
@ -2021,10 +2040,11 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
};
|
||||
|
||||
state.cursor.move_to(position.unwrap_or(0));
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
#[cfg(feature = "wayland")]
|
||||
Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if rectangle == Some(dnd_id) => {
|
||||
Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if *rectangle == Some(dnd_id) => {
|
||||
cold();
|
||||
let state = state();
|
||||
if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() {
|
||||
|
|
@ -2033,15 +2053,16 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
.find(|&&m| mime_types.iter().any(|t| t == m))
|
||||
else {
|
||||
state.dnd_offer = DndOfferState::None;
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
};
|
||||
state.dnd_offer = DndOfferState::Dropped;
|
||||
}
|
||||
|
||||
return event::Status::Ignored;
|
||||
return;
|
||||
}
|
||||
#[cfg(feature = "wayland")]
|
||||
Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if Some(dnd_id) != id => {}
|
||||
Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if Some(dnd_id) != *id => {}
|
||||
#[cfg(feature = "wayland")]
|
||||
Event::Dnd(DndEvent::Offer(
|
||||
rectangle,
|
||||
|
|
@ -2057,21 +2078,24 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
state.dnd_offer = DndOfferState::None;
|
||||
}
|
||||
};
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
#[cfg(feature = "wayland")]
|
||||
Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type }))
|
||||
if rectangle == Some(dnd_id) =>
|
||||
if *rectangle == Some(dnd_id) =>
|
||||
{
|
||||
cold();
|
||||
let state = state();
|
||||
if matches!(&state.dnd_offer, DndOfferState::Dropped) {
|
||||
state.dnd_offer = DndOfferState::None;
|
||||
if !SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type.as_str()) || data.is_empty() {
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
let Ok(content) = String::from_utf8(data) else {
|
||||
return event::Status::Captured;
|
||||
let Ok(content) = String::from_utf8(data.clone()) else {
|
||||
shell.capture_event();
|
||||
return;
|
||||
};
|
||||
|
||||
let mut editor = Editor::new(unsecured_value, &mut state.cursor);
|
||||
|
|
@ -2091,14 +2115,13 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
unsecured_value
|
||||
};
|
||||
update_cache(state, &value);
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
return event::Status::Ignored;
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
|
||||
|
|
@ -2212,6 +2235,7 @@ pub fn draw<'a, Message>(
|
|||
color: Color::TRANSPARENT,
|
||||
blur_radius: 0.0,
|
||||
},
|
||||
snap: true,
|
||||
},
|
||||
appearance.background,
|
||||
);
|
||||
|
|
@ -2228,6 +2252,7 @@ pub fn draw<'a, Message>(
|
|||
color: Color::TRANSPARENT,
|
||||
blur_radius: 0.0,
|
||||
},
|
||||
snap: true,
|
||||
},
|
||||
Background::Color(Color::TRANSPARENT),
|
||||
);
|
||||
|
|
@ -2245,6 +2270,7 @@ pub fn draw<'a, Message>(
|
|||
color: Color::TRANSPARENT,
|
||||
blur_radius: 0.0,
|
||||
},
|
||||
snap: true,
|
||||
},
|
||||
appearance.background,
|
||||
);
|
||||
|
|
@ -2258,8 +2284,8 @@ pub fn draw<'a, Message>(
|
|||
size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)),
|
||||
font: font.unwrap_or_else(|| renderer.default_font()),
|
||||
bounds: label_layout.bounds().size(),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
align_x: text::Alignment::Left,
|
||||
align_y: alignment::Vertical::Top,
|
||||
line_height,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::None,
|
||||
|
|
@ -2320,11 +2346,9 @@ pub fn draw<'a, Message>(
|
|||
cursor::State::Index(position) => {
|
||||
let (text_value_width, offset) =
|
||||
measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, position);
|
||||
|
||||
let is_cursor_visible = handling_dnd_offer
|
||||
|| ((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS)
|
||||
% 2
|
||||
== 0;
|
||||
.is_multiple_of(2);
|
||||
if is_cursor_visible {
|
||||
if dnd_icon {
|
||||
(None, 0.0)
|
||||
|
|
@ -2353,6 +2377,7 @@ pub fn draw<'a, Message>(
|
|||
color: Color::TRANSPARENT,
|
||||
blur_radius: 0.0,
|
||||
},
|
||||
snap: true,
|
||||
},
|
||||
text_color,
|
||||
)),
|
||||
|
|
@ -2403,6 +2428,7 @@ pub fn draw<'a, Message>(
|
|||
color: Color::TRANSPARENT,
|
||||
blur_radius: 0.0,
|
||||
},
|
||||
snap: true,
|
||||
},
|
||||
appearance.selected_fill,
|
||||
)),
|
||||
|
|
@ -2448,8 +2474,8 @@ pub fn draw<'a, Message>(
|
|||
font,
|
||||
bounds: bounds.size(),
|
||||
size: iced::Pixels(size),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
align_x: text::Alignment::Left,
|
||||
align_y: alignment::Vertical::Center,
|
||||
line_height: text::LineHeight::default(),
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::None,
|
||||
|
|
@ -2457,7 +2483,7 @@ pub fn draw<'a, Message>(
|
|||
},
|
||||
bounds.position(),
|
||||
color,
|
||||
*viewport,
|
||||
text_bounds,
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -2497,8 +2523,8 @@ pub fn draw<'a, Message>(
|
|||
size: iced::Pixels(helper_text_size),
|
||||
font,
|
||||
bounds: helper_text_layout.bounds().size(),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
align_x: text::Alignment::Left,
|
||||
align_y: alignment::Vertical::Top,
|
||||
line_height: helper_line_height,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::None,
|
||||
|
|
@ -2811,6 +2837,14 @@ impl operation::TextInput for State {
|
|||
fn select_all(&mut self) {
|
||||
Self::select_all(self);
|
||||
}
|
||||
|
||||
fn text(&self) -> &str {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn select_range(&mut self, start: usize, end: usize) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
|
|
@ -2876,11 +2910,11 @@ fn replace_paragraph(
|
|||
state.value = crate::Plain::new(Text {
|
||||
font,
|
||||
line_height,
|
||||
content: &value.to_string(),
|
||||
content: value.to_string(),
|
||||
bounds,
|
||||
size: text_size,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
align_x: text::Alignment::Left,
|
||||
align_y: alignment::Vertical::Top,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::None,
|
||||
ellipsize: text::Ellipsize::None,
|
||||
|
|
|
|||
|
|
@ -45,13 +45,13 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, limits)
|
||||
}
|
||||
|
||||
|
|
@ -85,29 +85,29 @@ where
|
|||
}
|
||||
|
||||
fn operate<'b>(
|
||||
&'b self,
|
||||
&'b mut self,
|
||||
state: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
self.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.operate(&mut state.children[0], layout, renderer, operation);
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
state: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
self.content.as_widget_mut().on_event(
|
||||
) {
|
||||
self.content.as_widget_mut().update(
|
||||
&mut state.children[0],
|
||||
event,
|
||||
layout,
|
||||
|
|
@ -139,8 +139,9 @@ where
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
state: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
//TODO: this hides the overlay of the content during the toast
|
||||
|
|
@ -149,6 +150,7 @@ where
|
|||
&mut state.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
} else {
|
||||
|
|
@ -201,7 +203,7 @@ where
|
|||
|
||||
let node = self
|
||||
.element
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.layout(self.state, renderer, &limits);
|
||||
|
||||
let offset = 15.;
|
||||
|
|
@ -228,16 +230,16 @@ where
|
|||
.draw(self.state, renderer, theme, style, layout, cursor, &bounds);
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<Message>,
|
||||
) -> event::Status {
|
||||
self.element.as_widget_mut().on_event(
|
||||
) {
|
||||
self.element.as_widget_mut().update(
|
||||
self.state,
|
||||
event,
|
||||
layout,
|
||||
|
|
@ -246,29 +248,36 @@ where
|
|||
clipboard,
|
||||
shell,
|
||||
&layout.bounds(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.element
|
||||
.as_widget()
|
||||
.mouse_interaction(self.state, layout, cursor, viewport, renderer)
|
||||
self.element.as_widget().mouse_interaction(
|
||||
self.state,
|
||||
layout,
|
||||
cursor,
|
||||
&layout.bounds(),
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn overlay<'c>(
|
||||
&'c mut self,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'c>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'c, Message, Theme, Renderer>> {
|
||||
self.element
|
||||
.as_widget_mut()
|
||||
.overlay(self.state, layout, renderer, Default::default())
|
||||
self.element.as_widget_mut().overlay(
|
||||
self.state,
|
||||
layout,
|
||||
renderer,
|
||||
&layout.bounds(),
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,433 @@
|
|||
// Copyright 2022 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
//! Show toggle controls using togglers.
|
||||
|
||||
use iced::{Length, widget};
|
||||
use iced_core::text;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub fn toggler<'a, Message, Theme: iced_widget::toggler::Catalog, Renderer>(
|
||||
is_checked: bool,
|
||||
) -> widget::Toggler<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Renderer: iced_core::Renderer + text::Renderer,
|
||||
{
|
||||
widget::Toggler::new(is_checked)
|
||||
.size(24)
|
||||
.spacing(0)
|
||||
.width(Length::Shrink)
|
||||
use crate::{Element, anim, iced_core::Border, iced_widget::toggler::Status};
|
||||
use iced_core::{
|
||||
Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment, event,
|
||||
layout, mouse,
|
||||
renderer::{self, Renderer},
|
||||
text, touch,
|
||||
widget::{self, Tree, tree},
|
||||
window,
|
||||
};
|
||||
use iced_widget::Id;
|
||||
|
||||
pub use crate::iced_widget::toggler::{Catalog, Style};
|
||||
|
||||
pub fn toggler<'a, Message>(is_checked: bool) -> Toggler<'a, Message> {
|
||||
Toggler::new(is_checked)
|
||||
}
|
||||
/// A toggler widget.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Toggler<'a, Message> {
|
||||
id: Id,
|
||||
is_toggled: bool,
|
||||
on_toggle: Option<Box<dyn Fn(bool) -> Message + 'a>>,
|
||||
label: Option<String>,
|
||||
width: Length,
|
||||
size: f32,
|
||||
text_size: Option<f32>,
|
||||
text_line_height: text::LineHeight,
|
||||
text_alignment: text::Alignment,
|
||||
text_shaping: text::Shaping,
|
||||
spacing: f32,
|
||||
font: Option<crate::font::Font>,
|
||||
duration: Duration,
|
||||
ellipsize: text::Ellipsize,
|
||||
}
|
||||
|
||||
impl<'a, Message> Toggler<'a, Message> {
|
||||
/// The default size of a [`Toggler`].
|
||||
pub const DEFAULT_SIZE: f32 = 24.0;
|
||||
|
||||
/// Creates a new [`Toggler`].
|
||||
///
|
||||
/// It expects:
|
||||
/// * a boolean describing whether the [`Toggler`] is checked or not
|
||||
/// * An optional label for the [`Toggler`]
|
||||
/// * a function that will be called when the [`Toggler`] is toggled. It
|
||||
/// will receive the new state of the [`Toggler`] and must produce a
|
||||
/// `Message`.
|
||||
pub fn new(is_toggled: bool) -> Self {
|
||||
Toggler {
|
||||
id: Id::unique(),
|
||||
is_toggled,
|
||||
on_toggle: None,
|
||||
label: None,
|
||||
width: Length::Shrink,
|
||||
size: Self::DEFAULT_SIZE,
|
||||
text_size: None,
|
||||
text_line_height: text::LineHeight::default(),
|
||||
text_alignment: text::Alignment::Left,
|
||||
text_shaping: text::Shaping::Advanced,
|
||||
spacing: 0.0,
|
||||
font: None,
|
||||
duration: Duration::from_millis(200),
|
||||
ellipsize: text::Ellipsize::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the size of the [`Toggler`].
|
||||
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
|
||||
self.size = size.into().0;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Toggler`].
|
||||
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||
self.width = width.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the text size o the [`Toggler`].
|
||||
pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self {
|
||||
self.text_size = Some(text_size.into().0);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the text [`LineHeight`] of the [`Toggler`].
|
||||
pub fn text_line_height(mut self, line_height: impl Into<text::LineHeight>) -> Self {
|
||||
self.text_line_height = line_height.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the horizontal alignment of the text of the [`Toggler`]
|
||||
pub fn text_alignment(mut self, alignment: text::Alignment) -> Self {
|
||||
self.text_alignment = alignment;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`text::Shaping`] strategy of the [`Toggler`].
|
||||
pub fn text_shaping(mut self, shaping: text::Shaping) -> Self {
|
||||
self.text_shaping = shaping;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the spacing between the [`Toggler`] and the text.
|
||||
pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self {
|
||||
self.spacing = spacing.into().0;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`text::Ellipsize`] strategy of the [`Toggler`].
|
||||
pub fn ellipsize(mut self, ellipsize: text::Ellipsize) -> Self {
|
||||
self.ellipsize = ellipsize;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Font`] of the text of the [`Toggler`]
|
||||
///
|
||||
/// [`Font`]: cosmic::iced::text::Renderer::Font
|
||||
pub fn font(mut self, font: impl Into<crate::font::Font>) -> Self {
|
||||
self.font = Some(font.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn id(mut self, id: Id) -> Self {
|
||||
self.id = id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn duration(mut self, dur: Duration) -> Self {
|
||||
self.duration = dur;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_toggle(mut self, on_toggle: impl Fn(bool) -> Message + 'a) -> Self {
|
||||
self.on_toggle = Some(Box::new(on_toggle));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_toggle_maybe(mut self, on_toggle: Option<impl Fn(bool) -> Message + 'a>) -> Self {
|
||||
self.on_toggle = on_toggle.map(|t| Box::new(t) as _);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the label of the [`Button`].
|
||||
pub fn label(mut self, label: impl Into<Option<String>>) -> Self {
|
||||
self.label = label.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message> Widget<Message, crate::Theme, crate::Renderer> for Toggler<'a, Message> {
|
||||
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;
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let limits = limits.width(self.width);
|
||||
|
||||
let res = next_to_each_other(
|
||||
&limits,
|
||||
self.spacing,
|
||||
|limits| {
|
||||
if let Some(label) = self.label.as_deref() {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
let node = iced_core::widget::text::layout(
|
||||
&mut state.text,
|
||||
renderer,
|
||||
limits,
|
||||
label,
|
||||
widget::text::Format {
|
||||
width: self.width,
|
||||
height: Length::Shrink,
|
||||
line_height: self.text_line_height,
|
||||
size: self.text_size.map(iced::Pixels),
|
||||
font: self.font,
|
||||
align_x: self.text_alignment,
|
||||
align_y: alignment::Vertical::Top,
|
||||
shaping: self.text_shaping,
|
||||
wrapping: crate::iced_core::text::Wrapping::default(),
|
||||
ellipsize: self.ellipsize,
|
||||
},
|
||||
);
|
||||
match self.width {
|
||||
Length::Fill => {
|
||||
let size = node.size();
|
||||
layout::Node::with_children(
|
||||
Size::new(limits.width(Length::Fill).max().width, size.height),
|
||||
vec![node],
|
||||
)
|
||||
}
|
||||
_ => node,
|
||||
}
|
||||
} else {
|
||||
layout::Node::new(iced_core::Size::ZERO)
|
||||
}
|
||||
},
|
||||
|_| layout::Node::new(Size::new(48., 24.)),
|
||||
);
|
||||
res
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
_renderer: &crate::Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let Some(on_toggle) = self.on_toggle.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||
let mouse_over = cursor_position.is_over(layout.bounds());
|
||||
|
||||
if mouse_over {
|
||||
shell.publish((on_toggle)(!self.is_toggled));
|
||||
state.anim.changed(self.duration);
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
Event::Window(window::Event::RedrawRequested(now)) => {
|
||||
state.anim.anim_done(self.duration);
|
||||
if state.anim.last_change.is_some() {
|
||||
shell.request_redraw();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &crate::Renderer,
|
||||
) -> mouse::Interaction {
|
||||
if cursor_position.is_over(layout.bounds()) {
|
||||
mouse::Interaction::Pointer
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut crate::Renderer,
|
||||
theme: &crate::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
|
||||
let mut children = layout.children();
|
||||
let label_layout = children.next().unwrap();
|
||||
|
||||
if let Some(_label) = &self.label {
|
||||
let state: &State = tree.state.downcast_ref();
|
||||
iced_widget::text::draw(
|
||||
renderer,
|
||||
style,
|
||||
label_layout.bounds(),
|
||||
state.text.raw(),
|
||||
iced_widget::text::Style::default(),
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
let toggler_layout = children.next().unwrap();
|
||||
let bounds = toggler_layout.bounds();
|
||||
|
||||
let is_mouse_over = cursor_position.is_over(bounds);
|
||||
|
||||
// let style = blend_appearances(
|
||||
// theme.style(
|
||||
// &(),
|
||||
// if is_mouse_over {
|
||||
// Status::Hovered { is_toggled: false }
|
||||
// } else {
|
||||
// Status::Active { is_toggled: false }
|
||||
// },
|
||||
// ),
|
||||
// theme.style(
|
||||
// &(),
|
||||
// if is_mouse_over {
|
||||
// Status::Hovered { is_toggled: true }
|
||||
// } else {
|
||||
// Status::Active { is_toggled: true }
|
||||
// },
|
||||
// ),
|
||||
// percent,
|
||||
// );
|
||||
|
||||
let style = theme.style(
|
||||
&(),
|
||||
if is_mouse_over {
|
||||
Status::Hovered {
|
||||
is_toggled: self.is_toggled,
|
||||
}
|
||||
} else {
|
||||
Status::Active {
|
||||
is_toggled: self.is_toggled,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let space = style.handle_margin;
|
||||
|
||||
let toggler_background_bounds = Rectangle {
|
||||
x: bounds.x,
|
||||
y: bounds.y,
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
};
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: toggler_background_bounds,
|
||||
border: Border {
|
||||
radius: style.border_radius,
|
||||
..Default::default()
|
||||
},
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
style.background,
|
||||
);
|
||||
let mut t = state.anim.t(self.duration, self.is_toggled);
|
||||
|
||||
let toggler_foreground_bounds = Rectangle {
|
||||
x: bounds.x
|
||||
+ anim::slerp(
|
||||
space,
|
||||
bounds.width - space - (bounds.height - (2.0 * space)),
|
||||
t,
|
||||
),
|
||||
|
||||
y: bounds.y + space,
|
||||
width: bounds.height - (2.0 * space),
|
||||
height: bounds.height - (2.0 * space),
|
||||
};
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: toggler_foreground_bounds,
|
||||
border: Border {
|
||||
radius: style.handle_radius,
|
||||
..Default::default()
|
||||
},
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
style.foreground,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: 'static> From<Toggler<'a, Message>> for Element<'a, Message> {
|
||||
fn from(toggler: Toggler<'a, Message>) -> Element<'a, Message> {
|
||||
Element::new(toggler)
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a [`Node`] with two children nodes one right next to each other.
|
||||
pub fn next_to_each_other(
|
||||
limits: &iced::Limits,
|
||||
spacing: f32,
|
||||
left: impl FnOnce(&iced::Limits) -> iced_core::layout::Node,
|
||||
right: impl FnOnce(&iced::Limits) -> iced_core::layout::Node,
|
||||
) -> iced_core::layout::Node {
|
||||
let mut right_node = right(limits);
|
||||
let right_size = right_node.size();
|
||||
|
||||
let left_limits = limits.shrink(Size::new(right_size.width + spacing, 0.0));
|
||||
let mut left_node = left(&left_limits);
|
||||
let left_size = left_node.size();
|
||||
|
||||
let (left_y, right_y) = if left_size.height > right_size.height {
|
||||
(0.0, (left_size.height - right_size.height) / 2.0)
|
||||
} else {
|
||||
((right_size.height - left_size.height) / 2.0, 0.0)
|
||||
};
|
||||
|
||||
left_node = left_node.move_to(iced::Point::new(0.0, left_y));
|
||||
right_node = right_node.move_to(iced::Point::new(left_size.width + spacing, right_y));
|
||||
|
||||
iced_core::layout::Node::with_children(
|
||||
Size::new(
|
||||
left_size.width + spacing + right_size.width,
|
||||
left_size.height.max(right_size.height),
|
||||
),
|
||||
vec![left_node, right_node],
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct State {
|
||||
text: widget::text::State<<crate::Renderer as iced_core::text::Renderer>::Paragraph>,
|
||||
anim: anim::State,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,5 +73,6 @@ pub fn warning_container(theme: &Theme) -> widget::container::Style {
|
|||
offset: iced::Vector::new(0.0, 0.0),
|
||||
blur_radius: 0.0,
|
||||
},
|
||||
snap: true,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone>
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -224,21 +224,22 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone>
|
|||
self.padding,
|
||||
|renderer, limits| {
|
||||
self.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, limits)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
operation.container(None, layout.bounds(), &mut |operation| {
|
||||
self.content.as_widget().operate(
|
||||
operation.container(Some(&self.id), layout.bounds());
|
||||
operation.traverse(&mut |operation| {
|
||||
self.content.as_widget_mut().operate(
|
||||
&mut tree.children[0],
|
||||
layout
|
||||
.children()
|
||||
|
|
@ -251,18 +252,18 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone>
|
|||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
let status = update(
|
||||
) {
|
||||
update(
|
||||
self.id.clone(),
|
||||
event.clone(),
|
||||
layout,
|
||||
|
|
@ -275,22 +276,21 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone>
|
|||
&self.on_surface_action,
|
||||
|| tree.state.downcast_mut::<State>(),
|
||||
);
|
||||
status.merge(
|
||||
self.content.as_widget_mut().on_event(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout
|
||||
.children()
|
||||
.next()
|
||||
.unwrap()
|
||||
.with_virtual_offset(layout.virtual_offset()),
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
),
|
||||
)
|
||||
|
||||
self.content.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout
|
||||
.children()
|
||||
.next()
|
||||
.unwrap()
|
||||
.with_virtual_offset(layout.virtual_offset()),
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
|
|
@ -359,8 +359,9 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone>
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &crate::Renderer,
|
||||
viewport: &Rectangle,
|
||||
mut translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
let position = layout.bounds().position();
|
||||
|
|
@ -374,6 +375,7 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone>
|
|||
.unwrap()
|
||||
.with_virtual_offset(layout.virtual_offset()),
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
|
@ -451,7 +453,7 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>(
|
|||
on_leave: &Message,
|
||||
on_surface_action: &dyn Fn(crate::surface::Action) -> Message,
|
||||
state: impl FnOnce() -> &'a mut State,
|
||||
) -> event::Status {
|
||||
) {
|
||||
match event {
|
||||
Event::Touch(touch::Event::FingerLifted { .. }) => {
|
||||
let state = state();
|
||||
|
|
@ -461,7 +463,8 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>(
|
|||
|
||||
shell.publish(on_leave.clone());
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -579,8 +582,6 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>(
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
|
@ -611,6 +612,7 @@ pub fn draw<Renderer: iced_core::Renderer, Theme>(
|
|||
radius: styling.border_radius,
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
Color::TRANSPARENT,
|
||||
);
|
||||
|
|
@ -632,6 +634,7 @@ pub fn draw<Renderer: iced_core::Renderer, Theme>(
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
Background::Color([0.0, 0.0, 0.0, 0.5].into()),
|
||||
);
|
||||
|
|
@ -647,6 +650,7 @@ pub fn draw<Renderer: iced_core::Renderer, Theme>(
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
background,
|
||||
);
|
||||
|
|
@ -669,6 +673,7 @@ pub fn draw<Renderer: iced_core::Renderer, Theme>(
|
|||
radius: styling.border_radius,
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
Color::TRANSPARENT,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ impl<M> Widget<M, crate::Theme, crate::Renderer> for RcElementWrapper<M> {
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut tree::Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &crate::iced_core::layout::Limits,
|
||||
|
|
@ -132,30 +132,31 @@ impl<M> Widget<M, crate::Theme, crate::Renderer> for RcElementWrapper<M> {
|
|||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
state: &mut tree::Tree,
|
||||
layout: crate::iced_core::Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
operation: &mut dyn widget::Operation,
|
||||
) {
|
||||
self.element.with_data(|e| {
|
||||
e.as_widget().operate(state, layout, renderer, operation);
|
||||
self.element.with_data_mut(|e| {
|
||||
e.as_widget_mut()
|
||||
.operate(state, layout, renderer, operation);
|
||||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
state: &mut tree::Tree,
|
||||
event: crate::iced::Event,
|
||||
event: &crate::iced::Event,
|
||||
layout: crate::iced_core::Layout<'_>,
|
||||
cursor: crate::iced_core::mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn crate::iced_core::Clipboard,
|
||||
shell: &mut crate::iced_core::Shell<'_, M>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
) {
|
||||
self.element.with_data_mut(|e| {
|
||||
e.as_widget_mut().on_event(
|
||||
e.as_widget_mut().update(
|
||||
state, event, layout, cursor, renderer, clipboard, shell, viewport,
|
||||
)
|
||||
})
|
||||
|
|
@ -178,15 +179,16 @@ impl<M> Widget<M, crate::Theme, crate::Renderer> for RcElementWrapper<M> {
|
|||
fn overlay<'a>(
|
||||
&'a mut self,
|
||||
state: &'a mut tree::Tree,
|
||||
layout: crate::iced_core::Layout<'_>,
|
||||
layout: crate::iced_core::Layout<'a>,
|
||||
renderer: &crate::Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: crate::iced_core::Vector,
|
||||
) -> Option<crate::iced_core::overlay::Element<'a, M, crate::Theme, crate::Renderer>> {
|
||||
assert_eq!(self.element.thread_id, thread::current().id());
|
||||
Rc::get_mut(&mut self.element.data).and_then(|e| {
|
||||
e.get_mut()
|
||||
.as_widget_mut()
|
||||
.overlay(state, layout, renderer, translation)
|
||||
.overlay(state, layout, renderer, viewport, translation)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue