refactor: better tooltips and helper methods for managing subsurfaces/popups
This commit is contained in:
parent
44cf45ad4d
commit
f01a48ff72
7 changed files with 255 additions and 125 deletions
|
|
@ -119,7 +119,7 @@ impl cosmic::Application for Window {
|
|||
Message::Surface(_) => {}
|
||||
Message::Selected(i) => {
|
||||
self.selected = Some(i);
|
||||
return cosmic::task::message(cosmic::app::message::destroy_popup::<Window>(
|
||||
return cosmic::task::message(cosmic::app::message::destroy_popup::<Message>(
|
||||
self.dropdown_id,
|
||||
));
|
||||
}
|
||||
|
|
@ -135,112 +135,82 @@ impl cosmic::Application for Window {
|
|||
));
|
||||
}
|
||||
Message::PopupCloseRequested(id) => {
|
||||
return cosmic::task::message(cosmic::app::message::destroy_popup::<Window>(id));
|
||||
return cosmic::task::message(cosmic::app::message::destroy_popup::<Message>(id));
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Self::Message> {
|
||||
cosmic::widget::wayland::tooltip::widget::Tooltip::new(
|
||||
self.core.applet.icon_button("display-symbolic").on_press(
|
||||
if let Some(id) = self.popup {
|
||||
cosmic::app::message::destroy_popup::<Window>(id)
|
||||
} else {
|
||||
cosmic::app::message::app_popup::<Window>(
|
||||
|state: &mut Window| {
|
||||
let new_id = Id::unique();
|
||||
state.popup = Some(new_id);
|
||||
let mut popup_settings = state.core.applet.get_popup_settings(
|
||||
state.core.main_window_id().unwrap(),
|
||||
new_id,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
popup_settings.positioner.size_limits = Limits::NONE
|
||||
.max_width(372.0)
|
||||
.min_width(300.0)
|
||||
.min_height(200.0)
|
||||
.max_height(1080.0)
|
||||
.height(500)
|
||||
.width(500);
|
||||
popup_settings.positioner.size = Some((500, 500));
|
||||
popup_settings
|
||||
},
|
||||
Some(
|
||||
Box::new(
|
||||
move |state: &Window| -> cosmic::Element<
|
||||
'_,
|
||||
cosmic::app::Message<Message>,
|
||||
> {
|
||||
{
|
||||
let content_list = list_column()
|
||||
.padding(5)
|
||||
.spacing(0)
|
||||
.add(settings::item(
|
||||
"Example row",
|
||||
cosmic::widget::container(
|
||||
toggler(state.example_row).on_toggle(|value| {
|
||||
Message::ToggleExampleRow(value)
|
||||
}),
|
||||
)
|
||||
.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(
|
||||
state.core.applet.popup_container(content_list),
|
||||
)
|
||||
.map(cosmic::app::Message::App)
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
|layout| {
|
||||
let bounds = layout.bounds();
|
||||
let pos = bounds.position();
|
||||
let size = bounds.size();
|
||||
|
||||
cosmic::app::message::subsurface(
|
||||
move |app: &mut Window| subsurface::SctkSubsurfaceSettings {
|
||||
parent: window::Id::RESERVED,
|
||||
id: app.subsurface_id,
|
||||
loc: iced::Point {
|
||||
x: bounds.width / 2.,
|
||||
y: bounds.height,
|
||||
},
|
||||
size: None,
|
||||
z: 1,
|
||||
steal_keyboard_focus: false,
|
||||
offset: (0, 0),
|
||||
gravity: Gravity::Bottom,
|
||||
let btn = self.core.applet.icon_button("display-symbolic").on_press(
|
||||
if let Some(id) = self.popup {
|
||||
cosmic::app::message::destroy_popup::<Message>(id)
|
||||
} else {
|
||||
cosmic::app::message::app_popup::<Window>(
|
||||
|state: &mut Window| {
|
||||
let new_id = Id::unique();
|
||||
state.popup = Some(new_id);
|
||||
let mut popup_settings = state.core.applet.get_popup_settings(
|
||||
state.core.main_window_id().unwrap(),
|
||||
new_id,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
popup_settings.positioner.size_limits = Limits::NONE
|
||||
.max_width(372.0)
|
||||
.min_width(300.0)
|
||||
.min_height(200.0)
|
||||
.max_height(1080.0)
|
||||
.height(500)
|
||||
.width(500);
|
||||
popup_settings.positioner.size = Some((500, 500));
|
||||
popup_settings
|
||||
},
|
||||
Some(Box::new(|_: &'_ Window| {
|
||||
Element::from(autosize::autosize(
|
||||
layer_container(cosmic::widget::text("hello"))
|
||||
.layer(cosmic::cosmic_theme::Layer::Background),
|
||||
SUBSURFACE_ID.clone(),
|
||||
))
|
||||
})),
|
||||
Some(
|
||||
Box::new(
|
||||
move |state: &Window| -> cosmic::Element<
|
||||
'_,
|
||||
cosmic::app::Message<Message>,
|
||||
> {
|
||||
{
|
||||
let content_list = list_column()
|
||||
.padding(5)
|
||||
.spacing(0)
|
||||
.add(settings::item(
|
||||
"Example row",
|
||||
cosmic::widget::container(
|
||||
toggler(state.example_row).on_toggle(|value| {
|
||||
Message::ToggleExampleRow(value)
|
||||
}),
|
||||
)
|
||||
.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(
|
||||
state.core.applet.popup_container(content_list),
|
||||
)
|
||||
.map(cosmic::app::Message::App)
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
cosmic::app::message::destroy_subsurface::<Window>(self.subsurface_id),
|
||||
)
|
||||
.into()
|
||||
);
|
||||
|
||||
self.core.applet.applet_tooltip(btn, "test").into()
|
||||
}
|
||||
|
||||
fn view_window(&self, _id: Id) -> Element<Self::Message> {
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ where
|
|||
MessageWrapper::Surface(surface_message) => {
|
||||
#[cfg(feature = "wayland")]
|
||||
match surface_message {
|
||||
SurfaceMessage::Subsurface(settings, view) => {
|
||||
SurfaceMessage::AppSubsurface(settings, view) => {
|
||||
let Some(settings) = std::sync::Arc::try_unwrap(settings)
|
||||
.ok()
|
||||
.and_then(|s| s.downcast::<Box<dyn Fn(&mut T) -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else {
|
||||
|
|
@ -150,6 +150,7 @@ where
|
|||
tracing::error!(
|
||||
"Invalid view for subsurface view: {err:?}"
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
@ -162,6 +163,38 @@ where
|
|||
))
|
||||
}
|
||||
}
|
||||
SurfaceMessage::Subsurface(settings, view) => {
|
||||
let Some(settings) = std::sync::Arc::try_unwrap(settings)
|
||||
.ok()
|
||||
.and_then(|s| s.downcast::<Box<dyn Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else {
|
||||
tracing::error!("Invalid settings for subsurface");
|
||||
return Task::none();
|
||||
};
|
||||
|
||||
if let Some(view) = view.and_then(|view| {
|
||||
match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
|
||||
dyn Fn() -> Element<'static, super::Message<T::Message>>
|
||||
+ Send
|
||||
+ Sync,
|
||||
>>(
|
||||
) {
|
||||
Ok(v) => Some(v),
|
||||
Err(err) => {
|
||||
tracing::error!(
|
||||
"Invalid view for subsurface view: {err:?}"
|
||||
);
|
||||
dbg!("bad view");
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}) {
|
||||
let settings = settings();
|
||||
self.get_subsurface(settings, Box::new(move |_| view()))
|
||||
} else {
|
||||
iced_winit::commands::subsurface::get_subsurface(settings())
|
||||
}
|
||||
}
|
||||
SurfaceMessage::AppPopup(settings, view) => {
|
||||
let Some(settings) = std::sync::Arc::try_unwrap(settings)
|
||||
.ok()
|
||||
|
|
|
|||
|
|
@ -47,34 +47,19 @@ pub mod message {
|
|||
Message::None
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn destroy_popup<App: super::Application>(id: iced_core::window::Id) -> App::Message
|
||||
where
|
||||
App::Message:
|
||||
Into<crate::surface_message::MessageWrapper<App::Message>> + From<SurfaceMessage>,
|
||||
{
|
||||
let surface_msg = SurfaceMessage::DestroyPopup(id);
|
||||
App::Message::from(surface_msg)
|
||||
}
|
||||
|
||||
/// Used to produce a destroy popup message from within a widget.
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn destroy_popup_simple<Message>(id: iced_core::window::Id) -> Message
|
||||
pub fn destroy_popup<Message>(id: iced_core::window::Id) -> Message
|
||||
where
|
||||
Message:
|
||||
Into<crate::surface_message::MessageWrapper<Message>> + From<SurfaceMessage> + 'static,
|
||||
Message: From<SurfaceMessage> + 'static,
|
||||
{
|
||||
Message::from(SurfaceMessage::DestroyPopup(id))
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn destroy_subsurface<App: super::Application>(id: iced_core::window::Id) -> App::Message
|
||||
where
|
||||
App::Message:
|
||||
Into<crate::surface_message::MessageWrapper<App::Message>> + From<SurfaceMessage>,
|
||||
{
|
||||
pub fn destroy_subsurface<M: From<SurfaceMessage>>(id: iced_core::window::Id) -> M {
|
||||
let surface_msg = SurfaceMessage::DestroySubsurface(id);
|
||||
App::Message::from(surface_msg)
|
||||
M::from(surface_msg)
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
|
|
@ -116,6 +101,44 @@ pub mod message {
|
|||
))
|
||||
}
|
||||
|
||||
/// Used to create a subsurface message from within a widget.
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn simple_subsurface<Message, V>(
|
||||
settings: impl Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync + 'static,
|
||||
view: Option<
|
||||
Box<
|
||||
dyn Fn() -> crate::Element<'static, super::Message<Message>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
>,
|
||||
>,
|
||||
) -> Message
|
||||
where
|
||||
Message:
|
||||
Into<crate::surface_message::MessageWrapper<Message>> + From<SurfaceMessage> + 'static,
|
||||
V:,
|
||||
{
|
||||
use std::{any::Any, sync::Arc};
|
||||
|
||||
use crate::surface_message::SurfaceMessage;
|
||||
let boxed: Box<
|
||||
dyn Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
> = Box::new(settings);
|
||||
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
|
||||
|
||||
Message::from(SurfaceMessage::Subsurface(
|
||||
Arc::new(boxed),
|
||||
view.map(|view| {
|
||||
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(view);
|
||||
Arc::new(boxed)
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
/// Used to create a popup message from within a widget.
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn simple_popup<Message, V>(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
pub mod token;
|
||||
|
||||
use crate::{
|
||||
app::{self, iced_settings, Core},
|
||||
app::iced_settings,
|
||||
cctk::sctk,
|
||||
iced::{
|
||||
self,
|
||||
|
|
@ -11,10 +11,11 @@ use crate::{
|
|||
window, Color, Length, Limits, Rectangle,
|
||||
},
|
||||
iced_widget,
|
||||
surface_message::SurfaceMessage,
|
||||
theme::{self, system_dark, system_light, Button, THEME},
|
||||
widget::{
|
||||
self,
|
||||
autosize::{autosize, Autosize},
|
||||
autosize::{self, autosize, Autosize},
|
||||
layer_container,
|
||||
},
|
||||
Application, Element, Renderer,
|
||||
|
|
@ -23,11 +24,17 @@ use cctk::sctk::shell::xdg::window::WindowConfigure;
|
|||
pub use cosmic_panel_config;
|
||||
use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize};
|
||||
use cosmic_theme::Theme;
|
||||
use iced::platform_specific::runtime::wayland::subsurface;
|
||||
use iced::Pixels;
|
||||
use iced_core::{Padding, Shadow};
|
||||
use iced_core::{Layout, Padding, Shadow};
|
||||
use iced_widget::runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner};
|
||||
use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity};
|
||||
use std::{borrow::Cow, num::NonZeroU32, rc::Rc, sync::LazyLock};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
num::NonZeroU32,
|
||||
rc::Rc,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
use crate::app::cosmic;
|
||||
|
|
@ -35,6 +42,8 @@ static AUTOSIZE_ID: LazyLock<iced::id::Id> =
|
|||
LazyLock::new(|| iced::id::Id::new("cosmic-applet-autosize"));
|
||||
static AUTOSIZE_MAIN_ID: LazyLock<iced::id::Id> =
|
||||
LazyLock::new(|| iced::id::Id::new("cosmic-applet-autosize-main"));
|
||||
static TOOLTIP_ID: LazyLock<crate::widget::Id> = LazyLock::new(|| iced::id::Id::new("subsurface"));
|
||||
static TOOLTIP_WINDOW_ID: LazyLock<window::Id> = LazyLock::new(|| window::Id::unique());
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Context {
|
||||
|
|
@ -224,6 +233,93 @@ impl Context {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn applet_tooltip<
|
||||
'a,
|
||||
Message: 'static + From<SurfaceMessage> + Into<crate::surface_message::MessageWrapper<Message>>,
|
||||
>(
|
||||
&self,
|
||||
content: impl Into<Element<'a, Message>>,
|
||||
tooltip: impl Into<Cow<'static, str>>,
|
||||
) -> crate::widget::wayland::tooltip::widget::Tooltip<'a, Message> {
|
||||
let window_id = TOOLTIP_WINDOW_ID.clone();
|
||||
let subsurface_id = TOOLTIP_ID.clone();
|
||||
let anchor = self.anchor;
|
||||
let tooltip = tooltip.into();
|
||||
let on_hover = move |layout: Layout| {
|
||||
let bounds = layout.bounds();
|
||||
let subsurface_id = subsurface_id.clone();
|
||||
let tooltip = tooltip.clone();
|
||||
let window_id = window_id;
|
||||
let (loc, gravity) = match anchor {
|
||||
PanelAnchor::Left => (
|
||||
iced::Point {
|
||||
x: bounds.width,
|
||||
y: bounds.height / 2.,
|
||||
},
|
||||
Gravity::Right,
|
||||
),
|
||||
PanelAnchor::Right => (
|
||||
iced::Point {
|
||||
x: 0.,
|
||||
y: bounds.height / 2.,
|
||||
},
|
||||
Gravity::Left,
|
||||
),
|
||||
PanelAnchor::Top => (
|
||||
iced::Point {
|
||||
x: bounds.width / 2.,
|
||||
y: bounds.height,
|
||||
},
|
||||
Gravity::Bottom,
|
||||
),
|
||||
PanelAnchor::Bottom => (
|
||||
iced::Point {
|
||||
x: bounds.width / 2.,
|
||||
y: 0.,
|
||||
},
|
||||
Gravity::Top,
|
||||
),
|
||||
};
|
||||
|
||||
crate::app::message::simple_subsurface::<
|
||||
Message,
|
||||
Option<
|
||||
Box<
|
||||
dyn Fn() -> crate::Element<'static, crate::app::Message<Message>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
>,
|
||||
>,
|
||||
>(
|
||||
move || subsurface::SctkSubsurfaceSettings {
|
||||
parent: window::Id::RESERVED,
|
||||
id: window_id,
|
||||
loc,
|
||||
size: None,
|
||||
z: 1,
|
||||
steal_keyboard_focus: false,
|
||||
offset: (0, 0),
|
||||
gravity,
|
||||
input_region: Rectangle::default(),
|
||||
},
|
||||
Some(Box::new(move || {
|
||||
Element::<'static, crate::app::Message<Message>>::from(autosize::autosize(
|
||||
layer_container(crate::widget::text(tooltip.clone()))
|
||||
.layer(crate::cosmic_theme::Layer::Background)
|
||||
.padding(4.),
|
||||
subsurface_id.clone(),
|
||||
))
|
||||
})),
|
||||
)
|
||||
};
|
||||
crate::widget::wayland::tooltip::widget::Tooltip::new(
|
||||
content,
|
||||
on_hover,
|
||||
crate::app::message::destroy_subsurface::<Message>(window_id),
|
||||
)
|
||||
}
|
||||
|
||||
// TODO popup container which tracks the size of itself and requests the popup to resize to match
|
||||
pub fn popup_container<'a, Message: 'static>(
|
||||
&self,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ impl<M> From<M> for MessageWrapper<M> {
|
|||
/// Ignore this message in your application. It will be intercepted.
|
||||
#[derive(Clone)]
|
||||
pub enum SurfaceMessage {
|
||||
/// Create a subsurface with a view function accepting the App as a parameter
|
||||
AppSubsurface(
|
||||
std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>,
|
||||
Option<std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>>,
|
||||
),
|
||||
/// Create a subsurface with a view function
|
||||
Subsurface(
|
||||
std::sync::Arc<Box<dyn std::any::Any + Send + Sync>>,
|
||||
|
|
@ -50,6 +55,9 @@ pub enum SurfaceMessage {
|
|||
impl std::fmt::Debug for SurfaceMessage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::AppSubsurface(arg0, arg1) => {
|
||||
f.debug_tuple("Subsurface").field(arg0).field(arg1).finish()
|
||||
}
|
||||
Self::Subsurface(arg0, arg1) => {
|
||||
f.debug_tuple("Subsurface").field(arg0).field(arg1).finish()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ impl Catalog for crate::Theme {
|
|||
match style {
|
||||
Tooltip::Default => crate::widget::wayland::tooltip::Style {
|
||||
text_color: cosmic.on_bg_color().into(),
|
||||
background: Some(Background::Color(cosmic.background.component.base.into())),
|
||||
background: None,
|
||||
border_width: 0.0,
|
||||
border_radius: cosmic.corner_radii.radius_m.into(),
|
||||
border_radius: cosmic.corner_radii.radius_0.into(),
|
||||
border_color: Color::TRANSPARENT,
|
||||
shadow_offset: iced::Vector::default(),
|
||||
outline_width: Default::default(),
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ impl<'a, Message> Tooltip<'a, Message> {
|
|||
on_hover: Box::new(on_hover),
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
padding: Padding::new(5.0),
|
||||
padding: Padding::new(0.0),
|
||||
selected: false,
|
||||
style: crate::theme::Tooltip::default(),
|
||||
on_leave,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue