merge: upstream/libcosmic a l'etat pinne par epoch-1.9.0 (08a09e3df)
# Conflicts: # Cargo.toml # i18n/th/libcosmic.ftl # iced # src/surface/action.rs # src/surface/mod.rs
This commit is contained in:
commit
97f42e565a
51 changed files with 1212 additions and 611 deletions
|
|
@ -1,6 +1,5 @@
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn;
|
|
||||||
|
|
||||||
#[proc_macro_derive(CosmicConfigEntry, attributes(version, id, cosmic_config_entry))]
|
#[proc_macro_derive(CosmicConfigEntry, attributes(version, id, cosmic_config_entry))]
|
||||||
pub fn cosmic_config_entry_derive(input: TokenStream) -> TokenStream {
|
pub fn cosmic_config_entry_derive(input: TokenStream) -> TokenStream {
|
||||||
|
|
@ -100,16 +99,18 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
default.#field_name = value.into();
|
default.#field_name = value.into();
|
||||||
}
|
}
|
||||||
Err(why) if matches!(why, cosmic_config::Error::NoConfigDirectory) => (),
|
Err(why) => if why.is_err() {
|
||||||
Err(e) => errors.push(e),
|
errors.push(why);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) {
|
match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) {
|
||||||
Ok(#field_name) => default.#field_name = #field_name,
|
Ok(#field_name) => default.#field_name = #field_name,
|
||||||
Err(why) if matches!(why, cosmic_config::Error::NoConfigDirectory) => (),
|
Err(why) => if why.is_err() {
|
||||||
Err(e) => errors.push(e),
|
errors.push(why);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use notify::event::{EventKind, ModifyKind, RenameMode};
|
||||||
use notify::{RecommendedWatcher, Watcher};
|
use notify::{RecommendedWatcher, Watcher};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use std::io::Write;
|
use std::io::{self, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::{env, fmt, fs};
|
use std::{env, fmt, fs};
|
||||||
|
|
@ -417,10 +417,11 @@ impl ConfigGet for Config {
|
||||||
match self.key_path(key) {
|
match self.key_path(key) {
|
||||||
Ok(key_path) if key_path.is_file() => {
|
Ok(key_path) if key_path.is_file() => {
|
||||||
// Load user override
|
// Load user override
|
||||||
let data = fs::read_to_string(key_path)
|
match fs::read_to_string(key_path) {
|
||||||
.map_err(|err| Error::GetKey(key.to_string(), err))?;
|
Ok(data) => Ok(ron::from_str(&data)?),
|
||||||
|
Err(why) if why.kind() == io::ErrorKind::NotFound => Err(Error::NotFound),
|
||||||
Ok(ron::from_str(&data)?)
|
Err(why) => Err(Error::GetKey(key.to_string(), why)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
|
|
@ -436,9 +437,11 @@ impl ConfigGet for Config {
|
||||||
fn get_system_default<T: DeserializeOwned>(&self, key: &str) -> Result<T, Error> {
|
fn get_system_default<T: DeserializeOwned>(&self, key: &str) -> Result<T, Error> {
|
||||||
// Load system default
|
// Load system default
|
||||||
let default_path = self.default_path(key)?;
|
let default_path = self.default_path(key)?;
|
||||||
let data =
|
match fs::read_to_string(default_path) {
|
||||||
fs::read_to_string(default_path).map_err(|err| Error::GetKey(key.to_string(), err))?;
|
Ok(data) => Ok(ron::from_str(&data)?),
|
||||||
Ok(ron::from_str(&data)?)
|
Err(why) if why.kind() == io::ErrorKind::NotFound => Err(Error::NotFound),
|
||||||
|
Err(why) => Err(Error::GetKey(key.to_string(), why)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
use iced_futures::futures::SinkExt;
|
||||||
use iced_futures::futures::channel::mpsc;
|
use iced_futures::futures::channel::mpsc;
|
||||||
use iced_futures::futures::{SinkExt, Stream};
|
|
||||||
use iced_futures::stream;
|
use iced_futures::stream;
|
||||||
use notify::RecommendedWatcher;
|
use notify::RecommendedWatcher;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
|
||||||
|
|
@ -756,6 +756,7 @@ impl Theme {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the active theme based on the current theme mode.
|
/// Get the active theme based on the current theme mode.
|
||||||
|
#[allow(clippy::result_large_err)]
|
||||||
pub fn get_active() -> Result<Self, (Vec<cosmic_config::Error>, Self)> {
|
pub fn get_active() -> Result<Self, (Vec<cosmic_config::Error>, Self)> {
|
||||||
(|| {
|
(|| {
|
||||||
(if ThemeMode::is_dark(&Config::new(Self::id(), Self::VERSION)?)? {
|
(if ThemeMode::is_dark(&Config::new(Self::id(), Self::VERSION)?)? {
|
||||||
|
|
@ -768,7 +769,6 @@ impl Theme {
|
||||||
.and_then(|theme_config| Self::get_entry(&theme_config))
|
.and_then(|theme_config| Self::get_entry(&theme_config))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
/// Rebuild the current theme with the provided accent
|
/// Rebuild the current theme with the provided accent
|
||||||
pub fn with_accent(&self, c: Srgba) -> Self {
|
pub fn with_accent(&self, c: Srgba) -> Self {
|
||||||
let mut oklcha: Oklcha = c.into_color();
|
let mut oklcha: Oklcha = c.into_color();
|
||||||
|
|
@ -1103,7 +1103,7 @@ impl ThemeBuilder {
|
||||||
|
|
||||||
let container_alpha = alpha_map.blurred_alpha(frosted);
|
let container_alpha = alpha_map.blurred_alpha(frosted);
|
||||||
let actual_alpha =
|
let actual_alpha =
|
||||||
if (frosted_windows || frosted_system_interface || frosted_panel || frosted_applets) {
|
if frosted_windows || frosted_system_interface || frosted_panel || frosted_applets {
|
||||||
container_alpha
|
container_alpha
|
||||||
} else {
|
} else {
|
||||||
1.0
|
1.0
|
||||||
|
|
@ -1173,9 +1173,14 @@ impl ThemeBuilder {
|
||||||
control_steps_array[0]
|
control_steps_array[0]
|
||||||
};
|
};
|
||||||
component_hovered_overlay.alpha = 0.1;
|
component_hovered_overlay.alpha = 0.1;
|
||||||
|
let mut background_pressed_overlay = component_hovered_overlay;
|
||||||
|
background_pressed_overlay.alpha = 0.2;
|
||||||
|
#[allow(unused_assignments)]
|
||||||
let mut component_pressed_overlay = component_hovered_overlay;
|
let mut component_pressed_overlay = component_hovered_overlay;
|
||||||
component_pressed_overlay.alpha = 0.2;
|
#[allow(unused_assignments)]
|
||||||
|
{
|
||||||
|
component_pressed_overlay.alpha = 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
// Standard button background is neutral 7 with 25% opacity
|
// Standard button background is neutral 7 with 25% opacity
|
||||||
let button_bg = control_steps_array[7].with_alpha(0.25);
|
let button_bg = control_steps_array[7].with_alpha(0.25);
|
||||||
|
|
@ -1210,7 +1215,7 @@ impl ThemeBuilder {
|
||||||
);
|
);
|
||||||
|
|
||||||
let primary = {
|
let primary = {
|
||||||
let mut container_bg = if let Some(primary_container_bg_color) = primary_container_bg {
|
let container_bg = if let Some(primary_container_bg_color) = primary_container_bg {
|
||||||
primary_container_bg_color
|
primary_container_bg_color
|
||||||
} else {
|
} else {
|
||||||
get_surface_color(bg_index, 5, &step_array, is_dark, &control_steps_array[1])
|
get_surface_color(bg_index, 5, &step_array, is_dark, &control_steps_array[1])
|
||||||
|
|
@ -1228,7 +1233,7 @@ impl ThemeBuilder {
|
||||||
};
|
};
|
||||||
component_hovered_overlay.alpha = 0.1;
|
component_hovered_overlay.alpha = 0.1;
|
||||||
|
|
||||||
component_pressed_overlay = component_hovered_overlay;
|
let mut component_pressed_overlay = component_hovered_overlay;
|
||||||
component_pressed_overlay.alpha = 0.2;
|
component_pressed_overlay.alpha = 0.2;
|
||||||
|
|
||||||
Container::new(
|
Container::new(
|
||||||
|
|
@ -1363,7 +1368,7 @@ impl ThemeBuilder {
|
||||||
accent,
|
accent,
|
||||||
on_bg_component,
|
on_bg_component,
|
||||||
component_hovered_overlay,
|
component_hovered_overlay,
|
||||||
component_pressed_overlay,
|
background_pressed_overlay,
|
||||||
is_high_contrast,
|
is_high_contrast,
|
||||||
control_steps_array[8],
|
control_steps_array[8],
|
||||||
),
|
),
|
||||||
|
|
@ -1659,7 +1664,7 @@ impl ThemeBuilder {
|
||||||
/// but this represents the strength of the blur effect.
|
/// but this represents the strength of the blur effect.
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
|
||||||
pub enum BlurStrength {
|
pub enum BlurStrength {
|
||||||
ExtremelyLow,
|
ExtremelyLow,
|
||||||
ExtremelyLow2,
|
ExtremelyLow2,
|
||||||
|
|
@ -1667,6 +1672,7 @@ pub enum BlurStrength {
|
||||||
VeryLow2,
|
VeryLow2,
|
||||||
Low,
|
Low,
|
||||||
Low2,
|
Low2,
|
||||||
|
#[default]
|
||||||
Medium,
|
Medium,
|
||||||
Medium2,
|
Medium2,
|
||||||
High,
|
High,
|
||||||
|
|
@ -1677,12 +1683,6 @@ pub enum BlurStrength {
|
||||||
ExtremelyHigh2,
|
ExtremelyHigh2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BlurStrength {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Medium
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<u8> for BlurStrength {
|
impl TryFrom<u8> for BlurStrength {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
|
|
@ -1707,6 +1707,7 @@ impl TryFrom<u8> for BlurStrength {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct AlphaMap {
|
pub struct AlphaMap {
|
||||||
pub extremely_low: f32,
|
pub extremely_low: f32,
|
||||||
|
|
@ -1725,6 +1726,7 @@ pub struct AlphaMap {
|
||||||
pub extremely_high_2: f32,
|
pub extremely_high_2: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
impl AlphaMap {
|
impl AlphaMap {
|
||||||
pub fn blurred_alpha(&self, blur: BlurStrength) -> f32 {
|
pub fn blurred_alpha(&self, blur: BlurStrength) -> f32 {
|
||||||
match blur {
|
match blur {
|
||||||
|
|
|
||||||
|
|
@ -321,17 +321,15 @@ impl Theme {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(metadata) = fs::symlink_metadata(path) {
|
if let Ok(metadata) = fs::symlink_metadata(path)
|
||||||
if metadata.file_type().is_symlink() {
|
&& metadata.file_type().is_symlink()
|
||||||
if let Ok(actual_cosmic_css) = fs::read_link(path) {
|
&& let Ok(actual_cosmic_css) = fs::read_link(path)
|
||||||
let canonical_target = fs::canonicalize(&actual_cosmic_css)?;
|
{
|
||||||
let canonical_base = fs::canonicalize(cosmic_css)?;
|
let canonical_target = fs::canonicalize(&actual_cosmic_css)?;
|
||||||
return Ok(Some(
|
let canonical_base = fs::canonicalize(cosmic_css)?;
|
||||||
canonical_target == canonical_base
|
return Ok(Some(
|
||||||
|| canonical_target.starts_with(&canonical_base),
|
canonical_target == canonical_base || canonical_target.starts_with(&canonical_base),
|
||||||
));
|
));
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(Some(false))
|
Ok(Some(false))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,6 @@ inactive_colors={}
|
||||||
qt6ct_res?;
|
qt6ct_res?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[must_use]
|
|
||||||
#[cold]
|
#[cold]
|
||||||
fn write_ct(&self, ct: &str, qpalette: &str) -> Result<(), OutputError> {
|
fn write_ct(&self, ct: &str, qpalette: &str) -> Result<(), OutputError> {
|
||||||
let file_path = Self::get_qpalette_path(ct, self.is_dark)?;
|
let file_path = Self::get_qpalette_path(ct, self.is_dark)?;
|
||||||
|
|
@ -139,7 +138,6 @@ inactive_colors={}
|
||||||
qt6ct_res?;
|
qt6ct_res?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[must_use]
|
|
||||||
#[cold]
|
#[cold]
|
||||||
fn apply_ct(ct: &str, is_dark: bool) -> Result<(), OutputError> {
|
fn apply_ct(ct: &str, is_dark: bool) -> Result<(), OutputError> {
|
||||||
let path = Self::get_conf_path(ct)?;
|
let path = Self::get_conf_path(ct)?;
|
||||||
|
|
@ -213,7 +211,6 @@ inactive_colors={}
|
||||||
qt6ct_res?;
|
qt6ct_res?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[must_use]
|
|
||||||
#[cold]
|
#[cold]
|
||||||
fn reset_ct(ct: &str) -> Result<(), OutputError> {
|
fn reset_ct(ct: &str) -> Result<(), OutputError> {
|
||||||
let path = Self::get_conf_path(ct)?;
|
let path = Self::get_conf_path(ct)?;
|
||||||
|
|
@ -249,7 +246,7 @@ inactive_colors={}
|
||||||
let Some(mut config_dir) = dirs::config_dir() else {
|
let Some(mut config_dir) = dirs::config_dir() else {
|
||||||
return Err(OutputError::MissingConfigDir);
|
return Err(OutputError::MissingConfigDir);
|
||||||
};
|
};
|
||||||
config_dir.push(&ct);
|
config_dir.push(ct);
|
||||||
if !config_dir.exists() {
|
if !config_dir.exists() {
|
||||||
fs::create_dir_all(&config_dir).map_err(OutputError::Io)?;
|
fs::create_dir_all(&config_dir).map_err(OutputError::Io)?;
|
||||||
}
|
}
|
||||||
|
|
@ -272,7 +269,7 @@ inactive_colors={}
|
||||||
let Some(mut config_dir) = dirs::config_dir() else {
|
let Some(mut config_dir) = dirs::config_dir() else {
|
||||||
return Err(OutputError::MissingConfigDir);
|
return Err(OutputError::MissingConfigDir);
|
||||||
};
|
};
|
||||||
config_dir.push(&ct);
|
config_dir.push(ct);
|
||||||
config_dir.push("colors");
|
config_dir.push("colors");
|
||||||
if !config_dir.exists() {
|
if !config_dir.exists() {
|
||||||
fs::create_dir_all(&config_dir).map_err(OutputError::Io)?;
|
fs::create_dir_all(&config_dir).map_err(OutputError::Io)?;
|
||||||
|
|
|
||||||
|
|
@ -275,6 +275,7 @@ impl Theme {
|
||||||
/// Returns an `OutputError` if the user config dir is missing, the
|
/// Returns an `OutputError` if the user config dir is missing, the
|
||||||
/// settings file cannot be read/written, or its JSON is invalid.
|
/// settings file cannot be read/written, or its JSON is invalid.
|
||||||
#[cold]
|
#[cold]
|
||||||
|
/// Applies the theme to VS Code's user settings.
|
||||||
pub fn apply_vs_code(self) -> Result<(), OutputError> {
|
pub fn apply_vs_code(self) -> Result<(), OutputError> {
|
||||||
let vs_theme = VsTheme::from(self);
|
let vs_theme = VsTheme::from(self);
|
||||||
let mut config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?;
|
let mut config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?;
|
||||||
|
|
@ -307,6 +308,7 @@ impl Theme {
|
||||||
/// Returns an `OutputError` if the user config dir is missing, the
|
/// Returns an `OutputError` if the user config dir is missing, the
|
||||||
/// settings file cannot be read/written, or its JSON is invalid.
|
/// settings file cannot be read/written, or its JSON is invalid.
|
||||||
#[cold]
|
#[cold]
|
||||||
|
/// Removes COSMIC theme settings from VS Code's user settings.
|
||||||
pub fn reset_vs_code() -> Result<(), OutputError> {
|
pub fn reset_vs_code() -> Result<(), OutputError> {
|
||||||
let mut config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?;
|
let mut config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?;
|
||||||
config_dir.extend(["Code", "User", "settings.json"]);
|
config_dir.extend(["Code", "User", "settings.json"]);
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ pub enum Message {
|
||||||
PopupClosed(Id),
|
PopupClosed(Id),
|
||||||
ToggleExampleRow(bool),
|
ToggleExampleRow(bool),
|
||||||
Selected(usize),
|
Selected(usize),
|
||||||
Surface(cosmic::surface::Action),
|
Surface(cosmic::surface::Action<Message>),
|
||||||
Toggle(bool),
|
Toggle(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,9 +76,7 @@ impl cosmic::Application for Window {
|
||||||
self.example_row = toggled;
|
self.example_row = toggled;
|
||||||
}
|
}
|
||||||
Message::Surface(a) => {
|
Message::Surface(a) => {
|
||||||
return cosmic::task::message(cosmic::Action::Cosmic(
|
return cosmic::task::message(cosmic::Action::Surface(a));
|
||||||
cosmic::app::Action::Surface(a),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
Message::Selected(i) => {
|
Message::Selected(i) => {
|
||||||
self.selected = Some(i);
|
self.selected = Some(i);
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ pub enum Message {
|
||||||
Input2(String),
|
Input2(String),
|
||||||
Ignore,
|
Ignore,
|
||||||
ToggleHide,
|
ToggleHide,
|
||||||
Surface(cosmic::surface::Action),
|
Surface(cosmic::surface::Action<Message>),
|
||||||
Hi,
|
Hi,
|
||||||
Hi2,
|
Hi2,
|
||||||
Hi3,
|
Hi3,
|
||||||
|
|
@ -255,9 +255,7 @@ impl cosmic::Application for App {
|
||||||
self.hidden = !self.hidden;
|
self.hidden = !self.hidden;
|
||||||
}
|
}
|
||||||
Message::Surface(a) => {
|
Message::Surface(a) => {
|
||||||
return cosmic::task::message(cosmic::Action::Cosmic(
|
return cosmic::task::message(cosmic::Action::Surface(a));
|
||||||
cosmic::app::Action::Surface(a),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
Message::Hi => {
|
Message::Hi => {
|
||||||
dbg!("hi");
|
dbg!("hi");
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
Clicked,
|
Clicked,
|
||||||
WindowClose,
|
WindowClose,
|
||||||
Surface(cosmic::surface::Action),
|
Surface(cosmic::surface::Action<Message>),
|
||||||
ToggleHideContent,
|
ToggleHideContent,
|
||||||
ToggleSomeAction,
|
ToggleSomeAction,
|
||||||
WindowNew,
|
WindowNew,
|
||||||
|
|
@ -89,9 +89,7 @@ impl cosmic::Application for App {
|
||||||
self.button_label = format!("Clicked {message:?}");
|
self.button_label = format!("Clicked {message:?}");
|
||||||
}
|
}
|
||||||
Message::Surface(action) => {
|
Message::Surface(action) => {
|
||||||
return cosmic::task::message(cosmic::Action::Cosmic(
|
return cosmic::task::message(cosmic::Action::Surface(action));
|
||||||
cosmic::app::Action::Surface(action),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
Message::WindowClose
|
Message::WindowClose
|
||||||
| Message::ToggleHideContent
|
| Message::ToggleHideContent
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ pub enum Message {
|
||||||
OpenError(Arc<file_chooser::Error>),
|
OpenError(Arc<file_chooser::Error>),
|
||||||
OpenFile,
|
OpenFile,
|
||||||
Selected(Url),
|
Selected(Url),
|
||||||
Surface(cosmic::surface::Action),
|
Surface(cosmic::surface::Action<Message>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The [`App`] stores application-specific state.
|
/// The [`App`] stores application-specific state.
|
||||||
|
|
@ -187,9 +187,7 @@ impl cosmic::Application for App {
|
||||||
self.error_status = None;
|
self.error_status = None;
|
||||||
}
|
}
|
||||||
Message::Surface(action) => {
|
Message::Surface(action) => {
|
||||||
return cosmic::task::message(cosmic::Action::Cosmic(
|
return cosmic::task::message(cosmic::Action::Surface(action));
|
||||||
cosmic::app::Action::Surface(action),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
license = Llicència
|
||||||
|
close = Tancar
|
||||||
|
links = Enllaços
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
monday = Diluns
|
||||||
|
mon = Dil
|
||||||
|
tuesday = Dimars
|
||||||
|
tue = Dim
|
||||||
|
wednesday = Dimècres
|
||||||
|
wed = Dma
|
||||||
|
thursday = Dijòus
|
||||||
|
thu = Dij
|
||||||
|
friday = Divendres
|
||||||
|
fri = Div
|
||||||
|
saturday = Dissabte
|
||||||
|
sat = Dis
|
||||||
|
sunday = Dimenge
|
||||||
|
sun = Dim
|
||||||
0
i18n/sat/libcosmic_yoda.ftl
Normal file
0
i18n/sat/libcosmic_yoda.ftl
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
close = ปิด
|
||||||
2
iced
2
iced
|
|
@ -1 +1 @@
|
||||||
Subproject commit d12bb8e056712ab87efbdd4296261331772ec413
|
Subproject commit 24da1dc0e4e5c1b7f803bf8584d10939fe5acdec
|
||||||
|
|
@ -16,6 +16,11 @@ pub const fn none<M>() -> Action<M> {
|
||||||
Action::None
|
Action::None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wrap a surface action, typically produced by a widget, to be handled by libcosmic.
|
||||||
|
pub const fn surface<M>(action: crate::surface::Action<M>) -> Action<M> {
|
||||||
|
Action::Surface(action)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub enum Action<M> {
|
pub enum Action<M> {
|
||||||
|
|
@ -26,10 +31,45 @@ pub enum Action<M> {
|
||||||
#[cfg(feature = "single-instance")]
|
#[cfg(feature = "single-instance")]
|
||||||
/// Dbus activation messages
|
/// Dbus activation messages
|
||||||
DbusActivation(dbus_activation::Message),
|
DbusActivation(dbus_activation::Message),
|
||||||
|
/// Surface (popup, subsurface, window, layer shell) requests, handled by libcosmic.
|
||||||
|
Surface(crate::surface::Action<M>),
|
||||||
/// Do nothing
|
/// Do nothing
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<M: 'static> Action<M> {
|
||||||
|
/// Map the application message inside, leaving libcosmic's own variants untouched.
|
||||||
|
#[must_use]
|
||||||
|
pub fn map<N: 'static>(self, f: impl Fn(M) -> N + Clone + Send + Sync + 'static) -> Action<N> {
|
||||||
|
match self {
|
||||||
|
Action::App(message) => Action::App(f(message)),
|
||||||
|
#[cfg(feature = "winit")]
|
||||||
|
Action::Cosmic(action) => Action::Cosmic(action),
|
||||||
|
#[cfg(feature = "single-instance")]
|
||||||
|
Action::DbusActivation(message) => Action::DbusActivation(message),
|
||||||
|
Action::Surface(action) => Action::Surface(action.map(f)),
|
||||||
|
Action::None => Action::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: 'static> Action<Action<M>> {
|
||||||
|
/// Collapse a doubly wrapped action, as produced by widgets whose message type is already
|
||||||
|
/// an [`Action`], into a single one.
|
||||||
|
#[must_use]
|
||||||
|
pub fn flatten(self) -> Action<M> {
|
||||||
|
match self {
|
||||||
|
Action::App(action) => action,
|
||||||
|
#[cfg(feature = "winit")]
|
||||||
|
Action::Cosmic(action) => Action::Cosmic(action),
|
||||||
|
#[cfg(feature = "single-instance")]
|
||||||
|
Action::DbusActivation(message) => Action::DbusActivation(message),
|
||||||
|
Action::Surface(action) => Action::Surface(action.flatten()),
|
||||||
|
Action::None => Action::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<M> From<M> for Action<M> {
|
impl<M> From<M> for Action<M> {
|
||||||
fn from(value: M) -> Self {
|
fn from(value: M) -> Self {
|
||||||
Self::App(value)
|
Self::App(value)
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,6 @@ pub enum Action {
|
||||||
/// Tracks updates to window suggested size.
|
/// Tracks updates to window suggested size.
|
||||||
#[cfg(feature = "applet")]
|
#[cfg(feature = "applet")]
|
||||||
SuggestedBounds(Option<iced::Size>),
|
SuggestedBounds(Option<iced::Size>),
|
||||||
/// Internal surface message
|
|
||||||
Surface(surface::Action),
|
|
||||||
/// Notifies that a surface was closed.
|
/// Notifies that a surface was closed.
|
||||||
/// Any data relating to the surface should be cleaned up.
|
/// Any data relating to the surface should be cleaned up.
|
||||||
SurfaceClosed(iced::window::Id),
|
SurfaceClosed(iced::window::Id),
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ where
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn surface_update(
|
pub fn surface_update(
|
||||||
&mut self,
|
&mut self,
|
||||||
_surface_message: crate::surface::Action,
|
_surface_message: crate::surface::Action<T::Message>,
|
||||||
) -> iced::Task<crate::Action<T::Message>> {
|
) -> iced::Task<crate::Action<T::Message>> {
|
||||||
#[cfg(feature = "surface-message")]
|
#[cfg(feature = "surface-message")]
|
||||||
match _surface_message {
|
match _surface_message {
|
||||||
|
|
@ -194,18 +194,7 @@ where
|
||||||
};
|
};
|
||||||
let settings = settings();
|
let settings = settings();
|
||||||
|
|
||||||
if let Some(view) = view.and_then(|view| {
|
if let Some(view) = view {
|
||||||
match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
|
|
||||||
dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
|
|
||||||
>>() {
|
|
||||||
Ok(v) => Some(v),
|
|
||||||
Err(err) => {
|
|
||||||
tracing::error!("Invalid view for subsurface view: {err:?}");
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
self.get_subsurface(settings, Some(Box::new(move |_| view())))
|
self.get_subsurface(settings, Some(Box::new(move |_| view())))
|
||||||
} else {
|
} else {
|
||||||
self.get_subsurface(settings, None)
|
self.get_subsurface(settings, None)
|
||||||
|
|
@ -301,17 +290,7 @@ where
|
||||||
let settings = settings();
|
let settings = settings();
|
||||||
let live_settings = Box::new(move |_: &T| live_settings());
|
let live_settings = Box::new(move |_: &T| live_settings());
|
||||||
|
|
||||||
if let Some(view) = view.and_then(|view| {
|
if let Some(view) = view {
|
||||||
match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
|
|
||||||
dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
|
|
||||||
>>() {
|
|
||||||
Ok(v) => Some(v),
|
|
||||||
Err(err) => {
|
|
||||||
tracing::error!("Invalid view for subsurface view: {err:?}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
self.get_popup(settings, live_settings, Some(Box::new(move |_| view())))
|
self.get_popup(settings, live_settings, Some(Box::new(move |_| view())))
|
||||||
} else {
|
} else {
|
||||||
self.get_popup(settings, live_settings, None)
|
self.get_popup(settings, live_settings, None)
|
||||||
|
|
@ -377,17 +356,7 @@ where
|
||||||
return Task::none();
|
return Task::none();
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(view) = view.and_then(|view| {
|
if let Some(view) = view {
|
||||||
match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
|
|
||||||
dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
|
|
||||||
>>() {
|
|
||||||
Ok(v) => Some(v),
|
|
||||||
Err(err) => {
|
|
||||||
tracing::error!("Invalid view for Window: {err:?}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
let settings = settings();
|
let settings = settings();
|
||||||
|
|
||||||
self.get_window(
|
self.get_window(
|
||||||
|
|
@ -409,9 +378,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::surface::Action::Ignore => iced::Task::none(),
|
crate::surface::Action::Ignore => iced::Task::none(),
|
||||||
crate::surface::Action::Task(f) => {
|
crate::surface::Action::Task(f) => f().map(crate::Action::Surface),
|
||||||
f().map(|sm| crate::Action::Cosmic(Action::Surface(sm)))
|
|
||||||
}
|
|
||||||
#[cfg(wayland_platform)]
|
#[cfg(wayland_platform)]
|
||||||
crate::surface::Action::AppLayerShell(settings, live_settings, view) => {
|
crate::surface::Action::AppLayerShell(settings, live_settings, view) => {
|
||||||
let Some(settings) = std::sync::Arc::try_unwrap(settings)
|
let Some(settings) = std::sync::Arc::try_unwrap(settings)
|
||||||
|
|
@ -474,17 +441,7 @@ where
|
||||||
let live_settings = live_settings();
|
let live_settings = live_settings();
|
||||||
let live_settings = Box::new(move |_app: &T| live_settings);
|
let live_settings = Box::new(move |_app: &T| live_settings);
|
||||||
|
|
||||||
if let Some(view) = view.and_then(|view| {
|
if let Some(view) = view {
|
||||||
match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
|
|
||||||
dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
|
|
||||||
>>() {
|
|
||||||
Ok(v) => Some(v),
|
|
||||||
Err(err) => {
|
|
||||||
tracing::error!("Invalid view for layer surface: {err:?}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
self.get_layer_shell(settings, live_settings, Some(Box::new(move |_| view())))
|
self.get_layer_shell(settings, live_settings, Some(Box::new(move |_| view())))
|
||||||
} else {
|
} else {
|
||||||
self.get_layer_shell(settings, live_settings, None)
|
self.get_layer_shell(settings, live_settings, None)
|
||||||
|
|
@ -494,6 +451,33 @@ where
|
||||||
crate::surface::Action::DestroyLayerShell(id) => {
|
crate::surface::Action::DestroyLayerShell(id) => {
|
||||||
iced_winit::commands::layer_surface::destroy_layer_surface(id)
|
iced_winit::commands::layer_surface::destroy_layer_surface(id)
|
||||||
}
|
}
|
||||||
|
#[cfg(wayland_platform)]
|
||||||
|
crate::surface::Action::Lock(id, output, live_settings, view) => {
|
||||||
|
let Some(live_settings) =
|
||||||
|
std::sync::Arc::try_unwrap(live_settings)
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| {
|
||||||
|
s.downcast::<Box<dyn Fn() -> LiveSettings + Send + Sync>>()
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
else {
|
||||||
|
tracing::error!("Invalid live settings for popup");
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
|
||||||
|
let live_settings = live_settings();
|
||||||
|
let live_settings = Box::new(move |_app: &T| live_settings);
|
||||||
|
|
||||||
|
if let Some(view) = view {
|
||||||
|
self.get_lock(id, output, live_settings, Some(Box::new(move |_| view())))
|
||||||
|
} else {
|
||||||
|
self.get_lock(id, output, live_settings, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(wayland_platform)]
|
||||||
|
crate::surface::Action::DestroyLock(id) => {
|
||||||
|
iced_winit::commands::session_lock::destroy_lock_surface(id)
|
||||||
|
}
|
||||||
crate::surface::Action::SyncLiveSettings(id) => {
|
crate::surface::Action::SyncLiveSettings(id) => {
|
||||||
if let Some((_, id, live_settings, _)) = self.surface_views.get(&id) {
|
if let Some((_, id, live_settings, _)) = self.surface_views.get(&id) {
|
||||||
let live_settings = live_settings(&self.app);
|
let live_settings = live_settings(&self.app);
|
||||||
|
|
@ -515,6 +499,7 @@ where
|
||||||
let mut task = match message {
|
let mut task = match message {
|
||||||
crate::Action::App(message) => self.app.update(message),
|
crate::Action::App(message) => self.app.update(message),
|
||||||
crate::Action::Cosmic(message) => self.cosmic_update(message),
|
crate::Action::Cosmic(message) => self.cosmic_update(message),
|
||||||
|
crate::Action::Surface(action) => self.surface_update(action),
|
||||||
crate::Action::None => iced::Task::none(),
|
crate::Action::None => iced::Task::none(),
|
||||||
#[cfg(feature = "single-instance")]
|
#[cfg(feature = "single-instance")]
|
||||||
crate::Action::DbusActivation(message) => {
|
crate::Action::DbusActivation(message) => {
|
||||||
|
|
@ -604,9 +589,13 @@ where
|
||||||
#[cfg(wayland_platform)]
|
#[cfg(wayland_platform)]
|
||||||
iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => {
|
iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => {
|
||||||
match event {
|
match event {
|
||||||
wayland::Event::Popup(wayland::PopupEvent::Done, _, id)
|
wayland::Event::Popup(wayland::PopupEvent::Done, _, popup) => {
|
||||||
| wayland::Event::Layer(wayland::LayerEvent::Done, _, id) => {
|
if popup == id {
|
||||||
return Some(Action::SurfaceClosed(id));
|
return Some(Action::SurfaceClosed(popup));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wayland::Event::Layer(wayland::LayerEvent::Done, _, layer) => {
|
||||||
|
return Some(Action::SurfaceClosed(layer));
|
||||||
}
|
}
|
||||||
#[cfg(feature = "applet")]
|
#[cfg(feature = "applet")]
|
||||||
wayland::Event::Window(
|
wayland::Event::Window(
|
||||||
|
|
@ -1227,8 +1216,6 @@ impl<T: Application> Cosmic<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Action::Surface(action) => return self.surface_update(action),
|
|
||||||
|
|
||||||
Action::SurfaceClosed(id) => {
|
Action::SurfaceClosed(id) => {
|
||||||
if self.opened_surfaces.get_mut(&id).is_some_and(|v| {
|
if self.opened_surfaces.get_mut(&id).is_some_and(|v| {
|
||||||
*v = v.saturating_sub(1);
|
*v = v.saturating_sub(1);
|
||||||
|
|
@ -1685,7 +1672,7 @@ impl<App: Application> Cosmic<App> {
|
||||||
view,
|
view,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
Task::batch([live_settings_task, get_subsurface(settings)])
|
live_settings_task.chain(get_subsurface(settings))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(wayland_platform)]
|
#[cfg(wayland_platform)]
|
||||||
|
|
@ -1741,16 +1728,13 @@ impl<App: Application> Cosmic<App> {
|
||||||
view,
|
view,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
Task::batch([
|
|
||||||
iced_runtime::task::oneshot(|channel| {
|
// We don't control window creation in the same way
|
||||||
iced_runtime::Action::Window(iced_runtime::window::Action::Open(
|
iced_runtime::task::oneshot(|channel| {
|
||||||
id, settings, channel,
|
iced_runtime::Action::Window(iced_runtime::window::Action::Open(id, settings, channel))
|
||||||
))
|
})
|
||||||
})
|
.discard()
|
||||||
.discard(),
|
.chain(live_settings_task)
|
||||||
// We don't control window creation in the same way
|
|
||||||
live_settings_task,
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(wayland_platform)]
|
#[cfg(wayland_platform)]
|
||||||
|
|
@ -1778,7 +1762,35 @@ impl<App: Application> Cosmic<App> {
|
||||||
view,
|
view,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
Task::batch([live_settings_task, get_layer_surface(settings)])
|
live_settings_task.chain(get_layer_surface(settings))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(wayland_platform)]
|
||||||
|
pub fn get_lock(
|
||||||
|
&mut self,
|
||||||
|
id: window::Id,
|
||||||
|
output: cctk::wayland_client::protocol::wl_output::WlOutput,
|
||||||
|
live_settings: Box<dyn for<'a> Fn(&'a App) -> LiveSettings + Send + Sync>,
|
||||||
|
view: Option<
|
||||||
|
Box<dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync>,
|
||||||
|
>,
|
||||||
|
) -> Task<crate::Action<App::Message>> {
|
||||||
|
use iced_winit::SurfaceIdWrapper;
|
||||||
|
use iced_winit::platform_specific::commands::session_lock::get_lock_surface;
|
||||||
|
*self.opened_surfaces.entry(id).or_insert(0) += 1;
|
||||||
|
let live_settings_task =
|
||||||
|
self.apply_live_settings(SurfaceIdWrapper::SessionLock(id), &live_settings(&self.app));
|
||||||
|
self.surface_views.insert(
|
||||||
|
id,
|
||||||
|
(
|
||||||
|
None, // TODO parent for layer shell, platform specific option maybe?
|
||||||
|
SurfaceIdWrapper::SessionLock(id),
|
||||||
|
live_settings,
|
||||||
|
view,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
live_settings_task.chain(get_lock_surface(id, output))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -394,7 +394,7 @@ where
|
||||||
{
|
{
|
||||||
nav = nav
|
nav = nav
|
||||||
.window_id_maybe(self.core().main_window_id())
|
.window_id_maybe(self.core().main_window_id())
|
||||||
.on_surface_action(|m| crate::Action::Cosmic(crate::app::Action::Surface(m)))
|
.on_surface_action(|action| crate::Action::Surface(action.flatten()))
|
||||||
}
|
}
|
||||||
let mut nav = nav
|
let mut nav = nav
|
||||||
.into_container()
|
.into_container()
|
||||||
|
|
|
||||||
|
|
@ -295,7 +295,7 @@ impl Context {
|
||||||
content: impl Into<Element<'a, Message>>,
|
content: impl Into<Element<'a, Message>>,
|
||||||
tooltip: impl Into<Cow<'static, str>>,
|
tooltip: impl Into<Cow<'static, str>>,
|
||||||
has_popup: bool,
|
has_popup: bool,
|
||||||
on_surface_action: impl Fn(crate::surface::Action) -> Message + 'static,
|
on_surface_action: impl Fn(crate::surface::Action<Message>) -> Message + 'static,
|
||||||
parent_id: Option<window::Id>,
|
parent_id: Option<window::Id>,
|
||||||
) -> crate::widget::wayland::tooltip::widget::Tooltip<'a, Message, Message> {
|
) -> crate::widget::wayland::tooltip::widget::Tooltip<'a, Message, Message> {
|
||||||
let window_id = *TOOLTIP_WINDOW_ID;
|
let window_id = *TOOLTIP_WINDOW_ID;
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,40 @@
|
||||||
// Copyright 2025 System76 <info@system76.com>
|
// Copyright 2025 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use super::Action;
|
use super::{Action, View};
|
||||||
use crate::Application;
|
use crate::Application;
|
||||||
|
|
||||||
use iced::{Rectangle, window};
|
use iced::window;
|
||||||
#[cfg(all(wayland_platform, feature = "winit"))]
|
#[cfg(all(wayland_platform, feature = "winit"))]
|
||||||
use iced_runtime::platform_specific::wayland::CornerRadius;
|
use iced_runtime::platform_specific::wayland::CornerRadius;
|
||||||
#[cfg(wayland_platform)]
|
#[cfg(wayland_platform)]
|
||||||
use iced_runtime::platform_specific::wayland::layer_surface::IcedMargin;
|
use iced_runtime::platform_specific::wayland::layer_surface::IcedMargin;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Used to produce a destroy popup message from within a widget.
|
/// Used to produce a destroy popup message from within a widget.
|
||||||
#[cfg(wayland_platform)]
|
#[cfg(wayland_platform)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn destroy_popup(id: iced_core::window::Id) -> Action {
|
pub fn destroy_popup<M>(id: iced_core::window::Id) -> Action<M> {
|
||||||
Action::DestroyPopup(id)
|
Action::DestroyPopup(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(wayland_platform)]
|
#[cfg(wayland_platform)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn destroy_subsurface(id: iced_core::window::Id) -> Action {
|
pub fn destroy_subsurface<M>(id: iced_core::window::Id) -> Action<M> {
|
||||||
Action::DestroySubsurface(id)
|
Action::DestroySubsurface(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(wayland_platform)]
|
#[cfg(wayland_platform)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn destroy_window(id: iced_core::window::Id) -> Action {
|
pub fn destroy_window<M>(id: iced_core::window::Id) -> Action<M> {
|
||||||
Action::DestroyWindow(id)
|
Action::DestroyWindow(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(wayland_platform)]
|
#[cfg(wayland_platform)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn destroy_layer_shell(id: iced_core::window::Id) -> Action {
|
pub fn destroy_layer_shell<M>(id: iced_core::window::Id) -> Action<M> {
|
||||||
Action::DestroyLayerShell(id)
|
Action::DestroyLayerShell(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,7 +66,7 @@ pub fn app_window<App: Application>(
|
||||||
live_settings: impl Fn(&App) -> LiveSettings + Send + Sync + 'static,
|
live_settings: impl Fn(&App) -> LiveSettings + Send + Sync + 'static,
|
||||||
settings: impl Fn(&mut App) -> window::Settings + Send + Sync + 'static,
|
settings: impl Fn(&mut App) -> window::Settings + Send + Sync + 'static,
|
||||||
view: BoxedView<App>,
|
view: BoxedView<App>,
|
||||||
) -> (window::Id, Action) {
|
) -> (window::Id, Action<App::Message>) {
|
||||||
let id = window::Id::unique();
|
let id = window::Id::unique();
|
||||||
|
|
||||||
let boxed: Box<dyn Fn(&mut App) -> window::Settings + Send + Sync + 'static> =
|
let boxed: Box<dyn Fn(&mut App) -> window::Settings + Send + Sync + 'static> =
|
||||||
|
|
@ -99,7 +100,7 @@ pub fn simple_window<Message: 'static>(
|
||||||
view: Option<
|
view: Option<
|
||||||
impl Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
|
impl Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
|
||||||
>,
|
>,
|
||||||
) -> (window::Id, Action) {
|
) -> (window::Id, Action<Message>) {
|
||||||
let id = window::Id::unique();
|
let id = window::Id::unique();
|
||||||
|
|
||||||
let boxed: Box<dyn Fn() -> window::Settings + Send + Sync + 'static> = Box::new(settings);
|
let boxed: Box<dyn Fn() -> window::Settings + Send + Sync + 'static> = Box::new(settings);
|
||||||
|
|
@ -114,16 +115,7 @@ pub fn simple_window<Message: 'static>(
|
||||||
id,
|
id,
|
||||||
Arc::new(boxed),
|
Arc::new(boxed),
|
||||||
Arc::new(boxed_live),
|
Arc::new(boxed_live),
|
||||||
view.map(|view| {
|
view.map(|view| Arc::new(view) as View<Message>),
|
||||||
let boxed: Box<
|
|
||||||
dyn Fn() -> crate::Element<'static, crate::Action<Message>>
|
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ 'static,
|
|
||||||
> = Box::new(view);
|
|
||||||
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
|
|
||||||
Arc::new(boxed)
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +129,7 @@ pub fn app_popup<App: Application>(
|
||||||
+ Sync
|
+ Sync
|
||||||
+ 'static,
|
+ 'static,
|
||||||
view: BoxedView<App>,
|
view: BoxedView<App>,
|
||||||
) -> Action {
|
) -> Action<App::Message> {
|
||||||
let boxed: Box<
|
let boxed: Box<
|
||||||
dyn Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
|
dyn Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
|
||||||
+ Send
|
+ Send
|
||||||
|
|
@ -171,7 +163,7 @@ pub fn simple_subsurface<Message: 'static>(
|
||||||
view: Option<
|
view: Option<
|
||||||
Box<dyn Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static>,
|
Box<dyn Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static>,
|
||||||
>,
|
>,
|
||||||
) -> Action {
|
) -> Action<Message> {
|
||||||
let boxed: Box<
|
let boxed: Box<
|
||||||
dyn Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings
|
dyn Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings
|
||||||
+ Send
|
+ Send
|
||||||
|
|
@ -183,10 +175,7 @@ pub fn simple_subsurface<Message: 'static>(
|
||||||
Action::Subsurface(
|
Action::Subsurface(
|
||||||
Arc::new(boxed),
|
Arc::new(boxed),
|
||||||
Arc::new(Box::new(LiveSettings::default)),
|
Arc::new(Box::new(LiveSettings::default)),
|
||||||
view.map(|view| {
|
view.map(|view| Arc::from(view) as View<Message>),
|
||||||
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(view);
|
|
||||||
Arc::new(boxed)
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,7 +191,7 @@ pub fn simple_popup<Message: 'static>(
|
||||||
view: Option<
|
view: Option<
|
||||||
impl Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
|
impl Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
|
||||||
>,
|
>,
|
||||||
) -> Action {
|
) -> Action<Message> {
|
||||||
let boxed: Box<
|
let boxed: Box<
|
||||||
dyn Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
|
dyn Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
|
||||||
+ Send
|
+ Send
|
||||||
|
|
@ -217,13 +206,7 @@ pub fn simple_popup<Message: 'static>(
|
||||||
Action::Popup(
|
Action::Popup(
|
||||||
Arc::new(boxed),
|
Arc::new(boxed),
|
||||||
Arc::new(boxed_live),
|
Arc::new(boxed_live),
|
||||||
view.map(|view| {
|
view.map(|view| Arc::new(view) as View<Message>),
|
||||||
let boxed: Box<
|
|
||||||
dyn Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
|
|
||||||
> = Box::new(view);
|
|
||||||
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
|
|
||||||
Arc::new(boxed)
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,7 +222,7 @@ pub fn subsurface<App: Application>(
|
||||||
+ 'static,
|
+ 'static,
|
||||||
// XXX Boxed trait object is required for less cumbersome type inference, but we box it anyways.
|
// XXX Boxed trait object is required for less cumbersome type inference, but we box it anyways.
|
||||||
view: BoxedView<App>,
|
view: BoxedView<App>,
|
||||||
) -> Action {
|
) -> Action<App::Message> {
|
||||||
let boxed: Box<
|
let boxed: Box<
|
||||||
dyn Fn(
|
dyn Fn(
|
||||||
&mut App,
|
&mut App,
|
||||||
|
|
@ -273,7 +256,7 @@ pub fn simple_layer_shell<Message: 'static>(
|
||||||
view: Option<
|
view: Option<
|
||||||
impl Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
|
impl Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
|
||||||
>,
|
>,
|
||||||
) -> Action {
|
) -> Action<Message> {
|
||||||
let boxed: Box<
|
let boxed: Box<
|
||||||
dyn Fn()
|
dyn Fn()
|
||||||
-> iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings
|
-> iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings
|
||||||
|
|
@ -287,13 +270,27 @@ pub fn simple_layer_shell<Message: 'static>(
|
||||||
Action::LayerShell(
|
Action::LayerShell(
|
||||||
Arc::new(boxed),
|
Arc::new(boxed),
|
||||||
Arc::new(boxed_live),
|
Arc::new(boxed_live),
|
||||||
view.map(|view| {
|
view.map(|view| Arc::new(view) as View<Message>),
|
||||||
let boxed: Box<
|
)
|
||||||
dyn Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
|
}
|
||||||
> = Box::new(view);
|
|
||||||
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
|
#[cfg(wayland_platform)]
|
||||||
Arc::new(boxed)
|
#[must_use]
|
||||||
}),
|
pub fn lock<Message: 'static>(
|
||||||
|
live_settings: impl Fn() -> LiveSettings + Send + Sync + 'static,
|
||||||
|
id: window::Id,
|
||||||
|
output: cctk::wayland_client::protocol::wl_output::WlOutput,
|
||||||
|
view: Option<
|
||||||
|
impl Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
|
||||||
|
>,
|
||||||
|
) -> Action<Message> {
|
||||||
|
let boxed_live: Box<dyn Fn() -> LiveSettings + Send + Sync + 'static> = Box::new(live_settings);
|
||||||
|
let boxed_live: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed_live);
|
||||||
|
Action::Lock(
|
||||||
|
id,
|
||||||
|
output,
|
||||||
|
Arc::new(boxed_live),
|
||||||
|
view.map(|view| Arc::new(view) as View<Message>),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -310,7 +307,7 @@ pub fn app_layer_shell<App: Application>(
|
||||||
+ 'static,
|
+ 'static,
|
||||||
// XXX Boxed trait object is required for less cumbersome type inference, but we box it anyways.
|
// XXX Boxed trait object is required for less cumbersome type inference, but we box it anyways.
|
||||||
view: BoxedView<App>,
|
view: BoxedView<App>,
|
||||||
) -> Action {
|
) -> Action<App::Message> {
|
||||||
let boxed: Box<
|
let boxed: Box<
|
||||||
dyn Fn(
|
dyn Fn(
|
||||||
&mut App,
|
&mut App,
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,28 @@ use std::sync::Arc;
|
||||||
|
|
||||||
type BoxedSetting = Arc<Box<dyn Any + Send + Sync + 'static>>;
|
type BoxedSetting = Arc<Box<dyn Any + Send + Sync + 'static>>;
|
||||||
|
|
||||||
|
/// Produces the content of a surface created from within a widget.
|
||||||
|
///
|
||||||
|
/// Typed on the message the widget publishes.
|
||||||
|
pub type View<M> =
|
||||||
|
Arc<dyn Fn() -> crate::Element<'static, crate::Action<M>> + Send + Sync + 'static>;
|
||||||
|
|
||||||
/// Ignore this message in your application. It will be intercepted.
|
/// Ignore this message in your application. It will be intercepted.
|
||||||
|
///
|
||||||
|
/// `M` is the message type of whoever created the action. The ones prefixed with `App` take the
|
||||||
|
/// application itself and are type-erased, the others carry a [`View`] typed on `M`.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Action {
|
pub enum Action<M> {
|
||||||
/// Create a subsurface with a view function accepting the App as a parameter
|
/// Create a subsurface with a view function accepting the App as a parameter
|
||||||
AppSubsurface(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
AppSubsurface(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
||||||
/// Create a subsurface with a view function
|
/// Create a subsurface with a view function
|
||||||
Subsurface(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
Subsurface(BoxedSetting, BoxedSetting, Option<View<M>>),
|
||||||
/// Destroy a subsurface with a view function
|
/// Destroy a subsurface with a view function
|
||||||
DestroySubsurface(iced::window::Id),
|
DestroySubsurface(iced::window::Id),
|
||||||
/// Create a popup with a view function accepting the App as a parameter
|
/// Create a popup with a view function accepting the App as a parameter
|
||||||
AppPopup(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
AppPopup(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
||||||
/// Create a popup
|
/// Create a popup
|
||||||
Popup(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
Popup(BoxedSetting, BoxedSetting, Option<View<M>>),
|
||||||
/// Destroy a subsurface with a view function
|
/// Destroy a subsurface with a view function
|
||||||
DestroyPopup(iced::window::Id),
|
DestroyPopup(iced::window::Id),
|
||||||
/// Destroys the global tooltip popup subsurface
|
/// Destroys the global tooltip popup subsurface
|
||||||
|
|
@ -41,7 +50,7 @@ pub enum Action {
|
||||||
iced::window::Id,
|
iced::window::Id,
|
||||||
BoxedSetting,
|
BoxedSetting,
|
||||||
BoxedSetting,
|
BoxedSetting,
|
||||||
Option<BoxedSetting>,
|
Option<View<M>>,
|
||||||
),
|
),
|
||||||
/// Destroy a window
|
/// Destroy a window
|
||||||
DestroyWindow(iced::window::Id),
|
DestroyWindow(iced::window::Id),
|
||||||
|
|
@ -50,11 +59,23 @@ pub enum Action {
|
||||||
AppLayerShell(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
AppLayerShell(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
||||||
|
|
||||||
/// Create a layer shell surface with a view function
|
/// Create a layer shell surface with a view function
|
||||||
LayerShell(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
LayerShell(BoxedSetting, BoxedSetting, Option<View<M>>),
|
||||||
|
|
||||||
/// Destroy a layer shell surface
|
/// Destroy a layer shell surface
|
||||||
DestroyLayerShell(iced::window::Id),
|
DestroyLayerShell(iced::window::Id),
|
||||||
|
|
||||||
|
#[cfg(wayland_platform)]
|
||||||
|
/// Create a lock shell surface with a view function
|
||||||
|
Lock(
|
||||||
|
iced::window::Id,
|
||||||
|
cctk::wayland_client::protocol::wl_output::WlOutput,
|
||||||
|
BoxedSetting,
|
||||||
|
Option<View<M>>,
|
||||||
|
),
|
||||||
|
|
||||||
|
/// Destroy a lock surface
|
||||||
|
DestroyLock(iced::window::Id),
|
||||||
|
|
||||||
/// Responsive menu bar update
|
/// Responsive menu bar update
|
||||||
ResponsiveMenuBar {
|
ResponsiveMenuBar {
|
||||||
/// Id of the menu bar
|
/// Id of the menu bar
|
||||||
|
|
@ -66,14 +87,76 @@ pub enum Action {
|
||||||
},
|
},
|
||||||
Ignore,
|
Ignore,
|
||||||
SyncLiveSettings(iced::window::Id),
|
SyncLiveSettings(iced::window::Id),
|
||||||
Task(Arc<dyn Fn() -> Task<Action> + Send + Sync>),
|
Task(Arc<dyn Fn() -> Task<Action<M>> + Send + Sync>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn surface_task<M: Send + 'static>(action: Action) -> Task<crate::Action<M>> {
|
impl<M: 'static> Action<M> {
|
||||||
crate::task::message(crate::Action::Cosmic(crate::app::Action::Surface(action)))
|
/// Re-type the action for a component whose messages are wrapped by `f`.
|
||||||
|
///
|
||||||
|
/// Similar to [`iced::Element::map`]. A component that maps a widget's messages must
|
||||||
|
/// map the widget's surface actions too.
|
||||||
|
#[must_use]
|
||||||
|
pub fn map<N: 'static>(self, f: impl Fn(M) -> N + Clone + Send + Sync + 'static) -> Action<N> {
|
||||||
|
self.map_actions(move |action| action.map(f.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_actions<N: 'static>(
|
||||||
|
self,
|
||||||
|
g: impl Fn(crate::Action<M>) -> crate::Action<N> + Clone + Send + Sync + 'static,
|
||||||
|
) -> Action<N> {
|
||||||
|
let map_view = |view: Option<View<M>>| -> Option<View<N>> {
|
||||||
|
let view = view?;
|
||||||
|
let g = g.clone();
|
||||||
|
Some(Arc::new(move || view().map(g.clone())))
|
||||||
|
};
|
||||||
|
match self {
|
||||||
|
Action::AppSubsurface(a, b, c) => Action::AppSubsurface(a, b, c),
|
||||||
|
Action::Subsurface(a, b, view) => Action::Subsurface(a, b, map_view(view)),
|
||||||
|
Action::DestroySubsurface(id) => Action::DestroySubsurface(id),
|
||||||
|
Action::AppPopup(a, b, c) => Action::AppPopup(a, b, c),
|
||||||
|
Action::Popup(a, b, view) => Action::Popup(a, b, map_view(view)),
|
||||||
|
Action::DestroyPopup(id) => Action::DestroyPopup(id),
|
||||||
|
Action::DestroyTooltipPopup => Action::DestroyTooltipPopup,
|
||||||
|
Action::AppWindow(id, a, b, c) => Action::AppWindow(id, a, b, c),
|
||||||
|
Action::Window(id, a, b, view) => Action::Window(id, a, b, map_view(view)),
|
||||||
|
Action::DestroyWindow(id) => Action::DestroyWindow(id),
|
||||||
|
Action::AppLayerShell(a, b, c) => Action::AppLayerShell(a, b, c),
|
||||||
|
Action::LayerShell(a, b, view) => Action::LayerShell(a, b, map_view(view)),
|
||||||
|
Action::DestroyLayerShell(id) => Action::DestroyLayerShell(id),
|
||||||
|
#[cfg(wayland_platform)]
|
||||||
|
Action::Lock(id, output, a, view) => Action::Lock(id, output, a, map_view(view)),
|
||||||
|
Action::DestroyLock(id) => Action::DestroyLock(id),
|
||||||
|
Action::ResponsiveMenuBar {
|
||||||
|
menu_bar,
|
||||||
|
limits,
|
||||||
|
size,
|
||||||
|
} => Action::ResponsiveMenuBar {
|
||||||
|
menu_bar,
|
||||||
|
limits,
|
||||||
|
size,
|
||||||
|
},
|
||||||
|
Action::Ignore => Action::Ignore,
|
||||||
|
Action::SyncLiveSettings(id) => Action::SyncLiveSettings(id),
|
||||||
|
Action::Task(task) => Action::Task(Arc::new(move || {
|
||||||
|
let g = g.clone();
|
||||||
|
task().map(move |action| action.map_actions(g.clone()))
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Action {
|
impl<M: 'static> Action<crate::Action<M>> {
|
||||||
|
#[must_use]
|
||||||
|
pub fn flatten(self) -> Action<M> {
|
||||||
|
self.map_actions(crate::Action::flatten)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn surface_task<M: Send + 'static>(action: Action<M>) -> Task<crate::Action<M>> {
|
||||||
|
crate::task::message(crate::Action::Surface(action))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> std::fmt::Debug for Action<M> {
|
||||||
#[cold]
|
#[cold]
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -83,11 +166,11 @@ impl std::fmt::Debug for Action {
|
||||||
.field(arg1)
|
.field(arg1)
|
||||||
.field(arg2)
|
.field(arg2)
|
||||||
.finish(),
|
.finish(),
|
||||||
Self::Subsurface(arg0, arg1, arg2) => f
|
Self::Subsurface(arg0, arg1, view) => f
|
||||||
.debug_tuple("Subsurface")
|
.debug_tuple("Subsurface")
|
||||||
.field(arg0)
|
.field(arg0)
|
||||||
.field(arg1)
|
.field(arg1)
|
||||||
.field(arg2)
|
.field(&view.as_ref().map(|_| "view"))
|
||||||
.finish(),
|
.finish(),
|
||||||
Self::DestroySubsurface(arg0) => {
|
Self::DestroySubsurface(arg0) => {
|
||||||
f.debug_tuple("DestroySubsurface").field(arg0).finish()
|
f.debug_tuple("DestroySubsurface").field(arg0).finish()
|
||||||
|
|
@ -98,11 +181,11 @@ impl std::fmt::Debug for Action {
|
||||||
.field(arg1)
|
.field(arg1)
|
||||||
.field(arg2)
|
.field(arg2)
|
||||||
.finish(),
|
.finish(),
|
||||||
Self::Popup(arg0, arg1, arg2) => f
|
Self::Popup(arg0, arg1, view) => f
|
||||||
.debug_tuple("Popup")
|
.debug_tuple("Popup")
|
||||||
.field(arg0)
|
.field(arg0)
|
||||||
.field(arg1)
|
.field(arg1)
|
||||||
.field(arg2)
|
.field(&view.as_ref().map(|_| "view"))
|
||||||
.finish(),
|
.finish(),
|
||||||
Self::DestroyPopup(arg0) => f.debug_tuple("DestroyPopup").field(arg0).finish(),
|
Self::DestroyPopup(arg0) => f.debug_tuple("DestroyPopup").field(arg0).finish(),
|
||||||
Self::DestroyTooltipPopup => f.debug_tuple("DestroyTooltipPopup").finish(),
|
Self::DestroyTooltipPopup => f.debug_tuple("DestroyTooltipPopup").finish(),
|
||||||
|
|
@ -124,12 +207,12 @@ impl std::fmt::Debug for Action {
|
||||||
.field(arg1)
|
.field(arg1)
|
||||||
.field(arg2)
|
.field(arg2)
|
||||||
.finish(),
|
.finish(),
|
||||||
Self::Window(id, arg0, arg1, arg2) => f
|
Self::Window(id, arg0, arg1, view) => f
|
||||||
.debug_tuple("Window")
|
.debug_tuple("Window")
|
||||||
.field(id)
|
.field(id)
|
||||||
.field(arg0)
|
.field(arg0)
|
||||||
.field(arg1)
|
.field(arg1)
|
||||||
.field(arg2)
|
.field(&view.as_ref().map(|_| "view"))
|
||||||
.finish(),
|
.finish(),
|
||||||
Self::DestroyWindow(arg0) => f.debug_tuple("DestroyWindow").field(arg0).finish(),
|
Self::DestroyWindow(arg0) => f.debug_tuple("DestroyWindow").field(arg0).finish(),
|
||||||
Self::Task(_) => f.debug_tuple("Future").finish(),
|
Self::Task(_) => f.debug_tuple("Future").finish(),
|
||||||
|
|
@ -139,15 +222,24 @@ impl std::fmt::Debug for Action {
|
||||||
.field(arg1)
|
.field(arg1)
|
||||||
.field(arg2)
|
.field(arg2)
|
||||||
.finish(),
|
.finish(),
|
||||||
Self::LayerShell(arg, arg1, arg2) => f
|
Self::LayerShell(arg0, arg1, view) => f
|
||||||
.debug_tuple("LayerShell")
|
.debug_tuple("LayerShell")
|
||||||
.field(arg)
|
.field(arg0)
|
||||||
.field(arg1)
|
.field(arg1)
|
||||||
.field(arg2)
|
.field(&view.as_ref().map(|_| "view"))
|
||||||
.finish(),
|
.finish(),
|
||||||
Self::DestroyLayerShell(arg0) => {
|
Self::DestroyLayerShell(arg0) => {
|
||||||
f.debug_tuple("DestroyLayerShell").field(arg0).finish()
|
f.debug_tuple("DestroyLayerShell").field(arg0).finish()
|
||||||
}
|
}
|
||||||
|
#[cfg(wayland_platform)]
|
||||||
|
Self::Lock(id, output, arg0, view) => f
|
||||||
|
.debug_tuple("Lock")
|
||||||
|
.field(id)
|
||||||
|
.field(output)
|
||||||
|
.field(arg0)
|
||||||
|
.field(&view.as_ref().map(|_| "view"))
|
||||||
|
.finish(),
|
||||||
|
Self::DestroyLock(arg0) => f.debug_tuple("DestroyLock").field(arg0).finish(),
|
||||||
Self::SyncLiveSettings(arg0) => f.debug_tuple("SyncLiveSettings").field(arg0).finish(),
|
Self::SyncLiveSettings(arg0) => f.debug_tuple("SyncLiveSettings").field(arg0).finish(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ pub enum Button {
|
||||||
IconVertical,
|
IconVertical,
|
||||||
Image,
|
Image,
|
||||||
Link,
|
Link,
|
||||||
|
LinkActive,
|
||||||
ListItem([f32; 4]),
|
ListItem([f32; 4]),
|
||||||
MenuFolder,
|
MenuFolder,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
|
@ -121,6 +122,13 @@ pub fn appearance(
|
||||||
corner_radii = &cosmic.corner_radii.radius_0;
|
corner_radii = &cosmic.corner_radii.radius_0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button::LinkActive => {
|
||||||
|
appearance.background = Some(Background::Color(cosmic.text_button.hover.into()));
|
||||||
|
appearance.icon_color = Some(cosmic.accent_text_color().into());
|
||||||
|
appearance.text_color = Some(cosmic.accent_text_color().into());
|
||||||
|
corner_radii = &cosmic.corner_radii.radius_xs;
|
||||||
|
}
|
||||||
|
|
||||||
Button::Custom { .. } => (),
|
Button::Custom { .. } => (),
|
||||||
Button::AppletMenu => {
|
Button::AppletMenu => {
|
||||||
let (background, _, _) = color(&cosmic.text_button);
|
let (background, _, _) = color(&cosmic.text_button);
|
||||||
|
|
|
||||||
|
|
@ -1368,18 +1368,22 @@ impl iced_widget::text::Catalog for Theme {
|
||||||
|
|
||||||
fn style(&self, class: &Self::Class<'_>) -> iced_widget::text::Style {
|
fn style(&self, class: &Self::Class<'_>) -> iced_widget::text::Style {
|
||||||
let selected_fill = self.cosmic().accent.base.into();
|
let selected_fill = self.cosmic().accent.base.into();
|
||||||
|
let selected_text_color = Some(self.cosmic().on_accent_color().into());
|
||||||
match class {
|
match class {
|
||||||
Text::Accent => iced_widget::text::Style {
|
Text::Accent => iced_widget::text::Style {
|
||||||
color: Some(self.cosmic().accent_text_color().into()),
|
color: Some(self.cosmic().accent_text_color().into()),
|
||||||
selected_fill,
|
selected_fill,
|
||||||
|
selected_text_color,
|
||||||
},
|
},
|
||||||
Text::Default => iced_widget::text::Style {
|
Text::Default => iced_widget::text::Style {
|
||||||
color: None,
|
color: None,
|
||||||
selected_fill,
|
selected_fill,
|
||||||
|
selected_text_color,
|
||||||
},
|
},
|
||||||
Text::Color(c) => iced_widget::text::Style {
|
Text::Color(c) => iced_widget::text::Style {
|
||||||
color: Some(*c),
|
color: Some(*c),
|
||||||
selected_fill,
|
selected_fill,
|
||||||
|
selected_text_color,
|
||||||
},
|
},
|
||||||
Text::Custom(f) => f(self),
|
Text::Custom(f) => f(self),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ use iced_core::{Length, Point, Size, mouse, touch};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::widget::RcWrapper;
|
||||||
|
|
||||||
/// A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation.
|
/// A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation.
|
||||||
pub fn context_menu<'a, Message: 'static + Clone>(
|
pub fn context_menu<'a, Message: 'static + Clone>(
|
||||||
content: impl Into<crate::Element<'a, Message>>,
|
content: impl Into<crate::Element<'a, Message>>,
|
||||||
|
|
@ -33,6 +35,9 @@ pub fn context_menu<'a, Message: 'static + Clone>(
|
||||||
}),
|
}),
|
||||||
close_on_escape: true,
|
close_on_escape: true,
|
||||||
window_id: window::Id::RESERVED,
|
window_id: window::Id::RESERVED,
|
||||||
|
item_width: ItemWidth::Uniform(240),
|
||||||
|
on_open: None,
|
||||||
|
on_close: None,
|
||||||
on_surface_action: None,
|
on_surface_action: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -53,12 +58,37 @@ pub struct ContextMenu<'a, Message> {
|
||||||
context_menu: Option<Vec<menu::Tree<Message>>>,
|
context_menu: Option<Vec<menu::Tree<Message>>>,
|
||||||
pub window_id: window::Id,
|
pub window_id: window::Id,
|
||||||
pub close_on_escape: bool,
|
pub close_on_escape: bool,
|
||||||
|
/// Width of each menu item, and therefore of the menu.
|
||||||
|
pub item_width: ItemWidth,
|
||||||
|
/// Emitted when the menu opens, so the application can mark what was right-clicked.
|
||||||
|
#[setters(strip_option)]
|
||||||
|
pub on_open: Option<Message>,
|
||||||
|
/// Emitted when the menu closes by any path, including the compositor dismissing it.
|
||||||
|
#[setters(strip_option)]
|
||||||
|
pub on_close: Option<Message>,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
pub(crate) on_surface_action:
|
pub(crate) on_surface_action:
|
||||||
Option<Arc<dyn Fn(crate::surface::Action) -> Message + Send + Sync + 'static>>,
|
Option<Arc<dyn Fn(crate::surface::Action<Message>) -> Message + Send + Sync + 'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message: Clone + 'static> ContextMenu<'_, Message> {
|
impl<Message: Clone + 'static> ContextMenu<'_, Message> {
|
||||||
|
/// Publish `on_open`/`on_close` when the open state changed since the last report.
|
||||||
|
fn report_open_state(
|
||||||
|
&self,
|
||||||
|
state: &mut LocalState<Message>,
|
||||||
|
shell: &mut iced_core::Shell<'_, Message>,
|
||||||
|
) {
|
||||||
|
let open = state.menu_bar_state.inner.with_data(|d| d.open);
|
||||||
|
if open == state.reported_open {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.reported_open = open;
|
||||||
|
let message = if open { &self.on_open } else { &self.on_close };
|
||||||
|
if let Some(message) = message.clone() {
|
||||||
|
shell.publish(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(wayland_platform)]
|
#[cfg(wayland_platform)]
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn create_popup(
|
fn create_popup(
|
||||||
|
|
@ -68,7 +98,7 @@ impl<Message: Clone + 'static> ContextMenu<'_, Message> {
|
||||||
renderer: &crate::Renderer,
|
renderer: &crate::Renderer,
|
||||||
shell: &mut iced_core::Shell<'_, Message>,
|
shell: &mut iced_core::Shell<'_, Message>,
|
||||||
viewport: &iced::Rectangle,
|
viewport: &iced::Rectangle,
|
||||||
my_state: &mut LocalState,
|
my_state: &mut LocalState<Message>,
|
||||||
) {
|
) {
|
||||||
if self.window_id != window::Id::NONE && self.on_surface_action.is_some() {
|
if self.window_id != window::Id::NONE && self.on_surface_action.is_some() {
|
||||||
use crate::surface::action::{LiveSettings, destroy_popup};
|
use crate::surface::action::{LiveSettings, destroy_popup};
|
||||||
|
|
@ -91,16 +121,12 @@ impl<Message: Clone + 'static> ContextMenu<'_, Message> {
|
||||||
|
|
||||||
shell.publish(self.on_surface_action.as_ref().unwrap()(destroy_popup(id)));
|
shell.publish(self.on_surface_action.as_ref().unwrap()(destroy_popup(id)));
|
||||||
state.view_cursor = view_cursor;
|
state.view_cursor = view_cursor;
|
||||||
(
|
|
||||||
id,
|
|
||||||
layout.children().map(|lo| lo.bounds()).collect::<Vec<_>>(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
window::Id::unique(),
|
|
||||||
layout.children().map(|lo| lo.bounds()).collect(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
// A fresh id per popup, so the old popup's Done cannot be mistaken for the new one's
|
||||||
|
(
|
||||||
|
window::Id::unique(),
|
||||||
|
layout.children().map(|lo| lo.bounds()).collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
});
|
});
|
||||||
let Some(context_menu) = self.context_menu.as_mut() else {
|
let Some(context_menu) = self.context_menu.as_mut() else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -116,7 +142,7 @@ impl<Message: Clone + 'static> ContextMenu<'_, Message> {
|
||||||
click_outside: true,
|
click_outside: true,
|
||||||
click_inside: true,
|
click_inside: true,
|
||||||
},
|
},
|
||||||
item_width: ItemWidth::Uniform(240),
|
item_width: self.item_width,
|
||||||
item_height: ItemHeight::Dynamic(40),
|
item_height: ItemHeight::Dynamic(40),
|
||||||
bar_bounds: bounds,
|
bar_bounds: bounds,
|
||||||
main_offset: -(bounds.height as i32),
|
main_offset: -(bounds.height as i32),
|
||||||
|
|
@ -177,6 +203,7 @@ impl<Message: Clone + 'static> ContextMenu<'_, Message> {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let parent = self.window_id;
|
let parent = self.window_id;
|
||||||
|
let roots = my_state.roots.clone();
|
||||||
let t = THEME.lock().unwrap();
|
let t = THEME.lock().unwrap();
|
||||||
let styling = t.appearance(&crate::theme::menu_bar::MenuBarStyle::Default, false);
|
let styling = t.appearance(&crate::theme::menu_bar::MenuBarStyle::Default, false);
|
||||||
drop(t);
|
drop(t);
|
||||||
|
|
@ -203,8 +230,12 @@ impl<Message: Clone + 'static> ContextMenu<'_, Message> {
|
||||||
input_zone: None,
|
input_zone: None,
|
||||||
},
|
},
|
||||||
Some(move || {
|
Some(move || {
|
||||||
|
// Latest roots from the owner widget
|
||||||
|
let mut popup_menu = popup_menu.clone();
|
||||||
|
popup_menu.menu_roots =
|
||||||
|
std::borrow::Cow::Owned(roots.with_data(Clone::clone));
|
||||||
crate::Element::from(
|
crate::Element::from(
|
||||||
crate::widget::container(popup_menu.clone()).center(Length::Fill),
|
crate::widget::container(popup_menu).center(Length::Fill),
|
||||||
)
|
)
|
||||||
.map(crate::action::app)
|
.map(crate::action::app)
|
||||||
}),
|
}),
|
||||||
|
|
@ -215,7 +246,7 @@ impl<Message: Clone + 'static> ContextMenu<'_, Message> {
|
||||||
|
|
||||||
pub fn on_surface_action(
|
pub fn on_surface_action(
|
||||||
mut self,
|
mut self,
|
||||||
handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static,
|
handler: impl Fn(crate::surface::Action<Message>) -> Message + Send + Sync + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.on_surface_action = Some(Arc::new(handler));
|
self.on_surface_action = Some(Arc::new(handler));
|
||||||
self
|
self
|
||||||
|
|
@ -226,7 +257,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
for ContextMenu<'_, Message>
|
for ContextMenu<'_, Message>
|
||||||
{
|
{
|
||||||
fn tag(&self) -> tree::Tag {
|
fn tag(&self) -> tree::Tag {
|
||||||
tree::Tag::of::<LocalState>()
|
tree::Tag::of::<LocalState<Message>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state(&self) -> tree::State {
|
fn state(&self) -> tree::State {
|
||||||
|
|
@ -235,6 +266,9 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
context_cursor: Point::default(),
|
context_cursor: Point::default(),
|
||||||
fingers_pressed: Default::default(),
|
fingers_pressed: Default::default(),
|
||||||
menu_bar_state: Default::default(),
|
menu_bar_state: Default::default(),
|
||||||
|
reported_open: false,
|
||||||
|
roots: RcWrapper::new(self.context_menu.clone().unwrap_or_default()),
|
||||||
|
reshape: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,10 +302,32 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
|
|
||||||
fn diff(&mut self, tree: &mut Tree) {
|
fn diff(&mut self, tree: &mut Tree) {
|
||||||
tree.diff_children(std::slice::from_mut(&mut self.content));
|
tree.diff_children(std::slice::from_mut(&mut self.content));
|
||||||
let state = tree.state.downcast_mut::<LocalState>();
|
let state = tree.state.downcast_mut::<LocalState<Message>>();
|
||||||
state.menu_bar_state.inner.with_data_mut(|inner| {
|
if let Some(context_menu) = self.context_menu.as_mut() {
|
||||||
menu_roots_diff(self.context_menu.as_mut().unwrap(), &mut inner.tree);
|
// The popup's item slots were measured at open from the item widgets. Items of
|
||||||
});
|
// another kind or count, such as a divider in a new place, do not fit those slots,
|
||||||
|
// so rebuild the popup on the next `update`. Same-shaped items, such as a
|
||||||
|
// relabeled button, update in place.
|
||||||
|
let popup_open = state
|
||||||
|
.menu_bar_state
|
||||||
|
.inner
|
||||||
|
.with_data(|d| !d.popup_id.is_empty());
|
||||||
|
let shape = |roots: &Vec<menu::Tree<Message>>| -> Vec<tree::Tag> {
|
||||||
|
roots.first().map_or_else(Vec::new, |root| {
|
||||||
|
root.flattern().iter().map(|mt| mt.item.tag()).collect()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
if popup_open && state.roots.with_data(shape) != shape(context_menu) {
|
||||||
|
state.reshape = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.menu_bar_state.inner.with_data_mut(|inner| {
|
||||||
|
menu_roots_diff(context_menu, &mut inner.tree);
|
||||||
|
});
|
||||||
|
state
|
||||||
|
.roots
|
||||||
|
.with_data_mut(|roots| roots.clone_from(context_menu));
|
||||||
|
}
|
||||||
|
|
||||||
// if let Some(ref mut context_menus) = self.context_menu {
|
// if let Some(ref mut context_menus) = self.context_menu {
|
||||||
// for (menu, tree) in context_menus
|
// for (menu, tree) in context_menus
|
||||||
|
|
@ -319,6 +375,38 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
layout: iced_core::Layout<'_>,
|
||||||
|
cursor: iced_core::mouse::Cursor,
|
||||||
|
viewport: &iced::Rectangle,
|
||||||
|
renderer: &crate::Renderer,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self.content.as_widget().mouse_interaction(
|
||||||
|
&tree.children[0],
|
||||||
|
layout,
|
||||||
|
cursor,
|
||||||
|
viewport,
|
||||||
|
renderer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drag_destinations(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
layout: iced_core::Layout<'_>,
|
||||||
|
renderer: &crate::Renderer,
|
||||||
|
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||||
|
) {
|
||||||
|
self.content.as_widget().drag_destinations(
|
||||||
|
&tree.children[0],
|
||||||
|
layout,
|
||||||
|
renderer,
|
||||||
|
dnd_rectangles,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn operate(
|
fn operate(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
|
|
@ -343,9 +431,41 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
shell: &mut iced_core::Shell<'_, Message>,
|
shell: &mut iced_core::Shell<'_, Message>,
|
||||||
viewport: &iced::Rectangle,
|
viewport: &iced::Rectangle,
|
||||||
) {
|
) {
|
||||||
let state = tree.state.downcast_mut::<LocalState>();
|
let state = tree.state.downcast_mut::<LocalState<Message>>();
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
|
// The compositor dismissed our popup: nothing else tells this state about it.
|
||||||
|
#[cfg(wayland_platform)]
|
||||||
|
if let iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(
|
||||||
|
iced::event::wayland::Event::Popup(iced::event::wayland::PopupEvent::Done, _, popup),
|
||||||
|
)) = event
|
||||||
|
{
|
||||||
|
state.menu_bar_state.inner.with_data_mut(|d| {
|
||||||
|
if d.popup_id.get(&self.window_id) == Some(popup) {
|
||||||
|
d.popup_id.remove(&self.window_id);
|
||||||
|
d.reset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// The menu changed shape while open
|
||||||
|
#[cfg(wayland_platform)]
|
||||||
|
if state.reshape {
|
||||||
|
state.reshape = false;
|
||||||
|
if state.menu_bar_state.inner.with_data(|d| d.open) {
|
||||||
|
if let Some(context_menu) = self.context_menu.as_mut() {
|
||||||
|
state.menu_bar_state.inner.with_data_mut(|inner| {
|
||||||
|
menu_roots_diff(context_menu, &mut inner.tree);
|
||||||
|
});
|
||||||
|
state
|
||||||
|
.roots
|
||||||
|
.with_data_mut(|roots| roots.clone_from(context_menu));
|
||||||
|
}
|
||||||
|
let view_cursor = state.menu_bar_state.inner.with_data(|d| d.view_cursor);
|
||||||
|
self.create_popup(layout, view_cursor, renderer, shell, viewport, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// XXX this should reset the state if there are no other copies of the state, which implies no dropdown menus open.
|
// XXX this should reset the state if there are no other copies of the state, which implies no dropdown menus open.
|
||||||
let reset = self.window_id != window::Id::NONE
|
let reset = self.window_id != window::Id::NONE
|
||||||
&& state
|
&& state
|
||||||
|
|
@ -415,7 +535,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
&& (right_button_released(event) || (touch_lifted(event) && fingers_pressed == 2))
|
&& (right_button_released(event) || (touch_lifted(event) && fingers_pressed == 2))
|
||||||
{
|
{
|
||||||
state.context_cursor = cursor.position().unwrap_or_default();
|
state.context_cursor = cursor.position().unwrap_or_default();
|
||||||
let state = tree.state.downcast_mut::<LocalState>();
|
let state = tree.state.downcast_mut::<LocalState<Message>>();
|
||||||
state.menu_bar_state.inner.with_data_mut(|state| {
|
state.menu_bar_state.inner.with_data_mut(|state| {
|
||||||
state.open = true;
|
state.open = true;
|
||||||
state.view_cursor = cursor;
|
state.view_cursor = cursor;
|
||||||
|
|
@ -425,7 +545,9 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
self.create_popup(layout, cursor, renderer, shell, viewport, state);
|
self.create_popup(layout, cursor, renderer, shell, viewport, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shell.request_redraw();
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
|
self.report_open_state(tree.state.downcast_mut::<LocalState<Message>>(), shell);
|
||||||
return;
|
return;
|
||||||
} else if !was_open && right_button_released(event)
|
} else if !was_open && right_button_released(event)
|
||||||
|| (touch_lifted(event))
|
|| (touch_lifted(event))
|
||||||
|
|
@ -461,62 +583,76 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
viewport,
|
||||||
);
|
);
|
||||||
|
self.report_open_state(tree.state.downcast_mut::<LocalState<Message>>(), shell);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overlay<'b>(
|
fn overlay<'b>(
|
||||||
&'b mut self,
|
&'b mut self,
|
||||||
tree: &'b mut Tree,
|
tree: &'b mut Tree,
|
||||||
layout: iced_core::Layout<'_>,
|
layout: iced_core::Layout<'b>,
|
||||||
_renderer: &crate::Renderer,
|
renderer: &crate::Renderer,
|
||||||
_viewport: &iced::Rectangle,
|
viewport: &iced::Rectangle,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||||
|
// The wrapped content's overlays (tooltips, dropdowns, ...) always pass through
|
||||||
|
let content = self.content.as_widget_mut().overlay(
|
||||||
|
&mut tree.children[0],
|
||||||
|
layout,
|
||||||
|
renderer,
|
||||||
|
viewport,
|
||||||
|
translation,
|
||||||
|
);
|
||||||
|
|
||||||
#[cfg(wayland_platform)]
|
#[cfg(wayland_platform)]
|
||||||
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
|
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
|
||||||
&& self.window_id != window::Id::NONE
|
&& self.window_id != window::Id::NONE
|
||||||
&& self.on_surface_action.is_some()
|
&& self.on_surface_action.is_some()
|
||||||
{
|
{
|
||||||
return None;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
let state = tree.state.downcast_ref::<LocalState>();
|
let state = tree.state.downcast_ref::<LocalState<Message>>();
|
||||||
|
let Some(context_menu) = self.context_menu.as_mut() else {
|
||||||
let context_menu = self.context_menu.as_mut()?;
|
return content;
|
||||||
|
};
|
||||||
if !state.menu_bar_state.inner.with_data(|state| state.open) {
|
if !state.menu_bar_state.inner.with_data(|state| state.open) {
|
||||||
return None;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut bounds = layout.bounds();
|
// Anchor the menu to a 1x1 rectangle at the click, like the popup path does
|
||||||
bounds.x = state.context_cursor.x;
|
let bounds = iced::Rectangle::new(state.context_cursor, Size::new(1.0, 1.0));
|
||||||
bounds.y = state.context_cursor.y;
|
let menu = crate::widget::menu::Menu {
|
||||||
Some(
|
tree: state.menu_bar_state.clone(),
|
||||||
crate::widget::menu::Menu {
|
menu_roots: std::borrow::Cow::Owned(context_menu.clone()),
|
||||||
tree: state.menu_bar_state.clone(),
|
bounds_expand: 16,
|
||||||
menu_roots: std::borrow::Cow::Owned(context_menu.clone()),
|
menu_overlays_parent: true,
|
||||||
bounds_expand: 16,
|
close_condition: CloseCondition {
|
||||||
menu_overlays_parent: true,
|
leave: false,
|
||||||
close_condition: CloseCondition {
|
click_outside: true,
|
||||||
leave: false,
|
click_inside: true,
|
||||||
click_outside: true,
|
},
|
||||||
click_inside: true,
|
item_width: self.item_width,
|
||||||
},
|
item_height: ItemHeight::Dynamic(40),
|
||||||
item_width: ItemWidth::Uniform(240),
|
bar_bounds: bounds,
|
||||||
item_height: ItemHeight::Dynamic(40),
|
main_offset: 0,
|
||||||
bar_bounds: bounds,
|
cross_offset: 0,
|
||||||
main_offset: -(bounds.height as i32),
|
root_bounds_list: vec![bounds],
|
||||||
cross_offset: 0,
|
path_highlight: Some(PathHighlight::MenuActive),
|
||||||
root_bounds_list: vec![bounds],
|
style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default),
|
||||||
path_highlight: Some(PathHighlight::MenuActive),
|
position: Point::new(translation.x, translation.y),
|
||||||
style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default),
|
is_overlay: true,
|
||||||
position: Point::new(translation.x, translation.y),
|
window_id: window::Id::NONE,
|
||||||
is_overlay: true,
|
depth: 0,
|
||||||
window_id: window::Id::NONE,
|
on_surface_action: None,
|
||||||
depth: 0,
|
}
|
||||||
on_surface_action: None,
|
.overlay();
|
||||||
|
|
||||||
|
Some(match content {
|
||||||
|
Some(content) => {
|
||||||
|
iced_core::overlay::Group::with_children(vec![content, menu]).overlay()
|
||||||
}
|
}
|
||||||
.overlay(),
|
None => menu,
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "a11y")]
|
#[cfg(feature = "a11y")]
|
||||||
|
|
@ -556,8 +692,12 @@ fn touch_lifted(event: &Event) -> bool {
|
||||||
matches!(event, Event::Touch(touch::Event::FingerLifted { .. }))
|
matches!(event, Event::Touch(touch::Event::FingerLifted { .. }))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LocalState {
|
pub struct LocalState<Message> {
|
||||||
context_cursor: Point,
|
context_cursor: Point,
|
||||||
fingers_pressed: HashSet<Finger>,
|
fingers_pressed: HashSet<Finger>,
|
||||||
menu_bar_state: MenuBarState,
|
menu_bar_state: MenuBarState,
|
||||||
|
reported_open: bool,
|
||||||
|
roots: RcWrapper<Vec<menu::Tree<Message>>>,
|
||||||
|
/// menu shape has changed, rebuild it on the next `update`
|
||||||
|
reshape: bool,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ pub fn popup_dropdown<
|
||||||
selected: Option<usize>,
|
selected: Option<usize>,
|
||||||
on_selected: impl Fn(usize) -> Message + Send + Sync + 'static,
|
on_selected: impl Fn(usize) -> Message + Send + Sync + 'static,
|
||||||
_parent_id: window::Id,
|
_parent_id: window::Id,
|
||||||
_on_surface_action: impl Fn(surface::Action) -> Message + Send + Sync + 'static,
|
_on_surface_action: impl Fn(surface::Action<AppMessage>) -> Message + Send + Sync + 'static,
|
||||||
_map_action: impl Fn(Message) -> AppMessage + Send + Sync + 'static,
|
_map_action: impl Fn(Message) -> AppMessage + Send + Sync + 'static,
|
||||||
) -> Dropdown<'a, S, Message, AppMessage> {
|
) -> Dropdown<'a, S, Message, AppMessage> {
|
||||||
let dropdown: Dropdown<'_, S, Message, AppMessage> =
|
let dropdown: Dropdown<'_, S, Message, AppMessage> =
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,8 @@ where
|
||||||
#[setters(strip_option)]
|
#[setters(strip_option)]
|
||||||
font: Option<crate::font::Font>,
|
font: Option<crate::font::Font>,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
on_surface_action: Option<Arc<dyn Fn(surface::Action) -> Message + Send + Sync + 'static>>,
|
on_surface_action:
|
||||||
|
Option<Arc<dyn Fn(surface::Action<AppMessage>) -> Message + Send + Sync + 'static>>,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
action_map: Option<Arc<dyn Fn(Message) -> AppMessage + 'static + Send + Sync>>,
|
action_map: Option<Arc<dyn Fn(Message) -> AppMessage + 'static + Send + Sync>>,
|
||||||
#[setters(strip_option)]
|
#[setters(strip_option)]
|
||||||
|
|
@ -108,7 +109,7 @@ where
|
||||||
pub fn with_popup<NewAppMessage>(
|
pub fn with_popup<NewAppMessage>(
|
||||||
self,
|
self,
|
||||||
parent_id: window::Id,
|
parent_id: window::Id,
|
||||||
on_surface_action: impl Fn(surface::Action) -> Message + Send + Sync + 'static,
|
on_surface_action: impl Fn(surface::Action<NewAppMessage>) -> Message + Send + Sync + 'static,
|
||||||
action_map: impl Fn(Message) -> NewAppMessage + Send + Sync + 'static,
|
action_map: impl Fn(Message) -> NewAppMessage + Send + Sync + 'static,
|
||||||
) -> Dropdown<'a, S, Message, NewAppMessage> {
|
) -> Dropdown<'a, S, Message, NewAppMessage> {
|
||||||
let Self {
|
let Self {
|
||||||
|
|
@ -551,7 +552,9 @@ pub fn update<
|
||||||
selections: &[S],
|
selections: &[S],
|
||||||
state: impl FnOnce() -> &'a mut State,
|
state: impl FnOnce() -> &'a mut State,
|
||||||
_window_id: Option<window::Id>,
|
_window_id: Option<window::Id>,
|
||||||
on_surface_action: Option<Arc<dyn Fn(surface::Action) -> Message + Send + Sync + 'static>>,
|
on_surface_action: Option<
|
||||||
|
Arc<dyn Fn(surface::Action<AppMessage>) -> Message + Send + Sync + 'static>,
|
||||||
|
>,
|
||||||
action_map: Option<Arc<dyn Fn(Message) -> AppMessage + Send + Sync + 'static>>,
|
action_map: Option<Arc<dyn Fn(Message) -> AppMessage + Send + Sync + 'static>>,
|
||||||
icons: &[icon::Handle],
|
icons: &[icon::Handle],
|
||||||
gap: f32,
|
gap: f32,
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,10 @@ use super::Icon;
|
||||||
use crate::widget::{image, svg};
|
use crate::widget::{image, svg};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
|
use std::fs::File;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::path::PathBuf;
|
use std::io::Read;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[derive(Clone, Debug, Hash, derive_setters::Setters)]
|
#[derive(Clone, Debug, Hash, derive_setters::Setters)]
|
||||||
|
|
@ -31,6 +33,52 @@ pub enum Data {
|
||||||
Svg(svg::Handle),
|
Svg(svg::Handle),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SvgSource {
|
||||||
|
Path,
|
||||||
|
Bytes(Vec<u8>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn svg_source(path: &Path) -> Option<SvgSource> {
|
||||||
|
if path
|
||||||
|
.extension()
|
||||||
|
.and_then(OsStr::to_str)
|
||||||
|
.is_some_and(|extension| extension.eq_ignore_ascii_case("svg"))
|
||||||
|
{
|
||||||
|
return Some(SvgSource::Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Ok(mut file) = File::open(path) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(metadata) = file.metadata() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
const MAX_SVG_SIZE: u64 = 16 * 1024 * 1024;
|
||||||
|
if !metadata.file_type().is_file() || metadata.len() > MAX_SVG_SIZE {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut prefix = [0; 32];
|
||||||
|
let Ok(length) = file.read(&mut prefix) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let prefix = &prefix[..length];
|
||||||
|
|
||||||
|
if ::image::guess_format(prefix).is_ok() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut bytes = Vec::with_capacity(metadata.len() as usize);
|
||||||
|
bytes.extend_from_slice(prefix);
|
||||||
|
if file.read_to_end(&mut bytes).is_err() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let document = roxmltree::Document::parse(std::str::from_utf8(&bytes).ok()?).ok()?;
|
||||||
|
(document.root_element().tag_name().name() == "svg").then_some(SvgSource::Bytes(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
/// Create an icon handle from its path.
|
/// Create an icon handle from its path.
|
||||||
pub fn from_path(path: PathBuf) -> Handle {
|
pub fn from_path(path: PathBuf) -> Handle {
|
||||||
Handle {
|
Handle {
|
||||||
|
|
@ -38,10 +86,10 @@ pub fn from_path(path: PathBuf) -> Handle {
|
||||||
.file_stem()
|
.file_stem()
|
||||||
.and_then(OsStr::to_str)
|
.and_then(OsStr::to_str)
|
||||||
.is_some_and(|name| name.ends_with("-symbolic")),
|
.is_some_and(|name| name.ends_with("-symbolic")),
|
||||||
data: if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
|
data: match svg_source(&path) {
|
||||||
Data::Svg(svg::Handle::from_path(path))
|
Some(SvgSource::Path) => Data::Svg(svg::Handle::from_path(path)),
|
||||||
} else {
|
Some(SvgSource::Bytes(bytes)) => Data::Svg(svg::Handle::from_memory(bytes)),
|
||||||
Data::Image(image::Handle::from_path(path))
|
None => Data::Image(image::Handle::from_path(path)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
use super::{Handle, Icon};
|
use super::{Handle, Icon};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::ffi::OsStr;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|
@ -138,11 +137,7 @@ impl Named {
|
||||||
Handle {
|
Handle {
|
||||||
symbolic: self.symbolic,
|
symbolic: self.symbolic,
|
||||||
data: if let Some(path) = self.path() {
|
data: if let Some(path) = self.path() {
|
||||||
if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
|
super::from_path(path).data
|
||||||
super::Data::Svg(iced_core::svg::Handle::from_path(path))
|
|
||||||
} else {
|
|
||||||
super::Data::Image(iced_core::image::Handle::from_path(path))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
super::bundle::get(&name).unwrap_or_else(|| {
|
super::bundle::get(&name).unwrap_or_else(|| {
|
||||||
let bytes: &'static [u8] = &[];
|
let bytes: &'static [u8] = &[];
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,8 @@ pub mod menu_column;
|
||||||
mod menu_inner;
|
mod menu_inner;
|
||||||
mod menu_tree;
|
mod menu_tree;
|
||||||
pub use menu_tree::{
|
pub use menu_tree::{
|
||||||
MenuItem as Item, MenuTree as Tree, menu_button, menu_items as items, menu_root as root,
|
Entry, IconSlot, MenuItem as Item, MenuTree as Tree, menu_button, menu_items as items,
|
||||||
nav_context,
|
menu_root as root, nav_context,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::style::menu_bar::{Appearance, StyleSheet};
|
pub use crate::style::menu_bar::{Appearance, StyleSheet};
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use super::menu_inner::{
|
use super::menu_inner::{
|
||||||
CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight,
|
CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight,
|
||||||
|
close_innermost,
|
||||||
};
|
};
|
||||||
use super::menu_tree::MenuTree;
|
use super::menu_tree::MenuTree;
|
||||||
use crate::Renderer;
|
use crate::Renderer;
|
||||||
|
|
@ -176,7 +177,7 @@ pub struct MenuBar<Message> {
|
||||||
#[cfg(wayland_platform)]
|
#[cfg(wayland_platform)]
|
||||||
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
||||||
pub(crate) on_surface_action:
|
pub(crate) on_surface_action:
|
||||||
Option<Arc<dyn Fn(crate::surface::Action) -> Message + Send + Sync + 'static>>,
|
Option<Arc<dyn Fn(crate::surface::Action<Message>) -> Message + Send + Sync + 'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message> MenuBar<Message>
|
impl<Message> MenuBar<Message>
|
||||||
|
|
@ -328,7 +329,7 @@ where
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn on_surface_action(
|
pub fn on_surface_action(
|
||||||
mut self,
|
mut self,
|
||||||
handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static,
|
handler: impl Fn(crate::surface::Action<Message>) -> Message + Send + Sync + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.on_surface_action = Some(Arc::new(handler));
|
self.on_surface_action = Some(Arc::new(handler));
|
||||||
self
|
self
|
||||||
|
|
@ -377,13 +378,12 @@ where
|
||||||
state.active_root.clear();
|
state.active_root.clear();
|
||||||
shell.publish(surface_action(destroy_popup(id)));
|
shell.publish(surface_action(destroy_popup(id)));
|
||||||
state.view_cursor = view_cursor;
|
state.view_cursor = view_cursor;
|
||||||
(id, layout.children().map(|lo| lo.bounds()).collect())
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
window::Id::unique(),
|
|
||||||
layout.children().map(|lo| lo.bounds()).collect(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
// A fresh id per popup, so the old popup's Done cannot be mistaken for the new one's
|
||||||
|
(
|
||||||
|
window::Id::unique(),
|
||||||
|
layout.children().map(|lo| lo.bounds()).collect(),
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut popup_menu: Menu<'static, _> = Menu {
|
let mut popup_menu: Menu<'static, _> = Menu {
|
||||||
|
|
@ -581,6 +581,21 @@ where
|
||||||
|
|
||||||
let my_state = tree.state.downcast_mut::<MenuBarState>();
|
let my_state = tree.state.downcast_mut::<MenuBarState>();
|
||||||
|
|
||||||
|
// The compositor dismissed our popup: nothing else tells this state about it.
|
||||||
|
#[cfg(wayland_platform)]
|
||||||
|
if let iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(
|
||||||
|
iced::event::wayland::Event::Popup(iced::event::wayland::PopupEvent::Done, _, popup),
|
||||||
|
)) = event
|
||||||
|
{
|
||||||
|
my_state.inner.with_data_mut(|d| {
|
||||||
|
if d.popup_id.get(&self.window_id) == Some(popup) {
|
||||||
|
// submenus were dismissed with it
|
||||||
|
d.popup_id.clear();
|
||||||
|
d.reset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// XXX this should reset the state if there are no other copies of the state, which implies no dropdown menus open.
|
// XXX this should reset the state if there are no other copies of the state, which implies no dropdown menus open.
|
||||||
let reset = self.window_id != window::Id::NONE
|
let reset = self.window_id != window::Id::NONE
|
||||||
&& my_state
|
&& my_state
|
||||||
|
|
@ -598,6 +613,23 @@ where
|
||||||
});
|
});
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
|
event::Event::Keyboard(iced_widget::core::keyboard::Event::KeyPressed {
|
||||||
|
key:
|
||||||
|
iced_widget::core::keyboard::Key::Named(
|
||||||
|
iced_widget::core::keyboard::key::Named::Escape,
|
||||||
|
),
|
||||||
|
..
|
||||||
|
}) if open => {
|
||||||
|
my_state.inner.with_data_mut(|state| {
|
||||||
|
let handler = self.on_surface_action.as_ref();
|
||||||
|
close_innermost(state, &mut |id| {
|
||||||
|
if let Some(handler) = handler {
|
||||||
|
shell.publish((handler)(crate::surface::Action::DestroyPopup(id)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
shell.capture_event();
|
||||||
|
}
|
||||||
Mouse(mouse::Event::ButtonPressed(Left))
|
Mouse(mouse::Event::ButtonPressed(Left))
|
||||||
| Touch(touch::Event::FingerPressed { .. })
|
| Touch(touch::Event::FingerPressed { .. })
|
||||||
if view_cursor.is_over(layout.bounds()) =>
|
if view_cursor.is_over(layout.bounds()) =>
|
||||||
|
|
|
||||||
|
|
@ -455,7 +455,7 @@ pub(crate) struct Menu<'b, Message: std::clone::Clone> {
|
||||||
pub(crate) window_id: window::Id,
|
pub(crate) window_id: window::Id,
|
||||||
pub(crate) depth: usize,
|
pub(crate) depth: usize,
|
||||||
pub(crate) on_surface_action:
|
pub(crate) on_surface_action:
|
||||||
Option<Arc<dyn Fn(crate::surface::Action) -> Message + Send + Sync + 'static>>,
|
Option<Arc<dyn Fn(crate::surface::Action<Message>) -> Message + Send + Sync + 'static>>,
|
||||||
}
|
}
|
||||||
impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
||||||
pub(crate) fn overlay(self) -> overlay::Element<'b, Message, crate::Theme, crate::Renderer> {
|
pub(crate) fn overlay(self) -> overlay::Element<'b, Message, crate::Theme, crate::Renderer> {
|
||||||
|
|
@ -546,7 +546,48 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Close every menu and, on Wayland, destroy the root popup this menu belongs to.
|
||||||
|
/// Used when clicking outside a menu.
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
|
fn close_all(
|
||||||
|
&self,
|
||||||
|
state: &mut super::menu_bar::MenuBarStateInner,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
) {
|
||||||
|
#[cfg(wayland_platform)]
|
||||||
|
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) {
|
||||||
|
if depth == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
root = *parent.0;
|
||||||
|
depth = depth.saturating_sub(1);
|
||||||
|
}
|
||||||
|
shell.publish((handler)(crate::surface::Action::DestroyPopup(root)));
|
||||||
|
}
|
||||||
|
|
||||||
|
state.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Close the innermost open menu, see [`close_innermost`].
|
||||||
|
/// Used when pressing `esc`.
|
||||||
|
fn close(
|
||||||
|
&self,
|
||||||
|
state: &mut super::menu_bar::MenuBarStateInner,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
) {
|
||||||
|
let handler = self.on_surface_action.as_ref();
|
||||||
|
close_innermost(state, &mut |id| {
|
||||||
|
if let Some(handler) = handler {
|
||||||
|
shell.publish((handler)(crate::surface::Action::DestroyPopup(id)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: &event::Event,
|
event: &event::Event,
|
||||||
|
|
@ -601,6 +642,19 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
||||||
);
|
);
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
|
event::Event::Keyboard(iced_widget::core::keyboard::Event::KeyPressed {
|
||||||
|
key:
|
||||||
|
iced_widget::core::keyboard::Key::Named(
|
||||||
|
iced_widget::core::keyboard::key::Named::Escape,
|
||||||
|
),
|
||||||
|
..
|
||||||
|
}) if !self.is_overlay => {
|
||||||
|
self.tree
|
||||||
|
.inner
|
||||||
|
.with_data_mut(|state| self.close(state, shell));
|
||||||
|
shell.capture_event();
|
||||||
|
}
|
||||||
|
|
||||||
Mouse(WheelScrolled { delta }) => process_scroll_events(
|
Mouse(WheelScrolled { delta }) => process_scroll_events(
|
||||||
self,
|
self,
|
||||||
shell,
|
shell,
|
||||||
|
|
@ -676,27 +730,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
||||||
needs_reset |= self.close_condition.click_outside && !is_inside;
|
needs_reset |= self.close_condition.click_outside && !is_inside;
|
||||||
|
|
||||||
if needs_reset {
|
if needs_reset {
|
||||||
#[cfg(wayland_platform)]
|
self.close_all(state, shell);
|
||||||
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;
|
|
||||||
}
|
|
||||||
root = *parent.0;
|
|
||||||
depth = depth.saturating_sub(1);
|
|
||||||
}
|
|
||||||
shell
|
|
||||||
.publish((handler)(crate::surface::Action::DestroyPopup(root)));
|
|
||||||
}
|
|
||||||
|
|
||||||
state.reset();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1782,6 +1816,45 @@ fn get_children_layout<Message>(
|
||||||
(Size::new(width, height), child_positions, child_sizes)
|
(Size::new(width, height), child_positions, child_sizes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Close the innermost open menu level
|
||||||
|
pub(super) fn close_innermost(
|
||||||
|
state: &mut super::menu_bar::MenuBarStateInner,
|
||||||
|
destroy: &mut dyn FnMut(window::Id),
|
||||||
|
) {
|
||||||
|
let outermost = state
|
||||||
|
.popup_id
|
||||||
|
.iter()
|
||||||
|
.find(|(parent, _)| !state.popup_id.values().any(|id| id == *parent))
|
||||||
|
.map(|(_, id)| *id);
|
||||||
|
let innermost = state
|
||||||
|
.popup_id
|
||||||
|
.values()
|
||||||
|
.find(|id| !state.popup_id.contains_key(id))
|
||||||
|
.copied();
|
||||||
|
|
||||||
|
let levels = state.menu_states.len();
|
||||||
|
if levels <= 1 {
|
||||||
|
if let Some(id) = outermost {
|
||||||
|
destroy(id);
|
||||||
|
}
|
||||||
|
state.popup_id.clear();
|
||||||
|
state.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.active_root.truncate(levels - 1);
|
||||||
|
state.menu_states.truncate(levels - 1);
|
||||||
|
|
||||||
|
// Forget the parent's hovered item, so hovering it again reopens the submenu.
|
||||||
|
if let Some(parent) = state.menu_states.last_mut() {
|
||||||
|
parent.index = None;
|
||||||
|
}
|
||||||
|
if let Some(id) = innermost {
|
||||||
|
state.popup_id.retain(|_, popup| *popup != id);
|
||||||
|
destroy(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn search_bound(
|
fn search_bound(
|
||||||
default: usize,
|
default: usize,
|
||||||
default_left: usize,
|
default_left: usize,
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,84 @@ pub enum MenuItem<A: MenuAction, L: Into<Cow<'static, str>>> {
|
||||||
Folder(L, Vec<MenuItem<A, L>>),
|
Folder(L, Vec<MenuItem<A, L>>),
|
||||||
/// Represents a divider between menu items.
|
/// Represents a divider between menu items.
|
||||||
Divider,
|
Divider,
|
||||||
|
/// A menu entry with every option available; see [`Entry`].
|
||||||
|
Entry(Entry<A, L>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: MenuAction, L: Into<Cow<'static, str>>> MenuItem<A, L> {
|
||||||
|
/// Create an [`Entry`] menu item, configure it with the builder methods on [`Entry`].
|
||||||
|
pub fn entry(label: L, action: A) -> Self {
|
||||||
|
MenuItem::Entry(Entry::new(label, action))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The leading icon column of a menu entry.
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub enum IconSlot {
|
||||||
|
/// No icon and no space reserved for one.
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
/// No icon, but the space for an icon is reserved (indented entry)
|
||||||
|
Reserved,
|
||||||
|
/// An icon.
|
||||||
|
Icon(icon::Handle),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Option<icon::Handle>> for IconSlot {
|
||||||
|
fn from(icon: Option<icon::Handle>) -> Self {
|
||||||
|
icon.map_or(IconSlot::None, IconSlot::Icon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A menu entry: label, optional leading icon, optional check column, enabled state and action.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Entry<A, L> {
|
||||||
|
label: L,
|
||||||
|
icon: IconSlot,
|
||||||
|
/// `Some` draws the check column
|
||||||
|
checked: Option<bool>,
|
||||||
|
enabled: bool,
|
||||||
|
action: A,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, L> Entry<A, L> {
|
||||||
|
pub fn new(label: L, action: A) -> Self {
|
||||||
|
Self {
|
||||||
|
label,
|
||||||
|
icon: IconSlot::None,
|
||||||
|
checked: None,
|
||||||
|
enabled: true,
|
||||||
|
action,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw a leading icon
|
||||||
|
#[must_use]
|
||||||
|
pub fn icon(mut self, icon: icon::Handle) -> Self {
|
||||||
|
self.icon = IconSlot::Icon(icon);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw no icon, but resever the space
|
||||||
|
#[must_use]
|
||||||
|
pub fn reserve_icon(mut self) -> Self {
|
||||||
|
self.icon = IconSlot::Reserved;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show a check column, ticked when `checked` is true, empty sapce when false
|
||||||
|
#[must_use]
|
||||||
|
pub fn checked(mut self, checked: bool) -> Self {
|
||||||
|
self.checked = Some(checked);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disabled entries are drawn dimmed and do not react to presses
|
||||||
|
#[must_use]
|
||||||
|
pub fn enabled(mut self, enabled: bool) -> Self {
|
||||||
|
self.enabled = enabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a root menu item.
|
/// Create a root menu item.
|
||||||
|
|
@ -201,6 +279,97 @@ where
|
||||||
.class(theme::Button::MenuRoot)
|
.class(theme::Button::MenuRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn entry_tree<
|
||||||
|
A: MenuAction<Message = Message>,
|
||||||
|
L: Into<Cow<'static, str>>,
|
||||||
|
Message: Clone + 'static,
|
||||||
|
>(
|
||||||
|
entry: Entry<A, L>,
|
||||||
|
key_binds: &HashMap<KeyBind, A>,
|
||||||
|
key_class: theme::Text,
|
||||||
|
) -> MenuTree<Message> {
|
||||||
|
let Entry {
|
||||||
|
label,
|
||||||
|
icon,
|
||||||
|
checked,
|
||||||
|
enabled,
|
||||||
|
action,
|
||||||
|
} = entry;
|
||||||
|
let spacing = crate::theme::spacing();
|
||||||
|
// Several bindings may map to one action
|
||||||
|
// choose based on these rules:
|
||||||
|
// - the fewest modifiers win (Delete vs Ctrl+D)
|
||||||
|
// - if equal then character key over a named one (Ctrl+C over Ctrl+Insert)
|
||||||
|
// - if equal then `Ord` to stay consistent since hashmap is arbitrary
|
||||||
|
let rank = |k: &KeyBind| {
|
||||||
|
(
|
||||||
|
k.modifiers.len(),
|
||||||
|
matches!(k.key, iced_core::keyboard::Key::Named(_)),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let key = key_binds
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, a)| **a == action)
|
||||||
|
.map(|(k, _)| k)
|
||||||
|
.min_by(|a, b| rank(a).cmp(&rank(b)).then_with(|| a.cmp(b)))
|
||||||
|
.map_or_else(String::new, ToString::to_string);
|
||||||
|
|
||||||
|
let mut items: Vec<crate::Element<'static, Message>> = Vec::with_capacity(7);
|
||||||
|
|
||||||
|
if let Some(checked) = checked {
|
||||||
|
items.push(if checked {
|
||||||
|
widget::icon::from_name("object-select-symbolic")
|
||||||
|
.size(16)
|
||||||
|
.icon()
|
||||||
|
.class(theme::Svg::Custom(Rc::new(|theme| {
|
||||||
|
iced_widget::svg::Style {
|
||||||
|
color: Some(theme.cosmic().accent_text_color().into()),
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
.width(Length::Fixed(16.0))
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
widget::space::horizontal()
|
||||||
|
.width(Length::Fixed(16.0))
|
||||||
|
.into()
|
||||||
|
});
|
||||||
|
items.push(widget::space::horizontal().width(spacing.space_xxs).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
match icon {
|
||||||
|
IconSlot::Icon(icon) => {
|
||||||
|
items.push(widget::icon::icon(icon).size(14).into());
|
||||||
|
items.push(widget::space::horizontal().width(spacing.space_xxs).into());
|
||||||
|
}
|
||||||
|
IconSlot::Reserved => {
|
||||||
|
items.push(
|
||||||
|
widget::space::horizontal()
|
||||||
|
.width(Length::Fixed(14.0))
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
items.push(widget::space::horizontal().width(spacing.space_xxs).into());
|
||||||
|
}
|
||||||
|
IconSlot::None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ellipsize =
|
||||||
|
iced_core::text::Ellipsize::Middle(iced_core::text::EllipsizeHeightLimit::Lines(1));
|
||||||
|
items.push(widget::text(label.into()).ellipsize(ellipsize).into());
|
||||||
|
items.push(widget::space::horizontal().into());
|
||||||
|
items.push(
|
||||||
|
widget::text(key)
|
||||||
|
.class(key_class)
|
||||||
|
.ellipsize(ellipsize)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut button = menu_button(items);
|
||||||
|
if enabled {
|
||||||
|
button = button.on_press(action.message());
|
||||||
|
}
|
||||||
|
MenuTree::from(Element::from(button))
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a list of menu items from a vector of `MenuItem`.
|
/// Create a list of menu items from a vector of `MenuItem`.
|
||||||
///
|
///
|
||||||
/// The `MenuItem` can be either an action or a separator.
|
/// The `MenuItem` can be either an action or a separator.
|
||||||
|
|
@ -220,15 +389,6 @@ pub fn menu_items<
|
||||||
key_binds: &HashMap<KeyBind, A>,
|
key_binds: &HashMap<KeyBind, A>,
|
||||||
children: Vec<MenuItem<A, L>>,
|
children: Vec<MenuItem<A, L>>,
|
||||||
) -> Vec<MenuTree<Message>> {
|
) -> Vec<MenuTree<Message>> {
|
||||||
fn find_key<A: MenuAction>(action: &A, key_binds: &HashMap<KeyBind, A>) -> String {
|
|
||||||
for (key_bind, key_action) in key_binds {
|
|
||||||
if action == key_action {
|
|
||||||
return key_bind.to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn key_style(theme: &crate::Theme) -> TextStyle {
|
fn key_style(theme: &crate::Theme) -> TextStyle {
|
||||||
let mut color = theme.cosmic().background(theme.transparent).component.on;
|
let mut color = theme.cosmic().background(theme.transparent).component.on;
|
||||||
color.alpha *= 0.75;
|
color.alpha *= 0.75;
|
||||||
|
|
@ -246,117 +406,25 @@ pub fn menu_items<
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.flat_map(|(i, item)| {
|
.flat_map(|(i, item)| {
|
||||||
let mut trees = vec![];
|
let mut trees = vec![];
|
||||||
let spacing = crate::theme::spacing();
|
|
||||||
|
|
||||||
match item {
|
match item {
|
||||||
MenuItem::Button(label, icon, action) => {
|
MenuItem::Button(label, icon, action) => {
|
||||||
let l: Cow<'static, str> = label.into();
|
let mut entry = Entry::new(label, action);
|
||||||
let key = find_key(&action, key_binds);
|
entry.icon = icon.into();
|
||||||
let mut items = vec![
|
trees.push(entry_tree(entry, key_binds, key_class.clone()));
|
||||||
widget::text(l)
|
|
||||||
.ellipsize(iced_core::text::Ellipsize::Middle(
|
|
||||||
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
|
||||||
))
|
|
||||||
.into(),
|
|
||||||
widget::space::horizontal().into(),
|
|
||||||
widget::text(key)
|
|
||||||
.class(key_class)
|
|
||||||
.ellipsize(iced_core::text::Ellipsize::Middle(
|
|
||||||
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
|
||||||
))
|
|
||||||
.into(),
|
|
||||||
];
|
|
||||||
|
|
||||||
if let Some(icon) = icon {
|
|
||||||
items.insert(0, widget::icon::icon(icon).size(14).into());
|
|
||||||
items.insert(
|
|
||||||
1,
|
|
||||||
widget::space::horizontal().width(spacing.space_xxs).into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let menu_button = menu_button(items).on_press(action.message());
|
|
||||||
|
|
||||||
trees.push(MenuTree::<Message>::from(Element::from(menu_button)));
|
|
||||||
}
|
}
|
||||||
MenuItem::ButtonDisabled(label, icon, action) => {
|
MenuItem::ButtonDisabled(label, icon, action) => {
|
||||||
let l: Cow<'static, str> = label.into();
|
let mut entry = Entry::new(label, action).enabled(false);
|
||||||
|
entry.icon = icon.into();
|
||||||
let key = find_key(&action, key_binds);
|
trees.push(entry_tree(entry, key_binds, key_class.clone()));
|
||||||
|
|
||||||
let mut items = vec![
|
|
||||||
widget::text(l)
|
|
||||||
.ellipsize(iced_core::text::Ellipsize::Middle(
|
|
||||||
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
|
||||||
))
|
|
||||||
.into(),
|
|
||||||
widget::space::horizontal().into(),
|
|
||||||
widget::text(key)
|
|
||||||
.ellipsize(iced_core::text::Ellipsize::Middle(
|
|
||||||
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
|
||||||
))
|
|
||||||
.class(key_class)
|
|
||||||
.into(),
|
|
||||||
];
|
|
||||||
|
|
||||||
if let Some(icon) = icon {
|
|
||||||
items.insert(0, widget::icon::icon(icon).size(14).into());
|
|
||||||
items.insert(
|
|
||||||
1,
|
|
||||||
widget::space::horizontal().width(spacing.space_xxs).into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let menu_button = menu_button(items);
|
|
||||||
|
|
||||||
trees.push(MenuTree::<Message>::from(Element::from(menu_button)));
|
|
||||||
}
|
}
|
||||||
MenuItem::CheckBox(label, icon, value, action) => {
|
MenuItem::CheckBox(label, icon, value, action) => {
|
||||||
let key = find_key(&action, key_binds);
|
let mut entry = Entry::new(label, action).checked(value);
|
||||||
let mut items = vec![
|
entry.icon = icon.into();
|
||||||
if value {
|
trees.push(entry_tree(entry, key_binds, key_class.clone()));
|
||||||
widget::icon::from_name("object-select-symbolic")
|
}
|
||||||
.size(16)
|
MenuItem::Entry(entry) => {
|
||||||
.icon()
|
trees.push(entry_tree(entry, key_binds, key_class.clone()));
|
||||||
.class(theme::Svg::Custom(Rc::new(|theme| {
|
|
||||||
iced_widget::svg::Style {
|
|
||||||
color: Some(theme.cosmic().accent_text_color().into()),
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
.width(Length::Fixed(16.0))
|
|
||||||
.into()
|
|
||||||
} else {
|
|
||||||
widget::space::horizontal()
|
|
||||||
.width(Length::Fixed(16.0))
|
|
||||||
.into()
|
|
||||||
},
|
|
||||||
widget::space::horizontal().width(spacing.space_xxs).into(),
|
|
||||||
widget::text(label)
|
|
||||||
.ellipsize(iced_core::text::Ellipsize::Middle(
|
|
||||||
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
|
||||||
))
|
|
||||||
.align_x(iced::Alignment::Start)
|
|
||||||
.into(),
|
|
||||||
widget::space::horizontal().into(),
|
|
||||||
widget::text(key)
|
|
||||||
.class(key_class)
|
|
||||||
.ellipsize(iced_core::text::Ellipsize::Middle(
|
|
||||||
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
|
||||||
))
|
|
||||||
.into(),
|
|
||||||
];
|
|
||||||
|
|
||||||
if let Some(icon) = icon {
|
|
||||||
items.insert(
|
|
||||||
1,
|
|
||||||
widget::space::horizontal().width(spacing.space_xxs).into(),
|
|
||||||
);
|
|
||||||
items.insert(2, widget::icon::icon(icon).size(14).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
trees.push(MenuTree::from(Element::from(
|
|
||||||
menu_button(items).on_press(action.message()),
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
MenuItem::Folder(label, children) => {
|
MenuItem::Folder(label, children) => {
|
||||||
let l: Cow<'static, str> = label.into();
|
let l: Cow<'static, str> = label.into();
|
||||||
|
|
@ -390,7 +458,8 @@ pub fn menu_items<
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
MenuItem::Divider => {
|
MenuItem::Divider => {
|
||||||
if i != size - 1 {
|
// A divider at either end separates nothing
|
||||||
|
if i != 0 && i != size - 1 {
|
||||||
trees.push(MenuTree::<Message>::from(Element::from(
|
trees.push(MenuTree::<Message>::from(Element::from(
|
||||||
widget::divider::horizontal::light(),
|
widget::divider::horizontal::light(),
|
||||||
)));
|
)));
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ impl<'a, Message: Clone + 'static> NavBar<'a, Message> {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn on_surface_action(
|
pub fn on_surface_action(
|
||||||
mut self,
|
mut self,
|
||||||
handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static,
|
handler: impl Fn(crate::surface::Action<Message>) -> Message + Send + Sync + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.segmented_button = self.segmented_button.on_surface_action(handler);
|
self.segmented_button = self.segmented_button.on_surface_action(handler);
|
||||||
self
|
self
|
||||||
|
|
|
||||||
|
|
@ -157,17 +157,9 @@ where
|
||||||
) {
|
) {
|
||||||
if self.popup.is_some() {
|
if self.popup.is_some() {
|
||||||
if self.modal {
|
if self.modal {
|
||||||
match event {
|
if matches!(event, Event::Mouse(_) | Event::Touch(_)) {
|
||||||
Event::Mouse(_) | Event::Touch(_) => {
|
shell.capture_event();
|
||||||
shell.capture_event();
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
// app-level keyboard navigation (Tab focus cycling, Escape)
|
|
||||||
// still receives keyboard events
|
|
||||||
Event::Keyboard(_) => {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
} else if let Some(on_close) = self.on_close.as_ref()
|
} else if let Some(on_close) = self.on_close.as_ref()
|
||||||
&& matches!(
|
&& matches!(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Show a circular progress indicator.
|
//! Show a circular progress indicator.
|
||||||
use super::animation::{Animation, Progress};
|
use super::animation::{Animation, Progress};
|
||||||
use super::style::StyleSheet;
|
use super::style::{self, Catalog};
|
||||||
use iced::advanced::widget::tree::{self, Tree};
|
use iced::advanced::widget::tree::{self, Tree};
|
||||||
use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer};
|
use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer};
|
||||||
use iced::widget::canvas;
|
use iced::widget::canvas;
|
||||||
|
|
@ -15,11 +15,11 @@ const MAX_WRAP: f32 = 1.0 - MIN_GAP_ANGLE.0 / (2.0 * PI);
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct Circular<Theme>
|
pub struct Circular<Theme>
|
||||||
where
|
where
|
||||||
Theme: StyleSheet,
|
Theme: Catalog,
|
||||||
{
|
{
|
||||||
size: f32,
|
size: f32,
|
||||||
bar_height: Option<f32>,
|
bar_height: Option<f32>,
|
||||||
style: Theme::Style,
|
class: Theme::Class,
|
||||||
cycle_duration: Duration,
|
cycle_duration: Duration,
|
||||||
period: Duration,
|
period: Duration,
|
||||||
progress: Option<f32>,
|
progress: Option<f32>,
|
||||||
|
|
@ -27,14 +27,14 @@ where
|
||||||
|
|
||||||
impl<Theme> Circular<Theme>
|
impl<Theme> Circular<Theme>
|
||||||
where
|
where
|
||||||
Theme: StyleSheet,
|
Theme: Catalog<Class = style::Class>,
|
||||||
{
|
{
|
||||||
/// Creates a new [`Circular`] with the given content.
|
/// Creates a new [`Circular`] with the given content.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Circular {
|
Circular {
|
||||||
size: 48.0,
|
size: 48.0,
|
||||||
bar_height: None,
|
bar_height: None,
|
||||||
style: Theme::Style::default(),
|
class: Theme::Class::default(),
|
||||||
cycle_duration: Duration::from_millis(1500),
|
cycle_duration: Duration::from_millis(1500),
|
||||||
period: Duration::from_secs(2),
|
period: Duration::from_secs(2),
|
||||||
progress: None,
|
progress: None,
|
||||||
|
|
@ -54,9 +54,9 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style variant of this [`Circular`].
|
/// Sets the style class of this [`Circular`].
|
||||||
pub fn style(mut self, style: Theme::Style) -> Self {
|
pub fn class(mut self, class: Theme::Class) -> Self {
|
||||||
self.style = style;
|
self.class = class;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,11 +78,29 @@ where
|
||||||
self.progress = Some(progress.clamp(0.0, 1.0));
|
self.progress = Some(progress.clamp(0.0, 1.0));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the track color of this [`Circular`].
|
||||||
|
pub fn track_color(mut self, color: impl Into<iced::Color>) -> Self {
|
||||||
|
self.class = self.class.track_color(color);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the bar color of this [`Circular`].
|
||||||
|
pub fn bar_color(mut self, color: impl Into<iced::Color>) -> Self {
|
||||||
|
self.class = self.class.bar_color(color);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the border color of this [`Circular`].
|
||||||
|
pub fn border_color(mut self, color: impl Into<iced::Color>) -> Self {
|
||||||
|
self.class = self.class.border_color(color);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Theme> Default for Circular<Theme>
|
impl<Theme> Default for Circular<Theme>
|
||||||
where
|
where
|
||||||
Theme: StyleSheet,
|
Theme: Catalog<Class = style::Class>,
|
||||||
{
|
{
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
|
|
@ -99,7 +117,7 @@ struct State {
|
||||||
impl<Message, Theme> Widget<Message, Theme, Renderer> for Circular<Theme>
|
impl<Message, Theme> Widget<Message, Theme, Renderer> for Circular<Theme>
|
||||||
where
|
where
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
Theme: StyleSheet,
|
Theme: Catalog,
|
||||||
{
|
{
|
||||||
fn tag(&self) -> tree::Tag {
|
fn tag(&self) -> tree::Tag {
|
||||||
tree::Tag::of::<State>()
|
tree::Tag::of::<State>()
|
||||||
|
|
@ -170,7 +188,7 @@ where
|
||||||
|
|
||||||
let state = tree.state.downcast_ref::<State>();
|
let state = tree.state.downcast_ref::<State>();
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let custom_style = Theme::appearance(theme, &self.style, self.progress.is_some(), true);
|
let custom_style = theme.style(&self.class, self.progress.is_some(), true);
|
||||||
|
|
||||||
let geometry = state.cache.draw(renderer, bounds.size(), |frame| {
|
let geometry = state.cache.draw(renderer, bounds.size(), |frame| {
|
||||||
let bar_height = self.bar_height.unwrap_or((frame.width() / 12.0).max(2.0));
|
let bar_height = self.bar_height.unwrap_or((frame.width() / 12.0).max(2.0));
|
||||||
|
|
@ -240,7 +258,7 @@ where
|
||||||
impl<'a, Message, Theme> From<Circular<Theme>> for Element<'a, Message, Theme, Renderer>
|
impl<'a, Message, Theme> From<Circular<Theme>> for Element<'a, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Message: Clone + 'a,
|
Message: Clone + 'a,
|
||||||
Theme: StyleSheet + 'a,
|
Theme: Catalog + 'a,
|
||||||
{
|
{
|
||||||
fn from(circular: Circular<Theme>) -> Self {
|
fn from(circular: Circular<Theme>) -> Self {
|
||||||
Self::new(circular)
|
Self::new(circular)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Show a linear progress indicator.
|
//! Show a linear progress indicator.
|
||||||
use super::animation::{Animation, Progress};
|
use super::animation::{Animation, Progress};
|
||||||
use super::style::StyleSheet;
|
use super::style::{self, Catalog};
|
||||||
use iced::advanced::widget::tree::{self, Tree};
|
use iced::advanced::widget::tree::{self, Tree};
|
||||||
use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer};
|
use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer};
|
||||||
use iced::{Border, Color, Element, Event, Length, Pixels, Rectangle, Size, mouse, window};
|
use iced::{Border, Color, Element, Event, Length, Pixels, Rectangle, Size, mouse, window};
|
||||||
|
|
@ -13,11 +13,11 @@ const WRAP_LENGTH: f32 = 0.618; // avoids animation repetition
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct Linear<Theme>
|
pub struct Linear<Theme>
|
||||||
where
|
where
|
||||||
Theme: StyleSheet,
|
Theme: Catalog,
|
||||||
{
|
{
|
||||||
width: Length,
|
width: Length,
|
||||||
girth: Length,
|
girth: Length,
|
||||||
style: Theme::Style,
|
class: Theme::Class,
|
||||||
cycle_duration: Duration,
|
cycle_duration: Duration,
|
||||||
period: Duration,
|
period: Duration,
|
||||||
progress: Option<f32>,
|
progress: Option<f32>,
|
||||||
|
|
@ -27,14 +27,14 @@ where
|
||||||
|
|
||||||
impl<Theme> Linear<Theme>
|
impl<Theme> Linear<Theme>
|
||||||
where
|
where
|
||||||
Theme: StyleSheet,
|
Theme: Catalog<Class = style::Class>,
|
||||||
{
|
{
|
||||||
/// Creates a new [`Linear`] with the given content.
|
/// Creates a new [`Linear`] with the given content.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Linear {
|
Linear {
|
||||||
width: Length::Fixed(100.0),
|
width: Length::Fixed(100.0),
|
||||||
girth: Length::Fixed(4.0),
|
girth: Length::Fixed(4.0),
|
||||||
style: Theme::Style::default(),
|
class: Theme::Class::default(),
|
||||||
cycle_duration: Duration::from_millis(1500),
|
cycle_duration: Duration::from_millis(1500),
|
||||||
period: Duration::from_secs(2),
|
period: Duration::from_secs(2),
|
||||||
progress: None,
|
progress: None,
|
||||||
|
|
@ -55,9 +55,9 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style variant of this [`Linear`].
|
/// Sets the style class of this [`Linear`].
|
||||||
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
|
pub fn class(mut self, class: Theme::Class) -> Self {
|
||||||
self.style = style.into();
|
self.class = class;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,11 +99,35 @@ where
|
||||||
self.segment_spacing = spacing.into().0.max(1.0);
|
self.segment_spacing = spacing.into().0.max(1.0);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the track color of this [`Linear`].
|
||||||
|
pub fn track_color(mut self, color: impl Into<iced::Color>) -> Self {
|
||||||
|
self.class = self.class.track_color(color);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the bar color of this [`Linear`].
|
||||||
|
pub fn bar_color(mut self, color: impl Into<iced::Color>) -> Self {
|
||||||
|
self.class = self.class.bar_color(color);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the border color of this [`Linear`].
|
||||||
|
pub fn border_color(mut self, color: impl Into<iced::Color>) -> Self {
|
||||||
|
self.class = self.class.border_color(color);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the border radius of this [`Linear`].
|
||||||
|
pub fn border_radius(mut self, radius: f32) -> Self {
|
||||||
|
self.class = self.class.border_radius(radius);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Theme> Default for Linear<Theme>
|
impl<Theme> Default for Linear<Theme>
|
||||||
where
|
where
|
||||||
Theme: StyleSheet,
|
Theme: Catalog<Class = style::Class>,
|
||||||
{
|
{
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
|
|
@ -119,7 +143,7 @@ struct State {
|
||||||
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Linear<Theme>
|
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Linear<Theme>
|
||||||
where
|
where
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
Theme: StyleSheet,
|
Theme: Catalog,
|
||||||
Renderer: advanced::Renderer,
|
Renderer: advanced::Renderer,
|
||||||
{
|
{
|
||||||
fn tag(&self) -> tree::Tag {
|
fn tag(&self) -> tree::Tag {
|
||||||
|
|
@ -186,7 +210,7 @@ where
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let custom_style = theme.appearance(&self.style, self.progress.is_some(), false);
|
let custom_style = theme.style(&self.class, self.progress.is_some(), false);
|
||||||
let state = tree.state.downcast_ref::<State>();
|
let state = tree.state.downcast_ref::<State>();
|
||||||
|
|
||||||
let border_width = if custom_style.border_color.is_some() {
|
let border_width = if custom_style.border_color.is_some() {
|
||||||
|
|
@ -307,7 +331,7 @@ where
|
||||||
impl<'a, Message, Theme, Renderer> From<Linear<Theme>> for Element<'a, Message, Theme, Renderer>
|
impl<'a, Message, Theme, Renderer> From<Linear<Theme>> for Element<'a, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Message: Clone + 'a,
|
Message: Clone + 'a,
|
||||||
Theme: StyleSheet + 'a,
|
Theme: Catalog + 'a,
|
||||||
Renderer: iced::advanced::Renderer + 'a,
|
Renderer: iced::advanced::Renderer + 'a,
|
||||||
{
|
{
|
||||||
fn from(linear: Linear<Theme>) -> Self {
|
fn from(linear: Linear<Theme>) -> Self {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use iced::Color;
|
use iced::Color;
|
||||||
|
use palette::WithAlpha;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Appearance {
|
pub struct Style {
|
||||||
/// The track [`Color`] of the progress indicator.
|
/// The track [`Color`] of the progress indicator.
|
||||||
pub track_color: Color,
|
pub track_color: Color,
|
||||||
/// The bar [`Color`] of the progress indicator.
|
/// The bar [`Color`] of the progress indicator.
|
||||||
|
|
@ -12,94 +13,101 @@ pub struct Appearance {
|
||||||
pub border_radius: f32,
|
pub border_radius: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::default::Default for Appearance {
|
/// [`Style`] field overrides
|
||||||
fn default() -> Self {
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
Self {
|
pub struct Class {
|
||||||
track_color: Color::TRANSPARENT,
|
pub track_color: Option<Color>,
|
||||||
bar_color: Color::BLACK,
|
pub bar_color: Option<Color>,
|
||||||
border_color: None,
|
pub border_color: Option<Color>,
|
||||||
border_radius: 0.0,
|
pub border_radius: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Class {
|
||||||
|
pub fn track_color(mut self, color: impl Into<Color>) -> Self {
|
||||||
|
self.track_color = Some(color.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bar_color(mut self, color: impl Into<Color>) -> Self {
|
||||||
|
self.bar_color = Some(color.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn border_color(mut self, color: impl Into<Color>) -> Self {
|
||||||
|
self.border_color = Some(color.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn border_radius(mut self, radius: f32) -> Self {
|
||||||
|
self.border_radius = Some(radius);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(&self, base: Style) -> Style {
|
||||||
|
Style {
|
||||||
|
track_color: self.track_color.unwrap_or(base.track_color),
|
||||||
|
bar_color: self.bar_color.unwrap_or(base.bar_color),
|
||||||
|
border_color: self.border_color.or(base.border_color),
|
||||||
|
border_radius: self.border_radius.unwrap_or(base.border_radius),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A set of rules that dictate the style of an indicator.
|
/// A set of rules that dictate the style of an indicator.
|
||||||
pub trait StyleSheet {
|
pub trait Catalog: Sized {
|
||||||
/// The supported style of the [`StyleSheet`].
|
/// The supported class of the [`Catalog`].
|
||||||
type Style: Default;
|
type Class: Default;
|
||||||
|
|
||||||
/// Produces the active [`Appearance`] of a indicator.
|
/// Produces the active [`Style`] of an indicator.
|
||||||
fn appearance(
|
fn style(&self, class: &Self::Class, is_determinate: bool, is_circular: bool) -> Style;
|
||||||
&self,
|
|
||||||
style: &Self::Style,
|
|
||||||
is_determinate: bool,
|
|
||||||
is_circular: bool,
|
|
||||||
) -> Appearance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyleSheet for iced::Theme {
|
impl Catalog for iced::Theme {
|
||||||
type Style = ();
|
type Class = Class;
|
||||||
|
|
||||||
fn appearance(
|
fn style(&self, class: &Self::Class, _is_determinate: bool, _is_circular: bool) -> Style {
|
||||||
&self,
|
|
||||||
_style: &Self::Style,
|
|
||||||
_is_determinate: bool,
|
|
||||||
_is_circular: bool,
|
|
||||||
) -> Appearance {
|
|
||||||
let palette = self.extended_palette();
|
let palette = self.extended_palette();
|
||||||
|
class.resolve(Style {
|
||||||
Appearance {
|
|
||||||
track_color: palette.background.weak.color,
|
track_color: palette.background.weak.color,
|
||||||
bar_color: palette.primary.base.color,
|
bar_color: palette.primary.base.color,
|
||||||
border_color: None,
|
border_color: None,
|
||||||
border_radius: 0.0,
|
border_radius: 0.0,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyleSheet for crate::Theme {
|
impl Catalog for crate::Theme {
|
||||||
type Style = ();
|
type Class = Class;
|
||||||
|
|
||||||
fn appearance(
|
fn style(&self, class: &Self::Class, is_determinate: bool, is_circular: bool) -> Style {
|
||||||
&self,
|
|
||||||
_style: &Self::Style,
|
|
||||||
is_determinate: bool,
|
|
||||||
is_circular: bool,
|
|
||||||
) -> Appearance {
|
|
||||||
let cur = self.current_container();
|
|
||||||
let mut cur_divider = cur.divider;
|
|
||||||
cur_divider.alpha = 0.5;
|
|
||||||
let theme = self.cosmic();
|
let theme = self.cosmic();
|
||||||
|
|
||||||
let (mut track_color, bar_color) = if theme.is_dark && theme.is_high_contrast {
|
let (mut track_color, bar_color) = match (theme.is_dark, theme.is_high_contrast) {
|
||||||
(
|
(true, true) => (
|
||||||
theme.palette.neutral_6.into(),
|
theme.palette.neutral_6.into(),
|
||||||
theme.accent_text_color().into(),
|
theme.accent_text_color().into(),
|
||||||
)
|
),
|
||||||
} else if theme.is_dark {
|
(true, false) => (theme.palette.neutral_5.into(), theme.accent_color().into()),
|
||||||
(theme.palette.neutral_5.into(), theme.accent_color().into())
|
(false, true) => (
|
||||||
} else if theme.is_high_contrast {
|
|
||||||
(
|
|
||||||
theme.palette.neutral_4.into(),
|
theme.palette.neutral_4.into(),
|
||||||
theme.accent_text_color().into(),
|
theme.accent_text_color().into(),
|
||||||
)
|
),
|
||||||
} else {
|
(false, false) => (theme.palette.neutral_3.into(), theme.accent_color().into()),
|
||||||
(theme.palette.neutral_3.into(), theme.accent_color().into())
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if !is_determinate && is_circular {
|
if !is_determinate && is_circular {
|
||||||
track_color = Color::TRANSPARENT;
|
track_color = Color::TRANSPARENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
Appearance {
|
class.resolve(Style {
|
||||||
track_color,
|
track_color,
|
||||||
bar_color,
|
bar_color,
|
||||||
border_color: if is_determinate && theme.is_high_contrast {
|
border_color: if is_determinate && theme.is_high_contrast {
|
||||||
Some(cur_divider.into())
|
Some(self.current_container().divider.with_alpha(0.5).into())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
border_radius: theme.corner_radii.radius_xl[0],
|
border_radius: theme.corner_radii.radius_xl[0],
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use iced_core::{
|
||||||
pub(crate) fn responsive_container<'a, Message: 'static, Theme, E>(
|
pub(crate) fn responsive_container<'a, Message: 'static, Theme, E>(
|
||||||
content: E,
|
content: E,
|
||||||
id: Id,
|
id: Id,
|
||||||
on_action: impl Fn(crate::surface::Action) -> Message + 'static,
|
on_action: impl Fn(crate::surface::Action<Message>) -> Message + 'static,
|
||||||
) -> ResponsiveContainer<'a, Message, Theme, crate::Renderer>
|
) -> ResponsiveContainer<'a, Message, Theme, crate::Renderer>
|
||||||
where
|
where
|
||||||
E: Into<Element<'a, Message, Theme, crate::Renderer>>,
|
E: Into<Element<'a, Message, Theme, crate::Renderer>>,
|
||||||
|
|
@ -32,7 +32,7 @@ where
|
||||||
content: Element<'a, Message, Theme, Renderer>,
|
content: Element<'a, Message, Theme, Renderer>,
|
||||||
id: Id,
|
id: Id,
|
||||||
size: Option<Size>,
|
size: Option<Size>,
|
||||||
on_action: Box<dyn Fn(crate::surface::Action) -> Message>,
|
on_action: Box<dyn Fn(crate::surface::Action<Message>) -> Message>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Theme, Renderer> ResponsiveContainer<'a, Message, Theme, Renderer>
|
impl<'a, Message, Theme, Renderer> ResponsiveContainer<'a, Message, Theme, Renderer>
|
||||||
|
|
@ -43,7 +43,7 @@ where
|
||||||
pub(crate) fn new<T>(
|
pub(crate) fn new<T>(
|
||||||
content: T,
|
content: T,
|
||||||
id: Id,
|
id: Id,
|
||||||
on_action: impl Fn(crate::surface::Action) -> Message + 'static,
|
on_action: impl Fn(crate::surface::Action<Message>) -> Message + 'static,
|
||||||
) -> Self
|
) -> Self
|
||||||
where
|
where
|
||||||
T: Into<Element<'a, Message, Theme, Renderer>>,
|
T: Into<Element<'a, Message, Theme, Renderer>>,
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,11 @@ impl ResponsiveMenuBar {
|
||||||
core: &Core,
|
core: &Core,
|
||||||
key_binds: &HashMap<menu::KeyBind, A>,
|
key_binds: &HashMap<menu::KeyBind, A>,
|
||||||
id: crate::widget::Id,
|
id: crate::widget::Id,
|
||||||
action_message: impl Fn(crate::surface::Action) -> Message + Send + Sync + Clone + 'static,
|
action_message: impl Fn(crate::surface::Action<Message>) -> Message
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ Clone
|
||||||
|
+ 'static,
|
||||||
trees: Vec<(S, Vec<menu::Item<A, S>>)>,
|
trees: Vec<(S, Vec<menu::Item<A, S>>)>,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
use crate::widget::id_container;
|
use crate::widget::id_container;
|
||||||
|
|
|
||||||
|
|
@ -2,30 +2,28 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use crate::{Element, Renderer};
|
use crate::{Element, Renderer};
|
||||||
use iced::widget;
|
use iced::widget::scrollable::{Direction, Scrollable, Scrollbar};
|
||||||
|
|
||||||
pub fn scrollable<'a, Message>(
|
pub fn scrollable<'a, Message>(
|
||||||
element: impl Into<Element<'a, Message>>,
|
element: impl Into<Element<'a, Message>>,
|
||||||
) -> widget::Scrollable<'a, Message, crate::Theme, Renderer> {
|
) -> Scrollable<'a, Message, crate::Theme, Renderer> {
|
||||||
vertical(element)
|
vertical(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vertical<'a, Message>(
|
pub fn vertical<'a, Message>(
|
||||||
element: impl Into<Element<'a, Message>>,
|
element: impl Into<Element<'a, Message>>,
|
||||||
) -> widget::Scrollable<'a, Message, crate::Theme, Renderer> {
|
) -> Scrollable<'a, Message, crate::Theme, Renderer> {
|
||||||
widget::scrollable(element)
|
iced::widget::scrollable(element)
|
||||||
.scroller_width(8.0)
|
.padding(8.0)
|
||||||
.scrollbar_width(8.0)
|
.direction(Direction::Vertical(
|
||||||
.scrollbar_padding(8.0)
|
Scrollbar::new().width(8.0).scroller_width(8.0),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn horizontal<'a, Message>(
|
pub fn horizontal<'a, Message>(
|
||||||
element: impl Into<Element<'a, Message>>,
|
element: impl Into<Element<'a, Message>>,
|
||||||
) -> widget::Scrollable<'a, Message, crate::Theme, Renderer> {
|
) -> Scrollable<'a, Message, crate::Theme, Renderer> {
|
||||||
widget::scrollable(element)
|
iced::widget::scrollable(element).direction(Direction::Horizontal(
|
||||||
.direction(widget::scrollable::Direction::Horizontal(
|
Scrollbar::new().width(8.0).scroller_width(8.0),
|
||||||
widget::scrollable::Scrollbar::new(),
|
))
|
||||||
))
|
|
||||||
.scroller_width(8.0)
|
|
||||||
.scrollbar_width(8.0)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,7 @@ where
|
||||||
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
pub(crate) on_surface_action:
|
pub(crate) on_surface_action:
|
||||||
Option<Arc<dyn Fn(crate::surface::Action) -> Message + Send + Sync + 'static>>,
|
Option<Arc<dyn Fn(crate::surface::Action<Message>) -> Message + Send + Sync + 'static>>,
|
||||||
|
|
||||||
/// Defines the implementation of this struct
|
/// Defines the implementation of this struct
|
||||||
variant: PhantomData<Variant>,
|
variant: PhantomData<Variant>,
|
||||||
|
|
@ -914,7 +914,7 @@ where
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn on_surface_action(
|
pub fn on_surface_action(
|
||||||
mut self,
|
mut self,
|
||||||
handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static,
|
handler: impl Fn(crate::surface::Action<Message>) -> Message + Send + Sync + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.on_surface_action = Some(Arc::new(handler));
|
self.on_surface_action = Some(Arc::new(handler));
|
||||||
self
|
self
|
||||||
|
|
@ -953,10 +953,9 @@ where
|
||||||
state.active_root.clear();
|
state.active_root.clear();
|
||||||
shell.publish(surface_action(destroy_popup(id)));
|
shell.publish(surface_action(destroy_popup(id)));
|
||||||
state.view_cursor = view_cursor;
|
state.view_cursor = view_cursor;
|
||||||
id
|
|
||||||
} else {
|
|
||||||
window::Id::unique()
|
|
||||||
}
|
}
|
||||||
|
// A fresh id per popup, so the old popup's Done cannot be mistaken for the new one's
|
||||||
|
window::Id::unique()
|
||||||
});
|
});
|
||||||
let Some(entity) = state.show_context else {
|
let Some(entity) = state.show_context else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -1088,7 +1087,7 @@ where
|
||||||
+ Sync
|
+ Sync
|
||||||
+ 'static,
|
+ 'static,
|
||||||
view: Option<impl Fn() -> crate::Element<'static, Message> + Send + Sync + 'static>,
|
view: Option<impl Fn() -> crate::Element<'static, Message> + Send + Sync + 'static>,
|
||||||
) -> crate::surface::Action {
|
) -> crate::surface::Action<Message> {
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
let boxed: Box<
|
let boxed: Box<
|
||||||
|
|
@ -1107,11 +1106,8 @@ where
|
||||||
Arc::new(boxed),
|
Arc::new(boxed),
|
||||||
Arc::new(boxed_live),
|
Arc::new(boxed_live),
|
||||||
view.map(|view| {
|
view.map(|view| {
|
||||||
let boxed: Box<
|
Arc::new(move || view().map(crate::Action::App))
|
||||||
dyn Fn() -> crate::Element<'static, Message> + Send + Sync + 'static,
|
as crate::surface::View<Message>
|
||||||
> = Box::new(view);
|
|
||||||
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
|
|
||||||
Arc::new(boxed)
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1260,6 +1256,29 @@ where
|
||||||
let my_bounds = layout.bounds();
|
let my_bounds = layout.bounds();
|
||||||
let state = tree.state.downcast_mut::<LocalState>();
|
let state = tree.state.downcast_mut::<LocalState>();
|
||||||
|
|
||||||
|
// The compositor dismissed our context menu popup: nothing else tells this state about it.
|
||||||
|
#[cfg(wayland_platform)]
|
||||||
|
if let iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(
|
||||||
|
iced::event::wayland::Event::Popup(iced::event::wayland::PopupEvent::Done, _, popup),
|
||||||
|
)) = &event
|
||||||
|
{
|
||||||
|
let dismissed = state.menu_state.inner.with_data_mut(|data| {
|
||||||
|
if data.popup_id.get(&self.window_id) == Some(popup) {
|
||||||
|
data.popup_id.clear();
|
||||||
|
data.reset();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if dismissed {
|
||||||
|
state.show_context = None;
|
||||||
|
for key in self.model.order.iter().copied() {
|
||||||
|
self.update_entity_paragraph(state, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let hovered_before = state.hovered;
|
let hovered_before = state.hovered;
|
||||||
|
|
||||||
let my_id = self.get_drag_id();
|
let my_id = self.get_drag_id();
|
||||||
|
|
@ -2240,12 +2259,7 @@ where
|
||||||
|
|
||||||
let menu_open = || {
|
let menu_open = || {
|
||||||
state.show_context == Some(key)
|
state.show_context == Some(key)
|
||||||
&& !tree.children.is_empty()
|
&& state.menu_state.inner.with_data(|data| data.open)
|
||||||
&& tree.children[0]
|
|
||||||
.state
|
|
||||||
.downcast_ref::<MenuBarState>()
|
|
||||||
.inner
|
|
||||||
.with_data(|data| data.open)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let key_is_active = self.model.is_active(key);
|
let key_is_active = self.model.is_active(key);
|
||||||
|
|
|
||||||
|
|
@ -355,6 +355,8 @@ impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, Renderer> for S
|
||||||
selected_text,
|
selected_text,
|
||||||
false,
|
false,
|
||||||
has_selection,
|
has_selection,
|
||||||
|
self.inner.has_text(&tree.children[0]),
|
||||||
|
self.inner.clipboard_has_text(&tree.children[0]),
|
||||||
&menu_bar_state,
|
&menu_bar_state,
|
||||||
&pending_action,
|
&pending_action,
|
||||||
renderer,
|
renderer,
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,12 @@ where
|
||||||
let selected_text = widget.selected_text(tree);
|
let selected_text = widget.selected_text(tree);
|
||||||
let is_editable = widget.is_editable();
|
let is_editable = widget.is_editable();
|
||||||
|
|
||||||
let mut menu_roots = build_menu_roots(is_editable, selected_text.is_some());
|
let mut menu_roots = build_menu_roots(
|
||||||
|
is_editable,
|
||||||
|
selected_text.is_some(),
|
||||||
|
widget.has_text(tree),
|
||||||
|
widget.clipboard_has_text(tree),
|
||||||
|
);
|
||||||
menu_roots.iter_mut().for_each(menu::Tree::set_index);
|
menu_roots.iter_mut().for_each(menu::Tree::set_index);
|
||||||
|
|
||||||
let bounds = Rectangle {
|
let bounds = Rectangle {
|
||||||
|
|
@ -283,28 +288,28 @@ pub(crate) enum TextCtxAction {
|
||||||
SelectAll,
|
SelectAll,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_menu_roots(is_editable: bool, has_selection: bool) -> Vec<menu::Tree<TextCtxAction>> {
|
fn build_menu_roots(
|
||||||
let mut items = Vec::with_capacity(4);
|
is_editable: bool,
|
||||||
|
has_selection: bool,
|
||||||
|
has_text: bool,
|
||||||
|
clipboard_has_text: bool,
|
||||||
|
) -> Vec<menu::Tree<TextCtxAction>> {
|
||||||
|
let item = |label: &'static str, action: TextCtxAction, enabled: bool| {
|
||||||
|
menu::Tree::from(crate::Element::from(
|
||||||
|
menu::menu_button(vec![widget::text(label).into()])
|
||||||
|
.on_press_maybe(enabled.then_some(action)),
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
if is_editable && has_selection {
|
let mut items = Vec::with_capacity(4);
|
||||||
items.push(menu::Tree::from(crate::Element::from(
|
|
||||||
menu::menu_button(vec![widget::text("Cut").into()]).on_press(TextCtxAction::Cut),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
if has_selection {
|
|
||||||
items.push(menu::Tree::from(crate::Element::from(
|
|
||||||
menu::menu_button(vec![widget::text("Copy").into()]).on_press(TextCtxAction::Copy),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
if is_editable {
|
if is_editable {
|
||||||
items.push(menu::Tree::from(crate::Element::from(
|
items.push(item("Cut", TextCtxAction::Cut, has_selection));
|
||||||
menu::menu_button(vec![widget::text("Paste").into()]).on_press(TextCtxAction::Paste),
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
items.push(menu::Tree::from(crate::Element::from(
|
items.push(item("Copy", TextCtxAction::Copy, has_selection));
|
||||||
menu::menu_button(vec![widget::text("Select All").into()])
|
if is_editable {
|
||||||
.on_press(TextCtxAction::SelectAll),
|
items.push(item("Paste", TextCtxAction::Paste, clipboard_has_text));
|
||||||
)));
|
}
|
||||||
|
items.push(item("Select All", TextCtxAction::SelectAll, has_text));
|
||||||
|
|
||||||
vec![menu::Tree::with_children(
|
vec![menu::Tree::with_children(
|
||||||
RcElementWrapper::new(crate::Element::from(widget::Row::new())),
|
RcElementWrapper::new(crate::Element::from(widget::Row::new())),
|
||||||
|
|
@ -486,6 +491,8 @@ pub(crate) fn create_text_context_popup(
|
||||||
selected_text: Option<String>,
|
selected_text: Option<String>,
|
||||||
is_editable: bool,
|
is_editable: bool,
|
||||||
has_selection: bool,
|
has_selection: bool,
|
||||||
|
has_text: bool,
|
||||||
|
clipboard_has_text: bool,
|
||||||
menu_bar_state: &MenuBarState,
|
menu_bar_state: &MenuBarState,
|
||||||
pending_action: &PendingAction,
|
pending_action: &PendingAction,
|
||||||
renderer: &crate::Renderer,
|
renderer: &crate::Renderer,
|
||||||
|
|
@ -499,7 +506,7 @@ pub(crate) fn create_text_context_popup(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut menu_roots = build_menu_roots(is_editable, has_selection);
|
let mut menu_roots = build_menu_roots(is_editable, has_selection, has_text, clipboard_has_text);
|
||||||
menu_roots.iter_mut().for_each(menu::Tree::set_index);
|
menu_roots.iter_mut().for_each(menu::Tree::set_index);
|
||||||
|
|
||||||
let id = menu_bar_state.inner.with_data_mut(|state| {
|
let id = menu_bar_state.inner.with_data_mut(|state| {
|
||||||
|
|
|
||||||
|
|
@ -297,6 +297,8 @@ impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer
|
||||||
selected_text,
|
selected_text,
|
||||||
self.inner.is_editable(),
|
self.inner.is_editable(),
|
||||||
has_selection,
|
has_selection,
|
||||||
|
self.inner.has_text(&tree.children[0]),
|
||||||
|
self.inner.clipboard_has_text(&tree.children[0]),
|
||||||
&menu_bar_state,
|
&menu_bar_state,
|
||||||
&pending_action,
|
&pending_action,
|
||||||
renderer,
|
renderer,
|
||||||
|
|
|
||||||
|
|
@ -1048,6 +1048,8 @@ where
|
||||||
.selection(&state.tracked_value)
|
.selection(&state.tracked_value)
|
||||||
.map(|(start, end)| state.tracked_value.select(start, end).to_string());
|
.map(|(start, end)| state.tracked_value.select(start, end).to_string());
|
||||||
let has_selection = selected_text.is_some();
|
let has_selection = selected_text.is_some();
|
||||||
|
let has_text = !state.tracked_value.is_empty();
|
||||||
|
let clipboard_has_text = state.clipboard_has_text;
|
||||||
let click_position = state.context_menu_position.unwrap();
|
let click_position = state.context_menu_position.unwrap();
|
||||||
let menu_bar_state = state.menu_bar_state.clone();
|
let menu_bar_state = state.menu_bar_state.clone();
|
||||||
let pending_action = state.pending_action.clone();
|
let pending_action = state.pending_action.clone();
|
||||||
|
|
@ -1057,6 +1059,8 @@ where
|
||||||
selected_text,
|
selected_text,
|
||||||
true,
|
true,
|
||||||
has_selection,
|
has_selection,
|
||||||
|
has_text,
|
||||||
|
clipboard_has_text,
|
||||||
&menu_bar_state,
|
&menu_bar_state,
|
||||||
&pending_action,
|
&pending_action,
|
||||||
renderer,
|
renderer,
|
||||||
|
|
@ -1611,6 +1615,7 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
state.focus();
|
state.focus();
|
||||||
}
|
}
|
||||||
state.context_menu_position = Some(pos);
|
state.context_menu_position = Some(pos);
|
||||||
|
state.clipboard_has_text = iced_core::widget::text::clipboard_has_text(clipboard);
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -2876,14 +2881,17 @@ pub fn draw<'a, Message>(
|
||||||
effective_alignment(state.value.raw()),
|
effective_alignment(state.value.raw()),
|
||||||
);
|
);
|
||||||
|
|
||||||
if cursors.is_empty() {
|
let shift = Vector::new(alignment_offset - offset, 0.0);
|
||||||
renderer.with_translation(Vector::ZERO, |_| {});
|
let fill_cursors = |renderer: &mut crate::Renderer| {
|
||||||
} else {
|
renderer.with_translation(shift, |renderer| {
|
||||||
renderer.with_translation(Vector::new(alignment_offset - offset, 0.0), |renderer| {
|
|
||||||
for (quad, color) in &cursors {
|
for (quad, color) in &cursors {
|
||||||
renderer.fill_quad(*quad, *color);
|
renderer.fill_quad(*quad, *color);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if !is_selecting {
|
||||||
|
fill_cursors(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
let bounds = Rectangle {
|
let bounds = Rectangle {
|
||||||
|
|
@ -2898,27 +2906,39 @@ pub fn draw<'a, Message>(
|
||||||
text_color
|
text_color
|
||||||
};
|
};
|
||||||
|
|
||||||
renderer.fill_text(
|
let text = Text {
|
||||||
Text {
|
content: if text.is_empty() {
|
||||||
content: if text.is_empty() {
|
placeholder.to_string()
|
||||||
placeholder.to_string()
|
} else {
|
||||||
} else {
|
text.clone()
|
||||||
text.clone()
|
|
||||||
},
|
|
||||||
font,
|
|
||||||
bounds: bounds.size(),
|
|
||||||
size: iced::Pixels(size),
|
|
||||||
align_x: text::Alignment::Default,
|
|
||||||
align_y: alignment::Vertical::Center,
|
|
||||||
line_height: text::LineHeight::default(),
|
|
||||||
shaping: text::Shaping::Advanced,
|
|
||||||
wrapping: text::Wrapping::None,
|
|
||||||
ellipsize: text::Ellipsize::None,
|
|
||||||
},
|
},
|
||||||
bounds.position(),
|
font,
|
||||||
color,
|
bounds: bounds.size(),
|
||||||
text_bounds,
|
size: iced::Pixels(size),
|
||||||
);
|
align_x: text::Alignment::Default,
|
||||||
|
align_y: alignment::Vertical::Center,
|
||||||
|
line_height: text::LineHeight::default(),
|
||||||
|
shaping: text::Shaping::Advanced,
|
||||||
|
wrapping: text::Wrapping::None,
|
||||||
|
ellipsize: text::Ellipsize::None,
|
||||||
|
};
|
||||||
|
renderer.fill_text(text.clone(), bounds.position(), color, text_bounds);
|
||||||
|
|
||||||
|
// Redraw the same text in the selected color, clipped to the selection quads,
|
||||||
|
// so glyph shaping and positioning stay identical to the unselected pass.
|
||||||
|
if is_selecting {
|
||||||
|
fill_cursors(renderer);
|
||||||
|
for (quad, _) in &cursors {
|
||||||
|
renderer.with_layer(quad.bounds + shift, |renderer| {
|
||||||
|
renderer.fill_text(
|
||||||
|
text.clone(),
|
||||||
|
bounds.position(),
|
||||||
|
appearance.selected_text_color,
|
||||||
|
text_bounds,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: we always must clip with a layer because of what appears to be a tiny-skia text clipping issue.
|
// FIXME: we always must clip with a layer because of what appears to be a tiny-skia text clipping issue.
|
||||||
|
|
@ -3062,6 +3082,7 @@ pub struct State {
|
||||||
keyboard_modifiers: keyboard::Modifiers,
|
keyboard_modifiers: keyboard::Modifiers,
|
||||||
scroll_offset: f32,
|
scroll_offset: f32,
|
||||||
context_menu_position: Option<iced_core::Point>,
|
context_menu_position: Option<iced_core::Point>,
|
||||||
|
clipboard_has_text: bool,
|
||||||
pub(crate) menu_bar_state: crate::widget::menu::MenuBarState,
|
pub(crate) menu_bar_state: crate::widget::menu::MenuBarState,
|
||||||
pub(crate) pending_action: crate::widget::text_context_menu::PendingAction,
|
pub(crate) pending_action: crate::widget::text_context_menu::PendingAction,
|
||||||
}
|
}
|
||||||
|
|
@ -3157,6 +3178,7 @@ impl State {
|
||||||
scroll_offset: 0.0,
|
scroll_offset: 0.0,
|
||||||
dirty: false,
|
dirty: false,
|
||||||
context_menu_position: None,
|
context_menu_position: None,
|
||||||
|
clipboard_has_text: false,
|
||||||
menu_bar_state: crate::widget::menu::MenuBarState::default(),
|
menu_bar_state: crate::widget::menu::MenuBarState::default(),
|
||||||
pending_action: crate::widget::text_context_menu::pending_action(),
|
pending_action: crate::widget::text_context_menu::pending_action(),
|
||||||
}
|
}
|
||||||
|
|
@ -3520,6 +3542,14 @@ impl<Message: Clone + 'static> iced_core::widget::text::HasSelectableText
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_text(&self, tree: &WidgetTree) -> bool {
|
||||||
|
!tree.state.downcast_ref::<State>().tracked_value.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clipboard_has_text(&self, tree: &WidgetTree) -> bool {
|
||||||
|
tree.state.downcast_ref::<State>().clipboard_has_text
|
||||||
|
}
|
||||||
|
|
||||||
fn is_focused(&self, tree: &WidgetTree) -> bool {
|
fn is_focused(&self, tree: &WidgetTree) -> bool {
|
||||||
tree.state.downcast_ref::<State>().is_focused()
|
tree.state.downcast_ref::<State>().is_focused()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ pub struct Tooltip<'a, Message, TopLevelMessage> {
|
||||||
label: Option<Vec<iced_accessibility::accesskit::NodeId>>,
|
label: Option<Vec<iced_accessibility::accesskit::NodeId>>,
|
||||||
content: crate::Element<'a, Message>,
|
content: crate::Element<'a, Message>,
|
||||||
on_leave: Message,
|
on_leave: Message,
|
||||||
on_surface_action: Box<dyn Fn(crate::surface::Action) -> Message>,
|
on_surface_action: Box<dyn Fn(crate::surface::Action<TopLevelMessage>) -> Message>,
|
||||||
width: Length,
|
width: Length,
|
||||||
height: Length,
|
height: Length,
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
|
|
@ -75,7 +75,7 @@ impl<'a, Message, TopLevelMessage> Tooltip<'a, Message, TopLevelMessage> {
|
||||||
+ Sync
|
+ Sync
|
||||||
+ 'static,
|
+ 'static,
|
||||||
on_leave: Message,
|
on_leave: Message,
|
||||||
on_surface_action: impl Fn(crate::surface::Action) -> Message + 'static,
|
on_surface_action: impl Fn(crate::surface::Action<TopLevelMessage>) -> Message + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: Id::unique(),
|
id: Id::unique(),
|
||||||
|
|
@ -440,7 +440,7 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>(
|
||||||
>,
|
>,
|
||||||
delay: Option<Duration>,
|
delay: Option<Duration>,
|
||||||
on_leave: &Message,
|
on_leave: &Message,
|
||||||
on_surface_action: &dyn Fn(crate::surface::Action) -> Message,
|
on_surface_action: &dyn Fn(crate::surface::Action<TopLevelMessage>) -> Message,
|
||||||
state: impl FnOnce() -> &'a mut State,
|
state: impl FnOnce() -> &'a mut State,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
|
|
@ -534,19 +534,8 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>(
|
||||||
crate::surface::Action::Popup(
|
crate::surface::Action::Popup(
|
||||||
Arc::new(boxed),
|
Arc::new(boxed),
|
||||||
Arc::new(boxed_live),
|
Arc::new(boxed_live),
|
||||||
Some({
|
Some(Arc::new(move || view())
|
||||||
let boxed: Box<
|
as crate::surface::View<TopLevelMessage>),
|
||||||
dyn Fn() -> crate::Element<
|
|
||||||
'static,
|
|
||||||
crate::Action<TopLevelMessage>,
|
|
||||||
> + Send
|
|
||||||
+ Sync
|
|
||||||
+ 'static,
|
|
||||||
> = Box::new(move || view());
|
|
||||||
let boxed: Box<dyn Any + Send + Sync + 'static> =
|
|
||||||
Box::new(boxed);
|
|
||||||
Arc::new(boxed)
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
@ -583,19 +572,8 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>(
|
||||||
let sm = crate::surface::Action::Popup(
|
let sm = crate::surface::Action::Popup(
|
||||||
Arc::new(boxed),
|
Arc::new(boxed),
|
||||||
Arc::new(boxed_live),
|
Arc::new(boxed_live),
|
||||||
Some({
|
Some(Arc::new(move || view())
|
||||||
let boxed: Box<
|
as crate::surface::View<TopLevelMessage>),
|
||||||
dyn Fn() -> crate::Element<
|
|
||||||
'static,
|
|
||||||
crate::Action<TopLevelMessage>,
|
|
||||||
> + Send
|
|
||||||
+ Sync
|
|
||||||
+ 'static,
|
|
||||||
> = Box::new(move || view());
|
|
||||||
let boxed: Box<dyn Any + Send + Sync + 'static> =
|
|
||||||
Box::new(boxed);
|
|
||||||
Arc::new(boxed)
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
shell.publish((on_surface_action)(sm));
|
shell.publish((on_surface_action)(sm));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue