wip: menus
This commit is contained in:
parent
b316d8901b
commit
04b51bc1ba
25 changed files with 1358 additions and 243 deletions
|
|
@ -66,6 +66,7 @@ tokio = [
|
||||||
# Wayland window support
|
# Wayland window support
|
||||||
wayland = [
|
wayland = [
|
||||||
"ashpd?/wayland",
|
"ashpd?/wayland",
|
||||||
|
"autosize",
|
||||||
"iced_runtime/wayland",
|
"iced_runtime/wayland",
|
||||||
"iced/wayland",
|
"iced/wayland",
|
||||||
"iced_winit/wayland",
|
"iced_winit/wayland",
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,65 @@
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use cosmic::app::message::{SurfaceMessage, SurfaceMessageHandler};
|
use cosmic::app::message::{SurfaceMessage, SurfaceMessageHandler};
|
||||||
use cosmic::app::Core;
|
use cosmic::app::Core;
|
||||||
use cosmic::iced::application;
|
|
||||||
use cosmic::iced::platform_specific::shell::commands::popup::{destroy_popup, get_popup};
|
|
||||||
use cosmic::iced::window::Id;
|
use cosmic::iced::window::Id;
|
||||||
use cosmic::iced::{Length, Limits, Task};
|
use cosmic::iced::{Length, Limits, Task};
|
||||||
use cosmic::iced_runtime::core::window;
|
use cosmic::iced_runtime::core::window;
|
||||||
|
use cosmic::iced_runtime::platform_specific::wayland::popup::SctkPopupSettings;
|
||||||
use cosmic::iced_runtime::platform_specific::wayland::subsurface;
|
use cosmic::iced_runtime::platform_specific::wayland::subsurface;
|
||||||
use cosmic::theme::iced;
|
use cosmic::widget::dropdown::DropdownView;
|
||||||
use cosmic::widget::{layer_container, list_column, settings, toggler};
|
use cosmic::widget::{autosize, dropdown, layer_container, list_column, settings, toggler};
|
||||||
use cosmic::{applet, iced_core, Element, Theme};
|
use cosmic::{iced_core, Element};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
const ID: &str = "com.system76.CosmicAppletExample";
|
const ID: &str = "com.system76.CosmicAppletExample";
|
||||||
|
|
||||||
#[derive(Default)]
|
static SUBSURFACE_ID: Lazy<cosmic::widget::Id> =
|
||||||
|
Lazy::new(|| cosmic::widget::Id::new("subsurface"));
|
||||||
|
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
core: Core,
|
core: Core,
|
||||||
popup: Option<Id>,
|
popup: Option<Id>,
|
||||||
example_row: bool,
|
example_row: bool,
|
||||||
subsurface_id: Option<Id>,
|
selected: Option<usize>,
|
||||||
|
subsurface_id: Id,
|
||||||
|
dropdown_id: Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
impl Default for Window {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
core: Core::default(),
|
||||||
|
popup: None,
|
||||||
|
example_row: false,
|
||||||
|
selected: None,
|
||||||
|
subsurface_id: Id::unique(),
|
||||||
|
dropdown_id: Id::unique(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
PopupClosed(Id),
|
PopupClosed(Id),
|
||||||
|
PopupCloseRequested(Id),
|
||||||
ToggleExampleRow(bool),
|
ToggleExampleRow(bool),
|
||||||
|
Selected(usize),
|
||||||
Surface(SurfaceMessage),
|
Surface(SurfaceMessage),
|
||||||
|
OpenDropdown(SctkPopupSettings, DropdownView<Message>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Message {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::PopupClosed(arg0) => f.debug_tuple("PopupClosed").field(arg0).finish(),
|
||||||
|
Self::PopupCloseRequested(arg0) => {
|
||||||
|
f.debug_tuple("PopupCloseRequested").field(arg0).finish()
|
||||||
|
}
|
||||||
|
Self::ToggleExampleRow(arg0) => f.debug_tuple("ToggleExampleRow").field(arg0).finish(),
|
||||||
|
Self::Selected(arg0) => f.debug_tuple("Selected").field(arg0).finish(),
|
||||||
|
Self::Surface(arg0) => f.debug_tuple("Surface").field(arg0).finish(),
|
||||||
|
Self::OpenDropdown(arg0, _) => f.debug_tuple("OpenDropdown").field(arg0).finish(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SurfaceMessageHandler for Message {
|
impl SurfaceMessageHandler for Message {
|
||||||
|
|
@ -82,6 +115,29 @@ impl cosmic::Application for Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::Surface(surface_message) => {}
|
Message::Surface(surface_message) => {}
|
||||||
|
Message::Selected(i) => {
|
||||||
|
self.selected = Some(i);
|
||||||
|
return cosmic::task::message(cosmic::app::message::destroy_popup::<Window>(
|
||||||
|
self.dropdown_id,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Message::OpenDropdown(sctk_popup_settings, view) => {
|
||||||
|
self.dropdown_id = sctk_popup_settings.id;
|
||||||
|
return cosmic::task::message(cosmic::app::message::get_popup::<
|
||||||
|
Window,
|
||||||
|
>(
|
||||||
|
move |_: &mut Window| sctk_popup_settings.clone(),
|
||||||
|
Some(
|
||||||
|
move |_: &Window| -> cosmic::Element<'static, cosmic::app::Message<Message>> {
|
||||||
|
view().map(cosmic::app::Message::App)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Message::PopupCloseRequested(id) => {
|
||||||
|
dbg!(id, self.dropdown_id);
|
||||||
|
return cosmic::task::message(cosmic::app::message::destroy_popup::<Window>(id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
@ -129,8 +185,8 @@ impl cosmic::Application for Window {
|
||||||
)
|
)
|
||||||
.height(Length::Fixed(50.)),
|
.height(Length::Fixed(50.)),
|
||||||
),
|
),
|
||||||
|
).add(dropdown(&["1", "asdf", "hello", "test"], state.selected, Message::Selected).with_popup(state.popup.unwrap_or(Id::NONE), Message::OpenDropdown).on_close_popup(Message::PopupCloseRequested)
|
||||||
);
|
);
|
||||||
|
|
||||||
Element::from(
|
Element::from(
|
||||||
state.core.applet.popup_container(content_list),
|
state.core.applet.popup_container(content_list),
|
||||||
)
|
)
|
||||||
|
|
@ -141,25 +197,29 @@ impl cosmic::Application for Window {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|layout| {
|
|_| {
|
||||||
cosmic::app::message::get_subsurface::<Window>(
|
cosmic::app::message::get_subsurface(
|
||||||
|app: &mut Window| {
|
|app: &mut Window| {
|
||||||
let id = window::Id::unique();
|
|
||||||
app.subsurface_id = Some(id);
|
|
||||||
|
|
||||||
subsurface::SctkSubsurfaceSettings {
|
subsurface::SctkSubsurfaceSettings {
|
||||||
parent: window::Id::RESERVED,
|
parent: window::Id::RESERVED,
|
||||||
id,
|
id: app.subsurface_id,
|
||||||
loc: iced_core::Point { x: -100., y: 0. },
|
loc: iced_core::Point { x: -100., y: 0. },
|
||||||
size: Some(iced_core::Size::new(100., 18.)),
|
size: Some((10., 10.).into()),
|
||||||
z: 1,
|
z: 1,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(|app: &Window| layer_container(cosmic::widget::text("hello")).into()),
|
Some(|_: &Window| {
|
||||||
|
autosize::autosize(
|
||||||
|
layer_container(cosmic::widget::text("hello"))
|
||||||
|
.layer(cosmic::cosmic_theme::Layer::Background),
|
||||||
|
SUBSURFACE_ID.clone(),
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
cosmic::app::message::destroy_subsurface::<Window>(
|
cosmic::app::message::destroy_subsurface::<Window>(
|
||||||
self.subsurface_id.unwrap_or(window::Id::NONE),
|
self.subsurface_id
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,26 @@
|
||||||
|
|
||||||
//! Application API example
|
//! Application API example
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
use cosmic::app::message::{SurfaceMessage, SurfaceMessageHandler};
|
||||||
use cosmic::app::{Core, Settings, Task};
|
use cosmic::app::{Core, Settings, Task};
|
||||||
|
use cosmic::iced::alignment::{Horizontal, Vertical};
|
||||||
use cosmic::iced::widget::column;
|
use cosmic::iced::widget::column;
|
||||||
|
use cosmic::iced::Length;
|
||||||
use cosmic::iced_core::Size;
|
use cosmic::iced_core::Size;
|
||||||
use cosmic::widget::nav_bar;
|
use cosmic::widget::icon::{from_name, Handle};
|
||||||
|
use cosmic::widget::menu::KeyBind;
|
||||||
|
use cosmic::widget::{
|
||||||
|
container,
|
||||||
|
menu::{self, action::MenuAction},
|
||||||
|
nav_bar, responsive,
|
||||||
|
};
|
||||||
use cosmic::{executor, iced, ApplicationExt, Element};
|
use cosmic::{executor, iced, ApplicationExt, Element};
|
||||||
|
|
||||||
|
static MENU_ID: LazyLock<iced::id::Id> = LazyLock::new(|| iced::id::Id::new("menu_id"));
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum Page {
|
pub enum Page {
|
||||||
Page1,
|
Page1,
|
||||||
|
|
@ -28,11 +42,24 @@ impl Page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum Action {
|
||||||
|
Hi,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MenuAction for Action {
|
||||||
|
type Message = Message;
|
||||||
|
|
||||||
|
fn message(&self) -> Message {
|
||||||
|
Message::Hi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Runs application with these settings
|
/// Runs application with these settings
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
tracing_subscriber::fmt::init();
|
// tracing_subscriber::fmt::init();
|
||||||
let _ = tracing_log::LogTracer::init();
|
// let _ = tracing_log::LogTracer::init();
|
||||||
|
|
||||||
let input = vec![
|
let input = vec![
|
||||||
(Page::Page1, "🖖 Hello from libcosmic.".into()),
|
(Page::Page1, "🖖 Hello from libcosmic.".into()),
|
||||||
|
|
@ -56,6 +83,23 @@ pub enum Message {
|
||||||
Input2(String),
|
Input2(String),
|
||||||
Ignore,
|
Ignore,
|
||||||
ToggleHide,
|
ToggleHide,
|
||||||
|
Surface(SurfaceMessage),
|
||||||
|
Hi,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SurfaceMessageHandler for Message {
|
||||||
|
fn to_surface_message(self) -> cosmic::app::message::MessageWrapper<Self> {
|
||||||
|
match self {
|
||||||
|
Message::Surface(msg) => cosmic::app::message::MessageWrapper::Surface(msg),
|
||||||
|
msg => cosmic::app::message::MessageWrapper::Message(msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SurfaceMessage> for Message {
|
||||||
|
fn from(value: SurfaceMessage) -> Self {
|
||||||
|
Message::Surface(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The [`App`] stores application-specific state.
|
/// The [`App`] stores application-specific state.
|
||||||
|
|
@ -65,6 +109,7 @@ pub struct App {
|
||||||
input_1: String,
|
input_1: String,
|
||||||
input_2: String,
|
input_2: String,
|
||||||
hidden: bool,
|
hidden: bool,
|
||||||
|
keybinds: HashMap<KeyBind, Action>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implement [`cosmic::Application`] to integrate with COSMIC.
|
/// Implement [`cosmic::Application`] to integrate with COSMIC.
|
||||||
|
|
@ -105,6 +150,7 @@ impl cosmic::Application for App {
|
||||||
input_1: String::new(),
|
input_1: String::new(),
|
||||||
input_2: String::new(),
|
input_2: String::new(),
|
||||||
hidden: true,
|
hidden: true,
|
||||||
|
keybinds: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let command = app.update_title();
|
let command = app.update_title();
|
||||||
|
|
@ -136,6 +182,12 @@ impl cosmic::Application for App {
|
||||||
Message::ToggleHide => {
|
Message::ToggleHide => {
|
||||||
self.hidden = !self.hidden;
|
self.hidden = !self.hidden;
|
||||||
}
|
}
|
||||||
|
Message::Surface(_) => {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
Message::Hi => {
|
||||||
|
dbg!("hi");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
@ -178,6 +230,49 @@ impl cosmic::Application for App {
|
||||||
|
|
||||||
Element::from(centered)
|
Element::from(centered)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn header_start(&self) -> Vec<Element<Self::Message>> {
|
||||||
|
vec![self.core.responsive_menu_bar(
|
||||||
|
MENU_ID.clone(),
|
||||||
|
vec![
|
||||||
|
menu::Tree::with_children(
|
||||||
|
menu::root("hiiiiiiiiiiiiiiiiiii 1"),
|
||||||
|
menu::items(
|
||||||
|
&self.keybinds,
|
||||||
|
vec![menu::Item::Button("hi", None, Action::Hi)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
menu::Tree::with_children(
|
||||||
|
menu::root("hiiiiiiiiiiiiiiiiii 2"),
|
||||||
|
menu::items(
|
||||||
|
&self.keybinds,
|
||||||
|
vec![menu::Item::Button("hi 2", None, Action::Hi)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
menu::Tree::with_children(
|
||||||
|
menu::root("hiiiiiiiiiiiiiiiiiiiii 3"),
|
||||||
|
menu::items(
|
||||||
|
&self.keybinds,
|
||||||
|
vec![menu::Item::Button("hi 3", None, Action::Hi)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
menu::Tree::with_children(
|
||||||
|
menu::root("hi 3"),
|
||||||
|
menu::items(
|
||||||
|
&self.keybinds,
|
||||||
|
vec![menu::Item::Button("hi 3", None, Action::Hi)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
menu::Tree::with_children(
|
||||||
|
menu::root("hi 4"),
|
||||||
|
menu::items(
|
||||||
|
&self.keybinds,
|
||||||
|
vec![menu::Item::Button("hi 3", None, Action::Hi)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App
|
impl App
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,24 @@
|
||||||
|
|
||||||
use std::{cell::OnceCell, collections::HashMap};
|
use std::{cell::OnceCell, collections::HashMap};
|
||||||
|
|
||||||
use crate::widget::nav_bar;
|
use crate::{
|
||||||
|
theme::menu_bar,
|
||||||
|
widget::{
|
||||||
|
button, icon,
|
||||||
|
menu::{self, Tree},
|
||||||
|
nav_bar, responsive_container,
|
||||||
|
},
|
||||||
|
};
|
||||||
use cosmic_config::CosmicConfigEntry;
|
use cosmic_config::CosmicConfigEntry;
|
||||||
use cosmic_theme::ThemeMode;
|
use cosmic_theme::ThemeMode;
|
||||||
use iced::window;
|
use iced::{window, Limits, Size};
|
||||||
use iced_core::window::Id;
|
use iced_core::{window::Id, Element};
|
||||||
use palette::Srgba;
|
use palette::Srgba;
|
||||||
use slotmap::Key;
|
use slotmap::Key;
|
||||||
|
|
||||||
use crate::Theme;
|
use crate::Theme;
|
||||||
|
|
||||||
use super::{Element, Task};
|
use super::{message::SurfaceMessage, Renderer, Task};
|
||||||
|
|
||||||
/// Status of the nav bar and its panels.
|
/// Status of the nav bar and its panels.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
@ -97,6 +104,8 @@ pub struct Core {
|
||||||
pub(crate) main_window: Option<window::Id>,
|
pub(crate) main_window: Option<window::Id>,
|
||||||
|
|
||||||
pub(crate) exit_on_main_window_closed: bool,
|
pub(crate) exit_on_main_window_closed: bool,
|
||||||
|
|
||||||
|
pub(crate) menu_bars: HashMap<crate::widget::Id, (Limits, Size)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Core {
|
impl Default for Core {
|
||||||
|
|
@ -153,6 +162,7 @@ impl Default for Core {
|
||||||
portal_is_high_contrast: None,
|
portal_is_high_contrast: None,
|
||||||
main_window: None,
|
main_window: None,
|
||||||
exit_on_main_window_closed: true,
|
exit_on_main_window_closed: true,
|
||||||
|
menu_bars: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -376,4 +386,35 @@ impl Core {
|
||||||
std::mem::swap(&mut self.main_window, &mut id);
|
std::mem::swap(&mut self.main_window, &mut id);
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn responsive_menu_bar<'a, Message: Clone + From<SurfaceMessage> + 'static>(
|
||||||
|
&self,
|
||||||
|
id: crate::widget::Id,
|
||||||
|
roots: Vec<Tree<'a, Message, Renderer>>,
|
||||||
|
) -> crate::Element<'a, Message> {
|
||||||
|
let menu_bar_size = self.menu_bars.get(&id);
|
||||||
|
if !menu_bar_size.is_some_and(|(limits, size)| {
|
||||||
|
let max_size = limits.max();
|
||||||
|
|
||||||
|
max_size.width < size.width
|
||||||
|
}) {
|
||||||
|
crate::Element::from(responsive_container::responsive_container(
|
||||||
|
menu::bar(roots),
|
||||||
|
id,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
crate::Element::from(
|
||||||
|
responsive_container::responsive_container(
|
||||||
|
menu::bar(vec![menu::Tree::<'a, _, _>::with_children(
|
||||||
|
button::icon(icon::from_name("open-menu-symbolic"))
|
||||||
|
.padding([4, 12])
|
||||||
|
.class(crate::theme::Button::MenuRoot),
|
||||||
|
roots,
|
||||||
|
)]),
|
||||||
|
id,
|
||||||
|
)
|
||||||
|
.size(menu_bar_size.unwrap().1),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,8 +124,7 @@ where
|
||||||
let message = match message {
|
let message = match message {
|
||||||
super::Message::App(message) => {
|
super::Message::App(message) => {
|
||||||
match super::message::SurfaceMessageHandler::to_surface_message(message) {
|
match super::message::SurfaceMessageHandler::to_surface_message(message) {
|
||||||
super::message::MessageWrapper::Surface(surface_message) =>
|
super::message::MessageWrapper::Surface(surface_message) => {
|
||||||
{
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
match surface_message {
|
match surface_message {
|
||||||
super::message::SurfaceMessage::Subsurface(settings, view) => {
|
super::message::SurfaceMessage::Subsurface(settings, view) => {
|
||||||
|
|
@ -160,7 +159,6 @@ where
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
super::message::SurfaceMessage::Popup(settings, view) => {
|
super::message::SurfaceMessage::Popup(settings, view) => {
|
||||||
let Some(settings) = std::sync::Arc::try_unwrap(settings)
|
let Some(settings) = std::sync::Arc::try_unwrap(settings)
|
||||||
.ok()
|
.ok()
|
||||||
|
|
@ -200,8 +198,19 @@ where
|
||||||
super::message::SurfaceMessage::DestroySubsurface(id) => {
|
super::message::SurfaceMessage::DestroySubsurface(id) => {
|
||||||
iced_winit::commands::subsurface::destroy_subsurface(id)
|
iced_winit::commands::subsurface::destroy_subsurface(id)
|
||||||
}
|
}
|
||||||
|
super::message::SurfaceMessage::ResponsiveMenuBar {
|
||||||
|
menu_bar,
|
||||||
|
limits,
|
||||||
|
size,
|
||||||
|
} => {
|
||||||
|
let core = self.app.core_mut();
|
||||||
|
core.menu_bars.insert(menu_bar, (limits, size));
|
||||||
|
iced::Task::none()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(not(feature = "wayland"))]
|
||||||
|
iced::Task::none()
|
||||||
|
}
|
||||||
super::message::MessageWrapper::Message(message) => self.app.update(message),
|
super::message::MessageWrapper::Message(message) => self.app.update(message),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ pub(crate) mod multi_window;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
|
||||||
pub mod message {
|
pub mod message {
|
||||||
|
use iced::{Limits, Size};
|
||||||
|
|
||||||
pub enum MessageWrapper<M> {
|
pub enum MessageWrapper<M> {
|
||||||
Surface(SurfaceMessage),
|
Surface(SurfaceMessage),
|
||||||
Message(M),
|
Message(M),
|
||||||
|
|
@ -31,6 +33,7 @@ pub mod message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ignore this message in your application. It will be intercepted.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum SurfaceMessage {
|
pub enum SurfaceMessage {
|
||||||
/// Create a subsurface with a view function
|
/// Create a subsurface with a view function
|
||||||
|
|
@ -47,20 +50,39 @@ pub mod message {
|
||||||
),
|
),
|
||||||
/// Destroy a subsurface with a view function
|
/// Destroy a subsurface with a view function
|
||||||
DestroyPopup(iced::window::Id),
|
DestroyPopup(iced::window::Id),
|
||||||
|
/// Responsive menu bar update
|
||||||
|
ResponsiveMenuBar {
|
||||||
|
/// Id of the menu bar
|
||||||
|
menu_bar: crate::widget::Id,
|
||||||
|
/// Limits of the menu bar
|
||||||
|
limits: Limits,
|
||||||
|
/// Requested Full Size for expanded menu bar
|
||||||
|
size: Size,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
impl std::fmt::Debug for SurfaceMessage {
|
impl std::fmt::Debug for SurfaceMessage {
|
||||||
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 {
|
||||||
SurfaceMessage::Subsurface(any, any1) => f.debug_tuple("Subsurface").finish(),
|
Self::Subsurface(arg0, arg1) => {
|
||||||
SurfaceMessage::DestroySubsurface(id) => {
|
f.debug_tuple("Subsurface").field(arg0).field(arg1).finish()
|
||||||
f.debug_tuple("DestroySubsurface").field(id).finish()
|
|
||||||
}
|
}
|
||||||
SurfaceMessage::Popup(any, any1) => f.debug_tuple("Popup").finish(),
|
Self::DestroySubsurface(arg0) => {
|
||||||
SurfaceMessage::DestroyPopup(id) => {
|
f.debug_tuple("DestroySubsurface").field(arg0).finish()
|
||||||
f.debug_tuple("DestroyPopup").field(id).finish()
|
|
||||||
}
|
}
|
||||||
|
Self::Popup(arg0, arg1) => f.debug_tuple("Popup").field(arg0).field(arg1).finish(),
|
||||||
|
Self::DestroyPopup(arg0) => f.debug_tuple("DestroyPopup").field(arg0).finish(),
|
||||||
|
Self::ResponsiveMenuBar {
|
||||||
|
menu_bar,
|
||||||
|
limits,
|
||||||
|
size,
|
||||||
|
} => f
|
||||||
|
.debug_struct("ResponsiveMenuBar")
|
||||||
|
.field("menu_bar", menu_bar)
|
||||||
|
.field("limits", limits)
|
||||||
|
.field("size", size)
|
||||||
|
.finish(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -149,6 +171,20 @@ pub mod message {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO could this somehow be used by widgets? It is the wrong message type though.
|
||||||
|
// #[cfg(feature = "wayland")]
|
||||||
|
// pub fn get_simple_popup<App: super::Application>(
|
||||||
|
// settings: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings,
|
||||||
|
// view: Option<
|
||||||
|
// impl Fn() -> crate::Element<'static, super::Message<App::Message>> + Send + Sync + 'static,
|
||||||
|
// >,
|
||||||
|
// ) -> App::Message
|
||||||
|
// where
|
||||||
|
// App::Message: SurfaceMessageHandler + From<SurfaceMessage>,
|
||||||
|
// {
|
||||||
|
// todo!()
|
||||||
|
// }
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
pub fn get_subsurface<App: super::Application>(
|
pub fn get_subsurface<App: super::Application>(
|
||||||
settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync + 'static,
|
settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync + 'static,
|
||||||
|
|
@ -477,7 +513,7 @@ impl DbusActivation {
|
||||||
pub fn run_single_instance<App: Application>(settings: Settings, flags: App::Flags) -> iced::Result
|
pub fn run_single_instance<App: Application>(settings: Settings, flags: App::Flags) -> iced::Result
|
||||||
where
|
where
|
||||||
App::Flags: CosmicFlags,
|
App::Flags: CosmicFlags,
|
||||||
App::Message: Clone + std::fmt::Debug + Send + 'static,
|
App::Message: Clone + std::fmt::Debug + Send + SurfaceMessageHandler + 'static,
|
||||||
{
|
{
|
||||||
let activation_token = std::env::var("XDG_ACTIVATION_TOKEN").ok();
|
let activation_token = std::env::var("XDG_ACTIVATION_TOKEN").ok();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -308,6 +308,7 @@ impl Context {
|
||||||
},
|
},
|
||||||
parent_size: None,
|
parent_size: None,
|
||||||
grab: true,
|
grab: true,
|
||||||
|
close_with_children: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// From iced_aw, license MIT
|
// From iced_aw, license MIT
|
||||||
|
|
||||||
//! Change the appearance of menu bars and their menus.
|
//! Change the appearance of menu bars and their menus.
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::Theme;
|
use crate::Theme;
|
||||||
use iced_widget::core::Color;
|
use iced_widget::core::Color;
|
||||||
|
|
||||||
|
|
@ -33,19 +35,19 @@ pub trait StyleSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The style of a menu bar and its menus
|
/// The style of a menu bar and its menus
|
||||||
#[derive(Default)]
|
#[derive(Default, Clone)]
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub enum MenuBarStyle {
|
pub enum MenuBarStyle {
|
||||||
/// The default style.
|
/// The default style.
|
||||||
#[default]
|
#[default]
|
||||||
Default,
|
Default,
|
||||||
/// A [`Theme`] that uses a `Custom` palette.
|
/// A [`Theme`] that uses a `Custom` palette.
|
||||||
Custom(Box<dyn StyleSheet<Style = Theme>>),
|
Custom(Arc<dyn StyleSheet<Style = Theme>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<fn(&Theme) -> Appearance> for MenuBarStyle {
|
impl From<fn(&Theme) -> Appearance> for MenuBarStyle {
|
||||||
fn from(f: fn(&Theme) -> Appearance) -> Self {
|
fn from(f: fn(&Theme) -> Appearance) -> Self {
|
||||||
Self::Custom(Box::new(f))
|
Self::Custom(Arc::new(f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,5 +32,7 @@ mod text_input;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use self::text_input::TextInput;
|
pub use self::text_input::TextInput;
|
||||||
|
|
||||||
|
#[cfg(feature = "wayland")]
|
||||||
pub mod tooltip;
|
pub mod tooltip;
|
||||||
|
#[cfg(feature = "wayland")]
|
||||||
pub use tooltip::Tooltip;
|
pub use tooltip::Tooltip;
|
||||||
|
|
|
||||||
|
|
@ -6,24 +6,30 @@
|
||||||
use crate::widget::menu::{
|
use crate::widget::menu::{
|
||||||
self, CloseCondition, ItemHeight, ItemWidth, MenuBarState, PathHighlight,
|
self, CloseCondition, ItemHeight, ItemWidth, MenuBarState, PathHighlight,
|
||||||
};
|
};
|
||||||
|
use crate::widget::ArcElementWrapper;
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
use iced::touch::Finger;
|
use iced::touch::Finger;
|
||||||
use iced::{Event, Vector};
|
use iced::{Event, Vector};
|
||||||
use iced_core::widget::{tree, Tree, Widget};
|
use iced_core::widget::{tree, Tree, Widget};
|
||||||
use iced_core::{event, mouse, touch, Length, Point, Size};
|
use iced_core::{event, mouse, touch, Length, Point, Size};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use super::dropdown;
|
||||||
|
|
||||||
/// 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: 'a>(
|
pub fn context_menu<Message: Clone + 'static>(
|
||||||
content: impl Into<crate::Element<'a, Message>> + 'a,
|
content: impl Into<crate::Element<'static, Message>> + 'static,
|
||||||
// on_context: Message,
|
// on_context: Message,
|
||||||
context_menu: Option<Vec<menu::Tree<'a, Message>>>,
|
context_menu: Option<Vec<menu::Tree<Message>>>,
|
||||||
) -> ContextMenu<'a, Message> {
|
) -> ContextMenu<'static, Message> {
|
||||||
let mut this = ContextMenu {
|
let mut this = ContextMenu {
|
||||||
content: content.into(),
|
content: content.into(),
|
||||||
context_menu: context_menu.map(|menus| {
|
context_menu: context_menu.map(|menus| {
|
||||||
vec![menu::Tree::with_children(
|
vec![menu::Tree::with_children(
|
||||||
|
ArcElementWrapper(Arc::new(Mutex::new(crate::Element::from(
|
||||||
crate::widget::row::<'static, Message>(),
|
crate::widget::row::<'static, Message>(),
|
||||||
|
)))),
|
||||||
menus,
|
menus,
|
||||||
)]
|
)]
|
||||||
}),
|
}),
|
||||||
|
|
@ -43,10 +49,10 @@ pub struct ContextMenu<'a, Message> {
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
content: crate::Element<'a, Message>,
|
content: crate::Element<'a, Message>,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
context_menu: Option<Vec<menu::Tree<'a, Message>>>,
|
context_menu: Option<Vec<menu::Tree<Message>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
|
impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
for ContextMenu<'a, Message>
|
for ContextMenu<'a, Message>
|
||||||
{
|
{
|
||||||
fn tag(&self) -> tree::Tag {
|
fn tag(&self) -> tree::Tag {
|
||||||
|
|
@ -58,6 +64,7 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
tree::State::new(LocalState {
|
tree::State::new(LocalState {
|
||||||
context_cursor: Point::default(),
|
context_cursor: Point::default(),
|
||||||
fingers_pressed: Default::default(),
|
fingers_pressed: Default::default(),
|
||||||
|
menu_state: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,9 +82,12 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
.map(|root| {
|
.map(|root| {
|
||||||
let mut tree = Tree::empty();
|
let mut tree = Tree::empty();
|
||||||
let flat = root
|
let flat = root
|
||||||
.flattern()
|
.flatten()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|mt| Tree::new(mt.item.as_widget()))
|
.map(|mt| {
|
||||||
|
let guard = mt.item.0.lock().unwrap();
|
||||||
|
Tree::new(guard.as_widget())
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
tree.children = flat;
|
tree.children = flat;
|
||||||
tree
|
tree
|
||||||
|
|
@ -214,7 +224,7 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
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>> {
|
||||||
let state = tree.state.downcast_ref::<LocalState>();
|
let state = tree.state.downcast_ref::<LocalState>();
|
||||||
|
let menu_state = state.menu_state.clone();
|
||||||
let Some(context_menu) = self.context_menu.as_mut() else {
|
let Some(context_menu) = self.context_menu.as_mut() else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
@ -229,8 +239,8 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
crate::widget::menu::Menu {
|
crate::widget::menu::Menu {
|
||||||
tree: &mut tree.children[1],
|
tree: menu_state,
|
||||||
menu_roots: context_menu,
|
menu_roots: std::borrow::Cow::Borrowed(context_menu),
|
||||||
bounds_expand: 16,
|
bounds_expand: 16,
|
||||||
menu_overlays_parent: true,
|
menu_overlays_parent: true,
|
||||||
close_condition: CloseCondition {
|
close_condition: CloseCondition {
|
||||||
|
|
@ -245,7 +255,7 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
cross_offset: 0,
|
cross_offset: 0,
|
||||||
root_bounds_list: vec![bounds],
|
root_bounds_list: vec![bounds],
|
||||||
path_highlight: Some(PathHighlight::MenuActive),
|
path_highlight: Some(PathHighlight::MenuActive),
|
||||||
style: &crate::theme::menu_bar::MenuBarStyle::Default,
|
style: std::borrow::Cow::Owned(crate::theme::menu_bar::MenuBarStyle::Default),
|
||||||
position: Point::new(translation.x, translation.y),
|
position: Point::new(translation.x, translation.y),
|
||||||
}
|
}
|
||||||
.overlay(),
|
.overlay(),
|
||||||
|
|
@ -265,8 +275,10 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message: Clone + 'a> From<ContextMenu<'a, Message>> for crate::Element<'a, Message> {
|
impl<Message: Clone + 'static> From<ContextMenu<'static, Message>>
|
||||||
fn from(widget: ContextMenu<'a, Message>) -> Self {
|
for crate::Element<'static, Message>
|
||||||
|
{
|
||||||
|
fn from(widget: ContextMenu<'static, Message>) -> Self {
|
||||||
Self::new(widget)
|
Self::new(widget)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -285,4 +297,5 @@ fn touch_lifted(event: &Event) -> bool {
|
||||||
pub struct LocalState {
|
pub struct LocalState {
|
||||||
context_cursor: Point,
|
context_cursor: Point,
|
||||||
fingers_pressed: HashSet<Finger>,
|
fingers_pressed: HashSet<Finger>,
|
||||||
|
menu_state: dropdown::menu::State,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,11 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0 AND MIT
|
// SPDX-License-Identifier: MPL-2.0 AND MIT
|
||||||
|
|
||||||
mod appearance;
|
mod appearance;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
pub use appearance::{Appearance, StyleSheet};
|
pub use appearance::{Appearance, StyleSheet};
|
||||||
|
use iced::advanced::widget;
|
||||||
|
|
||||||
use crate::widget::{icon, Container};
|
use crate::widget::{icon, Container};
|
||||||
use iced_core::event::{self, Event};
|
use iced_core::event::{self, Event};
|
||||||
|
|
@ -21,11 +25,12 @@ use iced_widget::scrollable::Scrollable;
|
||||||
pub struct Menu<'a, S, Message>
|
pub struct Menu<'a, S, Message>
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
|
[S]: std::borrow::ToOwned,
|
||||||
{
|
{
|
||||||
state: &'a mut State,
|
state: State,
|
||||||
options: &'a [S],
|
options: Cow<'a, [S]>,
|
||||||
icons: &'a [icon::Handle],
|
icons: Cow<'a, [icon::Handle]>,
|
||||||
hovered_option: &'a mut Option<usize>,
|
hovered_option: Arc<Mutex<Option<usize>>>,
|
||||||
selected_option: Option<usize>,
|
selected_option: Option<usize>,
|
||||||
on_selected: Box<dyn FnMut(usize) -> Message + 'a>,
|
on_selected: Box<dyn FnMut(usize) -> Message + 'a>,
|
||||||
on_option_hovered: Option<&'a dyn Fn(usize) -> Message>,
|
on_option_hovered: Option<&'a dyn Fn(usize) -> Message>,
|
||||||
|
|
@ -36,14 +41,17 @@ where
|
||||||
style: (),
|
style: (),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, S: AsRef<str>, Message: 'a> Menu<'a, S, Message> {
|
impl<'a, S: AsRef<str>, Message: 'a + std::clone::Clone> Menu<'a, S, Message>
|
||||||
|
where
|
||||||
|
[S]: std::borrow::ToOwned,
|
||||||
|
{
|
||||||
/// Creates a new [`Menu`] with the given [`State`], a list of options, and
|
/// Creates a new [`Menu`] with the given [`State`], a list of options, and
|
||||||
/// the message to produced when an option is selected.
|
/// the message to produced when an option is selected.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
state: &'a mut State,
|
state: State,
|
||||||
options: &'a [S],
|
options: Cow<'a, [S]>,
|
||||||
icons: &'a [icon::Handle],
|
icons: Cow<'a, [icon::Handle]>,
|
||||||
hovered_option: &'a mut Option<usize>,
|
hovered_option: Arc<Mutex<Option<usize>>>,
|
||||||
selected_option: Option<usize>,
|
selected_option: Option<usize>,
|
||||||
on_selected: impl FnMut(usize) -> Message + 'a,
|
on_selected: impl FnMut(usize) -> Message + 'a,
|
||||||
on_option_hovered: Option<&'a dyn Fn(usize) -> Message>,
|
on_option_hovered: Option<&'a dyn Fn(usize) -> Message>,
|
||||||
|
|
@ -102,20 +110,35 @@ impl<'a, S: AsRef<str>, Message: 'a> Menu<'a, S, Message> {
|
||||||
) -> overlay::Element<'a, Message, crate::Theme, crate::Renderer> {
|
) -> overlay::Element<'a, Message, crate::Theme, crate::Renderer> {
|
||||||
overlay::Element::new(Box::new(Overlay::new(self, target_height, position)))
|
overlay::Element::new(Box::new(Overlay::new(self, target_height, position)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Turns the [`Menu`] into a popup [`Element`] at the given target
|
||||||
|
/// position.
|
||||||
|
///
|
||||||
|
/// The `target_height` will be used to display the menu either on top
|
||||||
|
/// of the target or under it, depending on the screen position and the
|
||||||
|
/// dimensions of the [`Menu`].
|
||||||
|
#[must_use]
|
||||||
|
pub fn popup(self, position: Point, target_height: f32) -> crate::Element<'a, Message> {
|
||||||
|
Overlay::new(self, target_height, position).into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The local state of a [`Menu`].
|
/// The local state of a [`Menu`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
tree: Tree,
|
pub(crate) tree: Arc<Mutex<Tree>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XXX The tree is only used on one thread, but this is needed to make a message
|
||||||
|
unsafe impl Send for State {}
|
||||||
|
unsafe impl Sync for State {}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
/// Creates a new [`State`] for a [`Menu`].
|
/// Creates a new [`State`] for a [`Menu`].
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
tree: Tree::empty(),
|
tree: Arc::new(Mutex::new(Tree::empty())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +150,7 @@ impl Default for State {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Overlay<'a, Message> {
|
struct Overlay<'a, Message> {
|
||||||
state: &'a mut Tree,
|
state: Arc<Mutex<Tree>>,
|
||||||
container: Container<'a, Message, crate::Theme, crate::Renderer>,
|
container: Container<'a, Message, crate::Theme, crate::Renderer>,
|
||||||
width: f32,
|
width: f32,
|
||||||
target_height: f32,
|
target_height: f32,
|
||||||
|
|
@ -140,7 +163,10 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
|
||||||
menu: Menu<'a, S, Message>,
|
menu: Menu<'a, S, Message>,
|
||||||
target_height: f32,
|
target_height: f32,
|
||||||
position: Point,
|
position: Point,
|
||||||
) -> Self {
|
) -> Self
|
||||||
|
where
|
||||||
|
[S]: ToOwned,
|
||||||
|
{
|
||||||
let Menu {
|
let Menu {
|
||||||
state,
|
state,
|
||||||
options,
|
options,
|
||||||
|
|
@ -172,10 +198,11 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
|
||||||
))
|
))
|
||||||
.class(crate::style::Container::Dropdown);
|
.class(crate::style::Container::Dropdown);
|
||||||
|
|
||||||
state.tree.diff(&mut container as &mut dyn Widget<_, _, _>);
|
let mut tree_guard = state.tree.lock().unwrap();
|
||||||
|
tree_guard.diff(&mut container as &mut dyn Widget<_, _, _>);
|
||||||
|
drop(tree_guard);
|
||||||
Self {
|
Self {
|
||||||
state: &mut state.tree,
|
state: state.tree.clone(),
|
||||||
container,
|
container,
|
||||||
width,
|
width,
|
||||||
target_height,
|
target_height,
|
||||||
|
|
@ -183,20 +210,17 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
|
||||||
position,
|
position,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
|
fn _layout(&self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
|
||||||
for Overlay<'a, Message>
|
let mut guard = self.state.lock().unwrap();
|
||||||
{
|
|
||||||
fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
|
let space_below = bounds.height - (self.position.y + self.target_height);
|
||||||
let position = self.position;
|
let space_above = self.position.y;
|
||||||
let space_below = bounds.height - (position.y + self.target_height);
|
|
||||||
let space_above = position.y;
|
|
||||||
|
|
||||||
let limits = layout::Limits::new(
|
let limits = layout::Limits::new(
|
||||||
Size::ZERO,
|
Size::ZERO,
|
||||||
Size::new(
|
Size::new(
|
||||||
bounds.width - position.x,
|
bounds.width - self.position.x,
|
||||||
if space_below > space_above {
|
if space_below > space_above {
|
||||||
space_below
|
space_below
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -206,16 +230,16 @@ impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
|
||||||
)
|
)
|
||||||
.width(self.width);
|
.width(self.width);
|
||||||
|
|
||||||
let node = self.container.layout(self.state, renderer, &limits);
|
let node = self.container.layout(&mut guard, renderer, &limits);
|
||||||
|
|
||||||
node.clone().move_to(if space_below > space_above {
|
node.clone().move_to(if space_below > space_above {
|
||||||
position + Vector::new(0.0, self.target_height)
|
self.position + Vector::new(0.0, self.target_height)
|
||||||
} else {
|
} else {
|
||||||
position - Vector::new(0.0, node.size().height)
|
self.position - Vector::new(0.0, node.size().height)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn _on_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
|
|
@ -225,24 +249,28 @@ impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
let mut guard = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
let state = &mut *guard;
|
||||||
self.container.on_event(
|
self.container.on_event(
|
||||||
self.state, event, layout, cursor, renderer, clipboard, shell, &bounds,
|
state, event, layout, cursor, renderer, clipboard, shell, &bounds,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn _mouse_interaction(
|
||||||
&self,
|
&self,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
renderer: &crate::Renderer,
|
renderer: &crate::Renderer,
|
||||||
) -> mouse::Interaction {
|
) -> mouse::Interaction {
|
||||||
|
let guard = self.state.lock().unwrap();
|
||||||
|
|
||||||
self.container
|
self.container
|
||||||
.mouse_interaction(self.state, layout, cursor, viewport, renderer)
|
.mouse_interaction(&guard, layout, cursor, viewport, renderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn _draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut crate::Renderer,
|
renderer: &mut crate::Renderer,
|
||||||
theme: &crate::Theme,
|
theme: &crate::Theme,
|
||||||
|
|
@ -250,6 +278,8 @@ impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
) {
|
) {
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
|
||||||
let appearance = theme.appearance(&self.style);
|
let appearance = theme.appearance(&self.style);
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
|
|
@ -267,14 +297,127 @@ impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
|
||||||
);
|
);
|
||||||
|
|
||||||
self.container
|
self.container
|
||||||
.draw(self.state, renderer, theme, style, layout, cursor, &bounds);
|
.draw(&*state, renderer, theme, style, layout, cursor, &bounds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct List<'a, S: AsRef<str>, Message> {
|
impl<'a, Message: 'a> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
|
||||||
options: &'a [S],
|
for Overlay<'a, Message>
|
||||||
icons: &'a [icon::Handle],
|
{
|
||||||
hovered_option: &'a mut Option<usize>,
|
fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
|
||||||
|
let mut guard = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
self._layout(renderer, bounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
event: Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
renderer: &crate::Renderer,
|
||||||
|
clipboard: &mut dyn Clipboard,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
) -> event::Status {
|
||||||
|
self._on_event(event, layout, cursor, renderer, clipboard, shell)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
renderer: &crate::Renderer,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self._mouse_interaction(layout, cursor, viewport, renderer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut crate::Renderer,
|
||||||
|
theme: &crate::Theme,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
) {
|
||||||
|
let guard = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
self._draw(renderer, theme, style, layout, cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: 'a> crate::widget::Widget<Message, crate::Theme, crate::Renderer>
|
||||||
|
for Overlay<'a, Message>
|
||||||
|
{
|
||||||
|
fn size(&self) -> Size<Length> {
|
||||||
|
Size::new(Length::Fixed(self.width), Length::Shrink)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
_tree: &mut iced_core::widget::Tree,
|
||||||
|
renderer: &crate::Renderer,
|
||||||
|
limits: &iced::Limits,
|
||||||
|
) -> layout::Node {
|
||||||
|
let mut guard = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
let limits = limits.width(self.width);
|
||||||
|
|
||||||
|
self.container.layout(&mut guard, renderer, &limits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
_tree: &Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
renderer: &crate::Renderer,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self._mouse_interaction(layout, cursor, viewport, renderer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
_tree: &mut Tree,
|
||||||
|
event: Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
renderer: &crate::Renderer,
|
||||||
|
clipboard: &mut dyn Clipboard,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) -> event::Status {
|
||||||
|
self._on_event(event, layout, cursor, renderer, clipboard, shell)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
renderer: &mut crate::Renderer,
|
||||||
|
theme: &crate::Theme,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
self._draw(renderer, theme, style, layout, cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: Clone + 'a> From<Overlay<'a, Message>> for crate::Element<'a, Message> {
|
||||||
|
fn from(widget: Overlay<'a, Message>) -> Self {
|
||||||
|
Element::new(widget)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct List<'a, S: AsRef<str>, Message>
|
||||||
|
where
|
||||||
|
[S]: std::borrow::ToOwned,
|
||||||
|
{
|
||||||
|
options: Cow<'a, [S]>,
|
||||||
|
icons: Cow<'a, [icon::Handle]>,
|
||||||
|
hovered_option: Arc<Mutex<Option<usize>>>,
|
||||||
selected_option: Option<usize>,
|
selected_option: Option<usize>,
|
||||||
on_selected: Box<dyn FnMut(usize) -> Message + 'a>,
|
on_selected: Box<dyn FnMut(usize) -> Message + 'a>,
|
||||||
on_option_hovered: Option<&'a dyn Fn(usize) -> Message>,
|
on_option_hovered: Option<&'a dyn Fn(usize) -> Message>,
|
||||||
|
|
@ -285,6 +428,8 @@ struct List<'a, S: AsRef<str>, Message> {
|
||||||
|
|
||||||
impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
|
impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
for List<'a, S, Message>
|
for List<'a, S, Message>
|
||||||
|
where
|
||||||
|
[S]: std::borrow::ToOwned,
|
||||||
{
|
{
|
||||||
fn size(&self) -> Size<Length> {
|
fn size(&self) -> Size<Length> {
|
||||||
Size::new(Length::Fill, Length::Shrink)
|
Size::new(Length::Fill, Length::Shrink)
|
||||||
|
|
@ -330,8 +475,9 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
||||||
|
let hovered_guard = self.hovered_option.lock().unwrap();
|
||||||
if cursor.is_over(layout.bounds()) {
|
if cursor.is_over(layout.bounds()) {
|
||||||
if let Some(index) = *self.hovered_option {
|
if let Some(index) = *hovered_guard {
|
||||||
shell.publish((self.on_selected)(index));
|
shell.publish((self.on_selected)(index));
|
||||||
return event::Status::Captured;
|
return event::Status::Captured;
|
||||||
}
|
}
|
||||||
|
|
@ -348,14 +494,15 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
+ self.padding.vertical();
|
+ self.padding.vertical();
|
||||||
|
|
||||||
let new_hovered_option = (cursor_position.y / option_height) as usize;
|
let new_hovered_option = (cursor_position.y / option_height) as usize;
|
||||||
|
let mut hovered_guard = self.hovered_option.lock().unwrap();
|
||||||
|
|
||||||
if let Some(on_option_hovered) = self.on_option_hovered {
|
if let Some(on_option_hovered) = self.on_option_hovered {
|
||||||
if *self.hovered_option != Some(new_hovered_option) {
|
if *hovered_guard != Some(new_hovered_option) {
|
||||||
shell.publish(on_option_hovered(new_hovered_option));
|
shell.publish(on_option_hovered(new_hovered_option));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*self.hovered_option = Some(new_hovered_option);
|
*hovered_guard = Some(new_hovered_option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Touch(touch::Event::FingerPressed { .. }) => {
|
Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||||
|
|
@ -367,10 +514,11 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
let option_height =
|
let option_height =
|
||||||
f32::from(self.text_line_height.to_absolute(Pixels(text_size)))
|
f32::from(self.text_line_height.to_absolute(Pixels(text_size)))
|
||||||
+ self.padding.vertical();
|
+ self.padding.vertical();
|
||||||
|
let mut hovered_guard = self.hovered_option.lock().unwrap();
|
||||||
|
|
||||||
*self.hovered_option = Some((cursor_position.y / option_height) as usize);
|
*hovered_guard = Some((cursor_position.y / option_height) as usize);
|
||||||
|
|
||||||
if let Some(index) = *self.hovered_option {
|
if let Some(index) = *hovered_guard {
|
||||||
shell.publish((self.on_selected)(index));
|
shell.publish((self.on_selected)(index));
|
||||||
return event::Status::Captured;
|
return event::Status::Captured;
|
||||||
}
|
}
|
||||||
|
|
@ -434,6 +582,8 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
height: option_height,
|
height: option_height,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let hovered_guard = self.hovered_option.lock().unwrap();
|
||||||
|
|
||||||
let (color, font) = if self.selected_option == Some(i) {
|
let (color, font) = if self.selected_option == Some(i) {
|
||||||
let item_x = bounds.x + appearance.border_width;
|
let item_x = bounds.x + appearance.border_width;
|
||||||
let item_width = appearance.border_width.mul_add(-2.0, bounds.width);
|
let item_width = appearance.border_width.mul_add(-2.0, bounds.width);
|
||||||
|
|
@ -471,7 +621,7 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
);
|
);
|
||||||
|
|
||||||
(appearance.selected_text_color, crate::font::semibold())
|
(appearance.selected_text_color, crate::font::semibold())
|
||||||
} else if *self.hovered_option == Some(i) {
|
} else if *hovered_guard == Some(i) {
|
||||||
let item_x = bounds.x + appearance.border_width;
|
let item_x = bounds.x + appearance.border_width;
|
||||||
let item_width = appearance.border_width.mul_add(-2.0, bounds.width);
|
let item_width = appearance.border_width.mul_add(-2.0, bounds.width);
|
||||||
|
|
||||||
|
|
@ -538,6 +688,8 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
|
|
||||||
impl<'a, S: AsRef<str>, Message: 'a> From<List<'a, S, Message>>
|
impl<'a, S: AsRef<str>, Message: 'a> From<List<'a, S, Message>>
|
||||||
for Element<'a, Message, crate::Theme, crate::Renderer>
|
for Element<'a, Message, crate::Theme, crate::Renderer>
|
||||||
|
where
|
||||||
|
[S]: std::borrow::ToOwned,
|
||||||
{
|
{
|
||||||
fn from(list: List<'a, S, Message>) -> Self {
|
fn from(list: List<'a, S, Message>) -> Self {
|
||||||
Element::new(list)
|
Element::new(list)
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,14 @@ mod widget;
|
||||||
pub use widget::*;
|
pub use widget::*;
|
||||||
|
|
||||||
/// Displays a list of options in a popover menu on select.
|
/// Displays a list of options in a popover menu on select.
|
||||||
pub fn dropdown<'a, S: AsRef<str>, Message: 'a>(
|
pub fn dropdown<
|
||||||
|
'a,
|
||||||
|
S: AsRef<str> + std::clone::Clone + Send + Sync + 'static,
|
||||||
|
Message: 'static + Clone,
|
||||||
|
>(
|
||||||
selections: &'a [S],
|
selections: &'a [S],
|
||||||
selected: Option<usize>,
|
selected: Option<usize>,
|
||||||
on_selected: impl Fn(usize) -> Message + 'a,
|
on_selected: impl Fn(usize) -> Message + Send + Sync + 'static,
|
||||||
) -> Dropdown<'a, S, Message> {
|
) -> Dropdown<'a, S, Message> {
|
||||||
Dropdown::new(selections, selected, on_selected)
|
Dropdown::new(selections, selected, on_selected)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0 AND MIT
|
// SPDX-License-Identifier: MPL-2.0 AND MIT
|
||||||
|
|
||||||
use super::menu::{self, Menu};
|
use super::menu::{self, Menu};
|
||||||
use crate::widget::icon;
|
use crate::widget::icon::{self, Handle};
|
||||||
|
use crate::Element;
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
use iced::Radians;
|
use iced::{window, Radians};
|
||||||
use iced_core::event::{self, Event};
|
use iced_core::event::{self, Event};
|
||||||
use iced_core::text::{self, Paragraph, Text};
|
use iced_core::text::{self, Paragraph, Text};
|
||||||
use iced_core::widget::tree::{self, Tree};
|
use iced_core::widget::tree::{self, Tree};
|
||||||
|
|
@ -14,14 +15,23 @@ use iced_core::{
|
||||||
Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget,
|
Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget,
|
||||||
};
|
};
|
||||||
use iced_widget::pick_list::{self, Catalog};
|
use iced_widget::pick_list::{self, Catalog};
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::{Arc, LazyLock, Mutex};
|
||||||
|
|
||||||
|
pub type DropdownView<Message> = Arc<dyn Fn() -> Element<'static, Message> + Send + Sync>;
|
||||||
|
static AUTOSIZE_ID: LazyLock<crate::widget::Id> =
|
||||||
|
LazyLock::new(|| crate::widget::Id::new("cosmic-applet-autosize"));
|
||||||
/// A widget for selecting a single value from a list of selections.
|
/// A widget for selecting a single value from a list of selections.
|
||||||
#[derive(Setters)]
|
#[derive(Setters)]
|
||||||
pub struct Dropdown<'a, S: AsRef<str>, Message> {
|
pub struct Dropdown<'a, S: AsRef<str> + Send + Sync + Clone + 'static, Message>
|
||||||
|
where
|
||||||
|
[S]: std::borrow::ToOwned,
|
||||||
|
{
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
on_selected: Box<dyn Fn(usize) -> Message + 'a>,
|
on_selected: Arc<dyn Fn(usize) -> Message + Send + Sync>,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
selections: &'a [S],
|
selections: &'a [S],
|
||||||
#[setters]
|
#[setters]
|
||||||
|
|
@ -38,9 +48,29 @@ pub struct Dropdown<'a, S: AsRef<str>, Message> {
|
||||||
text_line_height: text::LineHeight,
|
text_line_height: text::LineHeight,
|
||||||
#[setters(strip_option)]
|
#[setters(strip_option)]
|
||||||
font: Option<crate::font::Font>,
|
font: Option<crate::font::Font>,
|
||||||
|
#[cfg(feature = "wayland")]
|
||||||
|
#[setters(skip)]
|
||||||
|
on_open: Option<
|
||||||
|
Arc<
|
||||||
|
dyn Fn(
|
||||||
|
iced_runtime::platform_specific::wayland::popup::SctkPopupSettings,
|
||||||
|
DropdownView<Message>,
|
||||||
|
) -> Message
|
||||||
|
+ 'a,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
#[setters(skip)]
|
||||||
|
on_close_popup: Option<Box<dyn Fn(window::Id) -> Message + 'a>>,
|
||||||
|
#[setters(strip_option)]
|
||||||
|
window_id: Option<window::Id>,
|
||||||
|
#[cfg(feature = "wayland")]
|
||||||
|
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, S: AsRef<str>, Message> Dropdown<'a, S, Message> {
|
impl<'a, S: AsRef<str> + Send + Sync + Clone + 'static, Message: 'static> Dropdown<'a, S, Message>
|
||||||
|
where
|
||||||
|
[S]: std::borrow::ToOwned,
|
||||||
|
{
|
||||||
/// The default gap.
|
/// The default gap.
|
||||||
pub const DEFAULT_GAP: f32 = 4.0;
|
pub const DEFAULT_GAP: f32 = 4.0;
|
||||||
|
|
||||||
|
|
@ -52,10 +82,10 @@ impl<'a, S: AsRef<str>, Message> Dropdown<'a, S, Message> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
selections: &'a [S],
|
selections: &'a [S],
|
||||||
selected: Option<usize>,
|
selected: Option<usize>,
|
||||||
on_selected: impl Fn(usize) -> Message + 'a,
|
on_selected: impl Fn(usize) -> Message + 'static + Send + Sync,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
on_selected: Box::new(on_selected),
|
on_selected: Arc::new(on_selected),
|
||||||
selections,
|
selections,
|
||||||
icons: &[],
|
icons: &[],
|
||||||
selected,
|
selected,
|
||||||
|
|
@ -65,12 +95,54 @@ impl<'a, S: AsRef<str>, Message> Dropdown<'a, S, Message> {
|
||||||
text_size: None,
|
text_size: None,
|
||||||
text_line_height: text::LineHeight::Relative(1.2),
|
text_line_height: text::LineHeight::Relative(1.2),
|
||||||
font: None,
|
font: None,
|
||||||
|
#[cfg(feature = "wayland")]
|
||||||
|
on_open: None,
|
||||||
|
window_id: None,
|
||||||
|
#[cfg(feature = "wayland")]
|
||||||
|
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(),
|
||||||
|
on_close_popup: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wayland")]
|
||||||
|
/// Handle dropdown requests for popup creation.
|
||||||
|
/// Intended to be used with [`crate::app::message::get_popup`]
|
||||||
|
pub fn with_popup(
|
||||||
|
mut self,
|
||||||
|
parent_id: window::Id,
|
||||||
|
on_open: impl Fn(
|
||||||
|
iced_runtime::platform_specific::wayland::popup::SctkPopupSettings,
|
||||||
|
DropdownView<Message>,
|
||||||
|
) -> Message
|
||||||
|
+ 'a,
|
||||||
|
) -> Self {
|
||||||
|
self.window_id = Some(parent_id);
|
||||||
|
self.on_open = Some(Arc::new(on_open));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wayland")]
|
||||||
|
pub fn with_positioner(
|
||||||
|
mut self,
|
||||||
|
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
||||||
|
) -> Self {
|
||||||
|
self.positioner = positioner;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wayland")]
|
||||||
|
/// Handle dropdown requests for popup removal.
|
||||||
|
/// Intended to be used with [`crate::app::message::destroy_popup`]
|
||||||
|
pub fn on_close_popup(mut self, on_close: impl Fn(window::Id) -> Message + 'a) -> Self {
|
||||||
|
self.on_close_popup = Some(Box::new(on_close));
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Renderer>
|
impl<'a, S: AsRef<str> + Send + Sync + Clone + 'static, Message: 'static + std::clone::Clone>
|
||||||
for Dropdown<'a, S, Message>
|
Widget<Message, crate::Theme, crate::Renderer> for Dropdown<'a, S, Message>
|
||||||
|
where
|
||||||
|
[S]: std::borrow::ToOwned,
|
||||||
{
|
{
|
||||||
fn tag(&self) -> tree::Tag {
|
fn tag(&self) -> tree::Tag {
|
||||||
tree::Tag::of::<State>()
|
tree::Tag::of::<State>()
|
||||||
|
|
@ -158,10 +230,22 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
|
||||||
layout,
|
layout,
|
||||||
cursor,
|
cursor,
|
||||||
shell,
|
shell,
|
||||||
self.on_selected.as_ref(),
|
#[cfg(feature = "wayland")]
|
||||||
|
self.on_open.clone(),
|
||||||
|
#[cfg(feature = "wayland")]
|
||||||
|
self.positioner.clone(),
|
||||||
|
self.on_selected.clone(),
|
||||||
self.selected,
|
self.selected,
|
||||||
self.selections,
|
self.selections,
|
||||||
|| tree.state.downcast_mut::<State>(),
|
|| tree.state.downcast_mut::<State>(),
|
||||||
|
self.window_id,
|
||||||
|
self.on_close_popup.as_deref(),
|
||||||
|
self.icons,
|
||||||
|
self.gap,
|
||||||
|
self.padding,
|
||||||
|
self.text_size,
|
||||||
|
self.font,
|
||||||
|
self.selected,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,6 +295,9 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
|
||||||
renderer: &crate::Renderer,
|
renderer: &crate::Renderer,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||||
|
#[cfg(feature = "wayland")]
|
||||||
|
return None;
|
||||||
|
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
overlay(
|
overlay(
|
||||||
|
|
@ -225,7 +312,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
|
||||||
self.selections,
|
self.selections,
|
||||||
self.icons,
|
self.icons,
|
||||||
self.selected,
|
self.selected,
|
||||||
&self.on_selected,
|
self.on_selected.as_ref(),
|
||||||
translation,
|
translation,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -242,8 +329,10 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, S: AsRef<str>, Message: 'a> From<Dropdown<'a, S, Message>>
|
impl<'a, S: AsRef<str> + Send + Sync + Clone + 'static, Message: 'static + std::clone::Clone>
|
||||||
for crate::Element<'a, Message>
|
From<Dropdown<'a, S, Message>> for crate::Element<'a, Message>
|
||||||
|
where
|
||||||
|
[S]: std::borrow::ToOwned,
|
||||||
{
|
{
|
||||||
fn from(pick_list: Dropdown<'a, S, Message>) -> Self {
|
fn from(pick_list: Dropdown<'a, S, Message>) -> Self {
|
||||||
Self::new(pick_list)
|
Self::new(pick_list)
|
||||||
|
|
@ -251,15 +340,16 @@ impl<'a, S: AsRef<str>, Message: 'a> From<Dropdown<'a, S, Message>>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The local state of a [`Dropdown`].
|
/// The local state of a [`Dropdown`].
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
icon: Option<svg::Handle>,
|
icon: Option<svg::Handle>,
|
||||||
menu: menu::State,
|
menu: menu::State,
|
||||||
keyboard_modifiers: keyboard::Modifiers,
|
keyboard_modifiers: keyboard::Modifiers,
|
||||||
is_open: bool,
|
is_open: Arc<AtomicBool>,
|
||||||
hovered_option: Option<usize>,
|
hovered_option: Arc<Mutex<Option<usize>>>,
|
||||||
hashes: Vec<u64>,
|
hashes: Vec<u64>,
|
||||||
selections: Vec<crate::Plain>,
|
selections: Vec<crate::Plain>,
|
||||||
|
popup_id: window::Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
|
@ -276,10 +366,11 @@ impl State {
|
||||||
},
|
},
|
||||||
menu: menu::State::default(),
|
menu: menu::State::default(),
|
||||||
keyboard_modifiers: keyboard::Modifiers::default(),
|
keyboard_modifiers: keyboard::Modifiers::default(),
|
||||||
is_open: false,
|
is_open: Arc::new(AtomicBool::new(false)),
|
||||||
hovered_option: None,
|
hovered_option: Arc::new(Mutex::new(None)),
|
||||||
selections: Vec::new(),
|
selections: Vec::new(),
|
||||||
hashes: Vec::new(),
|
hashes: Vec::new(),
|
||||||
|
popup_id: window::Id::unique(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -349,31 +440,111 @@ pub fn layout(
|
||||||
/// Processes an [`Event`] and updates the [`State`] of a [`Dropdown`]
|
/// Processes an [`Event`] and updates the [`State`] of a [`Dropdown`]
|
||||||
/// accordingly.
|
/// accordingly.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn update<'a, S: AsRef<str>, Message>(
|
pub fn update<
|
||||||
|
'a,
|
||||||
|
S: AsRef<str> + Send + Sync + std::clone::Clone + 'static,
|
||||||
|
Message: Clone + 'static,
|
||||||
|
>(
|
||||||
event: &Event,
|
event: &Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
on_selected: &dyn Fn(usize) -> Message,
|
#[cfg(feature = "wayland")] on_open: Option<
|
||||||
|
Arc<
|
||||||
|
dyn Fn(
|
||||||
|
iced_runtime::platform_specific::wayland::popup::SctkPopupSettings,
|
||||||
|
DropdownView<Message>,
|
||||||
|
) -> Message
|
||||||
|
+ 'a,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
#[cfg(feature = "wayland")]
|
||||||
|
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
||||||
|
on_selected: Arc<dyn Fn(usize) -> Message + Send + Sync + 'static>,
|
||||||
selected: Option<usize>,
|
selected: Option<usize>,
|
||||||
selections: &[S],
|
selections: &[S],
|
||||||
state: impl FnOnce() -> &'a mut State,
|
state: impl FnOnce() -> &'a mut State,
|
||||||
|
window_id: Option<window::Id>,
|
||||||
|
on_close: Option<&'a dyn Fn(window::Id) -> Message>,
|
||||||
|
icons: &[icon::Handle],
|
||||||
|
gap: f32,
|
||||||
|
padding: Padding,
|
||||||
|
text_size: Option<f32>,
|
||||||
|
font: Option<crate::font::Font>,
|
||||||
|
selected_option: Option<usize>,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||||
let state = state();
|
let state = state();
|
||||||
|
let is_open = state.is_open.load(Ordering::Relaxed);
|
||||||
if state.is_open {
|
if is_open {
|
||||||
// Event wasn't processed by overlay, so cursor was clicked either outside it's
|
// Event wasn't processed by overlay, so cursor was clicked either outside it's
|
||||||
// bounds or on the drop-down, either way we close the overlay.
|
// bounds or on the drop-down, either way we close the overlay.
|
||||||
state.is_open = false;
|
state.is_open.store(false, Ordering::Relaxed);
|
||||||
|
if let Some(on_close) = on_close {
|
||||||
|
shell.publish(on_close(state.popup_id));
|
||||||
|
}
|
||||||
event::Status::Captured
|
event::Status::Captured
|
||||||
} else if cursor.is_over(layout.bounds()) {
|
} else if cursor.is_over(layout.bounds()) {
|
||||||
state.is_open = true;
|
state.is_open.store(true, Ordering::Relaxed);
|
||||||
state.hovered_option = selected;
|
let mut hovered_guard = state.hovered_option.lock().unwrap();
|
||||||
|
*hovered_guard = selected;
|
||||||
|
let id = window::Id::unique();
|
||||||
|
state.popup_id = id;
|
||||||
|
#[cfg(feature = "wayland")]
|
||||||
|
if let Some((on_open, parent)) = on_open.zip(window_id) {
|
||||||
|
use iced_runtime::platform_specific::wayland::popup::{
|
||||||
|
SctkPopupSettings, SctkPositioner,
|
||||||
|
};
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let anchor_rect = Rectangle {
|
||||||
|
x: bounds.x as i32,
|
||||||
|
y: bounds.y as i32,
|
||||||
|
width: bounds.width as i32,
|
||||||
|
height: bounds.height as i32,
|
||||||
|
};
|
||||||
|
let icon_width = if icons.is_empty() { 0.0 } else { 24.0 };
|
||||||
|
let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 {
|
||||||
|
selection_paragraph.min_width().round()
|
||||||
|
};
|
||||||
|
let pad_width = padding.horizontal().mul_add(2.0, 16.0);
|
||||||
|
|
||||||
|
let selections_width = selections
|
||||||
|
.iter()
|
||||||
|
.zip(state.selections.iter_mut())
|
||||||
|
.map(|(label, selection)| measure(label.as_ref(), selection.raw()))
|
||||||
|
.fold(0.0, |next, current| current.max(next));
|
||||||
|
|
||||||
|
let icons: Cow<'static, [Handle]> = Cow::Owned(icons.to_vec());
|
||||||
|
let selections: Cow<'static, [S]> = Cow::Owned(selections.to_vec());
|
||||||
|
let state = state.clone();
|
||||||
|
|
||||||
|
shell.publish(on_open(
|
||||||
|
SctkPopupSettings {
|
||||||
|
parent,
|
||||||
|
id,
|
||||||
|
positioner: SctkPositioner {
|
||||||
|
size: Some((selections_width as u32 + gap as u32 + pad_width as u32 + icon_width as u32, 10)),
|
||||||
|
anchor_rect,
|
||||||
|
anchor: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::Top,
|
||||||
|
gravity:cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Bottom,
|
||||||
|
reactive: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
parent_size: None,
|
||||||
|
grab: true,
|
||||||
|
close_with_children: true
|
||||||
|
},
|
||||||
|
Arc::new(move || {
|
||||||
|
Element::from(
|
||||||
|
menu_widget(
|
||||||
|
bounds, &state, gap, padding,text_size.unwrap_or(14.0), selections.clone(), icons.clone(), selected_option, on_selected.clone()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
}
|
||||||
event::Status::Captured
|
event::Status::Captured
|
||||||
} else {
|
} else {
|
||||||
event::Status::Ignored
|
event::Status::Ignored
|
||||||
|
|
@ -383,11 +554,9 @@ pub fn update<'a, S: AsRef<str>, Message>(
|
||||||
delta: mouse::ScrollDelta::Lines { .. },
|
delta: mouse::ScrollDelta::Lines { .. },
|
||||||
}) => {
|
}) => {
|
||||||
let state = state();
|
let state = state();
|
||||||
|
let is_open = state.is_open.load(Ordering::Relaxed);
|
||||||
|
|
||||||
if state.keyboard_modifiers.command()
|
if state.keyboard_modifiers.command() && cursor.is_over(layout.bounds()) && !is_open {
|
||||||
&& cursor.is_over(layout.bounds())
|
|
||||||
&& !state.is_open
|
|
||||||
{
|
|
||||||
let next_index = selected.map(|index| index + 1).unwrap_or_default();
|
let next_index = selected.map(|index| index + 1).unwrap_or_default();
|
||||||
|
|
||||||
if selections.len() < next_index {
|
if selections.len() < next_index {
|
||||||
|
|
@ -423,9 +592,70 @@ pub fn mouse_interaction(layout: Layout<'_>, cursor: mouse::Cursor) -> mouse::In
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wayland")]
|
||||||
|
/// Returns the current menu widget of a [`Dropdown`].
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn menu_widget<
|
||||||
|
S: AsRef<str> + Send + Sync + Clone + 'static,
|
||||||
|
Message: 'static + std::clone::Clone,
|
||||||
|
>(
|
||||||
|
bounds: Rectangle,
|
||||||
|
state: &State,
|
||||||
|
gap: f32,
|
||||||
|
padding: Padding,
|
||||||
|
text_size: f32,
|
||||||
|
selections: Cow<'static, [S]>,
|
||||||
|
icons: Cow<'static, [icon::Handle]>,
|
||||||
|
selected_option: Option<usize>,
|
||||||
|
on_selected: Arc<dyn Fn(usize) -> Message + Send + Sync + 'static>,
|
||||||
|
) -> crate::Element<'static, Message>
|
||||||
|
where
|
||||||
|
[S]: std::borrow::ToOwned,
|
||||||
|
{
|
||||||
|
let icon_width = if icons.is_empty() { 0.0 } else { 24.0 };
|
||||||
|
let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 {
|
||||||
|
selection_paragraph.min_width().round()
|
||||||
|
};
|
||||||
|
let selections_width = selections
|
||||||
|
.iter()
|
||||||
|
.zip(state.selections.iter())
|
||||||
|
.map(|(label, selection)| measure(label.as_ref(), selection.raw()))
|
||||||
|
.fold(0.0, |next, current| current.max(next));
|
||||||
|
let pad_width = padding.horizontal().mul_add(2.0, 16.0);
|
||||||
|
|
||||||
|
let width = selections_width + gap + pad_width + icon_width;
|
||||||
|
let is_open = state.is_open.clone();
|
||||||
|
let menu: Menu<'static, S, Message> = Menu::new(
|
||||||
|
state.menu.clone(),
|
||||||
|
selections,
|
||||||
|
icons,
|
||||||
|
state.hovered_option.clone(),
|
||||||
|
selected_option,
|
||||||
|
move |option| {
|
||||||
|
is_open.store(false, Ordering::Relaxed);
|
||||||
|
|
||||||
|
(on_selected)(option)
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.width(width)
|
||||||
|
.padding(padding)
|
||||||
|
.text_size(text_size);
|
||||||
|
|
||||||
|
crate::widget::autosize::autosize(
|
||||||
|
menu.popup(iced::Point::new(0., 0.), bounds.height),
|
||||||
|
AUTOSIZE_ID.clone(),
|
||||||
|
)
|
||||||
|
.auto_height(true)
|
||||||
|
.auto_width(true)
|
||||||
|
.min_height(1.)
|
||||||
|
.min_width(width)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the current overlay of a [`Dropdown`].
|
/// Returns the current overlay of a [`Dropdown`].
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn overlay<'a, S: AsRef<str>, Message: 'a>(
|
pub fn overlay<'a, S: AsRef<str> + Send + Sync + Clone + 'static, Message: std::clone::Clone + 'a>(
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
_renderer: &crate::Renderer,
|
_renderer: &crate::Renderer,
|
||||||
state: &'a mut State,
|
state: &'a mut State,
|
||||||
|
|
@ -439,18 +669,21 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a>(
|
||||||
selected_option: Option<usize>,
|
selected_option: Option<usize>,
|
||||||
on_selected: &'a dyn Fn(usize) -> Message,
|
on_selected: &'a dyn Fn(usize) -> Message,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) -> Option<overlay::Element<'a, Message, crate::Theme, crate::Renderer>> {
|
) -> Option<overlay::Element<'a, Message, crate::Theme, crate::Renderer>>
|
||||||
if state.is_open {
|
where
|
||||||
|
[S]: std::borrow::ToOwned,
|
||||||
|
{
|
||||||
|
if state.is_open.load(Ordering::Relaxed) {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
let menu = Menu::new(
|
let menu = Menu::new(
|
||||||
&mut state.menu,
|
state.menu.clone(),
|
||||||
selections,
|
Cow::Borrowed(selections),
|
||||||
icons,
|
Cow::Borrowed(icons),
|
||||||
&mut state.hovered_option,
|
state.hovered_option.clone(),
|
||||||
selected_option,
|
selected_option,
|
||||||
|option| {
|
|option| {
|
||||||
state.is_open = false;
|
state.is_open.store(false, Ordering::Relaxed);
|
||||||
|
|
||||||
(on_selected)(option)
|
(on_selected)(option)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -306,7 +306,10 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
Density::Spacious => 48.0,
|
Density::Spacious => 48.0,
|
||||||
Density::Standard => 48.0,
|
Density::Standard => 48.0,
|
||||||
};
|
};
|
||||||
|
let portion = ((start.len().max(end.len()) as f32 / center.len().max(1) as f32).round()
|
||||||
|
as u16)
|
||||||
|
.max(1);
|
||||||
|
let center_empty = center.is_empty() && self.title.is_empty();
|
||||||
// Creates the headerbar widget.
|
// Creates the headerbar widget.
|
||||||
let mut widget = widget::row::with_capacity(3)
|
let mut widget = widget::row::with_capacity(3)
|
||||||
// If elements exist in the start region, append them here.
|
// If elements exist in the start region, append them here.
|
||||||
|
|
@ -316,7 +319,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
.align_y(iced::Alignment::Center)
|
.align_y(iced::Alignment::Center)
|
||||||
.apply(widget::container)
|
.apply(widget::container)
|
||||||
.align_x(iced::Alignment::Start)
|
.align_x(iced::Alignment::Start)
|
||||||
.width(Length::Shrink),
|
.width(Length::FillPortion(portion)),
|
||||||
)
|
)
|
||||||
// If elements exist in the center region, use them here.
|
// If elements exist in the center region, use them here.
|
||||||
// This will otherwise use the title as a widget if a title was defined.
|
// This will otherwise use the title as a widget if a title was defined.
|
||||||
|
|
@ -338,7 +341,11 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
.align_y(iced::Alignment::Center)
|
.align_y(iced::Alignment::Center)
|
||||||
.apply(widget::container)
|
.apply(widget::container)
|
||||||
.align_x(iced::Alignment::End)
|
.align_x(iced::Alignment::End)
|
||||||
.width(Length::Shrink),
|
.width(if center_empty {
|
||||||
|
Length::Fill
|
||||||
|
} else {
|
||||||
|
Length::FillPortion(portion)
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
.align_y(iced::Alignment::Center)
|
.align_y(iced::Alignment::Center)
|
||||||
.height(Length::Fixed(height))
|
.height(Length::Fixed(height))
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@
|
||||||
//!
|
//!
|
||||||
|
|
||||||
pub mod action;
|
pub mod action;
|
||||||
|
|
||||||
pub use action::MenuAction as Action;
|
pub use action::MenuAction as Action;
|
||||||
|
|
||||||
mod flex;
|
mod flex;
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ where
|
||||||
.map(|root| {
|
.map(|root| {
|
||||||
let mut tree = Tree::empty();
|
let mut tree = Tree::empty();
|
||||||
let flat = root
|
let flat = root
|
||||||
.flattern()
|
.flatten()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|mt| Tree::new(mt.item.as_widget()))
|
.map(|mt| Tree::new(mt.item.as_widget()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
@ -110,7 +110,7 @@ pub(crate) fn menu_roots_diff<'a, Message, Renderer>(
|
||||||
.zip(menu_roots.iter())
|
.zip(menu_roots.iter())
|
||||||
.for_each(|(t, root)| {
|
.for_each(|(t, root)| {
|
||||||
let mut flat = root
|
let mut flat = root
|
||||||
.flattern()
|
.flatten()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|mt| {
|
.map(|mt| {
|
||||||
let widget = mt.item.as_widget();
|
let widget = mt.item.as_widget();
|
||||||
|
|
@ -129,7 +129,7 @@ pub(crate) fn menu_roots_diff<'a, Message, Renderer>(
|
||||||
let extended = menu_roots[tree.children.len()..].iter().map(|root| {
|
let extended = menu_roots[tree.children.len()..].iter().map(|root| {
|
||||||
let mut tree = Tree::empty();
|
let mut tree = Tree::empty();
|
||||||
let flat = root
|
let flat = root
|
||||||
.flattern()
|
.flatten()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|mt| Tree::new(mt.item.as_widget()))
|
.map(|mt| Tree::new(mt.item.as_widget()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
@ -366,6 +366,8 @@ where
|
||||||
if state.menu_states.is_empty() && view_cursor.is_over(layout.bounds()) {
|
if state.menu_states.is_empty() && view_cursor.is_over(layout.bounds()) {
|
||||||
state.view_cursor = view_cursor;
|
state.view_cursor = view_cursor;
|
||||||
state.open = true;
|
state.open = true;
|
||||||
|
// #[cfg(feature = "wayland")]
|
||||||
|
// TODO emit Message to open menu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
|
@ -437,6 +439,9 @@ where
|
||||||
_renderer: &Renderer,
|
_renderer: &Renderer,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||||
|
// #[cfg(feature = "wayland")]
|
||||||
|
// return None;
|
||||||
|
|
||||||
let state = tree.state.downcast_ref::<MenuBarState>();
|
let state = tree.state.downcast_ref::<MenuBarState>();
|
||||||
if !state.open {
|
if !state.open {
|
||||||
return None;
|
return None;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
// From iced_aw, license MIT
|
// From iced_aw, license MIT
|
||||||
|
|
||||||
//! Menu tree overlay
|
//! Menu tree overlay
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
use super::{menu_bar::MenuBarState, menu_tree::MenuTree};
|
use super::{menu_bar::MenuBarState, menu_tree::MenuTree};
|
||||||
use crate::style::menu_bar::StyleSheet;
|
use crate::{style::menu_bar::StyleSheet, widget::dropdown};
|
||||||
|
|
||||||
use iced_core::{Border, Shadow};
|
use iced_core::{Border, Shadow};
|
||||||
use iced_widget::core::{
|
use iced_widget::core::{
|
||||||
|
|
@ -240,7 +245,7 @@ struct MenuBounds {
|
||||||
impl MenuBounds {
|
impl MenuBounds {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn new<Message, Renderer>(
|
fn new<Message, Renderer>(
|
||||||
menu_tree: &MenuTree<'_, Message, Renderer>,
|
menu_tree: &MenuTree<Message>,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
item_width: ItemWidth,
|
item_width: ItemWidth,
|
||||||
item_height: ItemHeight,
|
item_height: ItemHeight,
|
||||||
|
|
@ -300,7 +305,7 @@ impl MenuState {
|
||||||
overlay_offset: Vector,
|
overlay_offset: Vector,
|
||||||
slice: MenuSlice,
|
slice: MenuSlice,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
menu_tree: &MenuTree<'_, Message, Renderer>,
|
menu_tree: &MenuTree<Message>,
|
||||||
tree: &mut [Tree],
|
tree: &mut [Tree],
|
||||||
) -> Node
|
) -> Node
|
||||||
where
|
where
|
||||||
|
|
@ -354,7 +359,7 @@ impl MenuState {
|
||||||
overlay_offset: Vector,
|
overlay_offset: Vector,
|
||||||
index: usize,
|
index: usize,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
menu_tree: &MenuTree<'_, Message, Renderer>,
|
menu_tree: &MenuTree<Message>,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
) -> Node
|
) -> Node
|
||||||
where
|
where
|
||||||
|
|
@ -427,12 +432,9 @@ impl MenuState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Menu<'a, 'b, Message, Renderer>
|
pub(crate) struct Menu<'b, Message: std::clone::Clone> {
|
||||||
where
|
pub(crate) tree: dropdown::menu::State,
|
||||||
Renderer: renderer::Renderer,
|
pub(crate) menu_roots: Cow<'b, Vec<MenuTree<Message>>>,
|
||||||
{
|
|
||||||
pub(crate) tree: &'b mut Tree,
|
|
||||||
pub(crate) menu_roots: &'b mut Vec<MenuTree<'a, Message, Renderer>>,
|
|
||||||
pub(crate) bounds_expand: u16,
|
pub(crate) bounds_expand: u16,
|
||||||
/// Allows menu overlay items to overlap the parent
|
/// Allows menu overlay items to overlap the parent
|
||||||
pub(crate) menu_overlays_parent: bool,
|
pub(crate) menu_overlays_parent: bool,
|
||||||
|
|
@ -444,10 +446,10 @@ where
|
||||||
pub(crate) cross_offset: i32,
|
pub(crate) cross_offset: i32,
|
||||||
pub(crate) root_bounds_list: Vec<Rectangle>,
|
pub(crate) root_bounds_list: Vec<Rectangle>,
|
||||||
pub(crate) path_highlight: Option<PathHighlight>,
|
pub(crate) path_highlight: Option<PathHighlight>,
|
||||||
pub(crate) style: &'b <crate::Theme as StyleSheet>::Style,
|
pub(crate) style: Cow<'b, <crate::Theme as StyleSheet>::Style>,
|
||||||
pub(crate) position: Point,
|
pub(crate) position: Point,
|
||||||
}
|
}
|
||||||
impl<'a, 'b, Message, Renderer> Menu<'a, 'b, Message, Renderer>
|
impl<'b, Message: Clone + 'static, Renderer> Menu<'b, Message>
|
||||||
where
|
where
|
||||||
Renderer: renderer::Renderer,
|
Renderer: renderer::Renderer,
|
||||||
{
|
{
|
||||||
|
|
@ -751,7 +753,7 @@ fn pad_rectangle(rect: Rectangle, padding: Padding) -> Rectangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn init_root_menu<Message, Renderer>(
|
pub(super) fn init_root_menu<Message, Renderer>(
|
||||||
menu: &mut Menu<'_, '_, Message, Renderer>,
|
menu: &mut Menu<'_, Message>,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
overlay_cursor: Point,
|
overlay_cursor: Point,
|
||||||
|
|
@ -829,7 +831,7 @@ pub(super) fn init_root_menu<Message, Renderer>(
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn process_menu_events<'b, Message, Renderer>(
|
fn process_menu_events<'b, Message, Renderer>(
|
||||||
tree: &'b mut Tree,
|
tree: &'b mut Tree,
|
||||||
menu_roots: &'b mut [MenuTree<'_, Message, Renderer>],
|
menu_roots: &'b mut [MenuTree<Message>],
|
||||||
event: event::Event,
|
event: event::Event,
|
||||||
view_cursor: Cursor,
|
view_cursor: Cursor,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
|
|
@ -887,7 +889,7 @@ where
|
||||||
|
|
||||||
#[allow(unused_results)]
|
#[allow(unused_results)]
|
||||||
fn process_overlay_events<Message, Renderer>(
|
fn process_overlay_events<Message, Renderer>(
|
||||||
menu: &mut Menu<'_, '_, Message, Renderer>,
|
menu: &mut Menu<'_, Message>,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
viewport_size: Size,
|
viewport_size: Size,
|
||||||
overlay_offset: Vector,
|
overlay_offset: Vector,
|
||||||
|
|
@ -897,6 +899,7 @@ fn process_overlay_events<Message, Renderer>(
|
||||||
) -> event::Status
|
) -> event::Status
|
||||||
where
|
where
|
||||||
Renderer: renderer::Renderer,
|
Renderer: renderer::Renderer,
|
||||||
|
Message: std::clone::Clone,
|
||||||
{
|
{
|
||||||
use event::Status::{Captured, Ignored};
|
use event::Status::{Captured, Ignored};
|
||||||
/*
|
/*
|
||||||
|
|
@ -1077,16 +1080,13 @@ where
|
||||||
Captured
|
Captured
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_scroll_events<Message, Renderer>(
|
fn process_scroll_events<Message: Clone>(
|
||||||
menu: &mut Menu<'_, '_, Message, Renderer>,
|
menu: &mut Menu<'_, Message>,
|
||||||
delta: mouse::ScrollDelta,
|
delta: mouse::ScrollDelta,
|
||||||
overlay_cursor: Point,
|
overlay_cursor: Point,
|
||||||
viewport_size: Size,
|
viewport_size: Size,
|
||||||
overlay_offset: Vector,
|
overlay_offset: Vector,
|
||||||
) -> event::Status
|
) -> event::Status {
|
||||||
where
|
|
||||||
Renderer: renderer::Renderer,
|
|
||||||
{
|
|
||||||
use event::Status::{Captured, Ignored};
|
use event::Status::{Captured, Ignored};
|
||||||
use mouse::ScrollDelta;
|
use mouse::ScrollDelta;
|
||||||
|
|
||||||
|
|
@ -1155,16 +1155,13 @@ where
|
||||||
|
|
||||||
#[allow(clippy::pedantic)]
|
#[allow(clippy::pedantic)]
|
||||||
/// Returns (children_size, child_positions, child_sizes)
|
/// Returns (children_size, child_positions, child_sizes)
|
||||||
fn get_children_layout<Message, Renderer>(
|
fn get_children_layout<Message>(
|
||||||
menu_tree: &MenuTree<'_, Message, Renderer>,
|
menu_tree: &MenuTree<Message>,
|
||||||
renderer: &Renderer,
|
renderer: &crate::Renderer,
|
||||||
item_width: ItemWidth,
|
item_width: ItemWidth,
|
||||||
item_height: ItemHeight,
|
item_height: ItemHeight,
|
||||||
tree: &mut [Tree],
|
tree: &mut [Tree],
|
||||||
) -> (Size, Vec<f32>, Vec<Size>)
|
) -> (Size, Vec<f32>, Vec<Size>) {
|
||||||
where
|
|
||||||
Renderer: renderer::Renderer,
|
|
||||||
{
|
|
||||||
let width = match item_width {
|
let width = match item_width {
|
||||||
ItemWidth::Uniform(u) => f32::from(u),
|
ItemWidth::Uniform(u) => f32::from(u),
|
||||||
ItemWidth::Static(s) => f32::from(menu_tree.width.unwrap_or(s)),
|
ItemWidth::Static(s) => f32::from(menu_tree.width.unwrap_or(s)),
|
||||||
|
|
@ -1184,7 +1181,8 @@ where
|
||||||
.children
|
.children
|
||||||
.iter()
|
.iter()
|
||||||
.map(|mt| {
|
.map(|mt| {
|
||||||
let w = mt.item.as_widget();
|
let guard = mt.item.0.lock().unwrap();
|
||||||
|
let w = guard.as_widget();
|
||||||
match w.size().height {
|
match w.size().height {
|
||||||
Length::Fixed(f) => Size::new(width, f),
|
Length::Fixed(f) => Size::new(width, f),
|
||||||
Length::Shrink => {
|
Length::Shrink => {
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,14 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use iced_widget::core::{renderer, Element};
|
use iced_widget::core::{renderer, Element};
|
||||||
|
|
||||||
use crate::iced_core::{Alignment, Length};
|
use crate::iced_core::{Alignment, Length};
|
||||||
use crate::widget::icon;
|
|
||||||
use crate::widget::menu::action::MenuAction;
|
use crate::widget::menu::action::MenuAction;
|
||||||
use crate::widget::menu::key_bind::KeyBind;
|
use crate::widget::menu::key_bind::KeyBind;
|
||||||
|
use crate::widget::{icon, ArcElementWrapper};
|
||||||
use crate::{theme, widget};
|
use crate::{theme, widget};
|
||||||
|
|
||||||
/// Nested menu is essentially a tree of items, a menu is a collection of items
|
/// Nested menu is essentially a tree of items, a menu is a collection of items
|
||||||
|
|
@ -23,27 +24,27 @@ use crate::{theme, widget};
|
||||||
/// but there's no need to explicitly distinguish them here, if a menu tree
|
/// but there's no need to explicitly distinguish them here, if a menu tree
|
||||||
/// has children, it's a menu, otherwise it's an item
|
/// has children, it's a menu, otherwise it's an item
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct MenuTree<'a, Message, Renderer = crate::Renderer> {
|
#[derive(Clone)]
|
||||||
|
|
||||||
|
pub struct MenuTree<Message> {
|
||||||
/// The menu tree will be flatten into a vector to build a linear widget tree,
|
/// The menu tree will be flatten into a vector to build a linear widget tree,
|
||||||
/// the `index` field is the index of the item in that vector
|
/// the `index` field is the index of the item in that vector
|
||||||
pub(crate) index: usize,
|
pub(crate) index: usize,
|
||||||
|
|
||||||
|
// TODO needs to be wrapped in Arc<Mutex kinda like in cosmic-files. Check the widget there for details
|
||||||
/// The item of the menu tree
|
/// The item of the menu tree
|
||||||
pub(crate) item: Element<'a, Message, crate::Theme, Renderer>,
|
pub(crate) item: ArcElementWrapper<Message>,
|
||||||
/// The children of the menu tree
|
/// The children of the menu tree
|
||||||
pub(crate) children: Vec<MenuTree<'a, Message, Renderer>>,
|
pub(crate) children: Vec<MenuTree<Message>>,
|
||||||
/// The width of the menu tree
|
/// The width of the menu tree
|
||||||
pub(crate) width: Option<u16>,
|
pub(crate) width: Option<u16>,
|
||||||
/// The height of the menu tree
|
/// The height of the menu tree
|
||||||
pub(crate) height: Option<u16>,
|
pub(crate) height: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> MenuTree<'a, Message, Renderer>
|
impl<Message: Clone + 'static> MenuTree<Message> {
|
||||||
where
|
|
||||||
Renderer: renderer::Renderer,
|
|
||||||
{
|
|
||||||
/// Create a new menu tree from a widget
|
/// Create a new menu tree from a widget
|
||||||
pub fn new(item: impl Into<Element<'a, Message, crate::Theme, Renderer>>) -> Self {
|
pub fn new(item: impl Into<ArcElementWrapper<Message>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
index: 0,
|
index: 0,
|
||||||
item: item.into(),
|
item: item.into(),
|
||||||
|
|
@ -55,8 +56,8 @@ where
|
||||||
|
|
||||||
/// Create a menu tree from a widget and a vector of sub trees
|
/// Create a menu tree from a widget and a vector of sub trees
|
||||||
pub fn with_children(
|
pub fn with_children(
|
||||||
item: impl Into<Element<'a, Message, crate::Theme, Renderer>>,
|
item: impl Into<ArcElementWrapper<Message>>,
|
||||||
children: Vec<impl Into<MenuTree<'a, Message, Renderer>>>,
|
children: Vec<impl Into<MenuTree<Message>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
index: 0,
|
index: 0,
|
||||||
|
|
@ -92,7 +93,7 @@ where
|
||||||
/// Set the index of each item
|
/// Set the index of each item
|
||||||
pub(crate) fn set_index(&mut self) {
|
pub(crate) fn set_index(&mut self) {
|
||||||
/// inner counting function.
|
/// inner counting function.
|
||||||
fn rec<Message, Renderer>(mt: &mut MenuTree<'_, Message, Renderer>, count: &mut usize) {
|
fn rec<Message: Clone + 'static>(mt: &mut MenuTree<Message>, count: &mut usize) {
|
||||||
// keep items under the same menu line up
|
// keep items under the same menu line up
|
||||||
mt.children.iter_mut().for_each(|c| {
|
mt.children.iter_mut().for_each(|c| {
|
||||||
c.index = *count;
|
c.index = *count;
|
||||||
|
|
@ -109,36 +110,32 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Flatten the menu tree
|
/// Flatten the menu tree
|
||||||
pub(crate) fn flattern(&'a self) -> Vec<&Self> {
|
pub(crate) fn flatten(&self) -> Vec<Self> {
|
||||||
/// Inner flattening function
|
/// Inner flattening function
|
||||||
fn rec<'a, Message, Renderer>(
|
fn rec<Message: Clone + 'static>(
|
||||||
mt: &'a MenuTree<'a, Message, Renderer>,
|
mt: &MenuTree<Message>,
|
||||||
flat: &mut Vec<&MenuTree<'a, Message, Renderer>>,
|
flat: &mut Vec<MenuTree<Message>>,
|
||||||
) {
|
) {
|
||||||
mt.children.iter().for_each(|c| {
|
mt.children.clone().into_iter().for_each(|c| {
|
||||||
flat.push(c);
|
flat.push(c);
|
||||||
});
|
});
|
||||||
|
|
||||||
mt.children.iter().for_each(|c| {
|
mt.children.iter().for_each(|c| {
|
||||||
rec(c, flat);
|
rec(&c, flat);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut flat = Vec::new();
|
let mut flat = Vec::new();
|
||||||
flat.push(self);
|
flat.push(self.clone());
|
||||||
rec(self, &mut flat);
|
rec(self, &mut flat);
|
||||||
|
|
||||||
flat
|
flat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Element<'a, Message, crate::Theme, Renderer>>
|
impl<Message: Clone + 'static> From<crate::Element<'static, Message>> for MenuTree<Message> {
|
||||||
for MenuTree<'a, Message, Renderer>
|
fn from(value: crate::Element<'static, Message>) -> Self {
|
||||||
where
|
Self::new(ArcElementWrapper(Arc::new(Mutex::new(value))))
|
||||||
Renderer: renderer::Renderer,
|
|
||||||
{
|
|
||||||
fn from(value: Element<'a, Message, crate::Theme, Renderer>) -> Self {
|
|
||||||
Self::new(value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,6 +154,7 @@ pub fn menu_button<'a, Message: 'a>(
|
||||||
.class(theme::Button::MenuItem)
|
.class(theme::Button::MenuItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
/// Represents a menu item that performs an action when selected or a separator between menu items.
|
/// Represents a menu item that performs an action when selected or a separator between menu items.
|
||||||
///
|
///
|
||||||
/// - `Action` - Represents a menu item that performs an action when selected.
|
/// - `Action` - Represents a menu item that performs an action when selected.
|
||||||
|
|
@ -213,18 +211,13 @@ where
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// - A vector of `MenuTree`.
|
/// - A vector of `MenuTree`.
|
||||||
pub fn menu_items<
|
pub fn menu_items<
|
||||||
'a,
|
|
||||||
A: MenuAction<Message = Message>,
|
A: MenuAction<Message = Message>,
|
||||||
L: Into<Cow<'static, str>> + 'static,
|
L: Into<Cow<'static, str>> + 'static,
|
||||||
Message: 'a,
|
Message: 'static + std::clone::Clone,
|
||||||
Renderer: renderer::Renderer + 'a,
|
|
||||||
>(
|
>(
|
||||||
key_binds: &HashMap<KeyBind, A>,
|
key_binds: &HashMap<KeyBind, A>,
|
||||||
children: Vec<MenuItem<A, L>>,
|
children: Vec<MenuItem<A, L>>,
|
||||||
) -> Vec<MenuTree<'a, Message, Renderer>>
|
) -> Vec<MenuTree<Message>> {
|
||||||
where
|
|
||||||
Element<'a, Message, crate::Theme, Renderer>: From<widget::button::Button<'a, Message>>,
|
|
||||||
{
|
|
||||||
fn find_key<A: MenuAction>(action: &A, key_binds: &HashMap<KeyBind, A>) -> String {
|
fn find_key<A: MenuAction>(action: &A, key_binds: &HashMap<KeyBind, A>) -> String {
|
||||||
for (key_bind, key_action) in key_binds {
|
for (key_bind, key_action) in key_binds {
|
||||||
if action == key_action {
|
if action == key_action {
|
||||||
|
|
@ -259,7 +252,7 @@ where
|
||||||
|
|
||||||
let menu_button = menu_button(items).on_press(action.message());
|
let menu_button = menu_button(items).on_press(action.message());
|
||||||
|
|
||||||
trees.push(MenuTree::<Message, Renderer>::new(menu_button));
|
trees.push(MenuTree::<Message>::from(Element::from(menu_button)));
|
||||||
}
|
}
|
||||||
MenuItem::ButtonDisabled(label, icon, action) => {
|
MenuItem::ButtonDisabled(label, icon, action) => {
|
||||||
let key = find_key(&action, key_binds);
|
let key = find_key(&action, key_binds);
|
||||||
|
|
@ -277,7 +270,7 @@ where
|
||||||
|
|
||||||
let menu_button = menu_button(items);
|
let menu_button = menu_button(items);
|
||||||
|
|
||||||
trees.push(MenuTree::<Message, Renderer>::new(menu_button));
|
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 key = find_key(&action, key_binds);
|
||||||
|
|
@ -307,11 +300,14 @@ where
|
||||||
items.insert(2, widget::icon::icon(icon).size(14).into());
|
items.insert(2, widget::icon::icon(icon).size(14).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
trees.push(MenuTree::new(menu_button(items).on_press(action.message())));
|
trees.push(MenuTree::from(Element::from(
|
||||||
|
menu_button(items).on_press(action.message()),
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
MenuItem::Folder(label, children) => {
|
MenuItem::Folder(label, children) => {
|
||||||
trees.push(MenuTree::<Message, Renderer>::with_children(
|
trees.push(MenuTree::<Message>::with_children(
|
||||||
menu_button(vec![
|
ArcElementWrapper(Arc::new(Mutex::new(crate::Element::from(
|
||||||
|
menu_button::<'static, _>(vec![
|
||||||
widget::text(label).into(),
|
widget::text(label).into(),
|
||||||
widget::horizontal_space().into(),
|
widget::horizontal_space().into(),
|
||||||
widget::icon::from_name("pan-end-symbolic")
|
widget::icon::from_name("pan-end-symbolic")
|
||||||
|
|
@ -329,14 +325,15 @@ where
|
||||||
theme::Button::MenuFolder
|
theme::Button::MenuFolder
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
)))),
|
||||||
menu_items(key_binds, children),
|
menu_items(key_binds, children),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
MenuItem::Divider => {
|
MenuItem::Divider => {
|
||||||
if i != size - 1 {
|
if i != size - 1 {
|
||||||
trees.push(MenuTree::<Message, Renderer>::new(
|
trees.push(MenuTree::<Message>::from(Element::from(
|
||||||
widget::divider::horizontal::light(),
|
widget::divider::horizontal::light(),
|
||||||
));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,9 @@ pub mod aspect_ratio;
|
||||||
#[cfg(feature = "autosize")]
|
#[cfg(feature = "autosize")]
|
||||||
pub mod autosize;
|
pub mod autosize;
|
||||||
|
|
||||||
|
#[cfg(feature = "winit")]
|
||||||
|
pub(crate) mod responsive_container;
|
||||||
|
|
||||||
pub mod button;
|
pub mod button;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use button::{Button, IconButton, LinkButton, TextButton};
|
pub use button::{Button, IconButton, LinkButton, TextButton};
|
||||||
|
|
@ -366,6 +369,10 @@ pub mod warning;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use warning::*;
|
pub use warning::*;
|
||||||
|
|
||||||
|
pub mod wrapper;
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use wrapper::*;
|
||||||
|
|
||||||
#[cfg(feature = "markdown")]
|
#[cfg(feature = "markdown")]
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use iced::widget::markdown;
|
pub use iced::widget::markdown;
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ impl<'a, Message: Clone + 'static> NavBar<'a, Message> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context_menu(mut self, context_menu: Option<Vec<menu::Tree<'a, Message>>>) -> Self {
|
pub fn context_menu(mut self, context_menu: Option<Vec<menu::Tree<Message>>>) -> Self {
|
||||||
self.segmented_button = self.segmented_button.context_menu(context_menu);
|
self.segmented_button = self.segmented_button.context_menu(context_menu);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
||||||
293
src/widget/responsive_container.rs
Normal file
293
src/widget/responsive_container.rs
Normal file
|
|
@ -0,0 +1,293 @@
|
||||||
|
//! Autosize Container, which will resize the window to its contents.
|
||||||
|
|
||||||
|
use iced::{Limits, Size};
|
||||||
|
use iced_core::event::{self, Event};
|
||||||
|
use iced_core::layout;
|
||||||
|
use iced_core::mouse;
|
||||||
|
use iced_core::overlay;
|
||||||
|
use iced_core::renderer;
|
||||||
|
use iced_core::widget::{tree, Id, Tree};
|
||||||
|
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
|
||||||
|
|
||||||
|
use crate::app::message::SurfaceMessage;
|
||||||
|
|
||||||
|
pub(crate) fn responsive_container<'a, Message: 'static + From<SurfaceMessage>, Theme, E>(
|
||||||
|
content: E,
|
||||||
|
id: Id,
|
||||||
|
) -> ResponsiveContainer<'a, Message, Theme, crate::Renderer>
|
||||||
|
where
|
||||||
|
E: Into<Element<'a, Message, Theme, crate::Renderer>>,
|
||||||
|
Theme: iced_widget::container::Catalog,
|
||||||
|
<Theme as iced_widget::container::Catalog>::Class<'a>: From<crate::theme::Container<'a>>,
|
||||||
|
{
|
||||||
|
ResponsiveContainer::new(content, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An element decorating some content.
|
||||||
|
///
|
||||||
|
/// It is normally used for alignment purposes.
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct ResponsiveContainer<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: iced_core::Renderer,
|
||||||
|
{
|
||||||
|
content: Element<'a, Message, Theme, Renderer>,
|
||||||
|
id: Id,
|
||||||
|
size: Option<Size>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Theme, Renderer> ResponsiveContainer<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: iced_core::Renderer,
|
||||||
|
{
|
||||||
|
/// Creates an empty [`IdContainer`].
|
||||||
|
pub(crate) fn new<T>(content: T, id: Id) -> Self
|
||||||
|
where
|
||||||
|
T: Into<Element<'a, Message, Theme, Renderer>>,
|
||||||
|
{
|
||||||
|
ResponsiveContainer {
|
||||||
|
content: content.into(),
|
||||||
|
id,
|
||||||
|
size: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn size(mut self, size: Size) -> Self {
|
||||||
|
self.size = Some(size);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||||
|
for ResponsiveContainer<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: iced_core::Renderer,
|
||||||
|
Message: From<SurfaceMessage>,
|
||||||
|
{
|
||||||
|
fn tag(&self) -> tree::Tag {
|
||||||
|
tree::Tag::of::<State>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> tree::State {
|
||||||
|
tree::State::new(State::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children(&self) -> Vec<Tree> {
|
||||||
|
vec![Tree::new(&self.content)]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff(&mut self, tree: &mut Tree) {
|
||||||
|
tree.children[0].diff(&mut self.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> iced_core::Size<Length> {
|
||||||
|
self.content.as_widget().size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
renderer: &Renderer,
|
||||||
|
limits: &layout::Limits,
|
||||||
|
) -> layout::Node {
|
||||||
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
let unrestricted_size = self.size.unwrap_or_else(|| {
|
||||||
|
let node =
|
||||||
|
self.content
|
||||||
|
.as_widget()
|
||||||
|
.layout(&mut tree.children[0], renderer, &Limits::NONE);
|
||||||
|
node.size()
|
||||||
|
});
|
||||||
|
|
||||||
|
let max_size = limits.max();
|
||||||
|
let old_max = state.limits.max();
|
||||||
|
state.needs_update = (unrestricted_size.width > max_size.width)
|
||||||
|
^ (state.size.width > old_max.width)
|
||||||
|
|| (unrestricted_size.height > max_size.height) ^ (state.size.height > old_max.height);
|
||||||
|
if state.needs_update {
|
||||||
|
state.limits = *limits;
|
||||||
|
state.size = unrestricted_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
let node = self
|
||||||
|
.content
|
||||||
|
.as_widget()
|
||||||
|
.layout(&mut tree.children[0], renderer, limits);
|
||||||
|
let size = node.size();
|
||||||
|
layout::Node::with_children(size, vec![node])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(
|
||||||
|
&self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
renderer: &Renderer,
|
||||||
|
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||||
|
) {
|
||||||
|
operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
|
||||||
|
self.content.as_widget().operate(
|
||||||
|
&mut tree.children[0],
|
||||||
|
layout.children().next().unwrap(),
|
||||||
|
renderer,
|
||||||
|
operation,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
event: Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: mouse::Cursor,
|
||||||
|
renderer: &Renderer,
|
||||||
|
clipboard: &mut dyn Clipboard,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) -> event::Status {
|
||||||
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
|
if state.needs_update {
|
||||||
|
shell.publish(Message::from(SurfaceMessage::ResponsiveMenuBar {
|
||||||
|
menu_bar: self.id.clone(),
|
||||||
|
limits: state.limits,
|
||||||
|
size: state.size,
|
||||||
|
}));
|
||||||
|
state.needs_update = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.content.as_widget_mut().on_event(
|
||||||
|
&mut tree.children[0],
|
||||||
|
event.clone(),
|
||||||
|
layout.children().next().unwrap(),
|
||||||
|
cursor_position,
|
||||||
|
renderer,
|
||||||
|
clipboard,
|
||||||
|
shell,
|
||||||
|
viewport,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: mouse::Cursor,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
renderer: &Renderer,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
let content_layout = layout.children().next().unwrap();
|
||||||
|
self.content.as_widget().mouse_interaction(
|
||||||
|
&tree.children[0],
|
||||||
|
content_layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
renderer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
theme: &Theme,
|
||||||
|
renderer_style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: mouse::Cursor,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
let content_layout = layout.children().next().unwrap();
|
||||||
|
self.content.as_widget().draw(
|
||||||
|
&tree.children[0],
|
||||||
|
renderer,
|
||||||
|
theme,
|
||||||
|
renderer_style,
|
||||||
|
content_layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlay<'b>(
|
||||||
|
&'b mut self,
|
||||||
|
tree: &'b mut Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
renderer: &Renderer,
|
||||||
|
translation: Vector,
|
||||||
|
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||||
|
self.content.as_widget_mut().overlay(
|
||||||
|
&mut tree.children[0],
|
||||||
|
layout.children().next().unwrap(),
|
||||||
|
renderer,
|
||||||
|
translation,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drag_destinations(
|
||||||
|
&self,
|
||||||
|
state: &Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
renderer: &Renderer,
|
||||||
|
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||||
|
) {
|
||||||
|
let content_layout = layout.children().next().unwrap();
|
||||||
|
self.content.as_widget().drag_destinations(
|
||||||
|
&state.children[0],
|
||||||
|
content_layout,
|
||||||
|
renderer,
|
||||||
|
dnd_rectangles,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> Option<crate::widget::Id> {
|
||||||
|
Some(self.id.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_id(&mut self, id: crate::widget::Id) {
|
||||||
|
self.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "a11y")]
|
||||||
|
/// get the a11y nodes for the widget
|
||||||
|
fn a11y_nodes(
|
||||||
|
&self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
state: &Tree,
|
||||||
|
p: mouse::Cursor,
|
||||||
|
) -> iced_accessibility::A11yTree {
|
||||||
|
let c_layout = layout.children().next().unwrap();
|
||||||
|
let c_state = &state.children[0];
|
||||||
|
self.content.as_widget().a11y_nodes(c_layout, c_state, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Theme, Renderer> From<ResponsiveContainer<'a, Message, Theme, Renderer>>
|
||||||
|
for Element<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
Message: 'a + From<SurfaceMessage>,
|
||||||
|
Renderer: 'a + iced_core::Renderer,
|
||||||
|
Theme: 'a,
|
||||||
|
{
|
||||||
|
fn from(
|
||||||
|
c: ResponsiveContainer<'a, Message, Theme, Renderer>,
|
||||||
|
) -> Element<'a, Message, Theme, Renderer> {
|
||||||
|
Element::new(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct State {
|
||||||
|
limits: Limits,
|
||||||
|
size: Size,
|
||||||
|
needs_update: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
limits: Limits::NONE,
|
||||||
|
size: Size::new(0., 0.),
|
||||||
|
needs_update: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ use crate::widget::menu::{
|
||||||
self, menu_roots_children, menu_roots_diff, CloseCondition, ItemHeight, ItemWidth,
|
self, menu_roots_children, menu_roots_diff, CloseCondition, ItemHeight, ItemWidth,
|
||||||
MenuBarState, PathHighlight,
|
MenuBarState, PathHighlight,
|
||||||
};
|
};
|
||||||
use crate::widget::{icon, Icon};
|
use crate::widget::{dropdown, icon, Icon};
|
||||||
use crate::{Element, Renderer};
|
use crate::{Element, Renderer};
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
use iced::clipboard::dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent};
|
use iced::clipboard::dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent};
|
||||||
|
|
@ -127,7 +127,7 @@ where
|
||||||
pub(super) style: Style,
|
pub(super) style: Style,
|
||||||
/// The context menu to display when a context is activated
|
/// The context menu to display when a context is activated
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
pub(super) context_menu: Option<Vec<menu::Tree<'a, Message, crate::Renderer>>>,
|
pub(super) context_menu: Option<Vec<menu::Tree<Message>>>,
|
||||||
/// Emits the ID of the item that was activated.
|
/// Emits the ID of the item that was activated.
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
pub(super) on_activate: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
|
pub(super) on_activate: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
|
||||||
|
|
@ -197,13 +197,13 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context_menu(mut self, context_menu: Option<Vec<menu::Tree<'a, Message>>>) -> Self
|
pub fn context_menu(mut self, context_menu: Option<Vec<menu::Tree<Message>>>) -> Self
|
||||||
where
|
where
|
||||||
Message: 'static,
|
Message: Clone + 'static,
|
||||||
{
|
{
|
||||||
self.context_menu = context_menu.map(|menus| {
|
self.context_menu = context_menu.map(|menus| {
|
||||||
vec![menu::Tree::with_children(
|
vec![menu::Tree::with_children(
|
||||||
crate::widget::row::<'static, Message>(),
|
crate::Element::from(crate::widget::row::<'static, Message>()),
|
||||||
menus,
|
menus,
|
||||||
)]
|
)]
|
||||||
});
|
});
|
||||||
|
|
@ -592,6 +592,7 @@ where
|
||||||
wheel_timestamp: Default::default(),
|
wheel_timestamp: Default::default(),
|
||||||
dnd_state: Default::default(),
|
dnd_state: Default::default(),
|
||||||
fingers_pressed: Default::default(),
|
fingers_pressed: Default::default(),
|
||||||
|
menu_state: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1537,7 +1538,7 @@ where
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||||
let state = tree.state.downcast_ref::<LocalState>();
|
let state = tree.state.downcast_ref::<LocalState>();
|
||||||
|
let menu_state = state.menu_state.clone();
|
||||||
let Some(entity) = state.show_context else {
|
let Some(entity) = state.show_context else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
@ -1565,8 +1566,8 @@ where
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
crate::widget::menu::Menu {
|
crate::widget::menu::Menu {
|
||||||
tree: &mut tree.children[0],
|
tree: menu_state,
|
||||||
menu_roots: context_menu,
|
menu_roots: Cow::Borrowed(context_menu),
|
||||||
bounds_expand: 16,
|
bounds_expand: 16,
|
||||||
menu_overlays_parent: true,
|
menu_overlays_parent: true,
|
||||||
close_condition: CloseCondition {
|
close_condition: CloseCondition {
|
||||||
|
|
@ -1581,7 +1582,7 @@ where
|
||||||
cross_offset: 0,
|
cross_offset: 0,
|
||||||
root_bounds_list: vec![bounds],
|
root_bounds_list: vec![bounds],
|
||||||
path_highlight: Some(PathHighlight::MenuActive),
|
path_highlight: Some(PathHighlight::MenuActive),
|
||||||
style: &crate::theme::menu_bar::MenuBarStyle::Default,
|
style: Cow::Owned(crate::theme::menu_bar::MenuBarStyle::Default),
|
||||||
position: Point::new(translation.x, translation.y),
|
position: Point::new(translation.x, translation.y),
|
||||||
}
|
}
|
||||||
.overlay(),
|
.overlay(),
|
||||||
|
|
@ -1666,6 +1667,8 @@ pub struct LocalState {
|
||||||
pub dnd_state: crate::widget::dnd_destination::State<Option<Entity>>,
|
pub dnd_state: crate::widget::dnd_destination::State<Option<Entity>>,
|
||||||
/// Tracks multi-touch events
|
/// Tracks multi-touch events
|
||||||
fingers_pressed: HashSet<Finger>,
|
fingers_pressed: HashSet<Finger>,
|
||||||
|
/// Menu state
|
||||||
|
pub(crate) menu_state: dropdown::menu::State,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
|
|
|
||||||
0
src/widget/wayland/tooltip/button_popup.rs
Normal file
0
src/widget/wayland/tooltip/button_popup.rs
Normal file
0
src/widget/wayland/tooltip/dropdown.rs
Normal file
0
src/widget/wayland/tooltip/dropdown.rs
Normal file
155
src/widget/wrapper.rs
Normal file
155
src/widget/wrapper.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use crate::Element;
|
||||||
|
use iced::{event, Length, Rectangle, Size};
|
||||||
|
use iced_core::{id::Id, widget, widget::tree, Widget};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ArcElementWrapper<M>(pub Arc<Mutex<Element<'static, M>>>);
|
||||||
|
|
||||||
|
impl<M> Widget<M, crate::Theme, crate::Renderer> for ArcElementWrapper<M> {
|
||||||
|
fn size(&self) -> Size<Length> {
|
||||||
|
self.0.lock().unwrap().as_widget().size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> Size<Length> {
|
||||||
|
self.0.lock().unwrap().as_widget().size_hint()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
tree: &mut tree::Tree,
|
||||||
|
renderer: &crate::Renderer,
|
||||||
|
limits: &crate::iced_core::layout::Limits,
|
||||||
|
) -> crate::iced_core::layout::Node {
|
||||||
|
self.0
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.as_widget_mut()
|
||||||
|
.layout(tree, renderer, limits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
tree: &tree::Tree,
|
||||||
|
renderer: &mut crate::Renderer,
|
||||||
|
theme: &crate::Theme,
|
||||||
|
style: &crate::iced_core::renderer::Style,
|
||||||
|
layout: crate::iced_core::Layout<'_>,
|
||||||
|
cursor: crate::iced_core::mouse::Cursor,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
self.0
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.as_widget()
|
||||||
|
.draw(tree, renderer, theme, style, layout, cursor, viewport)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tag(&self) -> tree::Tag {
|
||||||
|
self.0.lock().unwrap().as_widget().tag()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> tree::State {
|
||||||
|
self.0.lock().unwrap().as_widget().state()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children(&self) -> Vec<tree::Tree> {
|
||||||
|
self.0.lock().unwrap().as_widget().children()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff(&mut self, tree: &mut tree::Tree) {
|
||||||
|
self.0.lock().unwrap().as_widget_mut().diff(tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(
|
||||||
|
&self,
|
||||||
|
state: &mut tree::Tree,
|
||||||
|
layout: crate::iced_core::Layout<'_>,
|
||||||
|
renderer: &crate::Renderer,
|
||||||
|
operation: &mut dyn widget::Operation,
|
||||||
|
) {
|
||||||
|
self.0
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.as_widget()
|
||||||
|
.operate(state, layout, renderer, operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
_state: &mut tree::Tree,
|
||||||
|
_event: crate::iced::Event,
|
||||||
|
_layout: crate::iced_core::Layout<'_>,
|
||||||
|
_cursor: crate::iced_core::mouse::Cursor,
|
||||||
|
_renderer: &crate::Renderer,
|
||||||
|
_clipboard: &mut dyn crate::iced_core::Clipboard,
|
||||||
|
_shell: &mut crate::iced_core::Shell<'_, M>,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) -> event::Status {
|
||||||
|
self.0.lock().unwrap().as_widget_mut().on_event(
|
||||||
|
_state, _event, _layout, _cursor, _renderer, _clipboard, _shell, _viewport,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
_state: &tree::Tree,
|
||||||
|
_layout: crate::iced_core::Layout<'_>,
|
||||||
|
_cursor: crate::iced_core::mouse::Cursor,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
_renderer: &crate::Renderer,
|
||||||
|
) -> crate::iced_core::mouse::Interaction {
|
||||||
|
self.0
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.as_widget()
|
||||||
|
.mouse_interaction(_state, _layout, _cursor, _viewport, _renderer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlay<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
_state: &'a mut tree::Tree,
|
||||||
|
_layout: crate::iced_core::Layout<'_>,
|
||||||
|
_renderer: &crate::Renderer,
|
||||||
|
_translation: crate::iced_core::Vector,
|
||||||
|
) -> Option<crate::iced_core::overlay::Element<'a, M, crate::Theme, crate::Renderer>> {
|
||||||
|
// TODO
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> Option<Id> {
|
||||||
|
self.0.lock().unwrap().as_widget().id()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_id(&mut self, _id: Id) {
|
||||||
|
self.0.lock().unwrap().as_widget_mut().set_id(_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drag_destinations(
|
||||||
|
&self,
|
||||||
|
_state: &tree::Tree,
|
||||||
|
_layout: crate::iced_core::Layout<'_>,
|
||||||
|
renderer: &crate::Renderer,
|
||||||
|
_dnd_rectangles: &mut crate::iced_core::clipboard::DndDestinationRectangles,
|
||||||
|
) {
|
||||||
|
self.0.lock().unwrap().as_widget().drag_destinations(
|
||||||
|
_state,
|
||||||
|
_layout,
|
||||||
|
renderer,
|
||||||
|
_dnd_rectangles,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message: 'static> From<ArcElementWrapper<Message>> for Element<'static, Message> {
|
||||||
|
fn from(wrapper: ArcElementWrapper<Message>) -> Self {
|
||||||
|
Element::new(wrapper)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message: 'static> From<Element<'static, Message>> for ArcElementWrapper<Message> {
|
||||||
|
fn from(e: Element<'static, Message>) -> Self {
|
||||||
|
ArcElementWrapper(Arc::new(Mutex::new(e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue