refactor: better tooltips and helper methods for managing subsurfaces/popups

This commit is contained in:
Ashley Wulber 2025-02-26 14:14:15 -05:00
parent 44cf45ad4d
commit f01a48ff72
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820
7 changed files with 255 additions and 125 deletions

View file

@ -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()

View file

@ -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>(

View file

@ -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,

View file

@ -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()
}

View file

@ -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(),

View file

@ -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,