diff --git a/Cargo.toml b/Cargo.toml index ed0917e..482fa41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ default = ["wayland"] debug = ["iced/debug"] wayland = ["iced/wayland"] wgpu = ["iced/wgpu"] +winit = ["iced/winit", "iced_winit"] applet = ["cosmic-panel-config", "sctk"] [dependencies] diff --git a/examples/cosmic-sctk/src/window.rs b/examples/cosmic-sctk/src/window.rs index 17ccd8e..3951ee8 100644 --- a/examples/cosmic-sctk/src/window.rs +++ b/examples/cosmic-sctk/src/window.rs @@ -356,4 +356,8 @@ impl Application for Window { fn theme(&self) -> Theme { self.theme } + + fn subscription(&self) -> iced_sctk::Subscription { + Subscription::none() + } } diff --git a/examples/cosmic/Cargo.toml b/examples/cosmic/Cargo.toml index b128176..53bf385 100644 --- a/examples/cosmic/Cargo.toml +++ b/examples/cosmic/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" publish = false [dependencies] -libcosmic = { path = "../..", default-features = false, features = ["debug", "wgpu"] } +libcosmic = { path = "../..", default-features = false, features = ["debug", "wgpu", "winit"] } diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 723262b..b8f8a68 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -124,4 +124,3 @@ where .align_x(halign) .align_y(valign) } - diff --git a/src/lib.rs b/src/lib.rs index 136436d..1151b4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ pub use iced; pub use iced_lazy; pub use iced_native; pub use iced_style; -#[cfg(feature = "iced_winit")] +#[cfg(feature = "winit")] pub use iced_winit; #[cfg(feature = "applet")] diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 196a011..a68b9f5 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -24,3 +24,6 @@ pub use list::*; pub mod separator; pub use separator::*; + +pub mod popup; +pub use popup::*; diff --git a/src/widget/popup.rs b/src/widget/popup.rs new file mode 100644 index 0000000..780edc0 --- /dev/null +++ b/src/widget/popup.rs @@ -0,0 +1,311 @@ +use iced::futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; +use iced::futures::SinkExt; +use iced::{ + futures::StreamExt, + widget::{container, Container}, + Rectangle, +}; +use iced_native::alignment::{self, Alignment}; +use iced_native::command::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner}; +use iced_native::event::{self, Event}; +use iced_native::layout; +use iced_native::mouse; +use iced_native::overlay; +use iced_native::renderer; +use iced_native::widget::{Operation, Tree}; +use iced_native::{ + window, Background, Clipboard, Color, Element, Layout, Length, Padding, Point, Shell, Widget, +}; +use std::u32; + +pub use iced_style::container::{Appearance, StyleSheet}; +pub struct SizeTrackingContainer<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, + Renderer::Theme: StyleSheet, +{ + container: Container<'a, Message, Renderer>, + tx: UnboundedSender>, +} + +impl<'a, Message, Renderer> SizeTrackingContainer<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, + Renderer::Theme: StyleSheet, +{ + /// Creates an empty [`Container`]. + pub fn new(content: T, tx: UnboundedSender>) -> Self + where + T: Into>, + { + SizeTrackingContainer { + container: container(content), + tx, + } + } + + /// Sets the [`Padding`] of the [`Container`]. + pub fn padding>(mut self, padding: P) -> Self { + self.container = self.container.padding(padding); + self + } + + /// Sets the width of the [`Container`]. + pub fn width(mut self, width: Length) -> Self { + self.container = self.container.width(width); + self + } + + /// Sets the height of the [`Container`]. + pub fn height(mut self, height: Length) -> Self { + self.container = self.container.height(height); + self + } + + /// Sets the maximum width of the [`Container`]. + pub fn max_width(mut self, max_width: u32) -> Self { + self.container = self.container.max_width(max_width); + self + } + + /// Sets the maximum height of the [`Container`] in pixels. + pub fn max_height(mut self, max_height: u32) -> Self { + self.container = self.container.max_height(max_height); + self + } + + /// Sets the content alignment for the horizontal axis of the [`Container`]. + pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self { + self.container = self.container.align_x(alignment); + self + } + + /// Sets the content alignment for the vertical axis of the [`Container`]. + pub fn align_y(mut self, alignment: alignment::Vertical) -> Self { + self.container = self.container.align_y(alignment); + self + } + + /// Centers the contents in the horizontal axis of the [`Container`]. + pub fn center_x(mut self) -> Self { + self.container = self.container.center_x(); + self + } + + /// Centers the contents in the vertical axis of the [`Container`]. + pub fn center_y(mut self) -> Self { + self.container = self.container.center_y(); + self + } + + /// Sets the style of the [`Container`]. + pub fn style(mut self, style: impl Into<::Style>) -> Self { + self.container = self.container.style(style); + self + } +} + +impl<'a, Message, Renderer> Widget + for SizeTrackingContainer<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, + Renderer::Theme: container::StyleSheet, +{ + fn children(&self) -> Vec { + self.container.children() + } + + fn diff(&self, tree: &mut Tree) { + self.container.diff(tree) + } + + fn width(&self) -> Length { + Widget::width(&self.container) + } + + fn height(&self) -> Length { + Widget::height(&self.container) + } + + fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { + self.container.layout(renderer, limits) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.container.on_event( + &mut tree.children[0], + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.container.mouse_interaction( + &tree.children[0], + layout, + cursor_position, + viewport, + renderer, + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Renderer::Theme, + inherited_style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + let Rectangle { + x, + y, + width, + height, + } = layout.bounds(); + let _ = self.tx.unbounded_send(Rectangle { + x: x as i32, + y: y as i32, + width: width as i32, + height: height as i32, + }); + self.container.draw( + &tree.children[0], + renderer, + theme, + inherited_style, + layout, + cursor_position, + viewport, + ); + } + + fn overlay<'b>( + &'b self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.container + .overlay(&mut tree.children[0], layout, renderer) + } +} + +pub struct PopupParentSubscription { + id: window::Id, + settings: SctkPopupSettings, +} + +impl PopupParentSubscription { + pub fn new(id: window::Id, settings: SctkPopupSettings) -> Self { + Self { id, settings } + } + + pub fn get_popup_container<'a, T, Message, Renderer>( + &self, + content: T, + tx: UnboundedSender>, + ) -> SizeTrackingContainer<'a, Message, Renderer> + where + T: Into>, + Renderer: iced_native::Renderer, + Renderer::Theme: StyleSheet, + { + SizeTrackingContainer::new(content, tx.clone()) + } + + pub fn subscription(&self) -> iced::Subscription<(window::Id, PositionerUpdate)> { + popup_resize(self.id, self.settings.clone()) + } +} + +pub fn popup_resize( + id: window::Id, + settings: SctkPopupSettings, +) -> iced::Subscription<(window::Id, PositionerUpdate)> { + iced_native::subscription::unfold( + id, + State::Init(settings.positioner.anchor_rect.clone()), + move |state| rectangle_size(id, state), + ) + .with(settings) + .map(|(settings, (id, update))| match update { + RectangleUpdate::Update(rect) => { + let mut new_pos = settings.positioner.clone(); + new_pos.anchor_rect = rect; + (id, PositionerUpdate::Update(new_pos)) + } + RectangleUpdate::Finished => (id, PositionerUpdate::Finished), + RectangleUpdate::Sender(sender) => (id, PositionerUpdate::Sender(sender)), + }) +} + +#[derive(Debug, Clone)] +pub enum PositionerUpdate { + Sender(UnboundedSender>), + Update(SctkPositioner), + Finished, +} + +#[derive(Debug, Clone)] +pub enum RectangleUpdate { + Sender(UnboundedSender>), + Update(Rectangle), + Finished, +} + +pub enum State { + Init(Rectangle), + WaitForUpdate(Rectangle, UnboundedReceiver>), + Finished, +} + +async fn rectangle_size(id: I, state: State) -> (Option<(I, RectangleUpdate)>, State) { + match state { + State::Init(rectangle) => { + let (tx, rx) = unbounded(); + ( + Some((id, RectangleUpdate::Sender(tx))), + State::WaitForUpdate(rectangle, rx), + ) + } + State::WaitForUpdate(old_rectangle, mut rx) => { + let response = rx.next().await; + + match response { + Some(new_rectangle) => { + let new_update = if new_rectangle == old_rectangle { + None + } else { + Some((id, RectangleUpdate::Update(new_rectangle))) + }; + (new_update, State::WaitForUpdate(new_rectangle, rx)) + } + None => (Some((id, RectangleUpdate::Finished)), State::Finished), + } + } + State::Finished => iced::futures::future::pending().await, + } +}