diff --git a/examples/applet/src/window.rs b/examples/applet/src/window.rs index 98dcd57c..eb372f15 100644 --- a/examples/applet/src/window.rs +++ b/examples/applet/src/window.rs @@ -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::( + return cosmic::task::message(cosmic::app::message::destroy_popup::( self.dropdown_id, )); } @@ -135,112 +135,82 @@ impl cosmic::Application for Window { )); } Message::PopupCloseRequested(id) => { - return cosmic::task::message(cosmic::app::message::destroy_popup::(id)); + return cosmic::task::message(cosmic::app::message::destroy_popup::(id)); } } Task::none() } fn view(&self) -> Element { - 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::(id) - } else { - cosmic::app::message::app_popup::( - |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, - > { - { - 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::(id) + } else { + cosmic::app::message::app_popup::( + |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, + > { + { + 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::(self.subsurface_id), - ) - .into() + ); + + self.core.applet.applet_tooltip(btn, "test").into() } fn view_window(&self, _id: Id) -> Element { diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index c10d7b65..713cda7d 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -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:: 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:: 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:: Element<'static, super::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() diff --git a/src/app/mod.rs b/src/app/mod.rs index cbdcd60a..8c8a89ba 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -47,34 +47,19 @@ pub mod message { Message::None } - #[cfg(feature = "wayland")] - pub fn destroy_popup(id: iced_core::window::Id) -> App::Message - where - App::Message: - Into> + From, - { - 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(id: iced_core::window::Id) -> Message + pub fn destroy_popup(id: iced_core::window::Id) -> Message where - Message: - Into> + From + 'static, + Message: From + 'static, { Message::from(SurfaceMessage::DestroyPopup(id)) } #[cfg(feature = "wayland")] - pub fn destroy_subsurface(id: iced_core::window::Id) -> App::Message - where - App::Message: - Into> + From, - { + pub fn destroy_subsurface>(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( + settings: impl Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync + 'static, + view: Option< + Box< + dyn Fn() -> crate::Element<'static, super::Message> + + Send + + Sync + + 'static, + >, + >, + ) -> Message + where + Message: + Into> + From + '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 = Box::new(boxed); + + Message::from(SurfaceMessage::Subsurface( + Arc::new(boxed), + view.map(|view| { + let boxed: Box = Box::new(view); + Arc::new(boxed) + }), + )) + } + /// Used to create a popup message from within a widget. #[cfg(feature = "wayland")] pub fn simple_popup( diff --git a/src/applet/mod.rs b/src/applet/mod.rs index ca5f697d..0e017bd1 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -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 = LazyLock::new(|| iced::id::Id::new("cosmic-applet-autosize")); static AUTOSIZE_MAIN_ID: LazyLock = LazyLock::new(|| iced::id::Id::new("cosmic-applet-autosize-main")); +static TOOLTIP_ID: LazyLock = LazyLock::new(|| iced::id::Id::new("subsurface")); +static TOOLTIP_WINDOW_ID: LazyLock = 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 + Into>, + >( + &self, + content: impl Into>, + tooltip: impl Into>, + ) -> 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> + + 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>::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::(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, diff --git a/src/surface_message.rs b/src/surface_message.rs index 429fa463..a54b81df 100644 --- a/src/surface_message.rs +++ b/src/surface_message.rs @@ -17,6 +17,11 @@ impl From for MessageWrapper { /// 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>, + Option>>, + ), /// Create a subsurface with a view function Subsurface( std::sync::Arc>, @@ -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() } diff --git a/src/theme/style/tooltip.rs b/src/theme/style/tooltip.rs index a1e77774..df9a8e2c 100644 --- a/src/theme/style/tooltip.rs +++ b/src/theme/style/tooltip.rs @@ -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(), diff --git a/src/widget/wayland/tooltip/widget.rs b/src/widget/wayland/tooltip/widget.rs index 1950504f..ba7f8c5a 100644 --- a/src/widget/wayland/tooltip/widget.rs +++ b/src/widget/wayland/tooltip/widget.rs @@ -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,