From 379f9d7f0f18d770e92bc1f59059b45d173af0a1 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 16 Nov 2022 16:17:20 +0100 Subject: [PATCH 01/56] feat: Implement client-side window cursor resize support --- src/window/settings.rs | 5 ++ winit/Cargo.toml | 5 +- winit/src/application.rs | 148 +++++++++++++++++++++++++++++++++++++++ winit/src/settings.rs | 4 ++ 4 files changed, 159 insertions(+), 3 deletions(-) diff --git a/src/window/settings.rs b/src/window/settings.rs index 24d0f4f9ec..83e67257ca 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -3,6 +3,9 @@ use crate::window::{Icon, Position}; /// The window settings of an application. #[derive(Debug, Clone)] pub struct Settings { + /// The size of the resize-enabled border. + pub border_size: u32, + /// The initial size of the window. pub size: (u32, u32), @@ -37,6 +40,7 @@ pub struct Settings { impl Default for Settings { fn default() -> Settings { Settings { + border_size: 8, size: (1024, 768), position: Position::default(), min_size: None, @@ -54,6 +58,7 @@ impl Default for Settings { impl From for iced_winit::settings::Window { fn from(settings: Settings) -> Self { Self { + border_size: settings.border_size, size: settings.size, position: iced_winit::Position::from(settings.position), min_size: settings.min_size, diff --git a/winit/Cargo.toml b/winit/Cargo.toml index ebbadb12dc..5afb75e3cb 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -21,9 +21,8 @@ log = "0.4" thiserror = "1.0" [dependencies.winit] -version = "0.27" -git = "https://github.com/iced-rs/winit.git" -rev = "940457522e9fb9f5dac228b0ecfafe0138b4048c" +git = "https://github.com/pop-os/winit.git" +branch = "iced" [dependencies.iced_native] version = "0.7" diff --git a/winit/src/application.rs b/winit/src/application.rs index 0f9b562e67..4d2fba3ac9 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -23,6 +23,7 @@ use iced_native::user_interface::{self, UserInterface}; pub use iced_native::application::{Appearance, StyleSheet}; use std::mem::ManuallyDrop; +use winit::window::{CursorIcon, ResizeDirection}; /// An interactive, native cross-platform application. /// @@ -114,6 +115,9 @@ where let mut debug = Debug::new(); debug.startup_started(); + // Defines width of the window border for the resize handles. + let border_size = settings.window.border_size; + let event_loop = EventLoopBuilder::with_user_event().build(); let proxy = event_loop.create_proxy(); @@ -186,6 +190,7 @@ where init_command, window, settings.exit_on_close_request, + border_size, )); let mut context = task::Context::from_waker(task::noop_waker_ref()); @@ -236,6 +241,7 @@ async fn run_instance( init_command: Command, window: winit::window::Window, exit_on_close_request: bool, + border_size: u32, ) where A: Application + 'static, E: Executor + 'static, @@ -285,6 +291,11 @@ async fn run_instance( &mut debug, )); + let mut cursor_resize_event = cursor_resize_event_func( + &window, + border_size as f64 * window.scale_factor(), + ); + let mut mouse_interaction = mouse::Interaction::default(); let mut events = Vec::new(); let mut messages = Vec::new(); @@ -469,6 +480,12 @@ async fn run_instance( event: window_event, .. } => { + if let Some(func) = cursor_resize_event.as_mut() { + if func(&window, &window_event) { + continue; + } + } + if requests_exit(&window_event, state.modifiers()) && exit_on_close_request { @@ -769,3 +786,134 @@ mod platform { event_loop.run(event_handler) } } + +/// If supported by winit, returns a closure that implements cursor resize support. +fn cursor_resize_event_func( + window: &winit::window::Window, + border_size: f64, +) -> Option< + impl FnMut(&winit::window::Window, &winit::event::WindowEvent<'_>) -> bool, +> { + if window.drag_resize_window(ResizeDirection::East).is_ok() { + // Keep track of cursor when it is within a resizeable border. + let mut cursor_prev_resize_direction = None; + + Some( + move |window: &winit::window::Window, + window_event: &winit::event::WindowEvent<'_>| + -> bool { + // Keep track of border resize state and set cursor icon when in range + match window_event { + winit::event::WindowEvent::CursorMoved { + position, .. + } => { + if !window.is_decorated() { + let location = cursor_resize_direction( + window.inner_size(), + *position, + border_size, + ); + if location != cursor_prev_resize_direction { + window.set_cursor_icon( + resize_direction_cursor_icon(location), + ); + cursor_prev_resize_direction = location; + return true; + } + } + } + winit::event::WindowEvent::MouseInput { + state: winit::event::ElementState::Pressed, + button: winit::event::MouseButton::Left, + .. + } => { + if let Some(direction) = cursor_prev_resize_direction { + let _res = window.drag_resize_window(direction); + return true; + } + } + _ => (), + } + + false + }, + ) + } else { + None + } +} + +/// Get the cursor icon that corresponds to the resize direction. +fn resize_direction_cursor_icon( + resize_direction: Option, +) -> CursorIcon { + match resize_direction { + Some(resize_direction) => match resize_direction { + ResizeDirection::East => CursorIcon::EResize, + ResizeDirection::North => CursorIcon::NResize, + ResizeDirection::NorthEast => CursorIcon::NeResize, + ResizeDirection::NorthWest => CursorIcon::NwResize, + ResizeDirection::South => CursorIcon::SResize, + ResizeDirection::SouthEast => CursorIcon::SeResize, + ResizeDirection::SouthWest => CursorIcon::SwResize, + ResizeDirection::West => CursorIcon::WResize, + }, + None => CursorIcon::Default, + } +} + +/// Identifies resize direction based on cursor position and window dimensions. +#[allow(clippy::similar_names)] +fn cursor_resize_direction( + win_size: winit::dpi::PhysicalSize, + position: winit::dpi::PhysicalPosition, + border_size: f64, +) -> Option { + enum XDirection { + West, + East, + Default, + } + + enum YDirection { + North, + South, + Default, + } + + let xdir = if position.x < border_size { + XDirection::West + } else if position.x > (win_size.width as f64 - border_size) { + XDirection::East + } else { + XDirection::Default + }; + + let ydir = if position.y < border_size { + YDirection::North + } else if position.y > (win_size.height as f64 - border_size) { + YDirection::South + } else { + YDirection::Default + }; + + Some(match xdir { + XDirection::West => match ydir { + YDirection::North => ResizeDirection::NorthWest, + YDirection::South => ResizeDirection::SouthWest, + YDirection::Default => ResizeDirection::West, + }, + + XDirection::East => match ydir { + YDirection::North => ResizeDirection::NorthEast, + YDirection::South => ResizeDirection::SouthEast, + YDirection::Default => ResizeDirection::East, + }, + + XDirection::Default => match ydir { + YDirection::North => ResizeDirection::North, + YDirection::South => ResizeDirection::South, + YDirection::Default => return None, + }, + }) +} diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 9bbdef5cea..d925b4fba8 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -61,6 +61,9 @@ pub struct Settings { /// The window settings of an application. #[derive(Debug, Clone)] pub struct Window { + /// The size of the resize-enabled border. + pub border_size: u32, + /// The size of the window. pub size: (u32, u32), @@ -183,6 +186,7 @@ impl Window { impl Default for Window { fn default() -> Window { Window { + border_size: 8, size: (1024, 768), position: Position::default(), min_size: None, From 3d5d2f4552c58369ba64d489fcf8c7dc3ebaf887 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 12 Dec 2022 01:53:45 +0100 Subject: [PATCH 02/56] feat(native): Add MouseListener widget --- native/src/widget.rs | 3 + native/src/widget/helpers.rs | 10 + native/src/widget/mouse_listener.rs | 395 ++++++++++++++++++++++++++++ src/widget.rs | 8 + 4 files changed, 416 insertions(+) create mode 100644 native/src/widget/mouse_listener.rs diff --git a/native/src/widget.rs b/native/src/widget.rs index efe26fc78e..ea9a0574d5 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -17,6 +17,7 @@ pub mod column; pub mod container; pub mod helpers; pub mod image; +pub mod mouse_listener; pub mod operation; pub mod pane_grid; pub mod pick_list; @@ -51,6 +52,8 @@ pub use helpers::*; #[doc(no_inline)] pub use image::Image; #[doc(no_inline)] +pub use mouse_listener::MouseListener; +#[doc(no_inline)] pub use pane_grid::PaneGrid; #[doc(no_inline)] pub use pick_list::PickList; diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs index 8cc1ae8205..72ba093c3d 100644 --- a/native/src/widget/helpers.rs +++ b/native/src/widget/helpers.rs @@ -311,3 +311,13 @@ where { widget::Svg::new(handle) } + +/// A container intercepting mouse events. +pub fn mouse_listener<'a, Message, Renderer>( + widget: impl Into>, +) -> widget::MouseListener<'a, Message, Renderer> +where + Renderer: crate::Renderer, +{ + widget::MouseListener::new(widget) +} diff --git a/native/src/widget/mouse_listener.rs b/native/src/widget/mouse_listener.rs new file mode 100644 index 0000000000..78f416fc50 --- /dev/null +++ b/native/src/widget/mouse_listener.rs @@ -0,0 +1,395 @@ +//! A container for capturing mouse events. + +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; +use crate::overlay; +use crate::renderer; +use crate::touch; +use crate::widget::{tree, Operation, Tree}; +use crate::{ + Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Widget, +}; + +use std::u32; + +/// Emit messages on mouse events. +#[allow(missing_debug_implementations)] +pub struct MouseListener<'a, Message, Renderer> { + content: Element<'a, Message, Renderer>, + + /// Sets the message to emit on a left mouse button press. + on_press: Option, + + /// Sets the message to emit on a left mouse button release. + on_release: Option, + + /// Sets the message to emit on a right mouse button press. + on_right_press: Option, + + /// Sets the message to emit on a right mouse button release. + on_right_release: Option, + + /// Sets the message to emit on a middle mouse button press. + on_middle_press: Option, + + /// Sets the message to emit on a middle mouse button release. + on_middle_release: Option, + + /// Sets the message to emit when the mouse enters the widget. + on_mouse_enter: Option, + + /// Sets the messsage to emit when the mouse exits the widget. + on_mouse_exit: Option, +} + +impl<'a, Message, Renderer> MouseListener<'a, Message, Renderer> { + /// The message to emit on a left button press. + #[must_use] + pub fn on_press(mut self, message: Message) -> Self { + self.on_press = Some(message); + self + } + + /// The message to emit on a left button release. + #[must_use] + pub fn on_release(mut self, message: Message) -> Self { + self.on_release = Some(message); + self + } + + /// The message to emit on a right button press. + #[must_use] + pub fn on_right_press(mut self, message: Message) -> Self { + self.on_right_press = Some(message); + self + } + + /// The message to emit on a right button release. + #[must_use] + pub fn on_right_release(mut self, message: Message) -> Self { + self.on_right_release = Some(message); + self + } + + /// The message to emit on a middle button press. + #[must_use] + pub fn on_middle_press(mut self, message: Message) -> Self { + self.on_middle_press = Some(message); + self + } + + /// The message to emit on a middle button release. + #[must_use] + pub fn on_middle_release(mut self, message: Message) -> Self { + self.on_middle_release = Some(message); + self + } + + /// The message to emit when the mouse enters the widget. + #[must_use] + pub fn on_mouse_enter(mut self, message: Message) -> Self { + self.on_mouse_enter = Some(message); + self + } + + /// The messsage to emit when the mouse exits the widget. + #[must_use] + pub fn on_mouse_exit(mut self, message: Message) -> Self { + self.on_mouse_exit = Some(message); + self + } +} + +/// Local state of the [`MouseListener`]. +#[derive(Default)] +struct State { + hovered: bool, +} + +impl<'a, Message, Renderer> MouseListener<'a, Message, Renderer> { + /// Creates an empty [`MouseListener`]. + pub fn new(content: impl Into>) -> Self { + MouseListener { + content: content.into(), + on_press: None, + on_release: None, + on_right_press: None, + on_right_release: None, + on_middle_press: None, + on_middle_release: None, + on_mouse_enter: None, + on_mouse_exit: None, + } + } +} + +impl<'a, Message, Renderer> Widget + for MouseListener<'a, Message, Renderer> +where + Renderer: crate::Renderer, + Message: Clone, +{ + fn children(&self) -> Vec { + vec![Tree::new(&self.content)] + } + + fn diff(&self, tree: &mut Tree) { + tree.diff_children(std::slice::from_ref(&self.content)); + } + + fn width(&self) -> Length { + self.content.as_widget().width() + } + + fn height(&self) -> Length { + self.content.as_widget().height() + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + layout( + renderer, + limits, + Widget::::width(self), + Widget::::height(self), + u32::MAX, + |renderer, limits| { + self.content.as_widget().layout(renderer, limits) + }, + ) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn Operation, + ) { + operation.container(None, &mut |operation| { + self.content.as_widget().operate( + &mut tree.children[0], + layout.children().next().unwrap(), + operation, + ); + }); + } + + 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 { + if let event::Status::Captured = self.content.as_widget_mut().on_event( + &mut tree.children[0], + event.clone(), + layout.children().next().unwrap(), + cursor_position, + renderer, + clipboard, + shell, + ) { + return event::Status::Captured; + } + + update( + self, + &event, + layout, + cursor_position, + shell, + tree.state.downcast_mut::(), + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.content.as_widget().mouse_interaction( + &tree.children[0], + layout.children().next().unwrap(), + cursor_position, + viewport, + renderer, + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Renderer::Theme, + renderer_style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + self.content.as_widget().draw( + &tree.children[0], + renderer, + theme, + renderer_style, + layout.children().next().unwrap(), + cursor_position, + viewport, + ); + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.content.as_widget_mut().overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + ) + } + + fn state(&self) -> tree::State { + tree::State::new(State::default()) + } +} + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Message: 'a + Clone, + Renderer: 'a + crate::Renderer, +{ + fn from( + listener: MouseListener<'a, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { + Element::new(listener) + } +} + +/// Processes the given [`Event`] and updates the [`State`] of an [`MouseListener`] +/// accordingly. +fn update( + widget: &mut MouseListener<'_, Message, Renderer>, + event: &Event, + layout: Layout<'_>, + cursor_position: Point, + shell: &mut Shell<'_, Message>, + state: &mut State, +) -> event::Status { + let hovered = state.hovered; + + if !layout.bounds().contains(cursor_position) { + if hovered { + state.hovered = false; + if let Some(message) = widget.on_mouse_exit.clone() { + shell.publish(message); + return event::Status::Captured; + } + } + + return event::Status::Ignored; + } + + state.hovered = true; + + if !hovered { + if let Some(message) = widget.on_mouse_enter.clone() { + shell.publish(message); + return event::Status::Captured; + } + } + + if let Some(message) = widget.on_press.clone() { + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) = event + { + shell.publish(message); + return event::Status::Captured; + } + } + + if let Some(message) = widget.on_release.clone() { + if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) = event + { + shell.publish(message); + return event::Status::Captured; + } + } + + if let Some(message) = widget.on_right_press.clone() { + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) = + event + { + shell.publish(message); + return event::Status::Captured; + } + } + + if let Some(message) = widget.on_right_release.clone() { + if let Event::Mouse(mouse::Event::ButtonReleased( + mouse::Button::Right, + )) = event + { + shell.publish(message); + return event::Status::Captured; + } + } + + if let Some(message) = widget.on_middle_press.clone() { + if let Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Middle, + )) = event + { + shell.publish(message); + return event::Status::Captured; + } + } + + if let Some(message) = widget.on_middle_release.clone() { + if let Event::Mouse(mouse::Event::ButtonReleased( + mouse::Button::Middle, + )) = event + { + shell.publish(message); + return event::Status::Captured; + } + } + + event::Status::Ignored +} + +/// Computes the layout of a [`MouseListener`]. +pub fn layout( + renderer: &Renderer, + limits: &layout::Limits, + width: Length, + height: Length, + max_height: u32, + layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, +) -> layout::Node { + let limits = limits.max_height(max_height).width(width).height(height); + + let child_limits = layout::Limits::new( + Size::new(limits.min().width, 0.0), + Size::new(limits.max().width, f32::INFINITY), + ); + + let content = layout_content(renderer, &child_limits); + let size = limits.resolve(content.size()); + + layout::Node::with_children(size, vec![content]) +} diff --git a/src/widget.rs b/src/widget.rs index 76cea7be6c..4a7d353908 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -47,6 +47,14 @@ pub mod container { iced_native::widget::Container<'a, Message, Renderer>; } +pub mod mouse_listener { + //! Intercept mouse events on a widget. + + /// A container intercepting mouse events. + pub type MouseListener<'a, Message, Renderer = crate::Renderer> = + iced_native::widget::MouseListener<'a, Message, Renderer>; +} + pub mod pane_grid { //! Let your users split regions of your application and organize layout dynamically. //! From c2b96949faa4fba513a6e0dea59fb13d5becea90 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 12 Dec 2022 01:54:09 +0100 Subject: [PATCH 03/56] feat(style): Add Container::custom_fn method --- style/src/theme.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/style/src/theme.rs b/style/src/theme.rs index 271d9a29f2..139addb44d 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -351,6 +351,13 @@ pub enum Container { Custom(Box>), } +impl Container { + /// Creates a custom [`Container`] style. + pub fn custom_fn(f: fn(&Theme) -> container::Appearance) -> Self { + Self::Custom(Box::new(f)) + } +} + impl From container::Appearance> for Container { fn from(f: fn(&Theme) -> container::Appearance) -> Self { Self::Custom(Box::new(f)) @@ -362,7 +369,7 @@ impl container::StyleSheet for Theme { fn appearance(&self, style: &Self::Style) -> container::Appearance { match style { - Container::Transparent => Default::default(), + Container::Transparent => container::Appearance::default(), Container::Box => { let palette = self.extended_palette(); From 7b835f1f766d9479299164fa6162061dca1cf1de Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 14 Dec 2022 16:16:38 +0100 Subject: [PATCH 04/56] feat: sctk integration with iced --- Cargo.toml | 9 +- examples/clock_sctk_layer_surface/Cargo.toml | 12 + examples/clock_sctk_layer_surface/README.md | 16 + examples/clock_sctk_layer_surface/src/main.rs | 197 +++ examples/clock_sctk_window/Cargo.toml | 12 + examples/clock_sctk_window/README.md | 16 + examples/clock_sctk_window/src/main.rs | 237 +++ examples/todos_sctk/Cargo.toml | 28 + examples/todos_sctk/README.md | 20 + examples/todos_sctk/fonts/icons.ttf | Bin 0 -> 5596 bytes examples/todos_sctk/iced-todos.desktop | 4 + examples/todos_sctk/index.html | 12 + examples/todos_sctk/src/main.rs | 610 ++++++++ glutin/src/application.rs | 2 + native/Cargo.toml | 3 + native/src/command.rs | 2 + native/src/command/action.rs | 18 +- native/src/command/platform_specific/mod.rs | 46 + .../wayland/layer_surface.rs | 229 +++ .../command/platform_specific/wayland/mod.rs | 51 + .../platform_specific/wayland/popup.rs | 183 +++ .../platform_specific/wayland/window.rs | 253 ++++ native/src/event.rs | 7 +- native/src/event/wayland/layer.rs | 12 + native/src/event/wayland/mod.rs | 31 + native/src/event/wayland/output.rs | 24 + native/src/event/wayland/popup.rs | 23 + native/src/event/wayland/seat.rs | 11 + native/src/event/wayland/window.rs | 22 + native/src/window.rs | 8 + native/src/window/action.rs | 13 +- native/src/window/icon.rs | 12 + native/src/window/id.rs | 19 + native/src/window/position.rs | 22 + native/src/window/settings.rs | 52 + sctk/Cargo.toml | 38 + sctk/LICENSE.md | 359 +++++ sctk/src/application.rs | 1299 +++++++++++++++++ sctk/src/commands/data_device.rs | 1 + sctk/src/commands/layer_surface.rs | 125 ++ sctk/src/commands/mod.rs | 6 + sctk/src/commands/popup.rs | 57 + sctk/src/commands/window.rs | 62 + sctk/src/conversion.rs | 253 ++++ sctk/src/dpi.rs | 613 ++++++++ sctk/src/egl.rs | 96 ++ sctk/src/error.rs | 23 + sctk/src/event_loop/control_flow.rs | 56 + sctk/src/event_loop/mod.rs | 826 +++++++++++ sctk/src/event_loop/proxy.rs | 66 + sctk/src/event_loop/state.rs | 497 +++++++ sctk/src/handlers/compositor.rs | 65 + sctk/src/handlers/data_device/data_device.rs | 0 sctk/src/handlers/data_device/data_offer.rs | 0 sctk/src/handlers/data_device/data_source.rs | 0 sctk/src/handlers/data_device/mod.rs | 1 + sctk/src/handlers/mod.rs | 37 + sctk/src/handlers/output.rs | 48 + sctk/src/handlers/seat/keyboard.rs | 200 +++ sctk/src/handlers/seat/mod.rs | 5 + sctk/src/handlers/seat/pointer.rs | 59 + sctk/src/handlers/seat/seat.rs | 172 +++ sctk/src/handlers/seat/touch.rs | 1 + sctk/src/handlers/shell/layer.rs | 113 ++ sctk/src/handlers/shell/mod.rs | 3 + sctk/src/handlers/shell/xdg_popup.rs | 89 ++ sctk/src/handlers/shell/xdg_window.rs | 73 + sctk/src/lib.rs | 24 + sctk/src/result.rs | 6 + sctk/src/sctk_event.rs | 618 ++++++++ sctk/src/settings.rs | 31 + sctk/src/util.rs | 128 ++ sctk/src/widget.rs | 232 +++ sctk/src/window.rs | 3 + src/clipboard.rs | 2 +- src/error.rs | 18 + src/lib.rs | 48 +- src/settings.rs | 26 + src/wayland/mod.rs | 207 +++ src/wayland/sandbox.rs | 229 +++ src/window.rs | 9 +- src/window/position.rs | 1 + src/window/settings.rs | 7 +- winit/src/application.rs | 10 +- winit/src/conversion.rs | 46 +- winit/src/window.rs | 57 +- 86 files changed, 9101 insertions(+), 60 deletions(-) create mode 100644 examples/clock_sctk_layer_surface/Cargo.toml create mode 100644 examples/clock_sctk_layer_surface/README.md create mode 100644 examples/clock_sctk_layer_surface/src/main.rs create mode 100644 examples/clock_sctk_window/Cargo.toml create mode 100644 examples/clock_sctk_window/README.md create mode 100644 examples/clock_sctk_window/src/main.rs create mode 100644 examples/todos_sctk/Cargo.toml create mode 100644 examples/todos_sctk/README.md create mode 100644 examples/todos_sctk/fonts/icons.ttf create mode 100644 examples/todos_sctk/iced-todos.desktop create mode 100644 examples/todos_sctk/index.html create mode 100644 examples/todos_sctk/src/main.rs create mode 100644 native/src/command/platform_specific/mod.rs create mode 100644 native/src/command/platform_specific/wayland/layer_surface.rs create mode 100644 native/src/command/platform_specific/wayland/mod.rs create mode 100644 native/src/command/platform_specific/wayland/popup.rs create mode 100644 native/src/command/platform_specific/wayland/window.rs create mode 100644 native/src/event/wayland/layer.rs create mode 100644 native/src/event/wayland/mod.rs create mode 100644 native/src/event/wayland/output.rs create mode 100644 native/src/event/wayland/popup.rs create mode 100644 native/src/event/wayland/seat.rs create mode 100644 native/src/event/wayland/window.rs create mode 100644 native/src/window/icon.rs create mode 100644 native/src/window/id.rs create mode 100644 native/src/window/position.rs create mode 100644 native/src/window/settings.rs create mode 100644 sctk/Cargo.toml create mode 100644 sctk/LICENSE.md create mode 100644 sctk/src/application.rs create mode 100644 sctk/src/commands/data_device.rs create mode 100644 sctk/src/commands/layer_surface.rs create mode 100644 sctk/src/commands/mod.rs create mode 100644 sctk/src/commands/popup.rs create mode 100644 sctk/src/commands/window.rs create mode 100644 sctk/src/conversion.rs create mode 100644 sctk/src/dpi.rs create mode 100644 sctk/src/egl.rs create mode 100644 sctk/src/error.rs create mode 100644 sctk/src/event_loop/control_flow.rs create mode 100644 sctk/src/event_loop/mod.rs create mode 100644 sctk/src/event_loop/proxy.rs create mode 100644 sctk/src/event_loop/state.rs create mode 100644 sctk/src/handlers/compositor.rs create mode 100644 sctk/src/handlers/data_device/data_device.rs create mode 100644 sctk/src/handlers/data_device/data_offer.rs create mode 100644 sctk/src/handlers/data_device/data_source.rs create mode 100644 sctk/src/handlers/data_device/mod.rs create mode 100644 sctk/src/handlers/mod.rs create mode 100644 sctk/src/handlers/output.rs create mode 100644 sctk/src/handlers/seat/keyboard.rs create mode 100644 sctk/src/handlers/seat/mod.rs create mode 100644 sctk/src/handlers/seat/pointer.rs create mode 100644 sctk/src/handlers/seat/seat.rs create mode 100644 sctk/src/handlers/seat/touch.rs create mode 100644 sctk/src/handlers/shell/layer.rs create mode 100644 sctk/src/handlers/shell/mod.rs create mode 100644 sctk/src/handlers/shell/xdg_popup.rs create mode 100644 sctk/src/handlers/shell/xdg_window.rs create mode 100644 sctk/src/lib.rs create mode 100644 sctk/src/result.rs create mode 100644 sctk/src/sctk_event.rs create mode 100644 sctk/src/settings.rs create mode 100644 sctk/src/util.rs create mode 100644 sctk/src/widget.rs create mode 100644 sctk/src/window.rs create mode 100644 src/wayland/mod.rs create mode 100644 src/wayland/sandbox.rs diff --git a/Cargo.toml b/Cargo.toml index 9c45b2f588..194ddb8277 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] -default = ["wgpu"] +default = ["wgpu", "winit"] # Enables the `Image` widget image = ["iced_wgpu?/image", "iced_glow?/image", "image_rs"] # Enables the `Svg` widget @@ -39,6 +39,9 @@ smol = ["iced_futures/smol"] palette = ["iced_core/palette"] # Enables querying system information system = ["iced_winit/system"] +# Enables wayland shell +wayland = ["iced_sctk", "iced_glow", "iced_native/sctk"] +winit = ["iced_winit"] [badges] maintenance = { status = "actively-developed" } @@ -55,6 +58,7 @@ members = [ "style", "wgpu", "winit", + "sctk", "examples/*", ] @@ -63,9 +67,10 @@ iced_core = { version = "0.6", path = "core" } iced_futures = { version = "0.5", path = "futures" } iced_native = { version = "0.7", path = "native" } iced_graphics = { version = "0.5", path = "graphics" } -iced_winit = { version = "0.6", path = "winit", features = ["application"] } +iced_winit = { version = "0.6", path = "winit", features = ["application"], optional = true } iced_glutin = { version = "0.5", path = "glutin", optional = true } iced_glow = { version = "0.5", path = "glow", optional = true } +iced_sctk = { path = "./sctk", optional = true } thiserror = "1.0" [dependencies.image_rs] diff --git a/examples/clock_sctk_layer_surface/Cargo.toml b/examples/clock_sctk_layer_surface/Cargo.toml new file mode 100644 index 0000000000..04e10eb561 --- /dev/null +++ b/examples/clock_sctk_layer_surface/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "clock_sctk_layer_surface" +version = "0.1.0" +authors = ["Ashley Wulber "] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", default-features = false, features = ["canvas", "tokio", "debug", "wayland"] } +time = { version = "0.3.5", features = ["local-offset"] } +iced_native = { path = "../../native" } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "f1d9c3e" } diff --git a/examples/clock_sctk_layer_surface/README.md b/examples/clock_sctk_layer_surface/README.md new file mode 100644 index 0000000000..175091805b --- /dev/null +++ b/examples/clock_sctk_layer_surface/README.md @@ -0,0 +1,16 @@ +## Clock + +An application that uses the `Canvas` widget to draw a clock and its hands to display the current time. + +The __[`main`]__ file contains all the code of the example. + +
+ +
+ +You can run it with `cargo run`: +``` +cargo run --package clock +``` + +[`main`]: src/main.rs diff --git a/examples/clock_sctk_layer_surface/src/main.rs b/examples/clock_sctk_layer_surface/src/main.rs new file mode 100644 index 0000000000..23f7df127c --- /dev/null +++ b/examples/clock_sctk_layer_surface/src/main.rs @@ -0,0 +1,197 @@ +use iced::executor; +use iced::wayland::layer_surface::{get_layer_surface, set_size}; +use iced::wayland::SurfaceIdWrapper; +use iced::widget::canvas::{ + stroke, Cache, Cursor, Geometry, LineCap, Path, Stroke, +}; +use iced::widget::{canvas, container}; +use iced::{ + sctk_settings::InitialSurface, Application, Color, Command, Element, + Length, Point, Rectangle, Settings, Subscription, Theme, Vector, +}; +use iced_native::command::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings; +use iced_native::window::Id; +use sctk::shell::layer::Anchor; + +pub fn main() -> iced::Result { + Clock::run(Settings { + antialiasing: true, + initial_surface: InitialSurface::LayerSurface( + SctkLayerSurfaceSettings { + size: (None, Some(200)), + anchor: Anchor::LEFT.union(Anchor::RIGHT).union(Anchor::TOP), + exclusive_zone: 200, + ..Default::default() + }, + ), + ..Settings::default() + }) +} + +struct Clock { + now: time::OffsetDateTime, + clock: Cache, + count: u32, + to_destroy: Id, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + Tick(time::OffsetDateTime), +} + +impl Application for Clock { + type Message = Message; + type Theme = Theme; + type Executor = executor::Default; + type Flags = (); + + fn new(_flags: ()) -> (Self, Command) { + let to_destroy = Id::new(10); + ( + Clock { + now: time::OffsetDateTime::now_local() + .unwrap_or_else(|_| time::OffsetDateTime::now_utc()), + clock: Default::default(), + count: 0, + to_destroy, + }, + get_layer_surface(SctkLayerSurfaceSettings { + // XXX id must be unique! + id: to_destroy, + size: (None, Some(100)), + anchor: Anchor::LEFT.union(Anchor::RIGHT).union(Anchor::BOTTOM), + exclusive_zone: 100, + ..Default::default() + }), + ) + } + + fn title(&self) -> String { + String::from("Clock - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Tick(local_time) => { + let now = local_time; + + if now != self.now { + self.now = now; + self.clock.clear(); + } + // destroy the second layer surface after counting to 10. + self.count += 1; + if self.count == 10 { + println!("time to remove the bottom clock!"); + return set_size::( + self.to_destroy, + None, + Some(200), + ); + } + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription { + iced::time::every(std::time::Duration::from_millis(500)).map(|_| { + Message::Tick( + time::OffsetDateTime::now_local() + .unwrap_or_else(|_| time::OffsetDateTime::now_utc()), + ) + }) + } + + fn view( + &self, + _id: SurfaceIdWrapper, + ) -> Element<'_, Self::Message, iced::Renderer> { + let canvas = canvas(self as &Self) + .width(Length::Fill) + .height(Length::Fill); + + container(canvas) + .width(Length::Fill) + .height(Length::Fill) + .padding(20) + .into() + } + + fn close_requested(&self, _id: SurfaceIdWrapper) -> Self::Message { + unimplemented!() + } +} + +impl canvas::Program for Clock { + type State = (); + + fn draw( + &self, + _state: &Self::State, + _theme: &Theme, + bounds: Rectangle, + _cursor: Cursor, + ) -> Vec { + let clock = self.clock.draw(bounds.size(), |frame| { + let center = frame.center(); + let radius = frame.width().min(frame.height()) / 2.0; + + let background = Path::circle(center, radius); + frame.fill(&background, Color::from_rgb8(0x12, 0x93, 0xD8)); + + let short_hand = + Path::line(Point::ORIGIN, Point::new(0.0, -0.5 * radius)); + + let long_hand = + Path::line(Point::ORIGIN, Point::new(0.0, -0.8 * radius)); + + let width = radius / 100.0; + + let thin_stroke = || -> Stroke { + Stroke { + width, + style: stroke::Style::Solid(Color::WHITE), + line_cap: LineCap::Round, + ..Stroke::default() + } + }; + + let wide_stroke = || -> Stroke { + Stroke { + width: width * 3.0, + style: stroke::Style::Solid(Color::WHITE), + line_cap: LineCap::Round, + ..Stroke::default() + } + }; + + frame.translate(Vector::new(center.x, center.y)); + + frame.with_save(|frame| { + frame.rotate(hand_rotation(self.now.hour(), 12)); + frame.stroke(&short_hand, wide_stroke()); + }); + + frame.with_save(|frame| { + frame.rotate(hand_rotation(self.now.minute(), 60)); + frame.stroke(&long_hand, wide_stroke()); + }); + + frame.with_save(|frame| { + frame.rotate(hand_rotation(self.now.second(), 60)); + frame.stroke(&long_hand, thin_stroke()); + }) + }); + + vec![clock] + } +} + +fn hand_rotation(n: u8, total: u8) -> f32 { + let turns = n as f32 / total as f32; + + 2.0 * std::f32::consts::PI * turns +} diff --git a/examples/clock_sctk_window/Cargo.toml b/examples/clock_sctk_window/Cargo.toml new file mode 100644 index 0000000000..21ff84a05d --- /dev/null +++ b/examples/clock_sctk_window/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "clock_sctk_window" +version = "0.1.0" +authors = ["Ashley Wulber "] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", default-features = false, features = ["canvas", "tokio", "debug", "wayland"] } +time = { version = "0.3.5", features = ["local-offset"] } +iced_native = { path = "../../native" } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "f1d9c3e" } diff --git a/examples/clock_sctk_window/README.md b/examples/clock_sctk_window/README.md new file mode 100644 index 0000000000..175091805b --- /dev/null +++ b/examples/clock_sctk_window/README.md @@ -0,0 +1,16 @@ +## Clock + +An application that uses the `Canvas` widget to draw a clock and its hands to display the current time. + +The __[`main`]__ file contains all the code of the example. + +
+ +
+ +You can run it with `cargo run`: +``` +cargo run --package clock +``` + +[`main`]: src/main.rs diff --git a/examples/clock_sctk_window/src/main.rs b/examples/clock_sctk_window/src/main.rs new file mode 100644 index 0000000000..696ef78a23 --- /dev/null +++ b/examples/clock_sctk_window/src/main.rs @@ -0,0 +1,237 @@ +use iced::executor; +use iced::wayland::{ + popup::{destroy_popup, get_popup}, + window::{close_window, get_window}, + SurfaceIdWrapper, +}; +use iced::widget::canvas::{ + stroke, Cache, Cursor, Geometry, LineCap, Path, Stroke, +}; +use iced::widget::{button, canvas, column, container}; +use iced::{ + sctk_settings::InitialSurface, Application, Color, Command, Element, + Length, Point, Rectangle, Settings, Subscription, Theme, Vector, +}; +use iced_native::command::platform_specific::wayland::popup::{ + SctkPopupSettings, SctkPositioner, +}; +use iced_native::command::platform_specific::wayland::window::SctkWindowSettings; +use iced_native::window::{self, Id}; + +pub fn main() -> iced::Result { + Clock::run(Settings { + antialiasing: true, + initial_surface: InitialSurface::XdgWindow( + SctkWindowSettings::default(), + ), + ..Settings::default() + }) +} + +struct Clock { + now: time::OffsetDateTime, + clock: Cache, + count: u32, + to_destroy: Id, + id_ctr: u32, + popup: Option, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + Tick(time::OffsetDateTime), + Click(window::Id), + SurfaceClosed, +} + +impl Application for Clock { + type Message = Message; + type Theme = Theme; + type Executor = executor::Default; + type Flags = (); + + fn new(_flags: ()) -> (Self, Command) { + let to_destroy = Id::new(1); + ( + Clock { + now: time::OffsetDateTime::now_local() + .unwrap_or_else(|_| time::OffsetDateTime::now_utc()), + clock: Default::default(), + count: 0, + popup: None, + to_destroy, + id_ctr: 2, + }, + get_window(SctkWindowSettings { + window_id: to_destroy, + ..Default::default() + }), + ) + } + + fn title(&self) -> String { + String::from("Clock - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Tick(local_time) => { + let now = local_time; + + if now != self.now { + self.now = now; + self.clock.clear(); + } + // destroy the second window after counting to 10. + self.count += 1; + if self.count == 10 { + println!("time to remove the bottom clock!"); + return close_window::(self.to_destroy); + // return close_window(self.to_destroy); + } + } + Message::Click(parent_id) => { + if let Some(p) = self.popup.take() { + return destroy_popup(p); + } else { + self.id_ctr += 1; + let new_id = window::Id::new(self.id_ctr); + self.popup.replace(new_id); + return get_popup(SctkPopupSettings { + parent: parent_id, + id: new_id, + positioner: SctkPositioner { + anchor_rect: Rectangle { + x: 100, + y: 100, + width: 160, + height: 260, + }, + ..Default::default() + }, + parent_size: None, + grab: true, + }); + } + } + Message::SurfaceClosed => { + // ignored + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription { + iced::time::every(std::time::Duration::from_millis(500)).map(|_| { + Message::Tick( + time::OffsetDateTime::now_local() + .unwrap_or_else(|_| time::OffsetDateTime::now_utc()), + ) + }) + } + + fn view( + &self, + id: SurfaceIdWrapper, + ) -> Element<'_, Self::Message, iced::Renderer> { + match id { + SurfaceIdWrapper::LayerSurface(_) => unimplemented!(), + SurfaceIdWrapper::Window(_) => { + let canvas = canvas(self as &Self) + .width(Length::Fill) + .height(Length::Fill); + + container(column![ + button("Popup").on_press(Message::Click(id.inner())), + canvas, + ]) + .width(Length::Fill) + .height(Length::Fill) + .padding(20) + .into() + } + SurfaceIdWrapper::Popup(_) => button("Hi!, I'm a popup!") + .on_press(Message::Click(id.inner())) + .width(Length::Fill) + .height(Length::Fill) + .padding(20) + .into(), + } + } + + fn close_requested(&self, id: SurfaceIdWrapper) -> Self::Message { + Message::SurfaceClosed + } +} + +impl canvas::Program for Clock { + type State = (); + + fn draw( + &self, + _state: &Self::State, + _theme: &Theme, + bounds: Rectangle, + _cursor: Cursor, + ) -> Vec { + let clock = self.clock.draw(bounds.size(), |frame| { + let center = frame.center(); + let radius = frame.width().min(frame.height()) / 2.0; + + let background = Path::circle(center, radius); + frame.fill(&background, Color::from_rgb8(0x12, 0x93, 0xD8)); + + let short_hand = + Path::line(Point::ORIGIN, Point::new(0.0, -0.5 * radius)); + + let long_hand = + Path::line(Point::ORIGIN, Point::new(0.0, -0.8 * radius)); + + let width = radius / 100.0; + + let thin_stroke = || -> Stroke { + Stroke { + width, + style: stroke::Style::Solid(Color::WHITE), + line_cap: LineCap::Round, + ..Stroke::default() + } + }; + + let wide_stroke = || -> Stroke { + Stroke { + width: width * 3.0, + style: stroke::Style::Solid(Color::WHITE), + line_cap: LineCap::Round, + ..Stroke::default() + } + }; + + frame.translate(Vector::new(center.x, center.y)); + + frame.with_save(|frame| { + frame.rotate(hand_rotation(self.now.hour(), 12)); + frame.stroke(&short_hand, wide_stroke()); + }); + + frame.with_save(|frame| { + frame.rotate(hand_rotation(self.now.minute(), 60)); + frame.stroke(&long_hand, wide_stroke()); + }); + + frame.with_save(|frame| { + frame.rotate(hand_rotation(self.now.second(), 60)); + frame.stroke(&long_hand, thin_stroke()); + }) + }); + + vec![clock] + } +} + +fn hand_rotation(n: u8, total: u8) -> f32 { + let turns = n as f32 / total as f32; + + 2.0 * std::f32::consts::PI * turns +} diff --git a/examples/todos_sctk/Cargo.toml b/examples/todos_sctk/Cargo.toml new file mode 100644 index 0000000000..715bbb8047 --- /dev/null +++ b/examples/todos_sctk/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "todos_sctk" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", default-features=false, features = ["async-std", "wayland", "debug"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +once_cell = "1.15" +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "f1d9c3e" } +iced_native = { path = "../../native" } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +async-std = "1.0" +directories-next = "2.0" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +web-sys = { version = "0.3", features = ["Window", "Storage"] } +wasm-timer = "0.2" + +[package.metadata.deb] +assets = [ + ["target/release-opt/todos", "usr/bin/iced-todos", "755"], + ["iced-todos.desktop", "usr/share/applications/", "644"], +] diff --git a/examples/todos_sctk/README.md b/examples/todos_sctk/README.md new file mode 100644 index 0000000000..9c2598b95e --- /dev/null +++ b/examples/todos_sctk/README.md @@ -0,0 +1,20 @@ +## Todos + +A todos tracker inspired by [TodoMVC]. It showcases dynamic layout, text input, checkboxes, scrollables, icons, and async actions! It automatically saves your tasks in the background, even if you did not finish typing them. + +All the example code is located in the __[`main`]__ file. + + + +You can run the native version with `cargo run`: +``` +cargo run --package todos +``` +We have not yet implemented a `LocalStorage` version of the auto-save feature. Therefore, it does not work on web _yet_! + +[`main`]: src/main.rs +[TodoMVC]: http://todomvc.com/ diff --git a/examples/todos_sctk/fonts/icons.ttf b/examples/todos_sctk/fonts/icons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..4498299db26811ff8490001ea6f681529a4118f4 GIT binary patch literal 5596 zcmd^CO>7&-6@Ih3q$ui#vPDs`8QVjxer$0`$&xI|w&c*HBs!7Y$g=E^c3g8sQ9q6( zDiV`AMGpat2C14RX%9hBwCJS&3oU>ym|BH?TQ2u`RGN`X*GHNLR@~m?aR>o8~5~+=ev5wtmzPu zm_++xcG+J2m73zgx)Jvw;tO{uYtj4}1Rt6jj6eMYJc-Ze3U|T(3U?L~duC zrF_;F-zSXiei2IVvVDD3Xf>ar{R-N0#a_<+=6i?Wulr4m|J&94dg1KPCZ;g|S70Ar zUCXWh?Q|HvG)<%Z67kx-)J>;I8yTCJrurqjutNLEfSxb5-;Kr6;=E0svPHngRsoG5 zcSWneCSE5O=Kr$BtA3><#b4>D(4Zxk4($W3$+^*4ihWr7v93>TU!zO<6uki&`%t6(!CLa0IgB(OS@0VE->+IH z0Da{!ASxe1!#BtHqfbgV$Ms{__%27E^qbnZsfiB6_WJ}0kt9uMd2|waaOV8Ye%;j^ z7XB*XZs`#1eUFL$ojY@(m3W zKF+S~qP|zJ!O1;DT{J+KDHXdgeq8gokA(K^sTOW*X_9KG%3WKP@d^*QJ=1kHn%mGc z-sX%;*F<%-m}V)eQ&cUgC(@}4Q%{~vjwNF4EsgDbnf0y%;kG?}?P1a4ZrbAyoD@C% z1E0|ry&dfxrn}133cRwX}EaFp!@^!5Y}2|UC>ucy`Hbsn$X zfvr4E6 z2-bQ|yM%C^$I$=zXLKYU)f$~CuQWX>4*IX)=-^Z#u4lDvoMu1mqgHw;)_hQCt^6%Wu5IFhCakY0c73(0E=E{?%W2<4pR>PQe3t<>y3PKo9ks*xnV61&Nlk&TX z>DXSPkbI=M!B>r4LuEuD!_5O7RZYE3qR(tW{xtb}dj>>*N3$@G3BONt3~(w1e%*7U z_l&q&>oT_9GwNK1=+Y0~-s--spY>n4eZ_w=aKm5Kd!u(Kskv*7t=}7Xim37I?X9j4 z#CHxfUHRbYrluPKPJFH=xZj|c8m{_={zWzVZC3yJqf<_WC`m->HZqXw3{Hb{p^sC$ zi22*wc=AYhUj|#8dk-Yu6wWnB?~8dLW*hW0=Ql2m9z{)C2U@J*`p?&1`peFk$Ivc~ z&lUJs8EaHU!)2^PKT^g9@I)EAsD`G?*bV*FGWJrK=F7N-8tGenwvB4cbB%76v7iRw zD`So7#i26Rp^ucY0X$sBE((aVW$cDNTgG0xDAvokhT6ri68Y5^))wa%3i5E`i0tV; zdR)%DAoEZyuGmY`ey*^PUt5@G%&p|s>_TpqM_+$_zNb)_lXGkNWjR(JSFWz*ujR6Z=t7~edZMeV(v#<-1m$U! zUZ6EvM5q?1K#~qadjzrvuhOG*9B2j%31*44NGoL15;QhFhaL-#WgYDp?m4tppv{4? z1RSL-p3A%RQ((-a{}M)7+hx6fl#5`mA$b;^(Ixzf!n^xfNw8KNrtNqz3x7(!uha9G ztq0lyda;*lj#rY#oDuK%D-jR2UBft8u%k{?3ecWFY3|xJXJviJs>-=R>3QH~2u0Aux2x|X7WwfblOxjnaZWp5v5ylR4Sv*hC{BzWJd7ge4)qFk1$N`yDDeNJfHXqs^oAvWW-(q`tA$YOlu>Wru=OR|$SiR{}3&42Aj2G!+V(p>$^`qUx-orj4pudnBUjEi6Dv zRhxK%*9Bn4)2fbJQ)tzp6;VD6)8K?eA_7^st?CmQxsj2o9zlz!25WpeRWxQt(ygj4 zXI_t}J=XZS)cE<5G8lrs(b4a*Ou;_;14aj!e9*22LSgvpP!HHIUq$tnt# z0mPYQvsKhtK4KLOmiw%ti{*Z+=w=zTid8HhwTd2=2drW>%YzoFy71?4;^}S?mn)l0uOYH^%VG#Q-eoNnS;eH(S(&8#k%3>1G{99wf0~{^;ps7p@{1JEGjZA z3wj^6f&y(aDwBLN5yHneHj-u%l^}(hjhct!+ABnpAM+nW2?-$k@#j!fbt0VGh?-Ik zZD6eaJ7yUzjiC&T36@kDKFqOmsau-VW$>2PuJ2FBxxjf)Dls2sG{&_Zk(o7>p0H<8W3+@F1kR*!Fz@eU!zEN*bIcwLnwVh>>w<7*!FUgt1debeG;q2R zdlwQ3b^AU~FrtmlZH^Oo;x)o0?9N=sk^zo^#O$v2atzENgl5oDD-TYulw)R+C*$2Z z?u3jNP>v`~r=oHQFFy9Tti)hv5QNUah5#+MQe(v%E9#F``bCJxElxCd2RE z`fs%=!>)9_hjYqO$HEoMJ%c`Gss8W= za)^^<1IKaK#MqXo3S<756E04`N_087Oq_}+4oS(!(L||Q=tJ~l zsI|i1sCvLjTB;A?3`cDgag}3uXI0|#xW(zH&LFH$Serzr0mcCYg9&R>IGVEnj^+!@ ziNo|Ha~MoAhrv1KFqmS_DS-3LVKB`c1{ava;39Kk08cT8L5evH(#&CSi8%>?%gkZ$ zG;R;?y#JKq-DUsc98 J@S+$Y`Y%9~)?EMq literal 0 HcmV?d00001 diff --git a/examples/todos_sctk/iced-todos.desktop b/examples/todos_sctk/iced-todos.desktop new file mode 100644 index 0000000000..dd7ce53dad --- /dev/null +++ b/examples/todos_sctk/iced-todos.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Name=Todos - Iced +Exec=iced-todos +Type=Application diff --git a/examples/todos_sctk/index.html b/examples/todos_sctk/index.html new file mode 100644 index 0000000000..ee5570fb9e --- /dev/null +++ b/examples/todos_sctk/index.html @@ -0,0 +1,12 @@ + + + + + + Todos - Iced + + + + + + diff --git a/examples/todos_sctk/src/main.rs b/examples/todos_sctk/src/main.rs new file mode 100644 index 0000000000..e39ce49724 --- /dev/null +++ b/examples/todos_sctk/src/main.rs @@ -0,0 +1,610 @@ +use iced::alignment::{self, Alignment}; +use iced::event::{self, Event}; +use iced::keyboard; +use iced::sctk_settings::InitialSurface; +use iced::subscription; +use iced::theme::{self, Theme}; +use iced::wayland::SurfaceIdWrapper; +use iced::widget::{ + self, button, checkbox, column, container, row, scrollable, text, + text_input, Text, +}; +use iced::{Application, Element}; +use iced::{Color, Command, Font, Length, Settings, Subscription}; + +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; + +static INPUT_ID: Lazy = Lazy::new(text_input::Id::unique); + +pub fn main() -> iced::Result { + Todos::run(Settings { + antialiasing: true, + initial_surface: InitialSurface::XdgWindow(Default::default()), + ..Settings::default() + }) +} + +#[derive(Debug)] +enum Todos { + Loading, + Loaded(State), +} + +#[derive(Debug, Default)] +struct State { + input_value: String, + filter: Filter, + tasks: Vec, + dirty: bool, + saving: bool, +} + +#[derive(Debug, Clone)] +enum Message { + Loaded(Result), + Saved(Result<(), SaveError>), + InputChanged(String), + CreateTask, + FilterChanged(Filter), + TaskMessage(usize, TaskMessage), + TabPressed { shift: bool }, +} + +impl Application for Todos { + type Message = Message; + type Theme = Theme; + type Executor = iced::executor::Default; + type Flags = (); + + fn new(_flags: ()) -> (Todos, Command) { + ( + Todos::Loading, + Command::perform(SavedState::load(), Message::Loaded), + ) + } + + fn title(&self) -> String { + let dirty = match self { + Todos::Loading => false, + Todos::Loaded(state) => state.dirty, + }; + + format!("Todos{} - Iced", if dirty { "*" } else { "" }) + } + + fn update(&mut self, message: Message) -> Command { + match self { + Todos::Loading => { + match message { + Message::Loaded(Ok(state)) => { + *self = Todos::Loaded(State { + input_value: state.input_value, + filter: state.filter, + tasks: state.tasks, + ..State::default() + }); + } + Message::Loaded(Err(_)) => { + *self = Todos::Loaded(State::default()); + } + _ => {} + } + + text_input::focus(INPUT_ID.clone()) + } + Todos::Loaded(state) => { + let mut saved = false; + + let command = match message { + Message::InputChanged(value) => { + state.input_value = value; + + Command::none() + } + Message::CreateTask => { + if !state.input_value.is_empty() { + state + .tasks + .push(Task::new(state.input_value.clone())); + state.input_value.clear(); + } + + Command::none() + } + Message::FilterChanged(filter) => { + state.filter = filter; + + Command::none() + } + Message::TaskMessage(i, TaskMessage::Delete) => { + state.tasks.remove(i); + + Command::none() + } + Message::TaskMessage(i, task_message) => { + if let Some(task) = state.tasks.get_mut(i) { + let should_focus = + matches!(task_message, TaskMessage::Edit); + + task.update(task_message); + + if should_focus { + let id = Task::text_input_id(i); + Command::batch(vec![ + text_input::focus(id.clone()), + text_input::select_all(id), + ]) + } else { + Command::none() + } + } else { + Command::none() + } + } + Message::Saved(_) => { + state.saving = false; + saved = true; + + Command::none() + } + Message::TabPressed { shift } => { + if shift { + widget::focus_previous() + } else { + widget::focus_next() + } + } + _ => Command::none(), + }; + + if !saved { + state.dirty = true; + } + + let save = if state.dirty && !state.saving { + state.dirty = false; + state.saving = true; + + Command::perform( + SavedState { + input_value: state.input_value.clone(), + filter: state.filter, + tasks: state.tasks.clone(), + } + .save(), + Message::Saved, + ) + } else { + Command::none() + }; + + Command::batch(vec![command, save]) + } + } + } + + fn view(&self, id: SurfaceIdWrapper) -> Element { + match self { + Todos::Loading => loading_message(), + Todos::Loaded(State { + input_value, + filter, + tasks, + .. + }) => { + let title = text("todos") + .width(Length::Fill) + .size(100) + .style(Color::from([0.5, 0.5, 0.5])) + .horizontal_alignment(alignment::Horizontal::Center); + + let input = text_input( + "What needs to be done?", + input_value, + Message::InputChanged, + ) + .id(INPUT_ID.clone()) + .padding(15) + .size(30) + .on_submit(Message::CreateTask); + + let controls = view_controls(tasks, *filter); + let filtered_tasks = + tasks.iter().filter(|task| filter.matches(task)); + + let tasks: Element<_> = if filtered_tasks.count() > 0 { + column( + tasks + .iter() + .enumerate() + .filter(|(_, task)| filter.matches(task)) + .map(|(i, task)| { + task.view(i).map(move |message| { + Message::TaskMessage(i, message) + }) + }) + .collect(), + ) + .spacing(10) + .into() + } else { + empty_message(match filter { + Filter::All => "You have not created a task yet...", + Filter::Active => "All your tasks are done! :D", + Filter::Completed => { + "You have not completed a task yet..." + } + }) + }; + + let content = column![title, input, controls, tasks] + .spacing(20) + .max_width(800); + + scrollable( + container(content) + .width(Length::Fill) + .padding(40) + .center_x(), + ) + .into() + } + } + } + + fn subscription(&self) -> Subscription { + subscription::events_with(|event, status| match (event, status) { + ( + Event::Keyboard(keyboard::Event::KeyPressed { + key_code: keyboard::KeyCode::Tab, + modifiers, + .. + }), + event::Status::Ignored, + ) => Some(Message::TabPressed { + shift: modifiers.shift(), + }), + _ => None, + }) + } + + fn close_requested(&self, id: SurfaceIdWrapper) -> Self::Message { + todo!() + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct Task { + description: String, + completed: bool, + + #[serde(skip)] + state: TaskState, +} + +#[derive(Debug, Clone)] +pub enum TaskState { + Idle, + Editing, +} + +impl Default for TaskState { + fn default() -> Self { + Self::Idle + } +} + +#[derive(Debug, Clone)] +pub enum TaskMessage { + Completed(bool), + Edit, + DescriptionEdited(String), + FinishEdition, + Delete, +} + +impl Task { + fn text_input_id(i: usize) -> text_input::Id { + text_input::Id::new(format!("task-{}", i)) + } + + fn new(description: String) -> Self { + Task { + description, + completed: false, + state: TaskState::Idle, + } + } + + fn update(&mut self, message: TaskMessage) { + match message { + TaskMessage::Completed(completed) => { + self.completed = completed; + } + TaskMessage::Edit => { + self.state = TaskState::Editing; + } + TaskMessage::DescriptionEdited(new_description) => { + self.description = new_description; + } + TaskMessage::FinishEdition => { + if !self.description.is_empty() { + self.state = TaskState::Idle; + } + } + TaskMessage::Delete => {} + } + } + + fn view(&self, i: usize) -> Element { + match &self.state { + TaskState::Idle => { + let checkbox = checkbox( + &self.description, + self.completed, + TaskMessage::Completed, + ) + .width(Length::Fill); + + row![ + checkbox, + button(edit_icon()) + .on_press(TaskMessage::Edit) + .padding(10) + .style(theme::Button::Text), + ] + .spacing(20) + .align_items(Alignment::Center) + .into() + } + TaskState::Editing => { + let text_input = text_input( + "Describe your task...", + &self.description, + TaskMessage::DescriptionEdited, + ) + .id(Self::text_input_id(i)) + .on_submit(TaskMessage::FinishEdition) + .padding(10); + + row![ + text_input, + button(row![delete_icon(), "Delete"].spacing(10)) + .on_press(TaskMessage::Delete) + .padding(10) + .style(theme::Button::Destructive) + ] + .spacing(20) + .align_items(Alignment::Center) + .into() + } + } + } +} + +fn view_controls(tasks: &[Task], current_filter: Filter) -> Element { + let tasks_left = tasks.iter().filter(|task| !task.completed).count(); + + let filter_button = |label, filter, current_filter| { + let label = text(label).size(16); + + let button = button(label).style(if filter == current_filter { + theme::Button::Primary + } else { + theme::Button::Text + }); + + button.on_press(Message::FilterChanged(filter)).padding(8) + }; + + row![ + text(format!( + "{} {} left", + tasks_left, + if tasks_left == 1 { "task" } else { "tasks" } + )) + .width(Length::Fill) + .size(16), + row![ + filter_button("All", Filter::All, current_filter), + filter_button("Active", Filter::Active, current_filter), + filter_button("Completed", Filter::Completed, current_filter,), + ] + .width(Length::Shrink) + .spacing(10) + ] + .spacing(20) + .align_items(Alignment::Center) + .into() +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum Filter { + All, + Active, + Completed, +} + +impl Default for Filter { + fn default() -> Self { + Filter::All + } +} + +impl Filter { + fn matches(&self, task: &Task) -> bool { + match self { + Filter::All => true, + Filter::Active => !task.completed, + Filter::Completed => task.completed, + } + } +} + +fn loading_message<'a>() -> Element<'a, Message> { + container( + text("Loading...") + .horizontal_alignment(alignment::Horizontal::Center) + .size(50), + ) + .width(Length::Fill) + .height(Length::Fill) + .center_y() + .into() +} + +fn empty_message(message: &str) -> Element<'_, Message> { + container( + text(message) + .width(Length::Fill) + .size(25) + .horizontal_alignment(alignment::Horizontal::Center) + .style(Color::from([0.7, 0.7, 0.7])), + ) + .width(Length::Fill) + .height(Length::Units(200)) + .center_y() + .into() +} + +// Fonts +const ICONS: Font = Font::External { + name: "Icons", + bytes: include_bytes!("../../todos/fonts/icons.ttf"), +}; + +fn icon(unicode: char) -> Text<'static> { + text(unicode.to_string()) + .font(ICONS) + .width(Length::Units(20)) + .horizontal_alignment(alignment::Horizontal::Center) + .size(20) +} + +fn edit_icon() -> Text<'static> { + icon('\u{F303}') +} + +fn delete_icon() -> Text<'static> { + icon('\u{F1F8}') +} + +// Persistence +#[derive(Debug, Clone, Serialize, Deserialize)] +struct SavedState { + input_value: String, + filter: Filter, + tasks: Vec, +} + +#[derive(Debug, Clone)] +enum LoadError { + File, + Format, +} + +#[derive(Debug, Clone)] +enum SaveError { + File, + Write, + Format, +} + +#[cfg(not(target_arch = "wasm32"))] +impl SavedState { + fn path() -> std::path::PathBuf { + let mut path = if let Some(project_dirs) = + directories_next::ProjectDirs::from("rs", "Iced", "Todos") + { + project_dirs.data_dir().into() + } else { + std::env::current_dir().unwrap_or_default() + }; + + path.push("todos.json"); + + path + } + + async fn load() -> Result { + use async_std::prelude::*; + + let mut contents = String::new(); + + let mut file = async_std::fs::File::open(Self::path()) + .await + .map_err(|_| LoadError::File)?; + + file.read_to_string(&mut contents) + .await + .map_err(|_| LoadError::File)?; + + serde_json::from_str(&contents).map_err(|_| LoadError::Format) + } + + async fn save(self) -> Result<(), SaveError> { + use async_std::prelude::*; + + let json = serde_json::to_string_pretty(&self) + .map_err(|_| SaveError::Format)?; + + let path = Self::path(); + + if let Some(dir) = path.parent() { + async_std::fs::create_dir_all(dir) + .await + .map_err(|_| SaveError::File)?; + } + + { + let mut file = async_std::fs::File::create(path) + .await + .map_err(|_| SaveError::File)?; + + file.write_all(json.as_bytes()) + .await + .map_err(|_| SaveError::Write)?; + } + + // This is a simple way to save at most once every couple seconds + async_std::task::sleep(std::time::Duration::from_secs(2)).await; + + Ok(()) + } +} + +#[cfg(target_arch = "wasm32")] +impl SavedState { + fn storage() -> Option { + let window = web_sys::window()?; + + window.local_storage().ok()? + } + + async fn load() -> Result { + let storage = Self::storage().ok_or(LoadError::File)?; + + let contents = storage + .get_item("state") + .map_err(|_| LoadError::File)? + .ok_or(LoadError::File)?; + + serde_json::from_str(&contents).map_err(|_| LoadError::Format) + } + + async fn save(self) -> Result<(), SaveError> { + let storage = Self::storage().ok_or(SaveError::File)?; + + let json = serde_json::to_string_pretty(&self) + .map_err(|_| SaveError::Format)?; + + storage + .set_item("state", &json) + .map_err(|_| SaveError::Write)?; + + let _ = wasm_timer::Delay::new(std::time::Duration::from_secs(2)).await; + + Ok(()) + } +} diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 3e9d11f9ff..df3dbcb034 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -403,6 +403,7 @@ async fn run_instance( // Maybe we can use `ControlFlow::WaitUntil` for this. } event::Event::WindowEvent { + window_id, event: window_event, .. } => { @@ -415,6 +416,7 @@ async fn run_instance( state.update(context.window(), &window_event, &mut debug); if let Some(event) = conversion::window_event( + crate::window::Id::MAIN, &window_event, state.scale_factor(), state.modifiers(), diff --git a/native/Cargo.toml b/native/Cargo.toml index bbf9295122..9589edc738 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -8,12 +8,15 @@ license = "MIT" repository = "https://github.com/iced-rs/iced" [features] +default = ["wayland"] +wayland = ["sctk"] debug = [] [dependencies] twox-hash = { version = "1.5", default-features = false } unicode-segmentation = "1.6" num-traits = "0.2" +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "f1d9c3e", optional = true } [dependencies.iced_core] version = "0.6" diff --git a/native/src/command.rs b/native/src/command.rs index 89ee7375af..9d70c5ebb8 100644 --- a/native/src/command.rs +++ b/native/src/command.rs @@ -1,5 +1,7 @@ //! Run asynchronous actions. mod action; +/// platform specific actions +pub mod platform_specific; pub use action::Action; diff --git a/native/src/command/action.rs b/native/src/command/action.rs index a6954f8f75..e6c00aaf8e 100644 --- a/native/src/command/action.rs +++ b/native/src/command/action.rs @@ -1,4 +1,5 @@ use crate::clipboard; +use crate::command::platform_specific; use crate::system; use crate::widget; use crate::window; @@ -20,13 +21,16 @@ pub enum Action { Clipboard(clipboard::Action), /// Run a window action. - Window(window::Action), + Window(window::Id, window::Action), /// Run a system action. System(system::Action), /// Run a widget action. Widget(widget::Action), + + /// Run a platform specific action + PlatformSpecific(platform_specific::Action), } impl Action { @@ -46,9 +50,12 @@ impl Action { match self { Self::Future(future) => Action::Future(Box::pin(future.map(f))), Self::Clipboard(action) => Action::Clipboard(action.map(f)), - Self::Window(window) => Action::Window(window.map(f)), + Self::Window(id, window) => Action::Window(id, window.map(f)), Self::System(system) => Action::System(system.map(f)), Self::Widget(widget) => Action::Widget(widget.map(f)), + Self::PlatformSpecific(action) => { + Action::PlatformSpecific(action.map(f)) + } } } } @@ -60,9 +67,14 @@ impl fmt::Debug for Action { Self::Clipboard(action) => { write!(f, "Action::Clipboard({:?})", action) } - Self::Window(action) => write!(f, "Action::Window({:?})", action), + Self::Window(id, action) => { + write!(f, "Action::Window({:?}, {:?})", id, action) + } Self::System(action) => write!(f, "Action::System({:?})", action), Self::Widget(_action) => write!(f, "Action::Widget"), + Self::PlatformSpecific(action) => { + write!(f, "Action::PlatformSpecific({:?})", action) + } } } } diff --git a/native/src/command/platform_specific/mod.rs b/native/src/command/platform_specific/mod.rs new file mode 100644 index 0000000000..2bc43bea5f --- /dev/null +++ b/native/src/command/platform_specific/mod.rs @@ -0,0 +1,46 @@ +use std::{fmt, marker::PhantomData}; + +use iced_futures::MaybeSend; + +/// wayland platform specific actions +#[cfg(feature = "wayland")] +pub mod wayland; + +/// Platform specific actions defined for wayland +pub enum Action { + /// LayerSurface Actions + #[cfg(feature = "wayland")] + Wayland(wayland::Action), + /// phantom data variant in case the platform has not specific actions implemented + Phantom(PhantomData), +} + +impl Action { + /// Maps the output of an [`Action`] using the given function. + pub fn map( + self, + f: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> Action + where + T: 'static, + A: 'static, + { + match self { + #[cfg(feature = "wayland")] + Action::Wayland(a) => Action::Wayland(a.map(f)), + Action::Phantom(_) => unimplemented!(), + } + } +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + #[cfg(feature = "wayland")] + Self::Wayland(arg0) => { + f.debug_tuple("LayerSurface").field(arg0).finish() + } + Action::Phantom(_) => unimplemented!(), + } + } +} diff --git a/native/src/command/platform_specific/wayland/layer_surface.rs b/native/src/command/platform_specific/wayland/layer_surface.rs new file mode 100644 index 0000000000..582381b4a8 --- /dev/null +++ b/native/src/command/platform_specific/wayland/layer_surface.rs @@ -0,0 +1,229 @@ +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; +use std::{collections::hash_map::DefaultHasher, fmt}; + +use iced_futures::MaybeSend; +use sctk::shell::layer::{Anchor, KeyboardInteractivity, Layer}; + +use crate::window; + +/// output for layer surface +#[derive(Debug, Clone)] +pub enum IcedOutput { + /// show on all outputs + All, + /// show on active output + Active, + /// show on a specific output + Output { + /// make + make: String, + /// model + model: String, + }, +} + +impl Default for IcedOutput { + fn default() -> Self { + Self::Active + } +} + +/// margins of the layer surface +#[derive(Debug, Clone, Copy, Default)] +pub struct IcedMargin { + /// top + pub top: i32, + /// right + pub right: i32, + /// bottom + pub bottom: i32, + /// left + pub left: i32, +} + +/// layer surface +#[derive(Debug, Clone)] +pub struct SctkLayerSurfaceSettings { + /// XXX id must be unique for every surface, window, and popup + pub id: window::Id, + /// layer + pub layer: Layer, + /// interactivity + pub keyboard_interactivity: KeyboardInteractivity, + /// anchor + pub anchor: Anchor, + /// output + pub output: IcedOutput, + /// namespace + pub namespace: String, + /// margin + pub margin: IcedMargin, + /// size, None in a given dimension lets the compositor decide, usually this would be done with a layer surface that is anchored to left & right or top & bottom + pub size: (Option, Option), + /// exclusive zone + pub exclusive_zone: i32, +} + +impl Default for SctkLayerSurfaceSettings { + fn default() -> Self { + Self { + id: window::Id::new(0), + layer: Layer::Top, + keyboard_interactivity: Default::default(), + anchor: Anchor::empty(), + output: Default::default(), + namespace: Default::default(), + margin: Default::default(), + size: (Some(200), Some(200)), + exclusive_zone: Default::default(), + } + } +} + +#[derive(Clone)] +/// LayerSurface Action +pub enum Action { + /// create a layer surface and receive a message with its Id + LayerSurface { + /// surface builder + builder: SctkLayerSurfaceSettings, + /// phantom + _phantom: PhantomData, + }, + /// Set size of the layer surface. + Size { + /// id of the layer surface + id: window::Id, + /// The new logical width of the window + width: Option, + /// The new logical height of the window + height: Option, + }, + /// Destroy the layer surface + Destroy(window::Id), + /// The edges which the layer surface is anchored to + Anchor { + /// id of the layer surface + id: window::Id, + /// anchor of the layer surface + anchor: Anchor, + }, + /// exclusive zone of the layer surface + ExclusiveZone { + /// id of the layer surface + id: window::Id, + /// exclusive zone of the layer surface + exclusive_zone: i32, + }, + /// margin of the layer surface, ignored for un-anchored edges + Margin { + /// id of the layer surface + id: window::Id, + /// margins of the layer surface + margin: IcedMargin, + }, + /// keyboard interactivity of the layer surface + KeyboardInteractivity { + /// id of the layer surface + id: window::Id, + /// keyboard interactivity of the layer surface + keyboard_interactivity: KeyboardInteractivity, + }, + /// layer of the layer surface + Layer { + /// id of the layer surface + id: window::Id, + /// layer of the layer surface + layer: Layer, + }, +} + +impl Action { + /// Maps the output of a window [`Action`] using the provided closure. + pub fn map( + self, + _: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> Action + where + T: 'static, + { + match self { + Action::LayerSurface { builder, .. } => Action::LayerSurface { + builder, + _phantom: PhantomData::default(), + }, + Action::Size { id, width, height } => { + Action::Size { id, width, height } + } + Action::Destroy(id) => Action::Destroy(id), + Action::Anchor { id, anchor } => Action::Anchor { id, anchor }, + Action::ExclusiveZone { id, exclusive_zone } => { + Action::ExclusiveZone { id, exclusive_zone } + } + Action::Margin { id, margin } => Action::Margin { id, margin }, + Action::KeyboardInteractivity { + id, + keyboard_interactivity, + } => Action::KeyboardInteractivity { + id, + keyboard_interactivity, + }, + Action::Layer { id, layer } => Action::Layer { id, layer }, + } + } +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Action::LayerSurface { builder, .. } => write!( + f, + "Action::LayerSurfaceAction::LayerSurface {{ builder: {:?} }}", + builder + ), + Action::Size { id, width, height } => write!( + f, + "Action::LayerSurfaceAction::Size {{ id: {:#?}, width: {:?}, height: {:?} }}", id, width, height + ), + Action::Destroy(id) => write!( + f, + "Action::LayerSurfaceAction::Destroy {{ id: {:#?} }}", id + ), + Action::Anchor { id, anchor } => write!( + f, + "Action::LayerSurfaceAction::Anchor {{ id: {:#?}, anchor: {:?} }}", id, anchor + ), + Action::ExclusiveZone { id, exclusive_zone } => write!( + f, + "Action::LayerSurfaceAction::ExclusiveZone {{ id: {:#?}, exclusive_zone: {exclusive_zone} }}", id + ), + Action::Margin { id, margin } => write!( + f, + "Action::LayerSurfaceAction::Margin {{ id: {:#?}, margin: {:?} }}", id, margin + ), + Action::KeyboardInteractivity { id, keyboard_interactivity } => write!( + f, + "Action::LayerSurfaceAction::Margin {{ id: {:#?}, keyboard_interactivity: {:?} }}", id, keyboard_interactivity + ), + Action::Layer { id, layer } => write!( + f, + "Action::LayerSurfaceAction::Margin {{ id: {:#?}, layer: {:?} }}", id, layer + ), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +/// TODO(derezzedex) +pub struct Id(u64); + +impl Id { + /// TODO(derezzedex) + pub fn new(id: impl Hash) -> Id { + let mut hasher = DefaultHasher::new(); + id.hash(&mut hasher); + + Id(hasher.finish()) + } +} diff --git a/native/src/command/platform_specific/wayland/mod.rs b/native/src/command/platform_specific/wayland/mod.rs new file mode 100644 index 0000000000..68af3644c6 --- /dev/null +++ b/native/src/command/platform_specific/wayland/mod.rs @@ -0,0 +1,51 @@ +use std::fmt::Debug; + +use iced_futures::MaybeSend; + +/// layer surface actions +pub mod layer_surface; +/// popup actions +pub mod popup; +/// window actions +pub mod window; + +#[derive(Clone)] +/// Platform specific actions defined for wayland +pub enum Action { + /// LayerSurface Actions + LayerSurface(layer_surface::Action), + /// Window Actions + Window(window::Action), + /// popup + Popup(popup::Action), +} + +impl Action { + /// Maps the output of an [`Action`] using the given function. + pub fn map( + self, + f: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> Action + where + T: 'static, + A: 'static, + { + match self { + Action::LayerSurface(a) => Action::LayerSurface(a.map(f)), + Action::Window(a) => Action::Window(a.map(f)), + Action::Popup(a) => Action::Popup(a.map(f)), + } + } +} + +impl Debug for Action { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::LayerSurface(arg0) => { + f.debug_tuple("LayerSurface").field(arg0).finish() + } + Self::Window(arg0) => f.debug_tuple("Window").field(arg0).finish(), + Self::Popup(arg0) => f.debug_tuple("Popup").field(arg0).finish(), + } + } +} diff --git a/native/src/command/platform_specific/wayland/popup.rs b/native/src/command/platform_specific/wayland/popup.rs new file mode 100644 index 0000000000..2f3ff46218 --- /dev/null +++ b/native/src/command/platform_specific/wayland/popup.rs @@ -0,0 +1,183 @@ +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; +use std::{collections::hash_map::DefaultHasher, fmt}; + +use iced_core::Rectangle; +use iced_futures::MaybeSend; +use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{ + Anchor, Gravity, +}; + +use crate::window; +/// Popup creation details +#[derive(Debug, Clone)] +pub struct SctkPopupSettings { + /// XXX must be unique, id of the parent + pub parent: window::Id, + /// XXX must be unique, id of the popup + pub id: window::Id, + /// positioner of the popup + pub positioner: SctkPositioner, + /// optional parent size, must be correct if specified or the behavior is undefined + pub parent_size: Option<(u32, u32)>, + /// whether a grab should be requested for the popup after creation + pub grab: bool, +} + +impl Hash for SctkPopupSettings { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + +/// Positioner of a popup +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SctkPositioner { + /// size of the popup + pub size: (u32, u32), + /// the rectangle which the popup will be anchored to + pub anchor_rect: Rectangle, + /// the anchor location on the popup + pub anchor: Anchor, + /// the gravity of the popup + pub gravity: Gravity, + /// the constraint adjustment, + /// Specify how the window should be positioned if the originally intended position caused the surface to be constrained, meaning at least partially outside positioning boundaries set by the compositor. The adjustment is set by constructing a bitmask describing the adjustment to be made when the surface is constrained on that axis. + /// If no bit for one axis is set, the compositor will assume that the child surface should not change its position on that axis when constrained. + /// + /// If more than one bit for one axis is set, the order of how adjustments are applied is specified in the corresponding adjustment descriptions. + /// + /// The default adjustment is none. + pub constraint_adjustment: u32, + /// offset of the popup + pub offset: (i32, i32), + /// whether the popup is reactive + pub reactive: bool, +} + +impl Hash for SctkPositioner { + fn hash(&self, state: &mut H) { + self.size.hash(state); + self.anchor_rect.x.hash(state); + self.anchor_rect.y.hash(state); + self.anchor_rect.width.hash(state); + self.anchor_rect.height.hash(state); + self.anchor.hash(state); + self.gravity.hash(state); + self.constraint_adjustment.hash(state); + self.offset.hash(state); + self.reactive.hash(state); + } +} + +impl Default for SctkPositioner { + fn default() -> Self { + Self { + size: (200, 100), + anchor_rect: Rectangle { + x: 0, + y: 0, + width: 1, + height: 1, + }, + anchor: Anchor::None, + gravity: Gravity::None, + constraint_adjustment: Default::default(), + offset: Default::default(), + reactive: true, + } + } +} + +#[derive(Clone)] +/// Window Action +pub enum Action { + /// create a window and receive a message with its Id + Popup { + /// popup + popup: SctkPopupSettings, + /// phantom + _phantom: PhantomData, + }, + /// destroy the popup + Destroy { + /// id of the popup + id: window::Id, + }, + /// request that the popup be repositioned + Reposition { + /// id of the popup + id: window::Id, + /// the positioner + positioner: SctkPositioner, + }, + /// request that the popup make an explicit grab + Grab { + /// id of the popup + id: window::Id, + }, +} + +impl Action { + /// Maps the output of a window [`Action`] using the provided closure. + pub fn map( + self, + f: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> Action + where + T: 'static, + { + match self { + Action::Popup { popup, .. } => Action::Popup { + popup, + _phantom: PhantomData::default(), + }, + Action::Destroy { id } => Action::Destroy { id }, + Action::Reposition { id, positioner } => { + Action::Reposition { id, positioner } + } + Action::Grab { id } => Action::Grab { id }, + } + } +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Action::Popup { popup, .. } => write!( + f, + "Action::PopupAction::Popup {{ popup: {:?} }}", + popup + ), + Action::Destroy { id } => write!( + f, + "Action::PopupAction::Destroy {{ id: {:?} }}", + id + ), + Action::Reposition { id, positioner } => write!( + f, + "Action::PopupAction::Reposition {{ id: {:?}, positioner: {:?} }}", + id, positioner + ), + Action::Grab { id } => write!( + f, + "Action::PopupAction::Grab {{ id: {:?} }}", + id + ), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +/// TODO(derezzedex) +pub struct Id(u64); + +impl Id { + /// TODO(derezzedex) + pub fn new(id: impl Hash) -> Id { + let mut hasher = DefaultHasher::new(); + id.hash(&mut hasher); + + Id(hasher.finish()) + } +} diff --git a/native/src/command/platform_specific/wayland/window.rs b/native/src/command/platform_specific/wayland/window.rs new file mode 100644 index 0000000000..233a6ace03 --- /dev/null +++ b/native/src/command/platform_specific/wayland/window.rs @@ -0,0 +1,253 @@ +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; +use std::{collections::hash_map::DefaultHasher, fmt}; + +use iced_futures::MaybeSend; +use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge; + +use crate::window; + +/// window settings +#[derive(Debug, Clone)] +pub struct SctkWindowSettings { + /// vanilla window settings + pub iced_settings: window::Settings, + /// window id + pub window_id: window::Id, + /// optional app id + pub app_id: Option, + /// optional window title + pub title: Option, + /// optional window parent + pub parent: Option, +} + +impl Default for SctkWindowSettings { + fn default() -> Self { + Self { + iced_settings: Default::default(), + window_id: window::Id::new(0), + app_id: Default::default(), + title: Default::default(), + parent: Default::default(), + } + } +} + +#[derive(Clone)] +/// Window Action +pub enum Action { + /// create a window and receive a message with its Id + Window { + /// window builder + builder: SctkWindowSettings, + /// phanton + _phantom: PhantomData, + }, + /// Destroy the window + Destroy(window::Id), + /// Set size of the window. + Size { + /// id of the window + id: window::Id, + /// The new logical width of the window + width: u32, + /// The new logical height of the window + height: u32, + }, + /// Set min size of the window. + MinSize { + /// id of the window + id: window::Id, + /// optional size + size: Option<(u32, u32)>, + }, + /// Set max size of the window. + MaxSize { + /// id of the window + id: window::Id, + /// optional size + size: Option<(u32, u32)>, + }, + /// Set title of the window. + Title { + /// id of the window + id: window::Id, + /// The new logical width of the window + title: String, + }, + /// Minimize the window. + Minimize { + /// id of the window + id: window::Id, + }, + /// Maximize the window. + Maximize { + /// id of the window + id: window::Id, + }, + /// UnsetMaximize the window. + UnsetMaximize { + /// id of the window + id: window::Id, + }, + /// Fullscreen the window. + Fullscreen { + /// id of the window + id: window::Id, + }, + /// UnsetFullscreen the window. + UnsetFullscreen { + /// id of the window + id: window::Id, + }, + /// Start an interactive move of the window. + InteractiveResize { + /// id of the window + id: window::Id, + /// edge being resized + edge: ResizeEdge, + }, + /// Start an interactive move of the window. + InteractiveMove { + /// id of the window + id: window::Id, + }, + /// Show the window context menu + ShowWindowMenu { + /// id of the window + id: window::Id, + /// x location of popup + x: i32, + /// y location of popup + y: i32, + }, +} + +impl Action { + /// Maps the output of a window [`Action`] using the provided closure. + pub fn map( + self, + _: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> Action + where + T: 'static, + { + match self { + Action::Window { builder, .. } => Action::Window { + builder, + _phantom: PhantomData::default(), + }, + Action::Size { id, width, height } => { + Action::Size { id, width, height } + } + Action::MinSize { id, size } => Action::MinSize { id, size }, + Action::MaxSize { id, size } => Action::MaxSize { id, size }, + Action::Title { id, title } => Action::Title { id, title }, + Action::Minimize { id } => Action::Minimize { id }, + Action::Maximize { id } => Action::Maximize { id }, + Action::UnsetMaximize { id } => Action::UnsetMaximize { id }, + Action::Fullscreen { id } => Action::Fullscreen { id }, + Action::UnsetFullscreen { id } => Action::UnsetFullscreen { id }, + Action::InteractiveMove { id } => Action::InteractiveMove { id }, + Action::ShowWindowMenu { id, x, y } => { + Action::ShowWindowMenu { id, x, y } + } + Action::InteractiveResize { id, edge } => { + Action::InteractiveResize { id, edge } + } + Action::Destroy(id) => Action::Destroy(id), + } + } +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Action::Window { builder, .. } => write!( + f, + "Action::Window::LayerSurface {{ builder: {:?} }}", + builder + ), + Action::Size { id, width, height } => write!( + f, + "Action::Window::Size {{ id: {:?}, width: {:?}, height: {:?} }}", + id, width, height + ), + Action::MinSize { id, size } => write!( + f, + "Action::Window::MinSize {{ id: {:?}, size: {:?} }}", + id, size + ), + Action::MaxSize { id, size } => write!( + f, + "Action::Window::MaxSize {{ id: {:?}, size: {:?} }}", + id, size + ), + Action::Title { id, title } => write!( + f, + "Action::Window::Title {{ id: {:?}, title: {:?} }}", + id, title + ), + Action::Minimize { id } => write!( + f, + "Action::Window::Minimize {{ id: {:?} }}", + id + ), + Action::Maximize { id } => write!( + f, + "Action::Window::Maximize {{ id: {:?} }}", + id + ), + Action::UnsetMaximize { id } => write!( + f, + "Action::Window::UnsetMaximize {{ id: {:?} }}", + id + ), + Action::Fullscreen { id } => write!( + f, + "Action::Window::Fullscreen {{ id: {:?} }}", + id + ), + Action::UnsetFullscreen { id } => write!( + f, + "Action::Window::UnsetFullscreen {{ id: {:?} }}", + id + ), + Action::InteractiveMove { id } => write!( + f, + "Action::Window::InteractiveMove {{ id: {:?} }}", + id + ), + Action::ShowWindowMenu { id, x, y } => write!( + f, + "Action::Window::ShowWindowMenu {{ id: {:?}, x: {x}, y: {y} }}", + id + ), + Action::InteractiveResize { id, edge } => write!( + f, + "Action::Window::InteractiveResize {{ id: {:?}, edge: {:?} }}", + id, edge + ), + Action::Destroy(id) => write!( + f, + "Action::Window::Destroy {{ id: {:?} }}", + id + ), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +/// TODO(derezzedex) +pub struct Id(u64); + +impl Id { + /// TODO(derezzedex) + pub fn new(id: impl Hash) -> Id { + let mut hasher = DefaultHasher::new(); + id.hash(&mut hasher); + + Id(hasher.finish()) + } +} diff --git a/native/src/event.rs b/native/src/event.rs index bcfaf891bc..6f68cc065b 100644 --- a/native/src/event.rs +++ b/native/src/event.rs @@ -4,6 +4,9 @@ use crate::mouse; use crate::touch; use crate::window; +#[cfg(feature = "wayland")] +/// platform specific wayland events +pub mod wayland; /// A user interface event. /// /// _**Note:** This type is largely incomplete! If you need to track @@ -19,7 +22,7 @@ pub enum Event { Mouse(mouse::Event), /// A window event - Window(window::Event), + Window(window::Id, window::Event), /// A touch event Touch(touch::Event), @@ -31,6 +34,8 @@ pub enum Event { /// A platform specific event #[derive(Debug, Clone, PartialEq, Eq)] pub enum PlatformSpecific { + /// A Wayland specific event + Wayland(wayland::Event), /// A MacOS specific event MacOS(MacOS), } diff --git a/native/src/event/wayland/layer.rs b/native/src/event/wayland/layer.rs new file mode 100644 index 0000000000..70c40f1e0e --- /dev/null +++ b/native/src/event/wayland/layer.rs @@ -0,0 +1,12 @@ +use crate::window; + +/// layer surface events +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum LayerEvent { + /// layer surface Done + Done, + /// layer surface focused + Focused, + /// layer_surface unfocused + Unfocused, +} diff --git a/native/src/event/wayland/mod.rs b/native/src/event/wayland/mod.rs new file mode 100644 index 0000000000..e514e1dde1 --- /dev/null +++ b/native/src/event/wayland/mod.rs @@ -0,0 +1,31 @@ +mod layer; +mod output; +mod popup; +mod seat; +mod window; + +use crate::window::Id; +use sctk::reexports::client::protocol::{ + wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface, +}; + +pub use layer::*; +pub use output::*; +pub use popup::*; +pub use seat::*; +pub use window::*; + +/// wayland events +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Event { + /// layer surface event + Layer(LayerEvent, WlSurface, Id), + /// popup event + Popup(PopupEvent, WlSurface, Id), + /// output event + Output(OutputEvent, WlOutput), + /// window event + Window(WindowEvent, WlSurface, Id), + /// Seat Event + Seat(SeatEvent, WlSeat), +} diff --git a/native/src/event/wayland/output.rs b/native/src/event/wayland/output.rs new file mode 100644 index 0000000000..7980142dc9 --- /dev/null +++ b/native/src/event/wayland/output.rs @@ -0,0 +1,24 @@ +/// output events +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum OutputEvent { + /// created output + Created { + /// make of the output + make: String, + /// model of the output + model: String, + }, + /// removed output + Removed { + /// make of the output + make: String, + /// model of the output + model: String, + }, + /// name of the output + Name(String), + /// logical size of the output + LogicalSize(u32, u32), + /// logical position of the output + LogicalPosition(u32, u32), +} diff --git a/native/src/event/wayland/popup.rs b/native/src/event/wayland/popup.rs new file mode 100644 index 0000000000..d70d617bc6 --- /dev/null +++ b/native/src/event/wayland/popup.rs @@ -0,0 +1,23 @@ +use crate::window; + +/// popup events +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PopupEvent { + /// Done + Done, + /// repositioned, + Configured { + /// x position + x: i32, + /// y position + y: i32, + /// width + width: u32, + /// height + height: u32, + }, + /// popup focused + Focused, + /// popup unfocused + Unfocused, +} diff --git a/native/src/event/wayland/seat.rs b/native/src/event/wayland/seat.rs new file mode 100644 index 0000000000..28143fa231 --- /dev/null +++ b/native/src/event/wayland/seat.rs @@ -0,0 +1,11 @@ +use sctk::reexports::client::protocol::wl_seat::WlSeat; + +/// seat events +/// Only one seat can interact with an iced_sctk application at a time, but many may interact with the application over the lifetime of the application +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SeatEvent { + /// A new seat is interacting with the application + Enter, + /// A seat is not interacting with the application anymore + Leave, +} diff --git a/native/src/event/wayland/window.rs b/native/src/event/wayland/window.rs new file mode 100644 index 0000000000..3db32f2ad7 --- /dev/null +++ b/native/src/event/wayland/window.rs @@ -0,0 +1,22 @@ +#![allow(missing_docs)] + +/// window events +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum WindowEvent { + /// window manager capabilities + WmCapabilities(Vec), + /// window state + State(WindowState), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +/// the state of the window +pub enum WindowState { + Maximized, + Fullscreen, + Activated, + TiledLeft, + TiledRight, + TiledTop, + TiledBottom, +} diff --git a/native/src/window.rs b/native/src/window.rs index f910b8f21b..56fc5349ce 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -1,8 +1,16 @@ //! Build window-based GUI applications. mod action; mod event; +mod icon; +mod id; mod mode; +mod position; +mod settings; pub use action::Action; pub use event::Event; +pub use icon::Icon; +pub use id::Id; pub use mode::Mode; +pub use position::Position; +pub use settings::Settings; diff --git a/native/src/window/action.rs b/native/src/window/action.rs index da307e97c6..7b3cc2bf52 100644 --- a/native/src/window/action.rs +++ b/native/src/window/action.rs @@ -1,4 +1,4 @@ -use crate::window::Mode; +use crate::window::{Mode, Settings}; use iced_futures::MaybeSend; use std::fmt; @@ -13,6 +13,11 @@ pub enum Action { /// There’s no guarantee that this will work unless the left mouse /// button was pressed immediately before this function is called. Drag, + /// TODO(derezzedex) + Spawn { + /// TODO(derezzedex) + settings: Settings, + }, /// Resize the window. Resize { /// The new logical width of the window @@ -58,6 +63,7 @@ impl Action { match self { Self::Close => Action::Close, Self::Drag => Action::Drag, + Self::Spawn { settings } => Action::Spawn { settings }, Self::Resize { width, height } => Action::Resize { width, height }, Self::Maximize(bool) => Action::Maximize(bool), Self::Minimize(bool) => Action::Minimize(bool), @@ -75,9 +81,12 @@ impl fmt::Debug for Action { match self { Self::Close => write!(f, "Action::Close"), Self::Drag => write!(f, "Action::Drag"), + Self::Spawn { settings } => { + write!(f, "Action::Spawn {{ settings: {:?} }}", settings) + } Self::Resize { width, height } => write!( f, - "Action::Resize {{ widget: {}, height: {} }}", + "Action::Resize {{ width: {}, height: {} }}", width, height ), Self::Maximize(value) => write!(f, "Action::Maximize({})", value), diff --git a/native/src/window/icon.rs b/native/src/window/icon.rs new file mode 100644 index 0000000000..e89baf03eb --- /dev/null +++ b/native/src/window/icon.rs @@ -0,0 +1,12 @@ +//! Attach an icon to the window of your application. + +/// The icon of a window. +#[derive(Debug, Clone)] +pub struct Icon { + /// TODO(derezzedex) + pub rgba: Vec, + /// TODO(derezzedex) + pub width: u32, + /// TODO(derezzedex) + pub height: u32, +} diff --git a/native/src/window/id.rs b/native/src/window/id.rs new file mode 100644 index 0000000000..5060e162af --- /dev/null +++ b/native/src/window/id.rs @@ -0,0 +1,19 @@ +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +/// TODO(derezzedex) +pub struct Id(u64); + +impl Id { + /// TODO(derezzedex): maybe change `u64` to an enum `Type::{Single, Multi(u64)}` + pub const MAIN: Self = Id(0); + + /// TODO(derezzedex) + pub fn new(id: impl Hash) -> Id { + let mut hasher = DefaultHasher::new(); + id.hash(&mut hasher); + + Id(hasher.finish()) + } +} diff --git a/native/src/window/position.rs b/native/src/window/position.rs new file mode 100644 index 0000000000..c260c29eb1 --- /dev/null +++ b/native/src/window/position.rs @@ -0,0 +1,22 @@ +/// The position of a window in a given screen. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Position { + /// The platform-specific default position for a new window. + Default, + /// The window is completely centered on the screen. + Centered, + /// The window is positioned with specific coordinates: `(X, Y)`. + /// + /// When the decorations of the window are enabled, Windows 10 will add some + /// invisible padding to the window. This padding gets included in the + /// position. So if you have decorations enabled and want the window to be + /// at (0, 0) you would have to set the position to + /// `(PADDING_X, PADDING_Y)`. + Specific(i32, i32), +} + +impl Default for Position { + fn default() -> Self { + Self::Default + } +} diff --git a/native/src/window/settings.rs b/native/src/window/settings.rs new file mode 100644 index 0000000000..67798fbe8a --- /dev/null +++ b/native/src/window/settings.rs @@ -0,0 +1,52 @@ +use crate::window::{Icon, Position}; + +/// The window settings of an application. +#[derive(Debug, Clone)] +pub struct Settings { + /// The initial size of the window. + pub size: (u32, u32), + + /// The initial position of the window. + pub position: Position, + + /// The minimum size of the window. + pub min_size: Option<(u32, u32)>, + + /// The maximum size of the window. + pub max_size: Option<(u32, u32)>, + + /// Whether the window should be visible or not. + pub visible: bool, + + /// Whether the window should be resizable or not. + pub resizable: bool, + + /// Whether the window should have a border, a title bar, etc. or not. + pub decorations: bool, + + /// Whether the window should be transparent. + pub transparent: bool, + + /// Whether the window will always be on top of other windows. + pub always_on_top: bool, + + /// The icon of the window. + pub icon: Option, +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + size: (1024, 768), + position: Position::default(), + min_size: None, + max_size: None, + visible: true, + resizable: true, + decorations: true, + transparent: false, + always_on_top: false, + icon: None, + } + } +} diff --git a/sctk/Cargo.toml b/sctk/Cargo.toml new file mode 100644 index 0000000000..306505194d --- /dev/null +++ b/sctk/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "iced_sctk" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +debug = ["iced_native/debug"] +system = ["sysinfo"] +application = [] +multi_window = [] + +[dependencies] +log = "0.4" +thiserror = "1.0" +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "f1d9c3e" } +glutin = "0.30.0-beta.2" +glow = "0.11.2" +raw-window-handle = "0.5.0" +enum-repr = "0.2.6" +futures = "0.3" +wayland-backend = {version = "=0.1.0-beta.13", features = ["client_system"]} + +[dependencies.iced_native] +features = ["wayland"] +path = "../native" + +[dependencies.iced_graphics] +features = ["opengl"] +path = "../graphics" + +[dependencies.iced_futures] +path = "../futures" + +[dependencies.sysinfo] +version = "0.26" +optional = true diff --git a/sctk/LICENSE.md b/sctk/LICENSE.md new file mode 100644 index 0000000000..8dc5b15d9a --- /dev/null +++ b/sctk/LICENSE.md @@ -0,0 +1,359 @@ +Mozilla Public License Version 2.0 +================================== + +## 1. Definitions + +### 1.1. "Contributor" +means each individual or legal entity that creates, contributes to +the creation of, or owns Covered Software. + +### 1.2. "Contributor Version" +means the combination of the Contributions of others (if any) used +by a Contributor and that particular Contributor's Contribution. + +### 1.3. "Contribution" +means Covered Software of a particular Contributor. + +### 1.4. "Covered Software" +means Source Code Form to which the initial Contributor has attached +the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case +including portions thereof. + +### 1.5. "Incompatible With Secondary Licenses" +means + ++ (a) that the initial Contributor has attached the notice described +in Exhibit B to the Covered Software; or + ++ (b) that the Covered Software was made available under the terms of +version 1.1 or earlier of the License, but not also under the +terms of a Secondary License. + +### 1.6. "Executable Form" +means any form of the work other than Source Code Form. + +### 1.7. "Larger Work" +means a work that combines Covered Software with other material, in +a separate file or files, that is not Covered Software. + +### 1.8. "License" +means this document. + +### 1.9. "Licensable" +means having the right to grant, to the maximum extent possible, +whether at the time of the initial grant or subsequently, any and +all of the rights conveyed by this License. + +### 1.10. "Modifications" +means any of the following: + ++ (a) any file in Source Code Form that results from an addition to, +deletion from, or modification of the contents of Covered +Software; or + ++ (b) any new file in Source Code Form that contains any Covered +Software. + +### 1.11. "Patent Claims" of a Contributor +means any patent claim(s), including without limitation, method, +process, and apparatus claims, in any patent Licensable by such +Contributor that would be infringed, but for the grant of the +License, by the making, using, selling, offering for sale, having +made, import, or transfer of either its Contributions or its +Contributor Version. + +### 1.12. "Secondary License" +means either the GNU General Public License, Version 2.0, the GNU +Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those +licenses. + +### 1.13. "Source Code Form" +means the form of the work preferred for making modifications. + +### 1.14. "You" (or "Your") +means an individual or a legal entity exercising rights under this +License. For legal entities, "You" includes any entity that +controls, is controlled by, or is under common control with You. For +purposes of this definition, "control" means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of more than +fifty percent (50%) of the outstanding shares or beneficial +ownership of such entity. + +## 2. License Grants and Conditions + +### 2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + ++ (a) under intellectual property rights (other than patent or trademark) +Licensable by such Contributor to use, reproduce, make available, +modify, display, perform, distribute, and otherwise exploit its +Contributions, either on an unmodified basis, with Modifications, or +as part of a Larger Work; and + ++ (b) under Patent Claims of such Contributor to make, use, sell, offer +for sale, have made, import, and otherwise transfer either its +Contributions or its Contributor Version. + +### 2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +### 2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + ++ (a) for any code that a Contributor has removed from Covered Software; +or + ++ (b) for infringements caused by: (i) Your and any other third party's +modifications of Covered Software, or (ii) the combination of its +Contributions with other software (except as part of its Contributor +Version); or + ++ (c) under Patent Claims infringed by Covered Software in the absence of +its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +### 2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +### 2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +### 2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +### 2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +## 3. Responsibilities + +### 3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +### 3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + ++ (a) such Covered Software must also be made available in Source Code +Form, as described in Section 3.1, and You must inform recipients of +the Executable Form how they can obtain a copy of such Source Code +Form by reasonable means in a timely manner, at a charge no more +than the cost of distribution to the recipient; and + ++ (b) You may distribute such Executable Form under the terms of this +License, or sublicense it under different terms, provided that the +license for the Executable Form does not attempt to limit or alter +the recipients' rights in the Source Code Form under this License. + +### 3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +### 3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +### 3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +## 4. Inability to Comply Due to Statute or Regulation + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +## 5. Termination + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + + +## 6. Disclaimer of Warranty + +**Covered Software is provided under this License on an "as is" +basis, without warranty of any kind, either expressed, implied, or +statutory, including, without limitation, warranties that the +Covered Software is free of defects, merchantable, fit for a +particular purpose or non-infringing. The entire risk as to the +quality and performance of the Covered Software is with You. +Should any Covered Software prove defective in any respect, You +(not any Contributor) assume the cost of any necessary servicing, +repair, or correction. This disclaimer of warranty constitutes an +essential part of this License. No use of any Covered Software is +authorized under this License except under this disclaimer.** + + +#7. Limitation of Liability + +**Under no circumstances and under no legal theory, whether tort +(including negligence), contract, or otherwise, shall any +Contributor, or anyone who distributes Covered Software as +permitted above, be liable to You for any direct, indirect, +special, incidental, or consequential damages of any character +including, without limitation, damages for lost profits, loss of +goodwill, work stoppage, computer failure or malfunction, or any +and all other commercial damages or losses, even if such party +shall have been informed of the possibility of such damages. This +limitation of liability shall not apply to liability for death or +personal injury resulting from such party's negligence to the +extent applicable law prohibits such limitation. Some +jurisdictions do not allow the exclusion or limitation of +incidental or consequential damages, so this exclusion and +limitation may not apply to You.** + + +## 8. Litigation + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +## 9. Miscellaneous + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +## 10. Versions of the License + +### 10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +### 10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +### 10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +### 10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +## Exhibit A - Source Code Form License Notice + + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +## Exhibit B - "Incompatible With Secondary Licenses" Notice + + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + diff --git a/sctk/src/application.rs b/sctk/src/application.rs new file mode 100644 index 0000000000..1eac4d40d4 --- /dev/null +++ b/sctk/src/application.rs @@ -0,0 +1,1299 @@ +use crate::{ + egl::{get_surface, init_egl}, + error::{self, Error}, + event_loop::{ + self, + control_flow::ControlFlow, + proxy, + state::{SctkState, SctkWindow}, + SctkEventLoop, + }, + sctk_event::{ + IcedSctkEvent, KeyboardEventVariant, LayerSurfaceEventVariant, + PopupEventVariant, SctkEvent, + }, + settings, Command, Debug, Executor, Runtime, Size, Subscription, +}; +use futures::{channel::mpsc, task, Future, FutureExt, StreamExt}; +use iced_native::{ + application::{self, StyleSheet}, + clipboard::{self, Null}, + command::platform_specific, + mouse::{self, Interaction}, + widget::operation, + Element, Renderer, +}; + +use sctk::{ + reexports::client::Proxy, + seat::{keyboard::Modifiers, pointer::PointerEventKind}, +}; +use std::{ + collections::HashMap, ffi::CString, fmt, marker::PhantomData, + num::NonZeroU32, +}; +use wayland_backend::client::ObjectId; + +use glutin::{api::egl, prelude::*, surface::WindowSurface}; +use iced_graphics::{compositor, renderer, window, Color, Point, Viewport}; +use iced_native::user_interface::{self, UserInterface}; +use iced_native::window::Id as SurfaceId; +use std::mem::ManuallyDrop; + +#[derive(Debug)] +pub enum Event { + /// A normal sctk event + SctkEvent(IcedSctkEvent), + /// TODO + // Create a wrapper variant of `window::Event` type instead + // (maybe we should also allow users to listen/react to those internal messages?) + + /// layer surface requests from the client + LayerSurface(platform_specific::wayland::layer_surface::Action), + /// window requests from the client + Window(platform_specific::wayland::window::Action), + /// popup requests from the client + Popup(platform_specific::wayland::popup::Action), + + /// request sctk to set the cursor of the active pointer + SetCursor(Interaction), +} + +pub struct IcedSctkState; + +/// An interactive, native cross-platform application. +/// +/// This trait is the main entrypoint of Iced. Once implemented, you can run +/// your GUI application by simply calling [`run`]. It will run in +/// its own window. +/// +/// An [`Application`] can execute asynchronous actions by returning a +/// [`Command`] in some of its methods. +/// +/// When using an [`Application`] with the `debug` feature enabled, a debug view +/// can be toggled by pressing `F12`. +pub trait Application: Sized +where + ::Theme: StyleSheet, +{ + /// The data needed to initialize your [`Application`]. + type Flags; + + /// The graphics backend to use to draw the [`Program`]. + type Renderer: Renderer; + + /// The type of __messages__ your [`Program`] will produce. + type Message: std::fmt::Debug + Send; + + /// Handles a __message__ and updates the state of the [`Program`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by either user interactions or commands, will be handled by + /// this method. + /// + /// Any [`Command`] returned will be executed immediately in the + /// background by shells. + fn update(&mut self, message: Self::Message) -> Command; + + /// Returns the widgets to display in the [`Application`]. + /// + /// These widgets can produce __messages__ based on user interaction. + fn view( + &self, + id: SurfaceIdWrapper, + ) -> Element<'_, Self::Message, Self::Renderer>; + + /// Initializes the [`Application`] with the flags provided to + /// [`run`] as part of the [`Settings`]. + /// + /// Here is where you should return the initial state of your app. + /// + /// Additionally, you can return a [`Command`] if you need to perform some + /// async action in the background on startup. This is useful if you want to + /// load state from a file, perform an initial HTTP request, etc. + fn new(flags: Self::Flags) -> (Self, Command); + + /// Returns the current title of the [`Application`]. + /// + /// This title can be dynamic! The runtime will automatically update the + /// title of your application when necessary. + fn title(&self) -> String; + + /// Returns the current [`Theme`] of the [`Application`]. + fn theme(&self) -> ::Theme; + + /// Returns the [`Style`] variation of the [`Theme`]. + fn style( + &self, + ) -> <::Theme as StyleSheet>::Style { + Default::default() + } + + /// Returns the event `Subscription` for the current state of the + /// application. + /// + /// The messages produced by the `Subscription` will be handled by + /// [`update`](#tymethod.update). + /// + /// A `Subscription` will be kept alive as long as you keep returning it! + /// + /// By default, it returns an empty subscription. + fn subscription(&self) -> Subscription { + Subscription::none() + } + + /// Returns the scale factor of the [`Application`]. + /// + /// It can be used to dynamically control the size of the UI at runtime + /// (i.e. zooming). + /// + /// For instance, a scale factor of `2.0` will make widgets twice as big, + /// while a scale factor of `0.5` will shrink them to half their size. + /// + /// By default, it returns `1.0`. + fn scale_factor(&self) -> f64 { + 1.0 + } + + /// Returns whether the [`Application`] should be terminated. + /// + /// By default, it returns `false`. + fn should_exit(&self) -> bool { + false + } + + /// TODO + fn close_requested(&self, id: SurfaceIdWrapper) -> Self::Message; +} + +/// Runs an [`Application`] with an executor, compositor, and the provided +/// settings. +pub fn run( + settings: settings::Settings, + compositor_settings: C::Settings, +) -> Result<(), error::Error> +where + A: Application + 'static, + E: Executor + 'static, + C: window::GLCompositor + 'static, + ::Theme: StyleSheet, + A::Flags: Clone, +{ + let mut debug = Debug::new(); + debug.startup_started(); + + let flags = settings.flags.clone(); + let exit_on_close_request = settings.exit_on_close_request; + let is_layer_surface = + matches!(settings.surface, settings::InitialSurface::LayerSurface(_)); + let mut event_loop = SctkEventLoop::::new(&settings) + .expect("Failed to initialize the event loop"); + + let (object_id, native_id, wl_surface) = match &settings.surface { + settings::InitialSurface::LayerSurface(l) => { + // TODO ASHLEY should an application panic if it's initial surface can't be created? + let (native_id, surface) = + event_loop.get_layer_surface(l.clone()).unwrap(); + ( + surface.id(), + SurfaceIdWrapper::LayerSurface(native_id), + surface, + ) + } + settings::InitialSurface::XdgWindow(w) => { + let (native_id, surface) = event_loop.get_window(w.clone()); + (surface.id(), SurfaceIdWrapper::Window(native_id), surface) + } + }; + + let surface_ids = HashMap::from([(object_id.clone(), native_id)]); + + let (runtime, ev_proxy) = { + let ev_proxy = event_loop.proxy(); + let executor = E::new().map_err(Error::ExecutorCreationFailed)?; + + (Runtime::new(executor, ev_proxy.clone()), ev_proxy) + }; + + let (application, init_command) = { + let flags = flags; + + runtime.enter(|| A::new(flags)) + }; + + let (display, context, config, surface) = init_egl(&wl_surface, 100, 100); + + let context = context.make_current(&surface).unwrap(); + let egl_surfaces = HashMap::from([(native_id.inner(), surface)]); + + #[allow(unsafe_code)] + let (compositor, renderer) = unsafe { + C::new(compositor_settings, |name| { + let name = CString::new(name).unwrap(); + display.get_proc_address(name.as_c_str()) + })? + }; + let (mut sender, receiver) = mpsc::unbounded::>(); + + let mut instance = Box::pin(run_instance::( + application, + compositor, + renderer, + runtime, + ev_proxy, + debug, + receiver, + egl_surfaces, + surface_ids, + display, + context, + config, + init_command, + exit_on_close_request, + if is_layer_surface { + SurfaceIdWrapper::LayerSurface(native_id.inner()) + } else { + SurfaceIdWrapper::Window(native_id.inner()) + }, + )); + + let mut context = task::Context::from_waker(task::noop_waker_ref()); + + let _ = event_loop.run_return(move |event, event_loop, control_flow| { + if let ControlFlow::ExitWithCode(_) = control_flow { + return; + } + + sender.start_send(event).expect("Send event"); + + let poll = instance.as_mut().poll(&mut context); + + *control_flow = match poll { + task::Poll::Pending => ControlFlow::Wait, + task::Poll::Ready(_) => ControlFlow::ExitWithCode(1), + }; + }); + + Ok(()) +} + +fn subscription_map(e: A::Message) -> Event +where + A: Application + 'static, + E: Executor + 'static, + C: window::GLCompositor + 'static, + ::Theme: StyleSheet, +{ + Event::SctkEvent(IcedSctkEvent::UserEvent(e)) +} + +// XXX Ashley careful, A, E, C must be exact same as in update, or the subscription map type will have a different hash +async fn run_instance( + mut application: A, + mut compositor: C, + mut renderer: A::Renderer, + mut runtime: Runtime>, Event>, + mut ev_proxy: proxy::Proxy>, + mut debug: Debug, + mut receiver: mpsc::UnboundedReceiver>, + mut egl_surfaces: HashMap< + SurfaceId, + glutin::api::egl::surface::Surface, + >, + mut surface_ids: HashMap, + mut egl_display: egl::display::Display, + mut egl_context: egl::context::PossiblyCurrentContext, + mut egl_config: glutin::api::egl::config::Config, + init_command: Command, + exit_on_close_request: bool, + init_id: SurfaceIdWrapper, +) -> Result<(), Error> +where + A: Application + 'static, + E: Executor + 'static, + C: window::GLCompositor + 'static, + ::Theme: StyleSheet, +{ + let mut cache = user_interface::Cache::default(); + + let init_id_inner = init_id.inner(); + let state = State::new(&application, init_id); + + let user_interface = build_user_interface( + &application, + user_interface::Cache::default(), + &mut renderer, + state.logical_size(), + &mut debug, + init_id, + ); + let mut states = HashMap::from([(init_id_inner, state)]); + let mut interfaces = + ManuallyDrop::new(HashMap::from([(init_id_inner, user_interface)])); + + { + let state = states.get(&init_id_inner).unwrap(); + + run_command( + &application, + &mut cache, + Some(state), + &mut renderer, + init_command, + &mut runtime, + &mut ev_proxy, + &mut debug, + || compositor.fetch_information(), + ); + } + runtime.track(application.subscription().map(subscription_map::)); + + let mut mouse_interaction = mouse::Interaction::default(); + let mut events: Vec = Vec::new(); + let mut messages: Vec = Vec::new(); + debug.startup_finished(); + + let mut current_context_window = init_id_inner; + + let mut kbd_surface_id: Option = None; + let mut mods = Modifiers::default(); + let mut destroyed_surface_ids: HashMap = + Default::default(); + + 'main: while let Some(event) = receiver.next().await { + match event { + IcedSctkEvent::NewEvents(_) => {} // TODO Ashley: Seems to be ignored in iced_winit so i'll ignore for now + IcedSctkEvent::UserEvent(message) => { + messages.push(message); + } + IcedSctkEvent::SctkEvent(event) => { + events.push(event.clone()); + match event { + SctkEvent::SeatEvent { .. } => {} // TODO Ashley: handle later possibly if multiseat support is wanted + SctkEvent::PointerEvent { + variant, + ptr_id, + seat_id, + } => { + let (state, _native_id) = match surface_ids + .get(&variant.surface.id()) + .and_then(|id| states.get_mut(&id.inner()).map(|state| (state, id))) + { + Some(s) => s, + None => continue, + }; + match variant.kind { + PointerEventKind::Enter { .. } => { + state.set_cursor_position(Point::new( + variant.position.0 as f32, + variant.position.1 as f32, + )); + } + PointerEventKind::Leave { .. } => { + state.set_cursor_position(Point::new(-1.0, -1.0)); + } + PointerEventKind::Motion { .. } => { + state.set_cursor_position(Point::new( + variant.position.0 as f32, + variant.position.1 as f32, + )); + } + PointerEventKind::Press { .. } + | PointerEventKind::Release { .. } + | PointerEventKind::Axis { .. } => {} + } + } + SctkEvent::KeyboardEvent { variant, .. } => match variant { + KeyboardEventVariant::Leave(_) => { + kbd_surface_id.take(); + } + KeyboardEventVariant::Enter(object_id) => { + kbd_surface_id.replace(object_id.id()); + } + KeyboardEventVariant::Press(_) + | KeyboardEventVariant::Release(_) + | KeyboardEventVariant::Repeat(_) => {} + KeyboardEventVariant::Modifiers(mods) => { + if let Some(state) = kbd_surface_id + .as_ref() + .and_then(|id| surface_ids.get(&id)) + .and_then(|id| states.get_mut(&id.inner())) + { + state.modifiers = mods; + } + } + }, + SctkEvent::WindowEvent { variant, id } => match variant { + crate::sctk_event::WindowEventVariant::Created(id, native_id) => { + surface_ids.insert(id, SurfaceIdWrapper::Window(native_id)); + } + crate::sctk_event::WindowEventVariant::Close => { + if let Some(surface_id) = surface_ids.remove(&id.id()) { + drop(egl_surfaces.remove(&surface_id.inner())); + interfaces.remove(&surface_id.inner()); + states.remove(&surface_id.inner()); + messages.push(application.close_requested(surface_id)); + destroyed_surface_ids.insert(id.id(), surface_id); + if exit_on_close_request && surface_id == init_id { + break 'main; + } + } + } + crate::sctk_event::WindowEventVariant::WmCapabilities(_) + | crate::sctk_event::WindowEventVariant::ConfigureBounds { .. } => {} + crate::sctk_event::WindowEventVariant::Configure( + configure, + wl_surface, + first, + ) => { + if let Some(id) = surface_ids.get(&id.id()) { + let new_size = configure.new_size.unwrap(); + + if first && !egl_surfaces.contains_key(&id.inner()) { + let egl_surface = get_surface( + &egl_display, + &egl_config, + &wl_surface, + new_size.0, + new_size.1, + ); + egl_surfaces.insert(id.inner(), egl_surface); + let state = State::new(&application, *id); + + let user_interface = build_user_interface( + &application, + user_interface::Cache::default(), + &mut renderer, + state.logical_size(), + &mut debug, + *id, + ); + states.insert(id.inner(), state); + interfaces.insert(id.inner(), user_interface); + } + if let Some(state) = states.get_mut(&id.inner()) { + state.set_logical_size(new_size.0 as f64, new_size.1 as f64); + } + } + } + }, + SctkEvent::LayerSurfaceEvent { variant, id } => match variant { + LayerSurfaceEventVariant::Created(id, native_id) => { + surface_ids.insert(id, SurfaceIdWrapper::LayerSurface(native_id)); + } + LayerSurfaceEventVariant::Done => { + if let Some(surface_id) = surface_ids.remove(&id.id()) { + drop(egl_surfaces.remove(&surface_id.inner())); + interfaces.remove(&surface_id.inner()); + states.remove(&surface_id.inner()); + messages.push(application.close_requested(surface_id)); + destroyed_surface_ids.insert(id.id(), surface_id); + if exit_on_close_request && surface_id == init_id { + break 'main; + } + } + } + LayerSurfaceEventVariant::Configure(configure, wl_surface, first) => { + if let Some(id) = surface_ids.get(&id.id()) { + if first && !egl_surfaces.contains_key(&id.inner()) { + let egl_surface = get_surface( + &egl_display, + &egl_config, + &wl_surface, + configure.new_size.0, + configure.new_size.1, + ); + egl_surfaces.insert(id.inner(), egl_surface); + let state = State::new(&application, *id); + + let user_interface = build_user_interface( + &application, + user_interface::Cache::default(), + &mut renderer, + state.logical_size(), + &mut debug, + *id, + ); + states.insert(id.inner(), state); + interfaces.insert(id.inner(), user_interface); + } + if let Some(state) = states.get_mut(&id.inner()) { + state.set_logical_size( + configure.new_size.0 as f64, + configure.new_size.1 as f64, + ); + } + } + } + }, + SctkEvent::PopupEvent { + variant, + toplevel_id: _, + parent_id: _, + id, + } => match variant { + PopupEventVariant::Created(_, native_id) => { + surface_ids.insert(id.id(), SurfaceIdWrapper::Popup(native_id)); + } + PopupEventVariant::Done => { + if let Some(surface_id) = surface_ids.remove(&id.id()) { + drop(egl_surfaces.remove(&surface_id.inner())); + interfaces.remove(&surface_id.inner()); + states.remove(&surface_id.inner()); + messages.push(application.close_requested(surface_id)); + destroyed_surface_ids.insert(id.id(), surface_id); + } + } + PopupEventVariant::WmCapabilities(_) => {} + PopupEventVariant::Configure(configure, wl_surface, first) => { + if let Some(id) = surface_ids.get(&id.id()) { + if first && !egl_surfaces.contains_key(&id.inner()) { + let egl_surface = get_surface( + &egl_display, + &egl_config, + &wl_surface, + configure.width as u32, + configure.height as u32, + ); + egl_surfaces.insert(id.inner(), egl_surface); + let state = State::new(&application, *id); + + let user_interface = build_user_interface( + &application, + user_interface::Cache::default(), + &mut renderer, + state.logical_size(), + &mut debug, + *id, + ); + states.insert(id.inner(), state); + interfaces.insert(id.inner(), user_interface); + } + if let Some(state) = states.get_mut(&id.inner()) { + state.set_logical_size( + configure.width as f64, + configure.height as f64, + ); + } + } + } + PopupEventVariant::RepositionionedPopup { .. } => {} + }, + // TODO forward these events to an application which requests them? + SctkEvent::NewOutput { id, info } => { + events.push(SctkEvent::NewOutput { id, info }); + } + SctkEvent::UpdateOutput { id, info } => { + events.push(SctkEvent::UpdateOutput { id, info }); + } + SctkEvent::RemovedOutput(id) => { + events.push(SctkEvent::RemovedOutput(id)); + } + SctkEvent::Draw(_) => unimplemented!(), // probably should never be forwarded here... + SctkEvent::ScaleFactorChanged { + factor, + id, + inner_size: _, + } => { + if let Some(state) = surface_ids + .get(&id.id()) + .and_then(|id| states.get_mut(&id.inner())) + { + state.set_scale_factor(factor); + } + } + } + } + IcedSctkEvent::MainEventsCleared => { + if surface_ids.is_empty() && !messages.is_empty() { + // Update application + let pure_states: HashMap<_, _> = + ManuallyDrop::into_inner(interfaces) + .drain() + .map(|(id, interface)| (id, interface.into_cache())) + .collect(); + + // Update application + update::( + &mut application, + &mut cache, + None, + &mut renderer, + &mut runtime, + &mut ev_proxy, + &mut debug, + &mut messages, + || compositor.fetch_information(), + ); + + interfaces = ManuallyDrop::new(build_user_interfaces( + &application, + &mut renderer, + &mut debug, + &states, + pure_states, + )); + + if application.should_exit() { + break 'main; + } + } else { + let mut needs_redraw = false; + for (object_id, surface_id) in &surface_ids { + // returns (remove, copy) + let filter_events = |e: &SctkEvent| match e { + SctkEvent::SeatEvent { id, .. } => { + (&id.id() == object_id, false) + } + SctkEvent::PointerEvent { variant, .. } => { + (&variant.surface.id() == object_id, false) + } + SctkEvent::KeyboardEvent { variant, .. } => { + match variant { + KeyboardEventVariant::Leave(id) => { + (&id.id() == object_id, false) + } + _ => ( + kbd_surface_id.as_ref() + == Some(&object_id), + false, + ), + } + } + SctkEvent::WindowEvent { id, .. } => { + (&id.id() == object_id, false) + } + SctkEvent::LayerSurfaceEvent { id, .. } => { + (&id.id() == object_id, false) + } + SctkEvent::PopupEvent { id, .. } => { + (&id.id() == object_id, false) + } + SctkEvent::NewOutput { .. } + | SctkEvent::UpdateOutput { .. } + | SctkEvent::RemovedOutput(_) => (false, true), + SctkEvent::Draw(_) => unimplemented!(), + SctkEvent::ScaleFactorChanged { id, .. } => { + (&id.id() == object_id, false) + } + }; + let mut filtered = Vec::with_capacity(events.len()); + let mut i = 0; + + while i < events.len() { + let (remove, copy) = filter_events(&mut events[i]); + if remove { + filtered.push(events.remove(i)); + } else if copy { + filtered.push(events[i].clone()); + i += 1; + } else { + i += 1; + } + } + let cursor_position = + match states.get(&surface_id.inner()) { + Some(s) => s.cursor_position(), + None => continue, + }; + if filtered.is_empty() && messages.is_empty() { + continue; + } else { + ev_proxy.send_event(Event::SctkEvent( + IcedSctkEvent::RedrawRequested( + object_id.clone(), + ), + )); + } + debug.event_processing_started(); + let native_events: Vec<_> = filtered + .into_iter() + .flat_map(|e| { + e.to_native( + &mut mods, + &surface_ids, + &destroyed_surface_ids, + ) + }) + .collect(); + let (interface_state, statuses) = { + let user_interface = interfaces + .get_mut(&surface_id.inner()) + .unwrap(); + user_interface.update( + native_events.as_slice(), + cursor_position, + &mut renderer, + &mut Null, + &mut messages, + ) + }; + debug.event_processing_finished(); + for event in + native_events.into_iter().zip(statuses.into_iter()) + { + runtime.broadcast(event); + } + if !messages.is_empty() + || matches!( + interface_state, + user_interface::State::Outdated + ) + { + needs_redraw = true; + } + } + if needs_redraw { + let mut pure_states: HashMap<_, _> = + ManuallyDrop::into_inner(interfaces) + .drain() + .map(|(id, interface)| { + (id, interface.into_cache()) + }) + .collect(); + + for (_object_id, surface_id) in &surface_ids { + let state = + match states.get_mut(&surface_id.inner()) { + Some(s) => s, + None => continue, + }; + let cache = match pure_states + .get_mut(&surface_id.inner()) + { + Some(cache) => cache, + None => continue, + }; + + // Update application + update::( + &mut application, + cache, + Some(state), + &mut renderer, + &mut runtime, + &mut ev_proxy, + &mut debug, + &mut messages, + || compositor.fetch_information(), + ); + + // Update state + state.synchronize(&application); + + if application.should_exit() { + break 'main; + } + } + interfaces = ManuallyDrop::new(build_user_interfaces( + &application, + &mut renderer, + &mut debug, + &states, + pure_states, + )); + } + } + events.clear(); + // clear the destroyed surfaces after they have been handled + destroyed_surface_ids.clear(); + } + IcedSctkEvent::RedrawRequested(id) => { + if let Some(( + native_id, + Some(egl_surface), + Some(mut user_interface), + Some(state), + )) = surface_ids.get(&id).map(|id| { + let surface = egl_surfaces.get_mut(&id.inner()); + let interface = interfaces.remove(&id.inner()); + let state = states.get_mut(&id.inner()); + (*id, surface, interface, state) + }) { + debug.render_started(); + + if current_context_window != native_id.inner() { + if egl_context.make_current(egl_surface).is_ok() { + current_context_window = native_id.inner(); + } else { + interfaces + .insert(native_id.inner(), user_interface); + continue; + } + } + + if state.viewport_changed() { + let physical_size = state.physical_size(); + let logical_size = state.logical_size(); + + debug.layout_started(); + user_interface = user_interface + .relayout(logical_size, &mut renderer); + debug.layout_finished(); + + debug.draw_started(); + let new_mouse_interaction = user_interface.draw( + &mut renderer, + state.theme(), + &renderer::Style { + text_color: state.text_color(), + }, + state.cursor_position(), + ); + debug.draw_finished(); + ev_proxy.send_event(Event::SetCursor( + new_mouse_interaction, + )); + + egl_surface.resize( + &egl_context, + NonZeroU32::new(physical_size.width).unwrap(), + NonZeroU32::new(physical_size.height).unwrap(), + ); + + compositor.resize_viewport(physical_size); + + let _ = interfaces + .insert(native_id.inner(), user_interface); + } else { + interfaces.insert(native_id.inner(), user_interface); + } + + compositor.present( + &mut renderer, + state.viewport(), + state.background_color(), + &debug.overlay(), + ); + let _ = egl_surface.swap_buffers(&egl_context); + + debug.render_finished(); + } + } + IcedSctkEvent::RedrawEventsCleared => { + // TODO + } + IcedSctkEvent::LoopDestroyed => todo!(), + } + } + + Ok(()) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SurfaceIdWrapper { + LayerSurface(SurfaceId), + Window(SurfaceId), + Popup(SurfaceId), +} + +impl SurfaceIdWrapper { + pub fn inner(&self) -> SurfaceId { + match self { + SurfaceIdWrapper::LayerSurface(id) => *id, + SurfaceIdWrapper::Window(id) => *id, + SurfaceIdWrapper::Popup(id) => *id, + } + } +} + +/// Builds a [`UserInterface`] for the provided [`Application`], logging +/// [`struct@Debug`] information accordingly. +pub fn build_user_interface<'a, A: Application>( + application: &'a A, + cache: user_interface::Cache, + renderer: &mut A::Renderer, + size: Size, + debug: &mut Debug, + id: SurfaceIdWrapper, +) -> UserInterface<'a, A::Message, A::Renderer> +where + ::Theme: StyleSheet, +{ + debug.view_started(); + let view = application.view(id); + debug.view_finished(); + + debug.layout_started(); + let user_interface = UserInterface::build(view, size, cache, renderer); + debug.layout_finished(); + + user_interface +} + +/// The state of a surface created by the application [`Application`]. +#[allow(missing_debug_implementations)] +pub struct State +where + ::Theme: application::StyleSheet, +{ + pub(crate) id: SurfaceIdWrapper, + title: String, + scale_factor: f64, + pub(crate) viewport: Viewport, + viewport_changed: bool, + cursor_position: Point, + modifiers: Modifiers, + theme: ::Theme, + appearance: application::Appearance, + application: PhantomData, +} + +impl State +where + ::Theme: application::StyleSheet, +{ + /// Creates a new [`State`] for the provided [`Application`] + pub fn new(application: &A, id: SurfaceIdWrapper) -> Self { + let title = application.title(); + let scale_factor = application.scale_factor(); + let theme = application.theme(); + let appearance = theme.appearance(&application.style()); + + let viewport = Viewport::with_physical_size(Size::new(1, 1), 1.0); + + Self { + id, + title, + scale_factor, + viewport, + viewport_changed: false, + // TODO: Encode cursor availability in the type-system + cursor_position: Point::new(-1.0, -1.0), + modifiers: Modifiers::default(), + theme, + appearance, + application: PhantomData, + } + } + + /// Returns the current [`Viewport`] of the [`State`]. + pub fn viewport(&self) -> &Viewport { + &self.viewport + } + + /// TODO + pub fn viewport_changed(&self) -> bool { + self.viewport_changed + } + + /// Returns the physical [`Size`] of the [`Viewport`] of the [`State`]. + pub fn physical_size(&self) -> Size { + self.viewport.physical_size() + } + + /// Returns the logical [`Size`] of the [`Viewport`] of the [`State`]. + pub fn logical_size(&self) -> Size { + self.viewport.logical_size() + } + + /// Sets the logical [`Size`] of the [`Viewport`] of the [`State`]. + pub fn set_logical_size(&mut self, w: f64, h: f64) { + let old_size = self.viewport.logical_size(); + if w != old_size.width.into() || h != old_size.height.into() { + self.viewport_changed = true; + self.viewport = Viewport::with_physical_size( + Size { + width: (w * self.scale_factor) as u32, + height: (h * self.scale_factor) as u32, + }, + self.scale_factor, + ); + } + } + + /// Returns the current scale factor of the [`Viewport`] of the [`State`]. + pub fn scale_factor(&self) -> f64 { + self.viewport.scale_factor() + } + + pub fn set_scale_factor(&mut self, scale_factor: f64) { + if scale_factor != self.scale_factor { + self.viewport_changed = true; + let logical_size = self.viewport.logical_size(); + self.viewport = Viewport::with_physical_size( + Size { + width: (logical_size.width as f64 * scale_factor) as u32, + height: (logical_size.height as f64 * scale_factor) as u32, + }, + self.scale_factor, + ); + } + } + + /// Returns the current cursor position of the [`State`]. + pub fn cursor_position(&self) -> Point { + self.cursor_position + } + + /// Returns the current keyboard modifiers of the [`State`]. + pub fn modifiers(&self) -> Modifiers { + self.modifiers + } + + /// Returns the current theme of the [`State`]. + pub fn theme(&self) -> &::Theme { + &self.theme + } + + /// Returns the current background [`Color`] of the [`State`]. + pub fn background_color(&self) -> Color { + self.appearance.background_color + } + + /// Returns the current text [`Color`] of the [`State`]. + pub fn text_color(&self) -> Color { + self.appearance.text_color + } + + pub fn set_cursor_position(&mut self, p: Point) { + self.cursor_position = p; + } + + /// Synchronizes the [`State`] with its [`Application`] and its respective + /// windows. + /// + /// Normally an [`Application`] should be synchronized with its [`State`] + /// and window after calling [`Application::update`]. + /// + /// [`Application::update`]: crate::Program::update + pub(crate) fn synchronize_window( + &mut self, + application: &A, + window: &SctkWindow, + proxy: &proxy::Proxy>, + ) { + self.synchronize(application); + } + + fn synchronize(&mut self, application: &A) { + // Update theme and appearance + self.theme = application.theme(); + self.appearance = self.theme.appearance(&application.style()); + } +} + +// XXX Ashley careful, A, E, C must be exact same as in run_instance, or the subscription map type will have a different hash +/// Updates an [`Application`] by feeding it the provided messages, spawning any +/// resulting [`Command`], and tracking its [`Subscription`] +pub(crate) fn update( + application: &mut A, + cache: &mut user_interface::Cache, + state: Option<&State>, + renderer: &mut A::Renderer, + runtime: &mut Runtime< + E, + proxy::Proxy>, + Event, + >, + proxy: &mut proxy::Proxy>, + debug: &mut Debug, + messages: &mut Vec, + graphics_info: impl FnOnce() -> compositor::Information + Copy, +) where + A: Application + 'static, + E: Executor + 'static, + C: window::GLCompositor + 'static, + ::Theme: StyleSheet, +{ + for message in messages.drain(..) { + debug.log_message(&message); + + debug.update_started(); + let command = runtime.enter(|| application.update(message)); + debug.update_finished(); + + run_command( + application, + cache, + state, + renderer, + command, + runtime, + proxy, + debug, + graphics_info, + ); + } + + runtime.track(application.subscription().map(subscription_map::)); +} + +/// Runs the actions of a [`Command`]. +fn run_command( + application: &A, + cache: &mut user_interface::Cache, + state: Option<&State>, + renderer: &mut A::Renderer, + command: Command, + runtime: &mut Runtime< + E, + proxy::Proxy>, + Event, + >, + proxy: &mut proxy::Proxy>, + debug: &mut Debug, + _graphics_info: impl FnOnce() -> compositor::Information + Copy, +) where + A: Application, + E: Executor, + ::Theme: StyleSheet, +{ + use iced_native::command; + use iced_native::system; + + for action in command.actions() { + match action { + command::Action::Future(future) => { + runtime + .spawn(Box::pin(future.map(|e| { + Event::SctkEvent(IcedSctkEvent::UserEvent(e)) + }))); + } + command::Action::Clipboard(action) => match action { + clipboard::Action::Read(tag) => { + todo!(); + } + clipboard::Action::Write(contents) => { + todo!(); + } + }, + command::Action::Window(id, action) => { + todo!() + } + command::Action::System(action) => match action { + system::Action::QueryInformation(_tag) => { + #[cfg(feature = "system")] + { + let graphics_info = _graphics_info(); + let proxy = proxy.clone(); + + let _ = std::thread::spawn(move || { + let information = + crate::system::information(graphics_info); + + let message = _tag(information); + + proxy + .send_event(Event::Application(message)) + .expect("Send message to event loop") + }); + } + } + }, + command::Action::Widget(action) => { + let state = match state { + Some(s) => s, + None => continue, + }; + let id = &state.id; + + let mut current_cache = std::mem::take(cache); + let mut current_operation = Some(action.into_operation()); + + let mut user_interface = build_user_interface( + application, + current_cache, + renderer, + state.logical_size(), + debug, + id.clone(), // TODO: run the operation on every widget tree + ); + + while let Some(mut operation) = current_operation.take() { + user_interface.operate(renderer, operation.as_mut()); + + match operation.finish() { + operation::Outcome::None => {} + operation::Outcome::Some(message) => { + proxy.send_event(Event::SctkEvent( + IcedSctkEvent::UserEvent(message), + )); + } + operation::Outcome::Chain(next) => { + current_operation = Some(next); + } + } + } + + current_cache = user_interface.into_cache(); + *cache = current_cache; + } + command::Action::PlatformSpecific( + platform_specific::Action::Wayland( + platform_specific::wayland::Action::LayerSurface( + layer_surface_action, + ), + ), + ) => { + proxy.send_event(Event::LayerSurface(layer_surface_action)); + } + command::Action::PlatformSpecific( + platform_specific::Action::Wayland( + platform_specific::wayland::Action::Window(window_action), + ), + ) => { + proxy.send_event(Event::Window(window_action)); + } + command::Action::PlatformSpecific( + platform_specific::Action::Wayland( + platform_specific::wayland::Action::Popup(popup_action), + ), + ) => { + proxy.send_event(Event::Popup(popup_action)); + } + _ => {} + } + } +} + +pub fn build_user_interfaces<'a, A>( + application: &'a A, + renderer: &mut A::Renderer, + debug: &mut Debug, + states: &HashMap>, + mut pure_states: HashMap, +) -> HashMap< + SurfaceId, + UserInterface< + 'a, + ::Message, + ::Renderer, + >, +> +where + A: Application + 'static, + ::Theme: StyleSheet, +{ + let mut interfaces = HashMap::new(); + + for (id, pure_state) in pure_states.drain() { + let state = &states.get(&id).unwrap(); + + let user_interface = build_user_interface( + application, + pure_state, + renderer, + state.logical_size(), + debug, + state.id, + ); + + let _ = interfaces.insert(id, user_interface); + } + + interfaces +} + +pub fn run_event_loop( + mut event_loop: event_loop::SctkEventLoop, + event_handler: F, +) -> Result<(), crate::error::Error> +where + F: 'static + FnMut(IcedSctkEvent, &SctkState, &mut ControlFlow), + T: 'static + fmt::Debug, +{ + let _ = event_loop.run_return(event_handler); + + Ok(()) +} diff --git a/sctk/src/commands/data_device.rs b/sctk/src/commands/data_device.rs new file mode 100644 index 0000000000..65199e4b9c --- /dev/null +++ b/sctk/src/commands/data_device.rs @@ -0,0 +1 @@ +//! Interact with the data device objects of your application. diff --git a/sctk/src/commands/layer_surface.rs b/sctk/src/commands/layer_surface.rs new file mode 100644 index 0000000000..9072546ac2 --- /dev/null +++ b/sctk/src/commands/layer_surface.rs @@ -0,0 +1,125 @@ +//! Interact with the window of your application. +use std::marker::PhantomData; + +use iced_native::command::platform_specific::wayland::layer_surface::IcedMargin; +use iced_native::window::Id as SurfaceId; +use iced_native::{ + command::{ + self, + platform_specific::{ + self, + wayland::{self, layer_surface::SctkLayerSurfaceSettings}, + }, + Command, + }, + window, +}; +pub use window::{Event, Mode}; + +pub use sctk::shell::layer::{Anchor, KeyboardInteractivity, Layer}; + +// TODO ASHLEY: maybe implement as builder that outputs a batched commands +/// +pub fn get_layer_surface( + builder: SctkLayerSurfaceSettings, +) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::LayerSurface { + builder, + _phantom: PhantomData::default(), + }, + )), + )) +} + +/// +pub fn destroy_layer_surface(id: SurfaceId) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::Destroy(id), + )), + )) +} + +/// +pub fn set_size( + id: SurfaceId, + width: Option, + height: Option, +) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::Size { id, width, height }, + )), + )) +} +/// +pub fn set_anchor(id: SurfaceId, anchor: Anchor) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::Anchor { id, anchor }, + )), + )) +} +/// +pub fn set_exclusive_zone( + id: SurfaceId, + zone: i32, +) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::ExclusiveZone { + id, + exclusive_zone: zone, + }, + )), + )) +} + +/// +pub fn set_margin( + id: SurfaceId, + top: i32, + right: i32, + bottom: i32, + left: i32, +) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::Margin { + id, + margin: IcedMargin { + top, + right, + bottom, + left, + }, + }, + )), + )) +} + +/// +pub fn set_keyboard_interactivity( + id: SurfaceId, + keyboard_interactivity: KeyboardInteractivity, +) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::KeyboardInteractivity { + id, + keyboard_interactivity, + }, + )), + )) +} + +/// +pub fn set_layer(id: SurfaceId, layer: Layer) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::Layer { id, layer }, + )), + )) +} diff --git a/sctk/src/commands/mod.rs b/sctk/src/commands/mod.rs new file mode 100644 index 0000000000..3c40a938cd --- /dev/null +++ b/sctk/src/commands/mod.rs @@ -0,0 +1,6 @@ +//! Interact with the wayland objects of your application. + +pub mod data_device; +pub mod layer_surface; +pub mod popup; +pub mod window; diff --git a/sctk/src/commands/popup.rs b/sctk/src/commands/popup.rs new file mode 100644 index 0000000000..3f21a1a00f --- /dev/null +++ b/sctk/src/commands/popup.rs @@ -0,0 +1,57 @@ +//! Interact with the popups of your application. +use iced_native::command::{ + self, + platform_specific::{ + self, + wayland::{ + self, + popup::{SctkPopupSettings, SctkPositioner}, + }, + }, +}; +use iced_native::window::Id as SurfaceId; +use iced_native::{command::Command, window}; +pub use window::{Event, Mode}; + +/// +/// +pub fn get_popup(popup: SctkPopupSettings) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Popup( + wayland::popup::Action::Popup { + popup, + _phantom: Default::default(), + }, + )), + )) +} + +/// +pub fn reposition_popup( + id: SurfaceId, + positioner: SctkPositioner, +) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Popup( + wayland::popup::Action::Reposition { id, positioner }, + )), + )) +} + +// https://wayland.app/protocols/xdg-shell#xdg_popup:request:grab +pub fn grab_popup(id: SurfaceId) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Popup( + wayland::popup::Action::Grab { id }, + )), + )) +} + +/// +pub fn destroy_popup(id: SurfaceId) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Popup( + wayland::popup::Action::Destroy { id }, + )), + )) +} diff --git a/sctk/src/commands/window.rs b/sctk/src/commands/window.rs new file mode 100644 index 0000000000..e038c8b4e0 --- /dev/null +++ b/sctk/src/commands/window.rs @@ -0,0 +1,62 @@ +//! Interact with the window of your application. +use std::marker::PhantomData; + +use crate::command::{self, Command}; +use iced_native::command::platform_specific::{ + self, + wayland::{self, window::SctkWindowSettings}, +}; +use iced_native::window; + +pub use window::Action; +pub use window::{Event, Mode}; + +pub fn get_window(builder: SctkWindowSettings) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Window( + wayland::window::Action::Window { + builder, + _phantom: PhantomData::default(), + }, + )), + )) +} + +// TODO Ashley refactor to use regular window events maybe... +/// close the window +pub fn close_window(id: window::Id) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Window( + wayland::window::Action::Destroy(id), + )), + )) +} + +/// Resizes the window to the given logical dimensions. +pub fn resize_window( + id: window::Id, + width: u32, + height: u32, +) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Window( + wayland::window::Action::Size { id, width, height }, + )), + )) +} + +/// Sets the [`Mode`] of the window. +pub fn set_mode_window( + id: window::Id, + mode: Mode, +) -> Command { + Command::single(command::Action::Window(id, Action::SetMode(mode))) +} + +/// Fetches the current [`Mode`] of the window. +pub fn fetch_mode_window( + id: window::Id, + f: impl FnOnce(Mode) -> Message + 'static, +) -> Command { + Command::single(command::Action::Window(id, Action::FetchMode(Box::new(f)))) +} diff --git a/sctk/src/conversion.rs b/sctk/src/conversion.rs new file mode 100644 index 0000000000..0481c370a5 --- /dev/null +++ b/sctk/src/conversion.rs @@ -0,0 +1,253 @@ +use iced_native::{ + keyboard::{self, KeyCode}, + mouse::{self, ScrollDelta}, +}; +use sctk::{ + reexports::client::protocol::wl_pointer::AxisSource, + seat::{keyboard::Modifiers, pointer::AxisScroll}, +}; +/// An error that occurred while running an application. +#[derive(Debug, thiserror::Error)] +#[error("the futures executor could not be created")] +pub struct KeyCodeError(u32); + +pub fn pointer_button_to_native(button: u32) -> Option { + match button { + BTN_LEFT => Some(mouse::Button::Left), + BTN_MIDDLE => Some(mouse::Button::Middle), + BTN_RIGHT => Some(mouse::Button::Right), + b => b.try_into().ok().map(|b| mouse::Button::Other(b)), + } +} + +pub fn pointer_axis_to_native( + source: Option, + horizontal: AxisScroll, + vertical: AxisScroll, +) -> Option { + source.map(|source| match source { + AxisSource::Wheel | AxisSource::WheelTilt => ScrollDelta::Lines { + x: horizontal.discrete as f32, + y: vertical.discrete as f32, + }, + AxisSource::Finger | AxisSource::Continuous | _ => { + ScrollDelta::Pixels { + x: horizontal.absolute as f32, + y: vertical.absolute as f32, + } + } + }) +} + +pub fn modifiers_to_native(mods: Modifiers) -> keyboard::Modifiers { + let mut native_mods = keyboard::Modifiers::empty(); + if mods.alt { + native_mods = native_mods.union(keyboard::Modifiers::ALT); + } + if mods.ctrl { + native_mods = native_mods.union(keyboard::Modifiers::CTRL); + } + if mods.logo { + native_mods = native_mods.union(keyboard::Modifiers::LOGO); + } + if mods.shift { + native_mods = native_mods.union(keyboard::Modifiers::SHIFT); + } + // TODO Ashley: missing modifiers as platform specific additions? + // if mods.caps_lock { + // native_mods = native_mods.union(keyboard::Modifier); + // } + // if mods.num_lock { + // native_mods = native_mods.union(keyboard::Modifiers::); + // } + native_mods +} + +pub fn keysym_to_vkey(keysym: u32) -> Option { + use sctk::seat::keyboard::keysyms; + match keysym { + // Numbers. + keysyms::XKB_KEY_1 => Some(KeyCode::Key1), + keysyms::XKB_KEY_2 => Some(KeyCode::Key2), + keysyms::XKB_KEY_3 => Some(KeyCode::Key3), + keysyms::XKB_KEY_4 => Some(KeyCode::Key4), + keysyms::XKB_KEY_5 => Some(KeyCode::Key5), + keysyms::XKB_KEY_6 => Some(KeyCode::Key6), + keysyms::XKB_KEY_7 => Some(KeyCode::Key7), + keysyms::XKB_KEY_8 => Some(KeyCode::Key8), + keysyms::XKB_KEY_9 => Some(KeyCode::Key9), + keysyms::XKB_KEY_0 => Some(KeyCode::Key0), + // Letters. + keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(KeyCode::A), + keysyms::XKB_KEY_B | keysyms::XKB_KEY_b => Some(KeyCode::B), + keysyms::XKB_KEY_C | keysyms::XKB_KEY_c => Some(KeyCode::C), + keysyms::XKB_KEY_D | keysyms::XKB_KEY_d => Some(KeyCode::D), + keysyms::XKB_KEY_E | keysyms::XKB_KEY_e => Some(KeyCode::E), + keysyms::XKB_KEY_F | keysyms::XKB_KEY_f => Some(KeyCode::F), + keysyms::XKB_KEY_G | keysyms::XKB_KEY_g => Some(KeyCode::G), + keysyms::XKB_KEY_H | keysyms::XKB_KEY_h => Some(KeyCode::H), + keysyms::XKB_KEY_I | keysyms::XKB_KEY_i => Some(KeyCode::I), + keysyms::XKB_KEY_J | keysyms::XKB_KEY_j => Some(KeyCode::J), + keysyms::XKB_KEY_K | keysyms::XKB_KEY_k => Some(KeyCode::K), + keysyms::XKB_KEY_L | keysyms::XKB_KEY_l => Some(KeyCode::L), + keysyms::XKB_KEY_M | keysyms::XKB_KEY_m => Some(KeyCode::M), + keysyms::XKB_KEY_N | keysyms::XKB_KEY_n => Some(KeyCode::N), + keysyms::XKB_KEY_O | keysyms::XKB_KEY_o => Some(KeyCode::O), + keysyms::XKB_KEY_P | keysyms::XKB_KEY_p => Some(KeyCode::P), + keysyms::XKB_KEY_Q | keysyms::XKB_KEY_q => Some(KeyCode::Q), + keysyms::XKB_KEY_R | keysyms::XKB_KEY_r => Some(KeyCode::R), + keysyms::XKB_KEY_S | keysyms::XKB_KEY_s => Some(KeyCode::S), + keysyms::XKB_KEY_T | keysyms::XKB_KEY_t => Some(KeyCode::T), + keysyms::XKB_KEY_U | keysyms::XKB_KEY_u => Some(KeyCode::U), + keysyms::XKB_KEY_V | keysyms::XKB_KEY_v => Some(KeyCode::V), + keysyms::XKB_KEY_W | keysyms::XKB_KEY_w => Some(KeyCode::W), + keysyms::XKB_KEY_X | keysyms::XKB_KEY_x => Some(KeyCode::X), + keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(KeyCode::Y), + keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(KeyCode::Z), + // Escape. + keysyms::XKB_KEY_Escape => Some(KeyCode::Escape), + // Function keys. + keysyms::XKB_KEY_F1 => Some(KeyCode::F1), + keysyms::XKB_KEY_F2 => Some(KeyCode::F2), + keysyms::XKB_KEY_F3 => Some(KeyCode::F3), + keysyms::XKB_KEY_F4 => Some(KeyCode::F4), + keysyms::XKB_KEY_F5 => Some(KeyCode::F5), + keysyms::XKB_KEY_F6 => Some(KeyCode::F6), + keysyms::XKB_KEY_F7 => Some(KeyCode::F7), + keysyms::XKB_KEY_F8 => Some(KeyCode::F8), + keysyms::XKB_KEY_F9 => Some(KeyCode::F9), + keysyms::XKB_KEY_F10 => Some(KeyCode::F10), + keysyms::XKB_KEY_F11 => Some(KeyCode::F11), + keysyms::XKB_KEY_F12 => Some(KeyCode::F12), + keysyms::XKB_KEY_F13 => Some(KeyCode::F13), + keysyms::XKB_KEY_F14 => Some(KeyCode::F14), + keysyms::XKB_KEY_F15 => Some(KeyCode::F15), + keysyms::XKB_KEY_F16 => Some(KeyCode::F16), + keysyms::XKB_KEY_F17 => Some(KeyCode::F17), + keysyms::XKB_KEY_F18 => Some(KeyCode::F18), + keysyms::XKB_KEY_F19 => Some(KeyCode::F19), + keysyms::XKB_KEY_F20 => Some(KeyCode::F20), + keysyms::XKB_KEY_F21 => Some(KeyCode::F21), + keysyms::XKB_KEY_F22 => Some(KeyCode::F22), + keysyms::XKB_KEY_F23 => Some(KeyCode::F23), + keysyms::XKB_KEY_F24 => Some(KeyCode::F24), + // Flow control. + keysyms::XKB_KEY_Print => Some(KeyCode::Snapshot), + keysyms::XKB_KEY_Scroll_Lock => Some(KeyCode::Scroll), + keysyms::XKB_KEY_Pause => Some(KeyCode::Pause), + keysyms::XKB_KEY_Insert => Some(KeyCode::Insert), + keysyms::XKB_KEY_Home => Some(KeyCode::Home), + keysyms::XKB_KEY_Delete => Some(KeyCode::Delete), + keysyms::XKB_KEY_End => Some(KeyCode::End), + keysyms::XKB_KEY_Page_Down => Some(KeyCode::PageDown), + keysyms::XKB_KEY_Page_Up => Some(KeyCode::PageUp), + // Arrows. + keysyms::XKB_KEY_Left => Some(KeyCode::Left), + keysyms::XKB_KEY_Up => Some(KeyCode::Up), + keysyms::XKB_KEY_Right => Some(KeyCode::Right), + keysyms::XKB_KEY_Down => Some(KeyCode::Down), + + keysyms::XKB_KEY_BackSpace => Some(KeyCode::Backspace), + keysyms::XKB_KEY_Return => Some(KeyCode::Enter), + keysyms::XKB_KEY_space => Some(KeyCode::Space), + + keysyms::XKB_KEY_Multi_key => Some(KeyCode::Compose), + keysyms::XKB_KEY_caret => Some(KeyCode::Caret), + + // Keypad. + keysyms::XKB_KEY_Num_Lock => Some(KeyCode::Numlock), + keysyms::XKB_KEY_KP_0 => Some(KeyCode::Numpad0), + keysyms::XKB_KEY_KP_1 => Some(KeyCode::Numpad1), + keysyms::XKB_KEY_KP_2 => Some(KeyCode::Numpad2), + keysyms::XKB_KEY_KP_3 => Some(KeyCode::Numpad3), + keysyms::XKB_KEY_KP_4 => Some(KeyCode::Numpad4), + keysyms::XKB_KEY_KP_5 => Some(KeyCode::Numpad5), + keysyms::XKB_KEY_KP_6 => Some(KeyCode::Numpad6), + keysyms::XKB_KEY_KP_7 => Some(KeyCode::Numpad7), + keysyms::XKB_KEY_KP_8 => Some(KeyCode::Numpad8), + keysyms::XKB_KEY_KP_9 => Some(KeyCode::Numpad9), + // Misc. + // => Some(KeyCode::AbntC1), + // => Some(KeyCode::AbntC2), + keysyms::XKB_KEY_plus => Some(KeyCode::Plus), + keysyms::XKB_KEY_apostrophe => Some(KeyCode::Apostrophe), + // => Some(KeyCode::Apps), + keysyms::XKB_KEY_at => Some(KeyCode::At), + // => Some(KeyCode::Ax), + keysyms::XKB_KEY_backslash => Some(KeyCode::Backslash), + keysyms::XKB_KEY_XF86Calculator => Some(KeyCode::Calculator), + // => Some(KeyCode::Capital), + keysyms::XKB_KEY_colon => Some(KeyCode::Colon), + keysyms::XKB_KEY_comma => Some(KeyCode::Comma), + // => Some(KeyCode::Convert), + keysyms::XKB_KEY_equal => Some(KeyCode::Equals), + keysyms::XKB_KEY_grave => Some(KeyCode::Grave), + // => Some(KeyCode::Kana), + keysyms::XKB_KEY_Kanji => Some(KeyCode::Kanji), + keysyms::XKB_KEY_Alt_L => Some(KeyCode::LAlt), + keysyms::XKB_KEY_bracketleft => Some(KeyCode::LBracket), + keysyms::XKB_KEY_Control_L => Some(KeyCode::LControl), + keysyms::XKB_KEY_Shift_L => Some(KeyCode::LShift), + keysyms::XKB_KEY_Super_L => Some(KeyCode::LWin), + keysyms::XKB_KEY_XF86Mail => Some(KeyCode::Mail), + // => Some(KeyCode::MediaSelect), + // => Some(KeyCode::MediaStop), + keysyms::XKB_KEY_minus => Some(KeyCode::Minus), + keysyms::XKB_KEY_asterisk => Some(KeyCode::Asterisk), + keysyms::XKB_KEY_XF86AudioMute => Some(KeyCode::Mute), + // => Some(KeyCode::MyComputer), + keysyms::XKB_KEY_XF86AudioNext => Some(KeyCode::NextTrack), + // => Some(KeyCode::NoConvert), + keysyms::XKB_KEY_KP_Separator => Some(KeyCode::NumpadComma), + keysyms::XKB_KEY_KP_Enter => Some(KeyCode::NumpadEnter), + keysyms::XKB_KEY_KP_Equal => Some(KeyCode::NumpadEquals), + keysyms::XKB_KEY_KP_Add => Some(KeyCode::NumpadAdd), + keysyms::XKB_KEY_KP_Subtract => Some(KeyCode::NumpadSubtract), + keysyms::XKB_KEY_KP_Multiply => Some(KeyCode::NumpadMultiply), + keysyms::XKB_KEY_KP_Divide => Some(KeyCode::NumpadDivide), + keysyms::XKB_KEY_KP_Decimal => Some(KeyCode::NumpadDecimal), + keysyms::XKB_KEY_KP_Page_Up => Some(KeyCode::PageUp), + keysyms::XKB_KEY_KP_Page_Down => Some(KeyCode::PageDown), + keysyms::XKB_KEY_KP_Home => Some(KeyCode::Home), + keysyms::XKB_KEY_KP_End => Some(KeyCode::End), + keysyms::XKB_KEY_KP_Left => Some(KeyCode::Left), + keysyms::XKB_KEY_KP_Up => Some(KeyCode::Up), + keysyms::XKB_KEY_KP_Right => Some(KeyCode::Right), + keysyms::XKB_KEY_KP_Down => Some(KeyCode::Down), + // => Some(KeyCode::OEM102), + keysyms::XKB_KEY_period => Some(KeyCode::Period), + // => Some(KeyCode::Playpause), + keysyms::XKB_KEY_XF86PowerOff => Some(KeyCode::Power), + keysyms::XKB_KEY_XF86AudioPrev => Some(KeyCode::PrevTrack), + keysyms::XKB_KEY_Alt_R => Some(KeyCode::RAlt), + keysyms::XKB_KEY_bracketright => Some(KeyCode::RBracket), + keysyms::XKB_KEY_Control_R => Some(KeyCode::RControl), + keysyms::XKB_KEY_Shift_R => Some(KeyCode::RShift), + keysyms::XKB_KEY_Super_R => Some(KeyCode::RWin), + keysyms::XKB_KEY_semicolon => Some(KeyCode::Semicolon), + keysyms::XKB_KEY_slash => Some(KeyCode::Slash), + keysyms::XKB_KEY_XF86Sleep => Some(KeyCode::Sleep), + // => Some(KeyCode::Stop), + // => Some(KeyCode::Sysrq), + keysyms::XKB_KEY_Tab => Some(KeyCode::Tab), + keysyms::XKB_KEY_ISO_Left_Tab => Some(KeyCode::Tab), + keysyms::XKB_KEY_underscore => Some(KeyCode::Underline), + // => Some(KeyCode::Unlabeled), + keysyms::XKB_KEY_XF86AudioLowerVolume => Some(KeyCode::VolumeDown), + keysyms::XKB_KEY_XF86AudioRaiseVolume => Some(KeyCode::VolumeUp), + // => Some(KeyCode::Wake), + // => Some(KeyCode::Webback), + // => Some(KeyCode::WebFavorites), + // => Some(KeyCode::WebForward), + // => Some(KeyCode::WebHome), + // => Some(KeyCode::WebRefresh), + // => Some(KeyCode::WebSearch), + // => Some(KeyCode::WebStop), + keysyms::XKB_KEY_yen => Some(KeyCode::Yen), + keysyms::XKB_KEY_XF86Copy => Some(KeyCode::Copy), + keysyms::XKB_KEY_XF86Paste => Some(KeyCode::Paste), + keysyms::XKB_KEY_XF86Cut => Some(KeyCode::Cut), + // Fallback. + _ => None, + } +} diff --git a/sctk/src/dpi.rs b/sctk/src/dpi.rs new file mode 100644 index 0000000000..afef5a3b0a --- /dev/null +++ b/sctk/src/dpi.rs @@ -0,0 +1,613 @@ +//! UI scaling is important, so read the docs for this module if you don't want to be confused. +//! +//! ## Why should I care about UI scaling? +//! +//! Modern computer screens don't have a consistent relationship between resolution and size. +//! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens +//! normally being less than a quarter the size of their desktop counterparts. What's more, neither +//! desktop nor mobile screens are consistent resolutions within their own size classes - common +//! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K +//! and beyond. +//! +//! Given that, it's a mistake to assume that 2D content will only be displayed on screens with +//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen, +//! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up +//! about a quarter of the physical space as it did on the 1080p screen. That issue is especially +//! problematic with text rendering, where quarter-sized text becomes a significant legibility +//! problem. +//! +//! Failure to account for the scale factor can create a significantly degraded user experience. +//! Most notably, it can make users feel like they have bad eyesight, which will potentially cause +//! them to think about growing elderly, resulting in them having an existential crisis. Once users +//! enter that state, they will no longer be focused on your application. +//! +//! ## How should I handle it? +//! +//! The solution to this problem is to account for the device's *scale factor*. The scale factor is +//! the factor UI elements should be scaled by to be consistent with the rest of the user's system - +//! for example, a button that's normally 50 pixels across would be 100 pixels across on a device +//! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`. +//! +//! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's +//! usually a mistake, since there's no consistent mapping between the scale factor and the screen's +//! actual DPI. Unless you're printing to a physical medium, you should work in scaled pixels rather +//! than any DPI-dependent units. +//! +//! ### Position and Size types +//! +//! Winit's [`PhysicalPosition`] / [`PhysicalSize`] types correspond with the actual pixels on the +//! device, and the [`LogicalPosition`] / [`LogicalSize`] types correspond to the physical pixels +//! divided by the scale factor. +//! All of Winit's functions return physical types, but can take either logical or physical +//! coordinates as input, allowing you to use the most convenient coordinate system for your +//! particular application. +//! +//! Winit's position and size types types are generic over their exact pixel type, `P`, to allow the +//! API to have integer precision where appropriate (e.g. most window manipulation functions) and +//! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch +//! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so +//! will truncate the fractional part of the float, rather than properly round to the nearest +//! integer. Use the provided `cast` function or [`From`]/[`Into`] conversions, which handle the +//! rounding properly. Note that precision loss will still occur when rounding from a float to an +//! int, although rounding lessens the problem. +//! +//! ### Events +//! +//! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed. +//! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI +//! monitor, or if the user changes their DPI settings. This gives you a chance to rescale your +//! application's UI elements and adjust how the platform changes the window's size to reflect the new +//! scale factor. If a window hasn't received a [`ScaleFactorChanged`] event, then its scale factor +//! can be found by calling [`window.scale_factor()`]. +//! +//! ## How is the scale factor calculated? +//! +//! Scale factor is calculated differently on different platforms: +//! +//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the +//! display settings. While users are free to select any option they want, they're only given a +//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is +//! global and changing it requires logging out. See [this article][windows_1] for technical +//! details. +//! - **macOS:** Recent versions of macOS allow the user to change the scaling factor for certain +//! displays. When this is available, the user may pick a per-monitor scaling factor from a set +//! of pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default but +//! the specific value varies across devices. +//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit +//! currently uses a three-pronged approach: +//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present. +//! + If not present, use the value set in `Xft.dpi` in Xresources. +//! + Otherwise, calculate the scale factor based on the millimeter monitor dimensions provided by XRandR. +//! +//! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the +//! XRandR scaling method. Generally speaking, you should try to configure the standard system +//! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`. +//! - **Wayland:** On Wayland, scale factors are set per-screen by the server, and are always +//! integers (most often 1 or 2). +//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range +//! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more +//! information. +//! - **Android:** Scale factors are set by the manufacturer to the value that best suits the +//! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information. +//! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels. +//! In other words, it is the value of [`window.devicePixelRatio`][web_1]. It is affected by +//! both the screen scaling and the browser zoom level and can go below `1.0`. +//! +//! +//! [points]: https://en.wikipedia.org/wiki/Point_(typography) +//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography) +//! [`ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged +//! [`window.scale_factor()`]: crate::window::Window::scale_factor +//! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows +//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html +//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/ +//! [android_1]: https://developer.android.com/training/multiscreen/screendensities +//! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio + +pub trait Pixel: Copy + Into { + fn from_f64(f: f64) -> Self; + fn cast(self) -> P { + P::from_f64(self.into()) + } +} + +impl Pixel for u8 { + fn from_f64(f: f64) -> Self { + f.round() as u8 + } +} +impl Pixel for u16 { + fn from_f64(f: f64) -> Self { + f.round() as u16 + } +} +impl Pixel for u32 { + fn from_f64(f: f64) -> Self { + f.round() as u32 + } +} +impl Pixel for i8 { + fn from_f64(f: f64) -> Self { + f.round() as i8 + } +} +impl Pixel for i16 { + fn from_f64(f: f64) -> Self { + f.round() as i16 + } +} +impl Pixel for i32 { + fn from_f64(f: f64) -> Self { + f.round() as i32 + } +} +impl Pixel for f32 { + fn from_f64(f: f64) -> Self { + f as f32 + } +} +impl Pixel for f64 { + fn from_f64(f: f64) -> Self { + f + } +} + +/// Checks that the scale factor is a normal positive `f64`. +/// +/// All functions that take a scale factor assert that this will return `true`. If you're sourcing scale factors from +/// anywhere other than winit, it's recommended to validate them using this function before passing them to winit; +/// otherwise, you risk panics. +#[inline] +pub fn validate_scale_factor(scale_factor: f64) -> bool { + scale_factor.is_sign_positive() && scale_factor.is_normal() +} + +/// A position represented in logical pixels. +/// +/// The position is stored as floats, so please be careful. Casting floats to integers truncates the +/// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>` +/// implementation is provided which does the rounding for you. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct LogicalPosition

{ + pub x: P, + pub y: P, +} + +impl

LogicalPosition

{ + #[inline] + pub const fn new(x: P, y: P) -> Self { + LogicalPosition { x, y } + } +} + +impl LogicalPosition

{ + #[inline] + pub fn from_physical>, X: Pixel>( + physical: T, + scale_factor: f64, + ) -> Self { + physical.into().to_logical(scale_factor) + } + + #[inline] + pub fn to_physical( + &self, + scale_factor: f64, + ) -> PhysicalPosition { + assert!(validate_scale_factor(scale_factor)); + let x = self.x.into() * scale_factor; + let y = self.y.into() * scale_factor; + PhysicalPosition::new(x, y).cast() + } + + #[inline] + pub fn cast(&self) -> LogicalPosition { + LogicalPosition { + x: self.x.cast(), + y: self.y.cast(), + } + } +} + +impl From<(X, X)> for LogicalPosition

{ + fn from((x, y): (X, X)) -> LogicalPosition

{ + LogicalPosition::new(x.cast(), y.cast()) + } +} + +impl From> for (X, X) { + fn from(p: LogicalPosition

) -> (X, X) { + (p.x.cast(), p.y.cast()) + } +} + +impl From<[X; 2]> for LogicalPosition

{ + fn from([x, y]: [X; 2]) -> LogicalPosition

{ + LogicalPosition::new(x.cast(), y.cast()) + } +} + +impl From> for [X; 2] { + fn from(p: LogicalPosition

) -> [X; 2] { + [p.x.cast(), p.y.cast()] + } +} + +#[cfg(feature = "mint")] +impl From> for LogicalPosition

{ + fn from(p: mint::Point2

) -> Self { + Self::new(p.x, p.y) + } +} + +#[cfg(feature = "mint")] +impl From> for mint::Point2

{ + fn from(p: LogicalPosition

) -> Self { + mint::Point2 { x: p.x, y: p.y } + } +} + +/// A position represented in physical pixels. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PhysicalPosition

{ + pub x: P, + pub y: P, +} + +impl

PhysicalPosition

{ + #[inline] + pub const fn new(x: P, y: P) -> Self { + PhysicalPosition { x, y } + } +} + +impl PhysicalPosition

{ + #[inline] + pub fn from_logical>, X: Pixel>( + logical: T, + scale_factor: f64, + ) -> Self { + logical.into().to_physical(scale_factor) + } + + #[inline] + pub fn to_logical( + &self, + scale_factor: f64, + ) -> LogicalPosition { + assert!(validate_scale_factor(scale_factor)); + let x = self.x.into() / scale_factor; + let y = self.y.into() / scale_factor; + LogicalPosition::new(x, y).cast() + } + + #[inline] + pub fn cast(&self) -> PhysicalPosition { + PhysicalPosition { + x: self.x.cast(), + y: self.y.cast(), + } + } +} + +impl From<(X, X)> for PhysicalPosition

{ + fn from((x, y): (X, X)) -> PhysicalPosition

{ + PhysicalPosition::new(x.cast(), y.cast()) + } +} + +impl From> for (X, X) { + fn from(p: PhysicalPosition

) -> (X, X) { + (p.x.cast(), p.y.cast()) + } +} + +impl From<[X; 2]> for PhysicalPosition

{ + fn from([x, y]: [X; 2]) -> PhysicalPosition

{ + PhysicalPosition::new(x.cast(), y.cast()) + } +} + +impl From> for [X; 2] { + fn from(p: PhysicalPosition

) -> [X; 2] { + [p.x.cast(), p.y.cast()] + } +} + +#[cfg(feature = "mint")] +impl From> for PhysicalPosition

{ + fn from(p: mint::Point2

) -> Self { + Self::new(p.x, p.y) + } +} + +#[cfg(feature = "mint")] +impl From> for mint::Point2

{ + fn from(p: PhysicalPosition

) -> Self { + mint::Point2 { x: p.x, y: p.y } + } +} + +/// A size represented in logical pixels. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct LogicalSize

{ + pub width: P, + pub height: P, +} + +impl

LogicalSize

{ + #[inline] + pub const fn new(width: P, height: P) -> Self { + LogicalSize { width, height } + } +} + +impl LogicalSize

{ + #[inline] + pub fn from_physical>, X: Pixel>( + physical: T, + scale_factor: f64, + ) -> Self { + physical.into().to_logical(scale_factor) + } + + #[inline] + pub fn to_physical(&self, scale_factor: f64) -> PhysicalSize { + assert!(validate_scale_factor(scale_factor)); + let width = self.width.into() * scale_factor; + let height = self.height.into() * scale_factor; + PhysicalSize::new(width, height).cast() + } + + #[inline] + pub fn cast(&self) -> LogicalSize { + LogicalSize { + width: self.width.cast(), + height: self.height.cast(), + } + } +} + +impl From<(X, X)> for LogicalSize

{ + fn from((x, y): (X, X)) -> LogicalSize

{ + LogicalSize::new(x.cast(), y.cast()) + } +} + +impl From> for (X, X) { + fn from(s: LogicalSize

) -> (X, X) { + (s.width.cast(), s.height.cast()) + } +} + +impl From<[X; 2]> for LogicalSize

{ + fn from([x, y]: [X; 2]) -> LogicalSize

{ + LogicalSize::new(x.cast(), y.cast()) + } +} + +impl From> for [X; 2] { + fn from(s: LogicalSize

) -> [X; 2] { + [s.width.cast(), s.height.cast()] + } +} + +#[cfg(feature = "mint")] +impl From> for LogicalSize

{ + fn from(v: mint::Vector2

) -> Self { + Self::new(v.x, v.y) + } +} + +#[cfg(feature = "mint")] +impl From> for mint::Vector2

{ + fn from(s: LogicalSize

) -> Self { + mint::Vector2 { + x: s.width, + y: s.height, + } + } +} + +/// A size represented in physical pixels. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PhysicalSize

{ + pub width: P, + pub height: P, +} + +impl

PhysicalSize

{ + #[inline] + pub const fn new(width: P, height: P) -> Self { + PhysicalSize { width, height } + } +} + +impl PhysicalSize

{ + #[inline] + pub fn from_logical>, X: Pixel>( + logical: T, + scale_factor: f64, + ) -> Self { + logical.into().to_physical(scale_factor) + } + + #[inline] + pub fn to_logical(&self, scale_factor: f64) -> LogicalSize { + assert!(validate_scale_factor(scale_factor)); + let width = self.width.into() / scale_factor; + let height = self.height.into() / scale_factor; + LogicalSize::new(width, height).cast() + } + + #[inline] + pub fn cast(&self) -> PhysicalSize { + PhysicalSize { + width: self.width.cast(), + height: self.height.cast(), + } + } +} + +impl From<(X, X)> for PhysicalSize

{ + fn from((x, y): (X, X)) -> PhysicalSize

{ + PhysicalSize::new(x.cast(), y.cast()) + } +} + +impl From> for (X, X) { + fn from(s: PhysicalSize

) -> (X, X) { + (s.width.cast(), s.height.cast()) + } +} + +impl From<[X; 2]> for PhysicalSize

{ + fn from([x, y]: [X; 2]) -> PhysicalSize

{ + PhysicalSize::new(x.cast(), y.cast()) + } +} + +impl From> for [X; 2] { + fn from(s: PhysicalSize

) -> [X; 2] { + [s.width.cast(), s.height.cast()] + } +} + +#[cfg(feature = "mint")] +impl From> for PhysicalSize

{ + fn from(v: mint::Vector2

) -> Self { + Self::new(v.x, v.y) + } +} + +#[cfg(feature = "mint")] +impl From> for mint::Vector2

{ + fn from(s: PhysicalSize

) -> Self { + mint::Vector2 { + x: s.width, + y: s.height, + } + } +} + +/// A size that's either physical or logical. +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum Size { + Physical(PhysicalSize), + Logical(LogicalSize), +} + +impl Size { + pub fn new>(size: S) -> Size { + size.into() + } + + pub fn to_logical(&self, scale_factor: f64) -> LogicalSize

{ + match *self { + Size::Physical(size) => size.to_logical(scale_factor), + Size::Logical(size) => size.cast(), + } + } + + pub fn to_physical(&self, scale_factor: f64) -> PhysicalSize

{ + match *self { + Size::Physical(size) => size.cast(), + Size::Logical(size) => size.to_physical(scale_factor), + } + } + + pub fn clamp>( + input: S, + min: S, + max: S, + scale_factor: f64, + ) -> Size { + let (input, min, max) = ( + input.into().to_physical::(scale_factor), + min.into().to_physical::(scale_factor), + max.into().to_physical::(scale_factor), + ); + + let clamp = |input: f64, min: f64, max: f64| { + if input < min { + min + } else if input > max { + max + } else { + input + } + }; + + let width = clamp(input.width, min.width, max.width); + let height = clamp(input.height, min.height, max.height); + + PhysicalSize::new(width, height).into() + } +} + +impl From> for Size { + #[inline] + fn from(size: PhysicalSize

) -> Size { + Size::Physical(size.cast()) + } +} + +impl From> for Size { + #[inline] + fn from(size: LogicalSize

) -> Size { + Size::Logical(size.cast()) + } +} + +/// A position that's either physical or logical. +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum Position { + Physical(PhysicalPosition), + Logical(LogicalPosition), +} + +impl Position { + pub fn new>(position: S) -> Position { + position.into() + } + + pub fn to_logical( + &self, + scale_factor: f64, + ) -> LogicalPosition

{ + match *self { + Position::Physical(position) => position.to_logical(scale_factor), + Position::Logical(position) => position.cast(), + } + } + + pub fn to_physical( + &self, + scale_factor: f64, + ) -> PhysicalPosition

{ + match *self { + Position::Physical(position) => position.cast(), + Position::Logical(position) => position.to_physical(scale_factor), + } + } +} + +impl From> for Position { + #[inline] + fn from(position: PhysicalPosition

) -> Position { + Position::Physical(position.cast()) + } +} + +impl From> for Position { + #[inline] + fn from(position: LogicalPosition

) -> Position { + Position::Logical(position.cast()) + } +} diff --git a/sctk/src/egl.rs b/sctk/src/egl.rs new file mode 100644 index 0000000000..05161f4fbb --- /dev/null +++ b/sctk/src/egl.rs @@ -0,0 +1,96 @@ +use std::num::NonZeroU32; + +use glutin::{ + api::egl, config::ConfigSurfaceTypes, prelude::GlDisplay, + surface::WindowSurface, +}; +use sctk::reexports::client::{protocol::wl_surface, Proxy}; + +/// helper for initializing egl after creation of the first layer surface / window +pub fn init_egl( + surface: &wl_surface::WlSurface, + width: u32, + height: u32, +) -> ( + egl::display::Display, + egl::context::NotCurrentContext, + glutin::api::egl::config::Config, + egl::surface::Surface, +) { + let mut display_handle = raw_window_handle::WaylandDisplayHandle::empty(); + display_handle.display = surface + .backend() + .upgrade() + .expect("Connection has been closed") + .display_ptr() as *mut _; + let display_handle = + raw_window_handle::RawDisplayHandle::Wayland(display_handle); + let mut window_handle = raw_window_handle::WaylandWindowHandle::empty(); + window_handle.surface = surface.id().as_ptr() as *mut _; + let window_handle = + raw_window_handle::RawWindowHandle::Wayland(window_handle); + + // Initialize the EGL Wayland platform + // + // SAFETY: The connection is valid. + let display = unsafe { egl::display::Display::new(display_handle) } + .expect("Failed to initialize Wayland EGL platform"); + + // Find a suitable config for the window. + let config_template = glutin::config::ConfigTemplateBuilder::default() + .compatible_with_native_window(window_handle) + .with_surface_type(ConfigSurfaceTypes::WINDOW) + .with_api(glutin::config::Api::GLES2) + .build(); + let config = unsafe { display.find_configs(config_template) } + .unwrap() + .next() + .expect("No available configs"); + let gl_attrs = glutin::context::ContextAttributesBuilder::default() + .with_context_api(glutin::context::ContextApi::OpenGl(None)) + .build(Some(window_handle)); + let gles_attrs = glutin::context::ContextAttributesBuilder::default() + .with_context_api(glutin::context::ContextApi::Gles(None)) + .build(Some(window_handle)); + + let context = unsafe { display.create_context(&config, &gl_attrs) } + .or_else(|_| unsafe { display.create_context(&config, &gles_attrs) }) + .expect("Failed to create context"); + + let surface_attrs = + glutin::surface::SurfaceAttributesBuilder::::default() + .build( + window_handle, + NonZeroU32::new(width).unwrap(), + NonZeroU32::new(height).unwrap(), + ); + let surface = + unsafe { display.create_window_surface(&config, &surface_attrs) } + .expect("Failed to create surface"); + + (display, context, config, surface) +} + +pub fn get_surface( + display: &egl::display::Display, + config: &glutin::api::egl::config::Config, + surface: &wl_surface::WlSurface, + width: u32, + height: u32, +) -> egl::surface::Surface { + let mut window_handle = raw_window_handle::WaylandWindowHandle::empty(); + window_handle.surface = surface.id().as_ptr() as *mut _; + let window_handle = + raw_window_handle::RawWindowHandle::Wayland(window_handle); + let surface_attrs = + glutin::surface::SurfaceAttributesBuilder::::default() + .build( + window_handle, + NonZeroU32::new(width).unwrap(), + NonZeroU32::new(height).unwrap(), + ); + let surface = + unsafe { display.create_window_surface(&config, &surface_attrs) } + .expect("Failed to create surface"); + surface +} diff --git a/sctk/src/error.rs b/sctk/src/error.rs new file mode 100644 index 0000000000..807a8f84f6 --- /dev/null +++ b/sctk/src/error.rs @@ -0,0 +1,23 @@ +use iced_futures::futures; + +/// An error that occurred while running an application. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// The futures executor could not be created. + #[error("the futures executor could not be created")] + ExecutorCreationFailed(futures::io::Error), + + /// The application window could not be created. + #[error("the application window could not be created")] + WindowCreationFailed(Box), + + /// The application graphics context could not be created. + #[error("the application graphics context could not be created")] + GraphicsCreationFailed(iced_graphics::Error), +} + +impl From for Error { + fn from(error: iced_graphics::Error) -> Error { + Error::GraphicsCreationFailed(error) + } +} diff --git a/sctk/src/event_loop/control_flow.rs b/sctk/src/event_loop/control_flow.rs new file mode 100644 index 0000000000..bc920ed478 --- /dev/null +++ b/sctk/src/event_loop/control_flow.rs @@ -0,0 +1,56 @@ +/// Set by the user callback given to the [`EventLoop::run`] method. +/// +/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`] is emitted. +/// +/// Defaults to [`Poll`]. +/// +/// ## Persistency +/// +/// Almost every change is persistent between multiple calls to the event loop closure within a +/// given run loop. The only exception to this is [`ExitWithCode`] which, once set, cannot be unset. +/// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will +/// reset the control flow to [`Poll`]. +/// +/// [`ExitWithCode`]: Self::ExitWithCode +/// [`Poll`]: Self::Poll +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ControlFlow { + /// When the current loop iteration finishes, immediately begin a new iteration regardless of + /// whether or not new events are available to process. + /// + /// ## Platform-specific + /// + /// - **Web:** Events are queued and usually sent when `requestAnimationFrame` fires but sometimes + /// the events in the queue may be sent before the next `requestAnimationFrame` callback, for + /// example when the scaling of the page has changed. This should be treated as an implementation + /// detail which should not be relied on. + Poll, + /// When the current loop iteration finishes, suspend the thread until another event arrives. + Wait, + /// When the current loop iteration finishes, suspend the thread until either another event + /// arrives or the given time is reached. + /// + /// Useful for implementing efficient timers. Applications which want to render at the display's + /// native refresh rate should instead use [`Poll`] and the VSync functionality of a graphics API + /// to reduce odds of missed frames. + /// + /// [`Poll`]: Self::Poll + WaitUntil(std::time::Instant), + /// Send a [`LoopDestroyed`] event and stop the event loop. This variant is *sticky* - once set, + /// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will + /// result in the `control_flow` parameter being reset to `ExitWithCode`. + /// + /// The contained number will be used as exit code. The [`Exit`] constant is a shortcut for this + /// with exit code 0. + /// + /// ## Platform-specific + /// + /// - **Android / iOS / WASM:** The supplied exit code is unused. + /// - **Unix:** On most Unix-like platforms, only the 8 least significant bits will be used, + /// which can cause surprises with negative exit values (`-42` would end up as `214`). See + /// [`std::process::exit`]. + /// + /// [`LoopDestroyed`]: Event::LoopDestroyed + /// [`Exit`]: ControlFlow::Exit + ExitWithCode(i32), +} diff --git a/sctk/src/event_loop/mod.rs b/sctk/src/event_loop/mod.rs new file mode 100644 index 0000000000..bce96ed183 --- /dev/null +++ b/sctk/src/event_loop/mod.rs @@ -0,0 +1,826 @@ +pub mod control_flow; +pub mod proxy; +pub mod state; + +use std::{ + collections::HashMap, + fmt::Debug, + mem, + time::{Duration, Instant}, +}; + +use crate::{ + application::Event, + sctk_event::{ + IcedSctkEvent, LayerSurfaceEventVariant, PopupEventVariant, SctkEvent, + StartCause, SurfaceUserRequest, WindowEventVariant, + }, + settings, +}; + +use iced_native::command::platform_specific::{ + self, + wayland::{ + layer_surface::SctkLayerSurfaceSettings, window::SctkWindowSettings, + }, +}; +use sctk::{ + compositor::CompositorState, + event_loop::WaylandSource, + output::OutputState, + reexports::{ + calloop::{self, EventLoop}, + client::{ + backend::ObjectId, globals::registry_queue_init, + protocol::wl_surface::WlSurface, ConnectError, Connection, + DispatchError, Proxy, + }, + }, + registry::RegistryState, + seat::SeatState, + shell::{ + layer::LayerShell, + xdg::{window::XdgWindowState, XdgShellState}, + }, + shm::ShmState, +}; +use wayland_backend::client::WaylandError; + +use self::{ + control_flow::ControlFlow, + state::{LayerSurfaceCreationError, SctkState}, +}; + +// impl SctkSurface { +// pub fn hash(&self) -> u64 { +// let hasher = DefaultHasher::new(); +// match self { +// SctkSurface::LayerSurface(s) => s.wl_surface().id().hash(.hash(&mut hasher)), +// SctkSurface::Window(s) => s.wl_surface().id().hash(.hash(&mut hasher)), +// SctkSurface::Popup(s) => s.wl_surface().id().hash(.hash(&mut hasher)), +// }; +// hasher.finish() +// } +// } + +#[derive(Debug, Default, Clone, Copy)] +pub struct Features { + // TODO +} + +#[derive(Debug)] +pub struct SctkEventLoop { + // TODO after merged + // pub data_device_manager_state: DataDeviceManagerState, + pub(crate) event_loop: EventLoop<'static, SctkState>, + pub(crate) wayland_dispatcher: + calloop::Dispatcher<'static, WaylandSource>, SctkState>, + pub(crate) features: Features, + /// A proxy to wake up event loop. + pub event_loop_awakener: calloop::ping::Ping, + /// A sender for submitting user events in the event loop + pub user_events_sender: calloop::channel::Sender>, + pub(crate) state: SctkState, +} + +impl SctkEventLoop +where + T: 'static + Debug, +{ + pub(crate) fn new( + _settings: &settings::Settings, + ) -> Result { + let connection = Connection::connect_to_env()?; + let _display = connection.display(); + let (globals, event_queue) = registry_queue_init(&connection).unwrap(); + let event_loop = calloop::EventLoop::>::try_new().unwrap(); + let loop_handle = event_loop.handle(); + + let qh = event_queue.handle(); + let registry_state = RegistryState::new(&globals); + + let (ping, ping_source) = calloop::ping::make_ping().unwrap(); + // TODO + loop_handle + .insert_source(ping_source, |_, _, _state| { + // Drain events here as well to account for application doing batch event processing + // on RedrawEventsCleared. + // shim::handle_window_requests(state); + todo!() + }) + .unwrap(); + let (user_events_sender, user_events_channel) = + calloop::channel::channel(); + + loop_handle + .insert_source(user_events_channel, |event, _, state| match event { + calloop::channel::Event::Msg(e) => { + state.pending_user_events.push(e); + } + calloop::channel::Event::Closed => {} + }) + .unwrap(); + let wayland_source = WaylandSource::new(event_queue).unwrap(); + + let wayland_dispatcher = calloop::Dispatcher::new( + wayland_source, + |_, queue, winit_state| queue.dispatch_pending(winit_state), + ); + + let _wayland_source_dispatcher = event_loop + .handle() + .register_dispatcher(wayland_dispatcher.clone()) + .unwrap(); + + Ok(Self { + event_loop, + wayland_dispatcher, + state: SctkState { + connection, + registry_state, + seat_state: SeatState::new(&globals, &qh), + output_state: OutputState::new(&globals, &qh), + compositor_state: CompositorState::bind(&globals, &qh) + .expect("wl_compositor is not available"), + shm_state: ShmState::bind(&globals, &qh) + .expect("wl_shm is not available"), + xdg_shell_state: XdgShellState::bind(&globals, &qh) + .expect("xdg shell is not available"), + xdg_window_state: XdgWindowState::bind(&globals, &qh), + layer_shell: LayerShell::bind(&globals, &qh).ok(), + + // data_device_manager_state: DataDeviceManagerState::new(), + queue_handle: qh, + loop_handle: loop_handle, + + cursor_surface: None, + multipool: None, + outputs: Vec::new(), + seats: Vec::new(), + windows: Vec::new(), + layer_surfaces: Vec::new(), + popups: Vec::new(), + kbd_focus: None, + window_user_requests: HashMap::new(), + window_compositor_updates: HashMap::new(), + sctk_events: Vec::new(), + popup_compositor_updates: Default::default(), + layer_surface_compositor_updates: Default::default(), + layer_surface_user_requests: Default::default(), + popup_user_requests: Default::default(), + pending_user_events: Vec::new(), + }, + features: Default::default(), + event_loop_awakener: ping, + user_events_sender, + }) + } + + pub fn proxy(&self) -> proxy::Proxy> { + proxy::Proxy::new(self.user_events_sender.clone()) + } + + pub fn get_layer_surface( + &mut self, + layer_surface: SctkLayerSurfaceSettings, + ) -> Result<(iced_native::window::Id, WlSurface), LayerSurfaceCreationError> + { + self.state.get_layer_surface(layer_surface) + } + + pub fn get_window( + &mut self, + settings: SctkWindowSettings, + ) -> (iced_native::window::Id, WlSurface) { + self.state.get_window(settings) + } + + pub fn run_return(&mut self, mut callback: F) -> i32 + where + F: FnMut(IcedSctkEvent, &SctkState, &mut ControlFlow), + { + let mut control_flow = ControlFlow::Poll; + + callback( + IcedSctkEvent::NewEvents(StartCause::Init), + &self.state, + &mut control_flow, + ); + + let mut surface_user_requests: Vec<(ObjectId, SurfaceUserRequest)> = + Vec::new(); + + let mut event_sink_back_buffer = Vec::new(); + + // NOTE We break on errors from dispatches, since if we've got protocol error + // libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not + // really an option. Instead we inform that the event loop got destroyed. We may + // communicate an error that something was terminated, but winit doesn't provide us + // with an API to do that via some event. + // Still, we set the exit code to the error's OS error code, or to 1 if not possible. + let exit_code = loop { + // Send pending events to the server. + match self.state.connection.flush() { + Ok(_) => {} + Err(error) => { + break match error { + WaylandError::Io(err) => err.raw_os_error(), + WaylandError::Protocol(_) => None, + } + .unwrap_or(1) + } + } + + // During the run of the user callback, some other code monitoring and reading the + // Wayland socket may have been run (mesa for example does this with vsync), if that + // is the case, some events may have been enqueued in our event queue. + // + // If some messages are there, the event loop needs to behave as if it was instantly + // woken up by messages arriving from the Wayland socket, to avoid delaying the + // dispatch of these events until we're woken up again. + let instant_wakeup = { + let mut wayland_source = + self.wayland_dispatcher.as_source_mut(); + let queue = wayland_source.queue(); + match queue.dispatch_pending(&mut self.state) { + Ok(dispatched) => dispatched > 0, + // TODO better error handling + Err(error) => { + break match error { + DispatchError::BadMessage { .. } => None, + DispatchError::Backend(err) => match err { + WaylandError::Io(err) => err.raw_os_error(), + WaylandError::Protocol(_) => None, + }, + } + .unwrap_or(1) + } + } + }; + + match control_flow { + ControlFlow::ExitWithCode(code) => break code, + ControlFlow::Poll => { + // Non-blocking dispatch. + let timeout = Duration::from_millis(0); + if let Err(error) = + self.event_loop.dispatch(Some(timeout), &mut self.state) + { + break raw_os_err(error); + } + + callback( + IcedSctkEvent::NewEvents(StartCause::Poll), + &self.state, + &mut control_flow, + ); + } + ControlFlow::Wait => { + let timeout = if instant_wakeup { + Some(Duration::from_millis(0)) + } else { + None + }; + + if let Err(error) = + self.event_loop.dispatch(timeout, &mut self.state) + { + break raw_os_err(error); + } + + callback( + IcedSctkEvent::NewEvents(StartCause::WaitCancelled { + start: Instant::now(), + requested_resume: None, + }), + &self.state, + &mut control_flow, + ); + } + ControlFlow::WaitUntil(deadline) => { + let start = Instant::now(); + + // Compute the amount of time we'll block for. + let duration = if deadline > start && !instant_wakeup { + deadline - start + } else { + Duration::from_millis(0) + }; + + if let Err(error) = self + .event_loop + .dispatch(Some(duration), &mut self.state) + { + break raw_os_err(error); + } + + let now = Instant::now(); + + if now < deadline { + callback( + IcedSctkEvent::NewEvents( + StartCause::WaitCancelled { + start, + requested_resume: Some(deadline), + }, + ), + &self.state, + &mut control_flow, + ) + } else { + callback( + IcedSctkEvent::NewEvents( + StartCause::ResumeTimeReached { + start, + requested_resume: deadline, + }, + ), + &self.state, + &mut control_flow, + ) + } + } + } + + // The purpose of the back buffer and that swap is to not hold borrow_mut when + // we're doing callback to the user, since we can double borrow if the user decides + // to create a window in one of those callbacks. + std::mem::swap( + &mut event_sink_back_buffer, + &mut self.state.sctk_events, + ); + + // Handle pending sctk events. + let mut must_redraw = Vec::new(); + + for event in event_sink_back_buffer.drain(..) { + match event { + SctkEvent::Draw(id) => must_redraw.push(id), + SctkEvent::PopupEvent { + variant: PopupEventVariant::Done, + toplevel_id, + parent_id, + id, + } => { + match self + .state + .popups + .iter() + .position(|s| s.popup.wl_surface().id() == id.id()) + { + Some(p) => { + let _p = self.state.popups.remove(p); + sticky_exit_callback( + IcedSctkEvent::SctkEvent( + SctkEvent::PopupEvent { + variant: PopupEventVariant::Done, + toplevel_id, + parent_id, + id, + }, + ), + &self.state, + &mut control_flow, + &mut callback, + ); + } + None => continue, + }; + } + SctkEvent::LayerSurfaceEvent { + variant: LayerSurfaceEventVariant::Done, + id, + } => { + if let Some(i) = + self.state.layer_surfaces.iter().position(|l| { + l.surface.wl_surface().id() == id.id() + }) + { + let _l = self.state.layer_surfaces.remove(i); + sticky_exit_callback( + IcedSctkEvent::SctkEvent( + SctkEvent::LayerSurfaceEvent { + variant: LayerSurfaceEventVariant::Done, + id, + }, + ), + &self.state, + &mut control_flow, + &mut callback, + ); + } + } + SctkEvent::WindowEvent { + variant: WindowEventVariant::Close, + id, + } => { + if let Some(i) = + self.state.layer_surfaces.iter().position(|l| { + l.surface.wl_surface().id() == id.id() + }) + { + let w = self.state.windows.remove(i); + w.window.xdg_toplevel().destroy(); + sticky_exit_callback( + IcedSctkEvent::SctkEvent( + SctkEvent::WindowEvent { + variant: WindowEventVariant::Close, + id, + }, + ), + &self.state, + &mut control_flow, + &mut callback, + ); + } + } + _ => sticky_exit_callback( + IcedSctkEvent::SctkEvent(event), + &self.state, + &mut control_flow, + &mut callback, + ), + } + } + + // handle events indirectly via callback to the user. + let (sctk_events, user_events): (Vec<_>, Vec<_>) = self + .state + .pending_user_events + .drain(..) + .partition(|e| matches!(e, Event::SctkEvent(_))); + let mut to_commit = HashMap::new(); + for event in sctk_events.into_iter().chain(user_events.into_iter()) + { + match event { + Event::SctkEvent(event) => { + sticky_exit_callback(event, &self.state, &mut control_flow, &mut callback) + } + Event::LayerSurface(action) => match action { + platform_specific::wayland::layer_surface::Action::LayerSurface { + builder, + _phantom, + } => { + // TODO ASHLEY: error handling + if let Ok((id, wl_surface)) = self.state.get_layer_surface(builder) { + let object_id = wl_surface.id(); + sticky_exit_callback( + IcedSctkEvent::SctkEvent(SctkEvent::LayerSurfaceEvent { + variant: LayerSurfaceEventVariant::Created(object_id.clone(), id), + id: wl_surface.clone(), + }), + &self.state, + &mut control_flow, + &mut callback, + ); + } + } + platform_specific::wayland::layer_surface::Action::Size { + id, + width, + height, + } => { + if let Some(layer_surface) = self.state.layer_surfaces.iter_mut().find(|l| l.id == id) { + layer_surface.requested_size = (width, height); + layer_surface.surface.set_size(width.unwrap_or_default(), height.unwrap_or_default()); + to_commit.insert(id, layer_surface.surface.wl_surface().clone()); + } + }, + platform_specific::wayland::layer_surface::Action::Destroy(id) => { + if let Some(i) = self.state.layer_surfaces.iter().position(|l| &l.id == &id) { + let l = self.state.layer_surfaces.remove(i); + sticky_exit_callback( + IcedSctkEvent::SctkEvent(SctkEvent::LayerSurfaceEvent { + variant: LayerSurfaceEventVariant::Done, + id: l.surface.wl_surface().clone(), + }), + &self.state, + &mut control_flow, + &mut callback, + ); + } + }, + platform_specific::wayland::layer_surface::Action::Anchor { id, anchor } => { + if let Some(layer_surface) = self.state.layer_surfaces.iter_mut().find(|l| l.id == id) { + layer_surface.anchor = anchor; + layer_surface.surface.set_anchor(anchor); + to_commit.insert(id, layer_surface.surface.wl_surface().clone()); + + } + } + platform_specific::wayland::layer_surface::Action::ExclusiveZone { + id, + exclusive_zone, + } => { + if let Some(layer_surface) = self.state.layer_surfaces.iter_mut().find(|l| l.id == id) { + layer_surface.exclusive_zone = exclusive_zone; + layer_surface.surface.set_exclusive_zone(exclusive_zone); + to_commit.insert(id, layer_surface.surface.wl_surface().clone()); + } + }, + platform_specific::wayland::layer_surface::Action::Margin { + id, + margin, + } => { + if let Some(layer_surface) = self.state.layer_surfaces.iter_mut().find(|l| l.id == id) { + layer_surface.margin = margin; + layer_surface.surface.set_margin(margin.top, margin.right, margin.bottom, margin.left); + to_commit.insert(id, layer_surface.surface.wl_surface().clone()); + } + }, + platform_specific::wayland::layer_surface::Action::KeyboardInteractivity { id, keyboard_interactivity } => { + if let Some(layer_surface) = self.state.layer_surfaces.iter_mut().find(|l| l.id == id) { + layer_surface.keyboard_interactivity = keyboard_interactivity; + layer_surface.surface.set_keyboard_interactivity(keyboard_interactivity); + to_commit.insert(id, layer_surface.surface.wl_surface().clone()); + + } + }, + platform_specific::wayland::layer_surface::Action::Layer { id, layer } => { + if let Some(layer_surface) = self.state.layer_surfaces.iter_mut().find(|l| l.id == id) { + layer_surface.layer = layer; + layer_surface.surface.set_layer(layer); + to_commit.insert(id, layer_surface.surface.wl_surface().clone()); + + } + }, + }, + Event::SetCursor(_) => { + // TODO set cursor after cursor theming PR is merged + // https://github.com/Smithay/client-toolkit/pull/306 + } + Event::Window(action) => match action { + platform_specific::wayland::window::Action::Window { builder, _phantom } => { + let (id, wl_surface) = self.state.get_window(builder); + let object_id = wl_surface.id(); + sticky_exit_callback( + IcedSctkEvent::SctkEvent(SctkEvent::WindowEvent { variant: WindowEventVariant::Created(object_id.clone(), id), id: wl_surface.clone() }), + &self.state, + &mut control_flow, + &mut callback, + ); + }, + platform_specific::wayland::window::Action::Size { id, width, height } => { + if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { + window.requested_size = Some((width, height)); + window.window.xdg_surface().set_window_geometry(0, 0, width.max(1) as i32, height.max(1) as i32); + to_commit.insert(id, window.window.wl_surface().clone()); + // TODO Ashley maybe don't force window size? + if let Some(mut prev_configure) = window.last_configure.clone() { + prev_configure.new_size = Some((width, height)); + sticky_exit_callback( + IcedSctkEvent::SctkEvent(SctkEvent::WindowEvent { variant: WindowEventVariant::Configure(prev_configure, window.window.wl_surface().clone(), false), id: window.window.wl_surface().clone()}), + &self.state, + &mut control_flow, + &mut callback, + ); + } + } + }, + platform_specific::wayland::window::Action::MinSize { id, size } => { + if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { + window.window.set_min_size(size); + to_commit.insert(id, window.window.wl_surface().clone()); + } + }, + platform_specific::wayland::window::Action::MaxSize { id, size } => { + if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { + window.window.set_max_size(size); + to_commit.insert(id, window.window.wl_surface().clone()); + } + }, + platform_specific::wayland::window::Action::Title { id, title } => { + if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { + window.window.set_title(title); + to_commit.insert(id, window.window.wl_surface().clone()); + } + }, + platform_specific::wayland::window::Action::Minimize { id } => { + if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { + window.window.set_mimimized(); + to_commit.insert(id, window.window.wl_surface().clone()); + } + }, + platform_specific::wayland::window::Action::Maximize { id } => { + if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { + window.window.set_maximized(); + to_commit.insert(id, window.window.wl_surface().clone()); + } + }, + platform_specific::wayland::window::Action::UnsetMaximize { id } => { + if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { + window.window.unset_maximized(); + to_commit.insert(id, window.window.wl_surface().clone()); + } + }, + platform_specific::wayland::window::Action::Fullscreen { id } => { + if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { + // TODO ASHLEY: allow specific output to be requested for fullscreen? + window.window.set_fullscreen(None); + to_commit.insert(id, window.window.wl_surface().clone()); + } + }, + platform_specific::wayland::window::Action::UnsetFullscreen { id } => { + if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { + window.window.unset_fullscreen(); + to_commit.insert(id, window.window.wl_surface().clone()); + } + }, + platform_specific::wayland::window::Action::InteractiveMove { id } => { + if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { + todo!(); + } + }, + platform_specific::wayland::window::Action::InteractiveResize { id, edge } => todo!(), + platform_specific::wayland::window::Action::ShowWindowMenu { id, x, y } => todo!(), + platform_specific::wayland::window::Action::Destroy(id) => { + if let Some(i) = self.state.windows.iter().position(|l| &l.id == &id) { + let window = self.state.windows.remove(i); + window.window.xdg_toplevel().destroy(); + sticky_exit_callback( + IcedSctkEvent::SctkEvent(SctkEvent::WindowEvent { + variant: WindowEventVariant::Close, + id: window.window.wl_surface().clone(), + }), + &self.state, + &mut control_flow, + &mut callback, + ); + } + }, + }, + Event::Popup(action) => match action { + platform_specific::wayland::popup::Action::Popup { popup, .. } => { + if let Ok((id, parent_id, toplevel_id, wl_surface)) = self.state.get_popup(popup) { + let object_id = wl_surface.id(); + sticky_exit_callback( + IcedSctkEvent::SctkEvent(SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Created(object_id.clone(), id), toplevel_id, parent_id, id: wl_surface.clone() }), + &self.state, + &mut control_flow, + &mut callback, + ); + } + }, + // XXX popup destruction must be done carefully + // first destroy the uppermost popup, then work down to the requested popup + platform_specific::wayland::popup::Action::Destroy { id } => { + let sctk_popup = match self.state + .popups + .iter() + .position(|s| s.id == id) + { + Some(p) => self.state.popups.remove(p), + None => continue, + }; + let mut to_destroy = vec![sctk_popup]; + while let Some(popup_to_destroy) = to_destroy.last() { + match popup_to_destroy.parent.clone() { + state::SctkSurface::LayerSurface(_) | state::SctkSurface::Window(_) => { + break; + } + state::SctkSurface::Popup(popup_to_destroy_first) => { + let popup_to_destroy_first = self + .state + .popups + .iter() + .position(|p| p.popup.wl_surface() == &popup_to_destroy_first) + .unwrap(); + let popup_to_destroy_first = self.state.popups.remove(popup_to_destroy_first); + to_destroy.push(popup_to_destroy_first); + } + } + } + for popup in to_destroy.into_iter().rev() { + sticky_exit_callback(IcedSctkEvent::SctkEvent(SctkEvent::PopupEvent { + variant: PopupEventVariant::Done, + toplevel_id: popup.toplevel.clone(), + parent_id: popup.parent.wl_surface().clone(), + id: popup.popup.wl_surface().clone(), + }), + &self.state, + &mut control_flow, + &mut callback, + ); + } + }, + platform_specific::wayland::popup::Action::Reposition { id, positioner } => todo!(), + platform_specific::wayland::popup::Action::Grab { id } => todo!(), + }, + } + } + + // commit changes made via actions + for s in to_commit { + s.1.commit(); + } + + // Send events cleared. + sticky_exit_callback( + IcedSctkEvent::MainEventsCleared, + &self.state, + &mut control_flow, + &mut callback, + ); + + // Apply user requests, so every event required resize and latter surface commit will + // be applied right before drawing. This will also ensure that every `RedrawRequested` + // event will be delivered in time. + // Process 'new' pending updates from compositor. + surface_user_requests.clear(); + surface_user_requests.extend( + self.state.window_user_requests.iter_mut().map( + |(wid, window_request)| { + (wid.clone(), mem::take(window_request)) + }, + ), + ); + + // Handle RedrawRequested requests. + for (surface_id, mut surface_request) in + surface_user_requests.iter() + { + if let Some(i) = + must_redraw.iter().position(|a_id| &a_id.id() == surface_id) + { + must_redraw.remove(i); + } + let wl_suface = self + .state + .windows + .iter() + .map(|w| w.window.wl_surface()) + .chain( + self.state + .layer_surfaces + .iter() + .map(|l| l.surface.wl_surface()), + ) + .find(|s| s.id() == *surface_id) + .unwrap(); + + // Handle refresh of the frame. + if surface_request.refresh_frame { + // In general refreshing the frame requires surface commit, those force user + // to redraw. + surface_request.redraw_requested = true; + } + + // Handle redraw request. + if surface_request.redraw_requested { + sticky_exit_callback( + IcedSctkEvent::RedrawRequested(surface_id.clone()), + &self.state, + &mut control_flow, + &mut callback, + ); + } + wl_suface.commit(); + } + + for id in must_redraw { + sticky_exit_callback( + IcedSctkEvent::RedrawRequested(id.id()), + &self.state, + &mut control_flow, + &mut callback, + ); + } + + // Send RedrawEventCleared. + sticky_exit_callback( + IcedSctkEvent::RedrawEventsCleared, + &self.state, + &mut control_flow, + &mut callback, + ); + }; + + callback(IcedSctkEvent::LoopDestroyed, &self.state, &mut control_flow); + exit_code + } +} + +fn sticky_exit_callback( + evt: IcedSctkEvent, + target: &SctkState, + control_flow: &mut ControlFlow, + callback: &mut F, +) where + F: FnMut(IcedSctkEvent, &SctkState, &mut ControlFlow), +{ + // make ControlFlow::ExitWithCode sticky by providing a dummy + // control flow reference if it is already ExitWithCode. + if let ControlFlow::ExitWithCode(code) = *control_flow { + callback(evt, target, &mut ControlFlow::ExitWithCode(code)) + } else { + callback(evt, target, control_flow) + } +} + +fn raw_os_err(err: calloop::Error) -> i32 { + match err { + calloop::Error::IoError(err) => err.raw_os_error(), + _ => None, + } + .unwrap_or(1) +} diff --git a/sctk/src/event_loop/proxy.rs b/sctk/src/event_loop/proxy.rs new file mode 100644 index 0000000000..be2e469fdc --- /dev/null +++ b/sctk/src/event_loop/proxy.rs @@ -0,0 +1,66 @@ +use iced_native::futures::{ + channel::mpsc, + task::{Context, Poll}, + Sink, +}; +use sctk::reexports::calloop; +use std::pin::Pin; + +/// An event loop proxy that implements `Sink`. +#[derive(Debug)] +pub struct Proxy { + raw: calloop::channel::Sender, +} + +impl Clone for Proxy { + fn clone(&self) -> Self { + Self { + raw: self.raw.clone(), + } + } +} + +impl Proxy { + /// Creates a new [`Proxy`] from an `EventLoopProxy`. + pub fn new(raw: calloop::channel::Sender) -> Self { + Self { raw } + } + /// send an event + pub fn send_event(&self, message: Message) { + let _ = self.raw.send(message); + } +} + +impl Sink for Proxy { + type Error = mpsc::SendError; + + fn poll_ready( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + + fn start_send( + self: Pin<&mut Self>, + message: Message, + ) -> Result<(), Self::Error> { + let _ = self.raw.send(message); + + Ok(()) + } + + fn poll_flush( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_close( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } +} diff --git a/sctk/src/event_loop/state.rs b/sctk/src/event_loop/state.rs new file mode 100644 index 0000000000..9e9fc36a0f --- /dev/null +++ b/sctk/src/event_loop/state.rs @@ -0,0 +1,497 @@ +use std::{collections::HashMap, fmt::Debug, sync::Arc}; + +use crate::{ + application::Event, + dpi::LogicalSize, + sctk_event::{SctkEvent, SurfaceCompositorUpdate, SurfaceUserRequest}, +}; + +use iced_native::{ + command::platform_specific::{ + self, + wayland::{ + layer_surface::{IcedMargin, SctkLayerSurfaceSettings}, + popup::SctkPopupSettings, + window::SctkWindowSettings, + }, + }, + keyboard::Modifiers, + window, +}; +use sctk::{ + compositor::CompositorState, + error::GlobalError, + output::OutputState, + reexports::{ + calloop::LoopHandle, + client::{ + backend::ObjectId, + protocol::{ + wl_data_device::WlDataDevice, + wl_keyboard::WlKeyboard, + wl_output::WlOutput, + wl_pointer::WlPointer, + wl_seat::WlSeat, + wl_surface::{self, WlSurface}, + wl_touch::WlTouch, + }, + Connection, Proxy, QueueHandle, + }, + }, + registry::RegistryState, + seat::{keyboard::KeyEvent, SeatState}, + shell::{ + layer::{ + Anchor, KeyboardInteractivity, Layer, LayerShell, LayerSurface, + LayerSurfaceConfigure, + }, + xdg::{ + popup::{Popup, PopupConfigure}, + window::{ + Window, WindowConfigure, WindowDecorations, XdgWindowState, + }, + XdgPositioner, XdgShellState, XdgShellSurface, + }, + }, + shm::{multi::MultiPool, ShmState}, +}; + +#[derive(Debug, Clone)] +pub(crate) struct SctkSeat { + pub(crate) seat: WlSeat, + pub(crate) kbd: Option, + pub(crate) kbd_focus: Option, + pub(crate) last_kbd_press: Option, + pub(crate) ptr: Option, + pub(crate) ptr_focus: Option, + pub(crate) last_ptr_press: Option<(u32, u32, u32)>, // (time, button, serial) + pub(crate) touch: Option, + pub(crate) data_device: Option, + pub(crate) modifiers: Modifiers, +} + +#[derive(Debug, Clone)] +pub struct SctkWindow { + pub(crate) id: iced_native::window::Id, + pub(crate) window: Window, + pub(crate) requested_size: Option<(u32, u32)>, + pub(crate) current_size: Option<(u32, u32)>, + pub(crate) last_configure: Option, + /// Requests that SCTK window should perform. + pub(crate) pending_requests: + Vec>, +} + +#[derive(Debug, Clone)] +pub struct SctkLayerSurface { + pub(crate) id: iced_native::window::Id, + pub(crate) surface: LayerSurface, + pub(crate) requested_size: (Option, Option), + pub(crate) current_size: Option>, + pub(crate) layer: Layer, + pub(crate) anchor: Anchor, + pub(crate) keyboard_interactivity: KeyboardInteractivity, + pub(crate) margin: IcedMargin, + pub(crate) exclusive_zone: i32, + pub(crate) last_configure: Option, + pub(crate) pending_requests: + Vec>, +} + +#[derive(Debug, Clone)] +pub enum SctkSurface { + LayerSurface(WlSurface), + Window(WlSurface), + Popup(WlSurface), +} + +impl SctkSurface { + pub fn wl_surface(&self) -> &WlSurface { + match self { + SctkSurface::LayerSurface(s) + | SctkSurface::Window(s) + | SctkSurface::Popup(s) => s, + } + } +} + +#[derive(Debug, Clone)] +pub struct SctkPopup { + pub(crate) id: iced_native::window::Id, + pub(crate) popup: Popup, + pub(crate) parent: SctkSurface, + pub(crate) toplevel: WlSurface, + pub(crate) requested_size: (u32, u32), + pub(crate) last_configure: Option, + // pub(crate) positioner: XdgPositioner, + pub(crate) pending_requests: + Vec>, +} + +/// Wrapper to carry sctk state. +#[derive(Debug)] +pub struct SctkState { + // egl + // pub(crate) context: Option, + // pub(crate) glow: Option, + // pub(crate) display: Option, + // pub(crate) config: Option, + /// the cursor wl_surface + pub(crate) cursor_surface: Option, + /// a memory pool + pub(crate) multipool: Option>, + + // all present outputs + pub(crate) outputs: Vec, + // though (for now) only one seat will be active in an iced application at a time, all ought to be tracked + // Active seat is the first seat in the list + pub(crate) seats: Vec, + // Windows / Surfaces + /// Window list containing all SCTK windows. Since those windows aren't allowed + /// to be sent to other threads, they live on the event loop's thread + /// and requests from winit's windows are being forwarded to them either via + /// `WindowUpdate` or buffer on the associated with it `WindowHandle`. + pub(crate) windows: Vec>, + pub(crate) layer_surfaces: Vec>, + pub(crate) popups: Vec>, + pub(crate) kbd_focus: Option, + + /// Window updates, which are coming from SCTK or the compositor, which require + /// calling back to the sctk's downstream. They are handled right in the event loop, + /// unlike the ones coming from buffers on the `WindowHandle`'s. + pub popup_compositor_updates: HashMap, + /// Window updates, which are coming from SCTK or the compositor, which require + /// calling back to the sctk's downstream. They are handled right in the event loop, + /// unlike the ones coming from buffers on the `WindowHandle`'s. + pub window_compositor_updates: HashMap, + /// Layer Surface updates, which are coming from SCTK or the compositor, which require + /// calling back to the sctk's downstream. They are handled right in the event loop, + /// unlike the ones coming from buffers on the `WindowHandle`'s. + pub layer_surface_compositor_updates: + HashMap, + + /// A sink for window and device events that is being filled during dispatching + /// event loop and forwarded downstream afterwards. + pub(crate) sctk_events: Vec, + /// Window updates comming from the user requests. Those are separatelly dispatched right after + /// `MainEventsCleared`. + pub window_user_requests: HashMap, + /// Layer Surface updates comming from the user requests. Those are separatelly dispatched right after + /// `MainEventsCleared`. + pub layer_surface_user_requests: HashMap, + /// Window updates comming from the user requests. Those are separatelly dispatched right after + /// `MainEventsCleared`. + pub popup_user_requests: HashMap, + + /// pending user events + pub pending_user_events: Vec>, + + // handles + pub(crate) queue_handle: QueueHandle, + pub(crate) loop_handle: LoopHandle<'static, Self>, + + // sctk state objects + pub(crate) registry_state: RegistryState, + pub(crate) seat_state: SeatState, + pub(crate) output_state: OutputState, + pub(crate) compositor_state: CompositorState, + pub(crate) shm_state: ShmState, + pub(crate) xdg_shell_state: XdgShellState, + pub(crate) xdg_window_state: XdgWindowState, + pub(crate) layer_shell: Option, + + pub(crate) connection: Connection, +} + +/// An error that occurred while running an application. +#[derive(Debug, thiserror::Error)] +pub enum PopupCreationError { + /// Positioner creation failed + #[error("Positioner creation failed")] + PositionerCreationFailed(GlobalError), + + /// The specified parent is missing + #[error("The specified parent is missing")] + ParentMissing, + + /// Popup creation failed + #[error("Popup creation failed")] + PopupCreationFailed(GlobalError), +} + +/// An error that occurred while running an application. +#[derive(Debug, thiserror::Error)] +pub enum LayerSurfaceCreationError { + /// Layer shell is not supported by the compositor + #[error("Layer shell is not supported by the compositor")] + LayerShellNotSupported, + + /// WlSurface creation failed + #[error("WlSurface creation failed")] + WlSurfaceCreationFailed(GlobalError), + + /// LayerSurface creation failed + #[error("Layer Surface creation failed")] + LayerSurfaceCreationFailed(GlobalError), +} + +impl SctkState +where + T: 'static + Debug, +{ + pub fn get_popup( + &mut self, + settings: SctkPopupSettings, + ) -> Result<(window::Id, WlSurface, WlSurface, WlSurface), PopupCreationError> + { + let positioner = XdgPositioner::new(&self.xdg_shell_state) + .map_err(|e| PopupCreationError::PositionerCreationFailed(e))?; + positioner.set_anchor(settings.positioner.anchor); + positioner.set_anchor_rect( + settings.positioner.anchor_rect.x, + settings.positioner.anchor_rect.y, + settings.positioner.anchor_rect.width, + settings.positioner.anchor_rect.height, + ); + positioner.set_constraint_adjustment( + settings.positioner.constraint_adjustment, + ); + positioner.set_gravity(settings.positioner.gravity); + positioner.set_offset( + settings.positioner.offset.0, + settings.positioner.offset.1, + ); + if settings.positioner.reactive { + positioner.set_reactive(); + } + positioner.set_size( + settings.positioner.size.0 as i32, + settings.positioner.size.1 as i32, + ); + + if let Some(parent) = + self.layer_surfaces.iter().find(|l| l.id == settings.parent) + { + let wl_surface = + self.compositor_state.create_surface(&self.queue_handle); + let popup = Popup::from_surface( + None, + &positioner, + &self.queue_handle, + wl_surface.clone(), + &self.xdg_shell_state, + ) + .map_err(|e| PopupCreationError::PopupCreationFailed(e))?; + + parent.surface.get_popup(popup.xdg_popup()); + wl_surface.commit(); + self.popups.push(SctkPopup { + id: settings.id, + popup: popup.clone(), + parent: SctkSurface::LayerSurface( + parent.surface.wl_surface().clone(), + ), + toplevel: parent.surface.wl_surface().clone(), + requested_size: settings.positioner.size, + last_configure: None, + pending_requests: Default::default(), + }); + Ok(( + settings.id, + parent.surface.wl_surface().clone(), + parent.surface.wl_surface().clone(), + popup.wl_surface().clone(), + )) + } else if let Some(parent) = + self.windows.iter().find(|w| w.id == settings.parent) + { + let popup = Popup::new( + parent.window.xdg_surface(), + &positioner, + &self.queue_handle, + &self.compositor_state, + &self.xdg_shell_state, + ) + .map_err(|e| PopupCreationError::PopupCreationFailed(e))?; + self.popups.push(SctkPopup { + id: settings.id, + popup: popup.clone(), + parent: SctkSurface::Window(parent.window.wl_surface().clone()), + toplevel: parent.window.wl_surface().clone(), + requested_size: settings.positioner.size, + last_configure: None, + pending_requests: Default::default(), + }); + Ok(( + settings.id, + parent.window.wl_surface().clone(), + parent.window.wl_surface().clone(), + popup.wl_surface().clone(), + )) + } else if let Some(i) = + self.popups.iter().position(|p| p.id == settings.parent) + { + let (popup, parent, toplevel) = { + let parent = &self.popups[i]; + ( + Popup::new( + parent.popup.xdg_surface(), + &positioner, + &self.queue_handle, + &self.compositor_state, + &self.xdg_shell_state, + ) + .map_err(|e| PopupCreationError::PopupCreationFailed(e))?, + parent.popup.wl_surface().clone(), + parent.toplevel.clone(), + ) + }; + self.popups.push(SctkPopup { + id: settings.id, + popup: popup.clone(), + parent: SctkSurface::Popup(parent.clone()), + toplevel: toplevel.clone(), + requested_size: settings.positioner.size, + last_configure: None, + pending_requests: Default::default(), + }); + Ok((settings.id, parent, toplevel, popup.wl_surface().clone())) + } else { + Err(PopupCreationError::ParentMissing) + } + } + + pub fn get_window( + &mut self, + settings: SctkWindowSettings, + ) -> (window::Id, WlSurface) { + let SctkWindowSettings { + iced_settings: + window::Settings { + size, + min_size, + max_size, + decorations, + transparent, + icon, + .. + }, + window_id, + app_id, + title, + parent, + } = settings; + // TODO Ashley: set window as opaque if transparency is false + // TODO Ashley: set icon + // TODO Ashley: save settings for window + // TODO Ashley: decorations + let wl_surface = + self.compositor_state.create_surface(&self.queue_handle); + let mut builder = if let Some(app_id) = app_id { + Window::builder().app_id(app_id) + } else { + Window::builder() + }; + builder = if let Some(min_size) = min_size { + builder.min_size(min_size) + } else { + builder + }; + builder = if let Some(max_size) = max_size { + builder.max_size(max_size) + } else { + builder + }; + builder = if let Some(title) = title { + builder.title(title) + } else { + builder + }; + + // builder = if let Some(parent) = parent.and_then(|p| self.windows.iter().find(|w| w.window.wl_surface().id() == p)) { + // builder.parent(&parent.window) + // } else { + // builder + // }; + let window = builder + .decorations(if decorations { + WindowDecorations::RequestServer + } else { + WindowDecorations::RequestClient + }) + .map( + &self.queue_handle, + &self.xdg_shell_state, + &mut self.xdg_window_state, + wl_surface.clone(), + ) + .expect("failed to create window"); + + window.xdg_surface().set_window_geometry( + 0, + 0, + size.0 as i32, + size.1 as i32, + ); + window.wl_surface().commit(); + self.windows.push(SctkWindow { + id: window_id, + window, + requested_size: Some(size), + current_size: Some((1, 1)), + last_configure: None, + pending_requests: Vec::new(), + }); + (window_id, wl_surface) + } + + pub fn get_layer_surface( + &mut self, + SctkLayerSurfaceSettings { + id, + layer, + keyboard_interactivity, + anchor, + output, + namespace, + margin, + size, + exclusive_zone, + }: SctkLayerSurfaceSettings, + ) -> Result<(iced_native::window::Id, WlSurface), LayerSurfaceCreationError> + { + let layer_shell = self + .layer_shell + .as_ref() + .ok_or(LayerSurfaceCreationError::LayerShellNotSupported)?; + let wl_surface = + self.compositor_state.create_surface(&self.queue_handle); + + let layer_surface = LayerSurface::builder() + .anchor(anchor) + .keyboard_interactivity(keyboard_interactivity) + .margin(margin.top, margin.right, margin.bottom, margin.left) + .size((size.0.unwrap_or_default(), size.1.unwrap_or_default())) + .namespace(namespace) + .exclusive_zone(exclusive_zone) + .map(&self.queue_handle, layer_shell, wl_surface.clone(), layer) + .map_err(|g_err| { + LayerSurfaceCreationError::LayerSurfaceCreationFailed(g_err) + })?; + self.layer_surfaces.push(SctkLayerSurface { + id, + surface: layer_surface, + requested_size: (None, None), + current_size: None, + layer, + // builder needs to be refactored such that these fields are accessible + anchor, + keyboard_interactivity, + margin, + exclusive_zone, + last_configure: None, + pending_requests: Vec::new(), + }); + Ok((id, wl_surface)) + } +} diff --git a/sctk/src/handlers/compositor.rs b/sctk/src/handlers/compositor.rs new file mode 100644 index 0000000000..b88b884078 --- /dev/null +++ b/sctk/src/handlers/compositor.rs @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MPL-2.0-only +use sctk::{ + compositor::CompositorHandler, + delegate_compositor, + reexports::client::{protocol::wl_surface, Connection, Proxy, QueueHandle}, +}; +use std::fmt::Debug; + +use crate::{event_loop::state::SctkState, sctk_event::SctkEvent}; + +impl CompositorHandler for SctkState { + fn scale_factor_changed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + surface: &wl_surface::WlSurface, + new_factor: i32, + ) { + if let Some(w) = self + .windows + .iter() + .find(|w| w.window.wl_surface().id() == surface.id()) + { + if let Some(e) = + self.window_compositor_updates.get_mut(&surface.id()) + { + e.scale_factor = Some(new_factor) + } + } + if let Some(w) = self + .layer_surfaces + .iter() + .find(|w| w.surface.wl_surface().id() == surface.id()) + { + if let Some(e) = + self.layer_surface_compositor_updates.get_mut(&surface.id()) + { + e.scale_factor = Some(new_factor) + } + } + if let Some(w) = self + .popups + .iter() + .find(|w| w.popup.wl_surface().id() == surface.id()) + { + if let Some(e) = + self.popup_compositor_updates.get_mut(&surface.id()) + { + e.scale_factor = Some(new_factor) + } + } + } + + fn frame( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + surface: &wl_surface::WlSurface, + _time: u32, + ) { + self.sctk_events.push(SctkEvent::Draw(surface.clone())); + } +} + +delegate_compositor!(@ SctkState); diff --git a/sctk/src/handlers/data_device/data_device.rs b/sctk/src/handlers/data_device/data_device.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sctk/src/handlers/data_device/data_offer.rs b/sctk/src/handlers/data_device/data_offer.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sctk/src/handlers/data_device/data_source.rs b/sctk/src/handlers/data_device/data_source.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sctk/src/handlers/data_device/mod.rs b/sctk/src/handlers/data_device/mod.rs new file mode 100644 index 0000000000..d7de6bb99a --- /dev/null +++ b/sctk/src/handlers/data_device/mod.rs @@ -0,0 +1 @@ +// TODO after merge diff --git a/sctk/src/handlers/mod.rs b/sctk/src/handlers/mod.rs new file mode 100644 index 0000000000..6356483934 --- /dev/null +++ b/sctk/src/handlers/mod.rs @@ -0,0 +1,37 @@ +// handlers +pub mod compositor; +pub mod data_device; +pub mod output; +pub mod seat; +pub mod shell; + +use sctk::{ + delegate_registry, delegate_shm, + output::OutputState, + registry::{ProvidesRegistryState, RegistryState}, + registry_handlers, + seat::SeatState, + shm::{ShmHandler, ShmState}, +}; +use std::fmt::Debug; + +use crate::event_loop::state::SctkState; + +impl ShmHandler for SctkState { + fn shm_state(&mut self) -> &mut ShmState { + &mut self.shm_state + } +} + +impl ProvidesRegistryState for SctkState +where + T: 'static, +{ + fn registry(&mut self) -> &mut RegistryState { + &mut self.registry_state + } + registry_handlers![OutputState, SeatState,]; +} + +delegate_shm!(@ SctkState); +delegate_registry!(@ SctkState); diff --git a/sctk/src/handlers/output.rs b/sctk/src/handlers/output.rs new file mode 100644 index 0000000000..ab49a7b0bf --- /dev/null +++ b/sctk/src/handlers/output.rs @@ -0,0 +1,48 @@ +use crate::{event_loop::state::SctkState, sctk_event::SctkEvent}; +use sctk::{delegate_output, output::OutputHandler, reexports::client::Proxy}; +use std::fmt::Debug; + +impl OutputHandler for SctkState { + fn output_state(&mut self) -> &mut sctk::output::OutputState { + &mut self.output_state + } + + fn new_output( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + output: sctk::reexports::client::protocol::wl_output::WlOutput, + ) { + self.sctk_events.push(SctkEvent::NewOutput { + id: output.clone(), + info: self.output_state.info(&output), + }); + self.outputs.push(output); + } + + fn update_output( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + output: sctk::reexports::client::protocol::wl_output::WlOutput, + ) { + if let Some(info) = self.output_state.info(&output) { + self.sctk_events.push(SctkEvent::UpdateOutput { + id: output.clone(), + info, + }); + } + } + + fn output_destroyed( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + output: sctk::reexports::client::protocol::wl_output::WlOutput, + ) { + self.sctk_events.push(SctkEvent::RemovedOutput(output.id())); + // TODO clean up any layer surfaces on this output? + } +} + +delegate_output!(@ SctkState); diff --git a/sctk/src/handlers/seat/keyboard.rs b/sctk/src/handlers/seat/keyboard.rs new file mode 100644 index 0000000000..5bf3879ac4 --- /dev/null +++ b/sctk/src/handlers/seat/keyboard.rs @@ -0,0 +1,200 @@ +use crate::{ + event_loop::state::SctkState, + sctk_event::{KeyboardEventVariant, SctkEvent}, +}; + +use sctk::{ + delegate_keyboard, reexports::client::Proxy, + seat::keyboard::KeyboardHandler, +}; +use std::fmt::Debug; + +impl KeyboardHandler for SctkState { + fn enter( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, + surface: &sctk::reexports::client::protocol::wl_surface::WlSurface, + _serial: u32, + _raw: &[u32], + _keysyms: &[u32], + ) { + let (i, mut is_active, seat) = { + let (i, is_active, my_seat) = + match self.seats.iter_mut().enumerate().find_map(|(i, s)| { + if s.kbd.as_ref() == Some(keyboard) { + Some((i, s)) + } else { + None + } + }) { + Some((i, s)) => (i, i == 0, s), + None => return, + }; + my_seat.kbd_focus.replace(surface.clone()); + + let seat = my_seat.seat.clone(); + (i, is_active, seat) + }; + + // TODO Ashley: thoroughly test this + // swap the active seat to be the current seat if the current "active" seat is not focused on the application anyway + if !is_active && self.seats[0].kbd_focus.is_none() { + is_active = true; + self.seats.swap(0, i); + } + + if is_active { + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Enter(surface.clone()), + kbd_id: keyboard.clone(), + seat_id: seat.clone(), + }) + } + } + + fn leave( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, + surface: &sctk::reexports::client::protocol::wl_surface::WlSurface, + _serial: u32, + ) { + let (is_active, seat, kbd) = { + let (is_active, my_seat) = + match self.seats.iter_mut().enumerate().find_map(|(i, s)| { + if s.kbd.as_ref() == Some(keyboard) { + Some((i, s)) + } else { + None + } + }) { + Some((i, s)) => (i == 0, s), + None => return, + }; + let seat = my_seat.seat.clone(); + let kbd = keyboard.clone(); + my_seat.kbd_focus.take(); + (is_active, seat, kbd) + }; + + if is_active { + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Leave(surface.clone()), + kbd_id: kbd, + seat_id: seat, + }); + // if there is another seat with a keyboard focused on a surface make that the new active seat + if let Some(i) = + self.seats.iter().position(|s| s.kbd_focus.is_some()) + { + self.seats.swap(0, i); + let s = &self.seats[0]; + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Enter( + s.kbd_focus.clone().unwrap(), + ), + kbd_id: s.kbd.clone().unwrap(), + seat_id: s.seat.clone(), + }) + } + } + } + + fn press_key( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, + _serial: u32, + event: sctk::seat::keyboard::KeyEvent, + ) { + let (is_active, my_seat) = + match self.seats.iter_mut().enumerate().find_map(|(i, s)| { + if s.kbd.as_ref() == Some(keyboard) { + Some((i, s)) + } else { + None + } + }) { + Some((i, s)) => (i == 0, s), + None => return, + }; + let seat_id = my_seat.seat.clone(); + let kbd_id = keyboard.clone(); + my_seat.last_kbd_press.replace(event.clone()); + if is_active { + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Press(event), + kbd_id, + seat_id, + }); + } + } + + fn release_key( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, + _serial: u32, + event: sctk::seat::keyboard::KeyEvent, + ) { + let (is_active, my_seat) = + match self.seats.iter_mut().enumerate().find_map(|(i, s)| { + if s.kbd.as_ref() == Some(keyboard) { + Some((i, s)) + } else { + None + } + }) { + Some((i, s)) => (i == 0, s), + None => return, + }; + let seat_id = my_seat.seat.clone(); + let kbd_id = keyboard.clone(); + + if is_active { + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Release(event), + kbd_id, + seat_id, + }); + } + } + + fn update_modifiers( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, + _serial: u32, + modifiers: sctk::seat::keyboard::Modifiers, + ) { + let (is_active, my_seat) = + match self.seats.iter_mut().enumerate().find_map(|(i, s)| { + if s.kbd.as_ref() == Some(keyboard) { + Some((i, s)) + } else { + None + } + }) { + Some((i, s)) => (i == 0, s), + None => return, + }; + let seat_id = my_seat.seat.clone(); + let kbd_id = keyboard.clone(); + + if is_active { + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Modifiers(modifiers), + kbd_id, + seat_id, + }) + } + } +} + +delegate_keyboard!(@ SctkState); diff --git a/sctk/src/handlers/seat/mod.rs b/sctk/src/handlers/seat/mod.rs new file mode 100644 index 0000000000..38369b437b --- /dev/null +++ b/sctk/src/handlers/seat/mod.rs @@ -0,0 +1,5 @@ +// TODO support multi-seat handling +pub mod keyboard; +pub mod pointer; +pub mod seat; +pub mod touch; diff --git a/sctk/src/handlers/seat/pointer.rs b/sctk/src/handlers/seat/pointer.rs new file mode 100644 index 0000000000..eb07f85d7a --- /dev/null +++ b/sctk/src/handlers/seat/pointer.rs @@ -0,0 +1,59 @@ +use crate::{event_loop::state::SctkState, sctk_event::SctkEvent}; +use sctk::{ + delegate_pointer, + reexports::client::Proxy, + seat::pointer::{PointerEventKind, PointerHandler}, +}; +use std::fmt::Debug; + +impl PointerHandler for SctkState { + fn pointer_frame( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + pointer: &sctk::reexports::client::protocol::wl_pointer::WlPointer, + events: &[sctk::seat::pointer::PointerEvent], + ) { + let (is_active, my_seat) = + match self.seats.iter_mut().enumerate().find_map(|(i, s)| { + if s.ptr.as_ref() == Some(pointer) { + Some((i, s)) + } else { + None + } + }) { + Some((i, s)) => (i == 0, s), + None => return, + }; + + // track events, but only forward for the active seat + for e in events { + if is_active { + self.sctk_events.push(SctkEvent::PointerEvent { + variant: e.clone(), + ptr_id: pointer.clone(), + seat_id: my_seat.seat.clone(), + }); + } + match e.kind { + PointerEventKind::Enter { .. } => { + my_seat.ptr_focus.replace(e.surface.clone()); + } + PointerEventKind::Leave { .. } => { + my_seat.ptr_focus.take(); + } + PointerEventKind::Press { + time, + button, + serial, + } => { + my_seat.last_ptr_press.replace((time, button, serial)); + } + // TODO revisit events that ought to be handled and change internal state + _ => {} + } + } + } +} + +delegate_pointer!(@ SctkState); diff --git a/sctk/src/handlers/seat/seat.rs b/sctk/src/handlers/seat/seat.rs new file mode 100644 index 0000000000..9221b0199d --- /dev/null +++ b/sctk/src/handlers/seat/seat.rs @@ -0,0 +1,172 @@ +use crate::{ + event_loop::{state::SctkSeat, state::SctkState}, + sctk_event::{KeyboardEventVariant, SctkEvent, SeatEventVariant}, +}; +use iced_native::keyboard::Modifiers; +use sctk::{delegate_seat, reexports::client::Proxy, seat::SeatHandler}; +use std::fmt::Debug; + +impl SeatHandler for SctkState +where + T: 'static, +{ + fn seat_state(&mut self) -> &mut sctk::seat::SeatState { + &mut self.seat_state + } + + fn new_seat( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + seat: sctk::reexports::client::protocol::wl_seat::WlSeat, + ) { + self.sctk_events.push(SctkEvent::SeatEvent { + variant: SeatEventVariant::New, + id: seat.clone(), + }); + self.seats.push(SctkSeat { + seat, + kbd: None, + ptr: None, + touch: None, + data_device: None, + modifiers: Modifiers::default(), + kbd_focus: None, + ptr_focus: None, + last_ptr_press: None, + last_kbd_press: None, + }); + } + + fn new_capability( + &mut self, + _conn: &sctk::reexports::client::Connection, + qh: &sctk::reexports::client::QueueHandle, + seat: sctk::reexports::client::protocol::wl_seat::WlSeat, + capability: sctk::seat::Capability, + ) { + let my_seat = match self.seats.iter_mut().find(|s| s.seat == seat) { + Some(s) => s, + None => { + self.seats.push(SctkSeat { + seat: seat.clone(), + kbd: None, + ptr: None, + touch: None, + data_device: None, + modifiers: Modifiers::default(), + kbd_focus: None, + ptr_focus: None, + last_ptr_press: None, + last_kbd_press: None, + }); + self.seats.last_mut().unwrap() + } + }; + // TODO data device + match capability { + sctk::seat::Capability::Keyboard => { + if let Ok((kbd, source)) = + self.seat_state.get_keyboard_with_repeat(qh, &seat, None) + { + self.sctk_events.push(SctkEvent::SeatEvent { + variant: SeatEventVariant::NewCapability( + capability, + kbd.id(), + ), + id: seat.clone(), + }); + let kbd_clone = kbd.clone(); + self.loop_handle + .insert_source(source, move |e, _, state| { + state.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Repeat(e), + kbd_id: kbd_clone.clone(), + seat_id: seat.clone(), + }); + }) + .expect("Failed to insert the repeating keyboard into the event loop"); + my_seat.kbd.replace(kbd); + } + } + sctk::seat::Capability::Pointer => { + if let Ok(ptr) = self.seat_state.get_pointer(qh, &seat) { + self.sctk_events.push(SctkEvent::SeatEvent { + variant: SeatEventVariant::NewCapability( + capability, + ptr.id(), + ), + id: seat.clone(), + }); + my_seat.ptr.replace(ptr); + } + } + sctk::seat::Capability::Touch => { + // TODO touch + } + _ => unimplemented!(), + } + } + + fn remove_capability( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + seat: sctk::reexports::client::protocol::wl_seat::WlSeat, + capability: sctk::seat::Capability, + ) { + let my_seat = match self.seats.iter_mut().find(|s| s.seat == seat) { + Some(s) => s, + None => return, + }; + + // TODO data device + match capability { + // TODO use repeating kbd? + sctk::seat::Capability::Keyboard => { + if let Some(kbd) = my_seat.kbd.take() { + self.sctk_events.push(SctkEvent::SeatEvent { + variant: SeatEventVariant::RemoveCapability( + capability, + kbd.id(), + ), + id: seat.clone(), + }); + } + } + sctk::seat::Capability::Pointer => { + if let Some(ptr) = my_seat.ptr.take() { + self.sctk_events.push(SctkEvent::SeatEvent { + variant: SeatEventVariant::RemoveCapability( + capability, + ptr.id(), + ), + id: seat.clone(), + }); + } + } + sctk::seat::Capability::Touch => { + // TODO touch + // my_seat.touch = self.seat_state.get_touch(qh, &seat).ok(); + } + _ => unimplemented!(), + } + } + + fn remove_seat( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + seat: sctk::reexports::client::protocol::wl_seat::WlSeat, + ) { + self.sctk_events.push(SctkEvent::SeatEvent { + variant: SeatEventVariant::Remove, + id: seat.clone(), + }); + if let Some(i) = self.seats.iter().position(|s| s.seat == seat) { + self.seats.remove(i); + } + } +} + +delegate_seat!(@ SctkState); diff --git a/sctk/src/handlers/seat/touch.rs b/sctk/src/handlers/seat/touch.rs new file mode 100644 index 0000000000..70b786d12e --- /dev/null +++ b/sctk/src/handlers/seat/touch.rs @@ -0,0 +1 @@ +// TODO diff --git a/sctk/src/handlers/shell/layer.rs b/sctk/src/handlers/shell/layer.rs new file mode 100644 index 0000000000..57a13fa960 --- /dev/null +++ b/sctk/src/handlers/shell/layer.rs @@ -0,0 +1,113 @@ +use crate::{ + dpi::LogicalSize, + event_loop::state::SctkState, + sctk_event::{LayerSurfaceEventVariant, SctkEvent}, +}; +use sctk::{ + delegate_layer, + reexports::client::Proxy, + shell::layer::{Anchor, KeyboardInteractivity, LayerShellHandler}, +}; +use std::fmt::Debug; + +impl LayerShellHandler for SctkState { + fn closed( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + layer: &sctk::shell::layer::LayerSurface, + ) { + let layer = match self.layer_surfaces.iter().position(|s| { + s.surface.wl_surface().id() == layer.wl_surface().id() + }) { + Some(w) => self.layer_surfaces.remove(w), + None => return, + }; + + self.sctk_events.push(SctkEvent::LayerSurfaceEvent { + variant: LayerSurfaceEventVariant::Done, + id: layer.surface.wl_surface().clone(), + }) + // TODO popup cleanup + } + + fn configure( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + layer: &sctk::shell::layer::LayerSurface, + mut configure: sctk::shell::layer::LayerSurfaceConfigure, + _serial: u32, + ) { + let layer = + match self.layer_surfaces.iter_mut().find(|s| { + s.surface.wl_surface().id() == layer.wl_surface().id() + }) { + Some(l) => l, + None => return, + }; + let id = layer.surface.wl_surface().id(); + configure.new_size.0 = if configure.new_size.0 > 0 { + configure.new_size.0 + } else { + layer.requested_size.0.unwrap_or(1) + }; + configure.new_size.1 = if configure.new_size.1 > 0 { + configure.new_size.1 + } else { + layer.requested_size.1.unwrap_or(1) + }; + layer.current_size.replace(LogicalSize::new( + configure.new_size.0, + configure.new_size.1, + )); + let first = layer.last_configure.is_none(); + layer.last_configure.replace(configure.clone()); + + self.sctk_events.push(SctkEvent::LayerSurfaceEvent { + variant: LayerSurfaceEventVariant::Configure( + configure, + layer.surface.wl_surface().clone(), + first, + ), + id: layer.surface.wl_surface().clone(), + }); + self.sctk_events + .push(SctkEvent::Draw(layer.surface.wl_surface().clone())); + } +} + +delegate_layer!(@ SctkState); + +/// A request to SCTK window from Winit window. +#[derive(Debug, Clone)] +pub enum LayerSurfaceRequest { + /// Set fullscreen. + /// + /// Passing `None` will set it on the current monitor. + Size(LogicalSize), + + /// Unset fullscreen. + UnsetFullscreen, + + /// Show cursor for the certain window or not. + ShowCursor(bool), + + /// Set anchor + Anchor(Anchor), + + /// Set margin + ExclusiveZone(i32), + + /// Set margin + Margin(u32), + + /// Passthrough mouse input to underlying windows. + KeyboardInteractivity(KeyboardInteractivity), + + /// Redraw was requested. + Redraw, + + /// Window should be closed. + Close, +} diff --git a/sctk/src/handlers/shell/mod.rs b/sctk/src/handlers/shell/mod.rs new file mode 100644 index 0000000000..5556c08d3e --- /dev/null +++ b/sctk/src/handlers/shell/mod.rs @@ -0,0 +1,3 @@ +pub mod layer; +pub mod xdg_popup; +pub mod xdg_window; diff --git a/sctk/src/handlers/shell/xdg_popup.rs b/sctk/src/handlers/shell/xdg_popup.rs new file mode 100644 index 0000000000..5e30ba167f --- /dev/null +++ b/sctk/src/handlers/shell/xdg_popup.rs @@ -0,0 +1,89 @@ +use crate::{ + commands::popup, + event_loop::state::{self, SctkState, SctkSurface}, + sctk_event::{PopupEventVariant, SctkEvent}, +}; +use sctk::{ + delegate_xdg_popup, reexports::client::Proxy, + shell::xdg::popup::PopupHandler, +}; +use std::fmt::Debug; + +impl PopupHandler for SctkState { + fn configure( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + popup: &sctk::shell::xdg::popup::Popup, + configure: sctk::shell::xdg::popup::PopupConfigure, + ) { + let sctk_popup = match self.popups.iter_mut().find(|s| { + s.popup.wl_surface().clone() == popup.wl_surface().clone() + }) { + Some(p) => p, + None => return, + }; + let first = sctk_popup.last_configure.is_none(); + sctk_popup.last_configure.replace(configure.clone()); + + self.sctk_events.push(SctkEvent::PopupEvent { + variant: PopupEventVariant::Configure( + configure, + popup.wl_surface().clone(), + first, + ), + id: popup.wl_surface().clone(), + toplevel_id: sctk_popup.toplevel.clone(), + parent_id: match &sctk_popup.parent { + SctkSurface::LayerSurface(s) => s.clone(), + SctkSurface::Window(s) => s.clone(), + SctkSurface::Popup(s) => s.clone(), + }, + }) + } + + fn done( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + popup: &sctk::shell::xdg::popup::Popup, + ) { + let sctk_popup = match self.popups.iter().position(|s| { + s.popup.wl_surface().clone() == popup.wl_surface().clone() + }) { + Some(p) => self.popups.remove(p), + None => return, + }; + let mut to_destroy = vec![sctk_popup]; + while let Some(popup_to_destroy) = to_destroy.last() { + match popup_to_destroy.parent.clone() { + state::SctkSurface::LayerSurface(_) + | state::SctkSurface::Window(_) => { + break; + } + state::SctkSurface::Popup(popup_to_destroy_first) => { + let popup_to_destroy_first = self + .popups + .iter() + .position(|p| { + p.popup.wl_surface() == &popup_to_destroy_first + }) + .unwrap(); + let popup_to_destroy_first = + self.popups.remove(popup_to_destroy_first); + to_destroy.push(popup_to_destroy_first); + } + } + } + for popup in to_destroy.into_iter().rev() { + self.sctk_events.push(SctkEvent::PopupEvent { + variant: PopupEventVariant::Done, + toplevel_id: popup.toplevel.clone(), + parent_id: popup.parent.wl_surface().clone(), + id: popup.popup.wl_surface().clone(), + }); + self.popups.push(popup); + } + } +} +delegate_xdg_popup!(@ SctkState); diff --git a/sctk/src/handlers/shell/xdg_window.rs b/sctk/src/handlers/shell/xdg_window.rs new file mode 100644 index 0000000000..bf0bcac453 --- /dev/null +++ b/sctk/src/handlers/shell/xdg_window.rs @@ -0,0 +1,73 @@ +use crate::{ + event_loop::state::SctkState, + sctk_event::{SctkEvent, WindowEventVariant}, +}; +use sctk::{ + delegate_xdg_shell, delegate_xdg_window, reexports::client::Proxy, + shell::xdg::window::WindowHandler, +}; +use std::fmt::Debug; + +impl WindowHandler for SctkState { + fn request_close( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + window: &sctk::shell::xdg::window::Window, + ) { + let window = match self + .windows + .iter() + .position(|s| s.window.wl_surface() == window.wl_surface()) + { + Some(w) => self.windows.remove(w), + None => return, + }; + + self.sctk_events.push(SctkEvent::WindowEvent { + variant: WindowEventVariant::Close, + id: window.window.wl_surface().clone(), + }) + // TODO popup cleanup + } + + fn configure( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + window: &sctk::shell::xdg::window::Window, + mut configure: sctk::shell::xdg::window::WindowConfigure, + _serial: u32, + ) { + let window = match self + .windows + .iter_mut() + .find(|w| w.window.wl_surface() == window.wl_surface()) + { + Some(w) => w, + None => return, + }; + + if configure.new_size.is_none() { + configure.new_size = + Some(window.requested_size.unwrap_or((300, 500))); + }; + + let wl_surface = window.window.wl_surface(); + let id = wl_surface.clone(); + let first = window.last_configure.is_none(); + window.last_configure.replace(configure.clone()); + + self.sctk_events.push(SctkEvent::WindowEvent { + variant: WindowEventVariant::Configure( + configure, + wl_surface.clone(), + first, + ), + id, + }) + } +} + +delegate_xdg_window!(@ SctkState); +delegate_xdg_shell!(@ SctkState); diff --git a/sctk/src/lib.rs b/sctk/src/lib.rs new file mode 100644 index 0000000000..35b8c506f7 --- /dev/null +++ b/sctk/src/lib.rs @@ -0,0 +1,24 @@ +pub use iced_native::*; + +pub mod application; +pub mod commands; +pub mod conversion; +pub mod dpi; +pub mod egl; +pub mod error; +pub mod event_loop; +mod handlers; +pub mod result; +pub mod sctk_event; +pub mod settings; +pub mod util; +pub mod window; + +pub use application::{run, Application}; +pub use clipboard::Clipboard; +pub use error::Error; +pub use event_loop::proxy::Proxy; +pub use settings::Settings; + +pub use iced_graphics::Viewport; +pub use iced_native::window::Position; diff --git a/sctk/src/result.rs b/sctk/src/result.rs new file mode 100644 index 0000000000..fc9af5c566 --- /dev/null +++ b/sctk/src/result.rs @@ -0,0 +1,6 @@ +use crate::error::Error; + +/// The result of running an [`Application`]. +/// +/// [`Application`]: crate::Application +pub type Result = std::result::Result<(), Error>; diff --git a/sctk/src/sctk_event.rs b/sctk/src/sctk_event.rs new file mode 100644 index 0000000000..bfb7b4fcb4 --- /dev/null +++ b/sctk/src/sctk_event.rs @@ -0,0 +1,618 @@ +use std::{collections::HashMap, time::Instant}; + +use crate::{ + application::SurfaceIdWrapper, + conversion::{ + keysym_to_vkey, modifiers_to_native, pointer_axis_to_native, + pointer_button_to_native, + }, + dpi::{LogicalSize, PhysicalSize}, +}; +use iced_graphics::Point; +use iced_native::{ + event::{ + wayland::{self, LayerEvent, PopupEvent}, + PlatformSpecific, + }, + keyboard::{self, KeyCode}, + mouse, + window::{self, Id as SurfaceId}, +}; +use sctk::{ + output::OutputInfo, + reexports::client::{ + backend::ObjectId, + protocol::{ + wl_keyboard::WlKeyboard, + wl_output::WlOutput, + wl_pointer::WlPointer, + wl_seat::{self, WlSeat}, + wl_surface::WlSurface, + }, + Proxy, + }, + seat::{ + keyboard::{KeyEvent, Modifiers}, + pointer::{PointerEvent, PointerEventKind}, + Capability, + }, + shell::{ + layer::LayerSurfaceConfigure, + xdg::{popup::PopupConfigure, window::WindowConfigure}, + }, +}; + +#[derive(Debug, Clone)] +pub enum IcedSctkEvent { + /// Emitted when new events arrive from the OS to be processed. + /// + /// This event type is useful as a place to put code that should be done before you start + /// processing events, such as updating frame timing information for benchmarking or checking + /// the [`StartCause`][crate::event::StartCause] to see if a timer set by + /// [`ControlFlow::WaitUntil`](crate::event_loop::ControlFlow::WaitUntil) has elapsed. + NewEvents(StartCause), + + /// Any user event from iced + UserEvent(T), + /// An event produced by sctk + SctkEvent(SctkEvent), + + /// Emitted when all of the event loop's input events have been processed and redraw processing + /// is about to begin. + /// + /// This event is useful as a place to put your code that should be run after all + /// state-changing events have been handled and you want to do stuff (updating state, performing + /// calculations, etc) that happens as the "main body" of your event loop. If your program only draws + /// graphics when something changes, it's usually better to do it in response to + /// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted + /// immediately after this event. Programs that draw graphics continuously, like most games, + /// can render here unconditionally for simplicity. + MainEventsCleared, + + /// Emitted after [`MainEventsCleared`] when a window should be redrawn. + /// + /// This gets triggered in two scenarios: + /// - The OS has performed an operation that's invalidated the window's contents (such as + /// resizing the window). + /// - The application has explicitly requested a redraw via [`Window::request_redraw`]. + /// + /// During each iteration of the event loop, Winit will aggregate duplicate redraw requests + /// into a single event, to help avoid duplicating rendering work. + /// + /// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless + /// something changes, like most non-game GUIs. + /// + /// [`MainEventsCleared`]: Self::MainEventsCleared + RedrawRequested(ObjectId), + + /// Emitted after all [`RedrawRequested`] events have been processed and control flow is about to + /// be taken away from the program. If there are no `RedrawRequested` events, it is emitted + /// immediately after `MainEventsCleared`. + /// + /// This event is useful for doing any cleanup or bookkeeping work after all the rendering + /// tasks have been completed. + /// + /// [`RedrawRequested`]: Self::RedrawRequested + RedrawEventsCleared, + + /// Emitted when the event loop is being shut down. + /// + /// This is irreversible - if this event is emitted, it is guaranteed to be the last event that + /// gets emitted. You generally want to treat this as an "do on quit" event. + LoopDestroyed, +} + +#[derive(Debug, Clone)] +pub enum SctkEvent { + // + // Input events + // + SeatEvent { + variant: SeatEventVariant, + id: WlSeat, + }, + PointerEvent { + variant: PointerEvent, + ptr_id: WlPointer, + seat_id: WlSeat, + }, + KeyboardEvent { + variant: KeyboardEventVariant, + kbd_id: WlKeyboard, + seat_id: WlSeat, + }, + // TODO data device & touch + + // + // Surface Events + // + WindowEvent { + variant: WindowEventVariant, + id: WlSurface, + }, + LayerSurfaceEvent { + variant: LayerSurfaceEventVariant, + id: WlSurface, + }, + PopupEvent { + variant: PopupEventVariant, + /// this may be the Id of a window or layer surface + toplevel_id: WlSurface, + /// this may be any SurfaceId + parent_id: WlSurface, + /// the id of this popup + id: WlSurface, + }, + + // + // output events + // + NewOutput { + id: WlOutput, + info: Option, + }, + UpdateOutput { + id: WlOutput, + info: OutputInfo, + }, + RemovedOutput(ObjectId), + + // + // compositor events + // + Draw(WlSurface), + ScaleFactorChanged { + factor: f64, + id: WlOutput, + inner_size: PhysicalSize, + }, +} + +#[derive(Debug, Clone)] +pub enum SeatEventVariant { + New, + Remove, + NewCapability(Capability, ObjectId), + RemoveCapability(Capability, ObjectId), +} + +#[derive(Debug, Clone)] +pub enum KeyboardEventVariant { + Leave(WlSurface), + Enter(WlSurface), + Press(KeyEvent), + Repeat(KeyEvent), + Release(KeyEvent), + Modifiers(Modifiers), +} + +#[derive(Debug, Clone)] +pub enum WindowEventVariant { + Created(ObjectId, SurfaceId), + /// + Close, + /// + WmCapabilities(Vec), + /// + ConfigureBounds { + width: u32, + height: u32, + }, + /// + Configure(WindowConfigure, WlSurface, bool), +} + +#[derive(Debug, Clone)] +pub enum PopupEventVariant { + Created(ObjectId, SurfaceId), + /// + Done, + /// + WmCapabilities(Vec), + /// + Configure(PopupConfigure, WlSurface, bool), + /// + RepositionionedPopup { + token: u32, + }, +} + +#[derive(Debug, Clone)] +pub enum LayerSurfaceEventVariant { + /// sent after creation of the layer surface + Created(ObjectId, SurfaceId), + /// + Done, + /// + Configure(LayerSurfaceConfigure, WlSurface, bool), +} + +/// Describes the reason the event loop is resuming. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum StartCause { + /// Sent if the time specified by [`ControlFlow::WaitUntil`] has been reached. Contains the + /// moment the timeout was requested and the requested resume time. The actual resume time is + /// guaranteed to be equal to or after the requested resume time. + /// + /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil + ResumeTimeReached { + start: Instant, + requested_resume: Instant, + }, + + /// Sent if the OS has new events to send to the window, after a wait was requested. Contains + /// the moment the wait was requested and the resume time, if requested. + WaitCancelled { + start: Instant, + requested_resume: Option, + }, + + /// Sent if the event loop is being resumed after the loop's control flow was set to + /// [`ControlFlow::Poll`]. + /// + /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll + Poll, + + /// Sent once, immediately after `run` is called. Indicates that the loop was just initialized. + Init, +} + +/// Pending update to a window requested by the user. +#[derive(Default, Debug, Clone, Copy)] +pub struct SurfaceUserRequest { + /// Whether `redraw` was requested. + pub redraw_requested: bool, + + /// Wether the frame should be refreshed. + pub refresh_frame: bool, +} + +// The window update coming from the compositor. +#[derive(Default, Debug, Clone)] +pub struct SurfaceCompositorUpdate { + /// New window configure. + pub configure: Option, + + /// first + pub first: bool, + + /// New scale factor. + pub scale_factor: Option, + + /// Close the window. + pub close_window: bool, +} + +impl SctkEvent { + pub fn to_native( + self, + modifiers: &mut Modifiers, + surface_ids: &HashMap, + destroyed_surface_ids: &HashMap, + ) -> Vec { + match self { + // TODO Ashley: Platform specific multi-seat events? + SctkEvent::SeatEvent { .. } => Default::default(), + SctkEvent::PointerEvent { variant, .. } => match variant.kind { + PointerEventKind::Enter { .. } => { + vec![iced_native::Event::Mouse(mouse::Event::CursorEntered)] + } + PointerEventKind::Leave { .. } => { + vec![iced_native::Event::Mouse(mouse::Event::CursorLeft)] + } + PointerEventKind::Motion { .. } => { + vec![iced_native::Event::Mouse(mouse::Event::CursorMoved { + position: Point::new( + variant.position.0 as f32, + variant.position.1 as f32, + ), + })] + } + PointerEventKind::Press { + time: _, + button, + serial: _, + } => pointer_button_to_native(button) + .map(|b| { + iced_native::Event::Mouse(mouse::Event::ButtonPressed( + b, + )) + }) + .into_iter() + .collect(), // TODO Ashley: conversion + PointerEventKind::Release { + time: _, + button, + serial: _, + } => pointer_button_to_native(button) + .map(|b| { + iced_native::Event::Mouse(mouse::Event::ButtonReleased( + b, + )) + }) + .into_iter() + .collect(), // TODO Ashley: conversion + PointerEventKind::Axis { + time: _, + horizontal, + vertical, + source, + } => pointer_axis_to_native(source, horizontal, vertical) + .map(|a| { + iced_native::Event::Mouse(mouse::Event::WheelScrolled { + delta: a, + }) + }) + .into_iter() + .collect(), // TODO Ashley: conversion + }, + SctkEvent::KeyboardEvent { + variant, + kbd_id: _, + seat_id, + } => match variant { + KeyboardEventVariant::Leave(surface) => surface_ids + .get(&surface.id()) + .map(|id| match id { + SurfaceIdWrapper::LayerSurface(_id) => { + iced_native::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::Layer( + LayerEvent::Unfocused, + surface, + id.inner(), + ), + ), + ) + } + SurfaceIdWrapper::Window(id) => { + iced_native::Event::Window( + *id, + window::Event::Unfocused, + ) + } + SurfaceIdWrapper::Popup(_id) => { + iced_native::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::Popup( + PopupEvent::Unfocused, + surface, + id.inner(), + ), + ), + ) + } + }) + .into_iter() + .chain([iced_native::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::Seat( + wayland::SeatEvent::Leave, + seat_id, + )), + )]) + .collect(), + KeyboardEventVariant::Enter(surface) => surface_ids + .get(&surface.id()) + .map(|id| match id { + SurfaceIdWrapper::LayerSurface(_id) => { + iced_native::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::Layer( + LayerEvent::Focused, + surface, + id.inner(), + ), + ), + ) + } + SurfaceIdWrapper::Window(id) => { + iced_native::Event::Window( + *id, + window::Event::Focused, + ) + } + SurfaceIdWrapper::Popup(_id) => { + iced_native::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::Popup( + PopupEvent::Focused, + surface, + id.inner(), + ), + ), + ) + } + }) + .into_iter() + .chain([iced_native::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::Seat( + wayland::SeatEvent::Enter, + seat_id, + )), + )]) + .collect(), + KeyboardEventVariant::Press(ke) => { + let mut skip_char = false; + + let mut events: Vec<_> = keysym_to_vkey(ke.keysym) + .map(|k| { + if k == KeyCode::Backspace { + skip_char = true; + } + iced_native::Event::Keyboard( + keyboard::Event::KeyPressed { + key_code: k, + modifiers: modifiers_to_native(*modifiers), + }, + ) + }) + .into_iter() + .collect(); + if !skip_char { + if let Some(s) = ke.utf8 { + let mut chars = s + .chars() + .map(|c| { + iced_native::Event::Keyboard( + keyboard::Event::CharacterReceived(c), + ) + }) + .collect(); + events.append(&mut chars); + } + } + events + } + KeyboardEventVariant::Repeat(ke) => { + let mut skip_char = false; + + let mut events: Vec<_> = keysym_to_vkey(ke.keysym) + .map(|k| { + if k == KeyCode::Backspace { + skip_char = true; + } + iced_native::Event::Keyboard( + keyboard::Event::KeyPressed { + key_code: k, + modifiers: modifiers_to_native(*modifiers), + }, + ) + }) + .into_iter() + .collect(); + if !skip_char { + if let Some(s) = ke.utf8 { + let mut chars = s + .chars() + .map(|c| { + iced_native::Event::Keyboard( + keyboard::Event::CharacterReceived(c), + ) + }) + .collect(); + events.append(&mut chars); + } + } + events + } + KeyboardEventVariant::Release(k) => keysym_to_vkey(k.keysym) + .map(|k| { + iced_native::Event::Keyboard( + keyboard::Event::KeyReleased { + key_code: k, + modifiers: modifiers_to_native(*modifiers), + }, + ) + }) + .into_iter() + .collect(), + KeyboardEventVariant::Modifiers(new_mods) => { + *modifiers = new_mods; + vec![iced_native::Event::Keyboard( + keyboard::Event::ModifiersChanged(modifiers_to_native( + new_mods, + )), + )] + } + }, + SctkEvent::WindowEvent { + variant, + id: surface, + } => match variant { + // TODO Ashley: platform specific events for window + WindowEventVariant::Created(..) => Default::default(), + WindowEventVariant::Close => destroyed_surface_ids + .get(&surface.id()) + .map(|id| { + iced_native::Event::Window( + id.inner(), + window::Event::CloseRequested, + ) + }) + .into_iter() + .collect(), + WindowEventVariant::WmCapabilities(_) => Default::default(), + WindowEventVariant::ConfigureBounds { .. } => { + Default::default() + } + WindowEventVariant::Configure(configure, surface, _) => { + if configure.is_resizing() { + let new_size = configure.new_size.unwrap(); + surface_ids + .get(&surface.id()) + .map(|id| { + iced_native::Event::Window( + id.inner(), + window::Event::Resized { + width: new_size.0, + height: new_size.1, + }, + ) + }) + .into_iter() + .collect() + } else { + Default::default() + } + } + }, + SctkEvent::LayerSurfaceEvent { + variant, + id: surface, + } => match variant { + LayerSurfaceEventVariant::Done => destroyed_surface_ids + .get(&surface.id()) + .map(|id| { + iced_native::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::Layer( + LayerEvent::Done, + surface, + id.inner(), + )), + ) + }) + .into_iter() + .collect(), + _ => Default::default(), + }, + SctkEvent::PopupEvent { + variant, + id: surface, + .. + } => { + match variant { + PopupEventVariant::Done => destroyed_surface_ids + .get(&surface.id()) + .map(|id| { + iced_native::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::Popup( + PopupEvent::Done, + surface, + id.inner(), + ), + ), + ) + }) + .into_iter() + .collect(), + PopupEventVariant::Created(_, _) => Default::default(), // TODO + PopupEventVariant::WmCapabilities(_) => Default::default(), // TODO + PopupEventVariant::Configure(_, _, _) => Default::default(), // TODO + PopupEventVariant::RepositionionedPopup { token } => { + Default::default() + } // TODO + } + } + SctkEvent::NewOutput { id, info } => Default::default(), + SctkEvent::UpdateOutput { id, info } => Default::default(), + SctkEvent::RemovedOutput(_) => Default::default(), + SctkEvent::Draw(_) => Default::default(), + SctkEvent::ScaleFactorChanged { + factor, + id, + inner_size, + } => Default::default(), + } + } +} diff --git a/sctk/src/settings.rs b/sctk/src/settings.rs new file mode 100644 index 0000000000..9e7ebb2d1b --- /dev/null +++ b/sctk/src/settings.rs @@ -0,0 +1,31 @@ +use iced_native::command::platform_specific::wayland::{ + layer_surface::SctkLayerSurfaceSettings, window::SctkWindowSettings, +}; + +#[derive(Debug)] +pub struct Settings { + /// The data needed to initialize an [`Application`]. + /// + /// [`Application`]: crate::Application + pub flags: Flags, + /// optional keyboard repetition config + pub kbd_repeat: Option, + /// optional name and size of a custom pointer theme + pub ptr_theme: Option<(String, u32)>, + /// surface + pub surface: InitialSurface, + /// whether the application should exit on close of all windows + pub exit_on_close_request: bool, +} + +#[derive(Debug, Clone)] +pub enum InitialSurface { + LayerSurface(SctkLayerSurfaceSettings), + XdgWindow(SctkWindowSettings), +} + +impl Default for InitialSurface { + fn default() -> Self { + Self::LayerSurface(SctkLayerSurfaceSettings::default()) + } +} diff --git a/sctk/src/util.rs b/sctk/src/util.rs new file mode 100644 index 0000000000..81329b263a --- /dev/null +++ b/sctk/src/util.rs @@ -0,0 +1,128 @@ +/// The behavior of cursor grabbing. +/// +/// Use this enum with [`Window::set_cursor_grab`] to grab the cursor. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum CursorGrabMode { + /// No grabbing of the cursor is performed. + None, + + /// The cursor is confined to the window area. + /// + /// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you + /// want to do so. + /// + /// ## Platform-specific + /// + /// - **macOS:** Not implemented. Always returns [`ExternalError::NotSupported`] for now. + /// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`]. + Confined, + + /// The cursor is locked inside the window area to the certain position. + /// + /// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you + /// want to do so. + /// + /// ## Platform-specific + /// + /// - **X11 / Windows:** Not implemented. Always returns [`ExternalError::NotSupported`] for now. + /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`]. + Locked, +} + +/// Describes the appearance of the mouse cursor. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum CursorIcon { + /// The platform-dependent default cursor. + Default, + /// A simple crosshair. + Crosshair, + /// A hand (often used to indicate links in web browsers). + Hand, + /// Self explanatory. + Arrow, + /// Indicates something is to be moved. + Move, + /// Indicates text that may be selected or edited. + Text, + /// Program busy indicator. + Wait, + /// Help indicator (often rendered as a "?") + Help, + /// Progress indicator. Shows that processing is being done. But in contrast + /// with "Wait" the user may still interact with the program. Often rendered + /// as a spinning beach ball, or an arrow with a watch or hourglass. + Progress, + + /// Cursor showing that something cannot be done. + NotAllowed, + ContextMenu, + Cell, + VerticalText, + Alias, + Copy, + NoDrop, + /// Indicates something can be grabbed. + Grab, + /// Indicates something is grabbed. + Grabbing, + AllScroll, + ZoomIn, + ZoomOut, + + /// Indicate that some edge is to be moved. For example, the 'SeResize' cursor + /// is used when the movement starts from the south-east corner of the box. + EResize, + NResize, + NeResize, + NwResize, + SResize, + SeResize, + SwResize, + WResize, + EwResize, + NsResize, + NeswResize, + NwseResize, + ColResize, + RowResize, +} + +impl Default for CursorIcon { + fn default() -> Self { + CursorIcon::Default + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Theme { + Light, + Dark, +} + +/// ## Platform-specific +/// +/// - **X11:** Sets the WM's `XUrgencyHint`. No distinction between [`Critical`] and [`Informational`]. +/// +/// [`Critical`]: Self::Critical +/// [`Informational`]: Self::Informational +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UserAttentionType { + /// ## Platform-specific + /// + /// - **macOS:** Bounces the dock icon until the application is in focus. + /// - **Windows:** Flashes both the window and the taskbar button until the application is in focus. + Critical, + /// ## Platform-specific + /// + /// - **macOS:** Bounces the dock icon once. + /// - **Windows:** Flashes the taskbar button until the application is in focus. + Informational, +} + +impl Default for UserAttentionType { + fn default() -> Self { + UserAttentionType::Informational + } +} diff --git a/sctk/src/widget.rs b/sctk/src/widget.rs new file mode 100644 index 0000000000..9f09cb8f21 --- /dev/null +++ b/sctk/src/widget.rs @@ -0,0 +1,232 @@ +//! Display information and interactive controls in your application. +pub use iced_native::widget::helpers::*; + +pub use iced_native::{column, row}; + +/// A container that distributes its contents vertically. +pub type Column<'a, Message, Renderer = crate::Renderer> = + iced_native::widget::Column<'a, Message, Renderer>; + +/// A container that distributes its contents horizontally. +pub type Row<'a, Message, Renderer = crate::Renderer> = + iced_native::widget::Row<'a, Message, Renderer>; + +pub mod text { + //! Write some text for your users to read. + pub use iced_native::widget::text::{Appearance, StyleSheet}; + + /// A paragraph of text. + pub type Text<'a, Renderer = crate::Renderer> = + iced_native::widget::Text<'a, Renderer>; +} + +pub mod button { + //! Allow your users to perform actions by pressing a button. + pub use iced_native::widget::button::{Appearance, StyleSheet}; + + /// A widget that produces a message when clicked. + pub type Button<'a, Message, Renderer = crate::Renderer> = + iced_native::widget::Button<'a, Message, Renderer>; +} + +pub mod checkbox { + //! Show toggle controls using checkboxes. + pub use iced_native::widget::checkbox::{Appearance, StyleSheet}; + + /// A box that can be checked. + pub type Checkbox<'a, Message, Renderer = crate::Renderer> = + iced_native::widget::Checkbox<'a, Message, Renderer>; +} + +pub mod container { + //! Decorate content and apply alignment. + pub use iced_native::widget::container::{Appearance, StyleSheet}; + + /// An element decorating some content. + pub type Container<'a, Message, Renderer = crate::Renderer> = + iced_native::widget::Container<'a, Message, Renderer>; +} + +pub mod pane_grid { + //! Let your users split regions of your application and organize layout dynamically. + //! + //! [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) + //! + //! # Example + //! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, + //! drag and drop, and hotkey support. + //! + //! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.4/examples/pane_grid + pub use iced_native::widget::pane_grid::{ + Axis, Configuration, Direction, DragEvent, Line, Node, Pane, + ResizeEvent, Split, State, StyleSheet, + }; + + /// A collection of panes distributed using either vertical or horizontal splits + /// to completely fill the space available. + /// + /// [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) + pub type PaneGrid<'a, Message, Renderer = crate::Renderer> = + iced_native::widget::PaneGrid<'a, Message, Renderer>; + + /// The content of a [`Pane`]. + pub type Content<'a, Message, Renderer = crate::Renderer> = + iced_native::widget::pane_grid::Content<'a, Message, Renderer>; + + /// The title bar of a [`Pane`]. + pub type TitleBar<'a, Message, Renderer = crate::Renderer> = + iced_native::widget::pane_grid::TitleBar<'a, Message, Renderer>; +} + +pub mod pick_list { + //! Display a dropdown list of selectable values. + pub use iced_native::widget::pick_list::{Appearance, StyleSheet}; + + /// A widget allowing the selection of a single value from a list of options. + pub type PickList<'a, T, Message, Renderer = crate::Renderer> = + iced_native::widget::PickList<'a, T, Message, Renderer>; +} + +pub mod radio { + //! Create choices using radio buttons. + pub use iced_native::widget::radio::{Appearance, StyleSheet}; + + /// A circular button representing a choice. + pub type Radio = + iced_native::widget::Radio; +} + +pub mod scrollable { + //! Navigate an endless amount of content with a scrollbar. + pub use iced_native::widget::scrollable::{ + snap_to, style::Scrollbar, style::Scroller, Id, StyleSheet, + }; + + /// A widget that can vertically display an infinite amount of content + /// with a scrollbar. + pub type Scrollable<'a, Message, Renderer = crate::Renderer> = + iced_native::widget::Scrollable<'a, Message, Renderer>; +} + +pub mod toggler { + //! Show toggle controls using togglers. + pub use iced_native::widget::toggler::{Appearance, StyleSheet}; + + /// A toggler widget. + pub type Toggler<'a, Message, Renderer = crate::Renderer> = + iced_native::widget::Toggler<'a, Message, Renderer>; +} + +pub mod text_input { + //! Display fields that can be filled with text. + pub use iced_native::widget::text_input::{ + focus, Appearance, Id, StyleSheet, + }; + + /// A field that can be filled with text. + pub type TextInput<'a, Message, Renderer = crate::Renderer> = + iced_native::widget::TextInput<'a, Message, Renderer>; +} + +pub mod tooltip { + //! Display a widget over another. + pub use iced_native::widget::tooltip::Position; + + /// A widget allowing the selection of a single value from a list of options. + pub type Tooltip<'a, Message, Renderer = crate::Renderer> = + iced_native::widget::Tooltip<'a, Message, Renderer>; +} + +pub use iced_native::widget::progress_bar; +pub use iced_native::widget::rule; +pub use iced_native::widget::slider; +pub use iced_native::widget::Space; + +pub use button::Button; +pub use checkbox::Checkbox; +pub use container::Container; +pub use pane_grid::PaneGrid; +pub use pick_list::PickList; +pub use progress_bar::ProgressBar; +pub use radio::Radio; +pub use rule::Rule; +pub use scrollable::Scrollable; +pub use slider::Slider; +pub use text::Text; +pub use text_input::TextInput; +pub use toggler::Toggler; +pub use tooltip::Tooltip; + +#[cfg(feature = "canvas")] +#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] +pub use iced_graphics::widget::canvas; + +#[cfg(feature = "canvas")] +#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] +/// Creates a new [`Canvas`]. +pub fn canvas(program: P) -> Canvas +where + P: canvas::Program, +{ + Canvas::new(program) +} + +#[cfg(feature = "image")] +#[cfg_attr(docsrs, doc(cfg(feature = "image")))] +pub mod image { + //! Display images in your user interface. + pub use iced_native::image::Handle; + + /// A frame that displays an image. + pub type Image = iced_native::widget::Image; + + pub use iced_native::widget::image::viewer; + pub use viewer::Viewer; +} + +#[cfg(feature = "qr_code")] +#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] +pub use iced_graphics::widget::qr_code; + +#[cfg(feature = "svg")] +#[cfg_attr(docsrs, doc(cfg(feature = "svg")))] +pub mod svg { + //! Display vector graphics in your application. + pub use iced_native::svg::Handle; + pub use iced_native::widget::Svg; +} + +#[cfg(feature = "canvas")] +#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] +pub use canvas::Canvas; + +#[cfg(feature = "image")] +#[cfg_attr(docsrs, doc(cfg(feature = "image")))] +pub use image::Image; + +#[cfg(feature = "qr_code")] +#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] +pub use qr_code::QRCode; + +#[cfg(feature = "svg")] +#[cfg_attr(docsrs, doc(cfg(feature = "svg")))] +pub use svg::Svg; + +use crate::Command; +use iced_native::widget::operation; + +/// Focuses the previous focusable widget. +pub fn focus_previous() -> Command +where + Message: 'static, +{ + Command::widget(operation::focusable::focus_previous()) +} + +/// Focuses the next focusable widget. +pub fn focus_next() -> Command +where + Message: 'static, +{ + Command::widget(operation::focusable::focus_next()) +} diff --git a/sctk/src/window.rs b/sctk/src/window.rs new file mode 100644 index 0000000000..2353a0c641 --- /dev/null +++ b/sctk/src/window.rs @@ -0,0 +1,3 @@ +pub fn resize() { + todo!() +} diff --git a/src/clipboard.rs b/src/clipboard.rs index dde170514b..4b2dd4ea66 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -1,3 +1,3 @@ //! Access the clipboard. -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(feature = "wayland")))] // TODO support in wayland pub use crate::runtime::clipboard::{read, write}; diff --git a/src/error.rs b/src/error.rs index 0bfa3ff1c4..efd19c5621 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,6 +16,24 @@ pub enum Error { GraphicsCreationFailed(iced_graphics::Error), } +#[cfg(feature = "wayland")] +impl From for Error { + fn from(error: iced_sctk::Error) -> Self { + match error { + iced_sctk::Error::ExecutorCreationFailed(error) => { + Error::ExecutorCreationFailed(error) + }, + iced_sctk::Error::WindowCreationFailed(error) => { + Error::WindowCreationFailed(error) + }, + iced_sctk::Error::GraphicsCreationFailed(error) => { + Error::GraphicsCreationFailed(error) + }, + } + } +} + +#[cfg(not(feature = "wayland"))] impl From for Error { fn from(error: iced_winit::Error) -> Error { match error { diff --git a/src/lib.rs b/src/lib.rs index 001768272b..ef0fe7765c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -164,12 +164,32 @@ #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] +#[cfg(all(not(feature = "glow"), feature = "wgpu", not(feature = "wayland")))] +pub mod application; + mod element; mod error; mod result; + +#[cfg(all( + not(feature = "wayland") +))] mod sandbox; -pub mod application; +#[cfg(all( + not(feature = "wayland") +))] +pub use application::Application; + +/// wayland application +#[cfg(feature = "wayland")] +pub mod wayland; +#[cfg(feature = "wayland")] +pub use wayland::Application; +#[cfg(feature = "wayland")] +pub use wayland::sandbox; + + pub mod clipboard; pub mod executor; pub mod keyboard; @@ -181,23 +201,37 @@ pub mod touch; pub mod widget; pub mod window; -#[cfg(all(not(feature = "glow"), feature = "wgpu"))] +#[cfg(all( + not(feature = "glow"), + feature = "wgpu", + not(feature = "wayland"), + feature = "multi_window" +))] +pub mod multi_window; + +#[cfg(feature = "wayland")] +use iced_sctk as runtime; + +#[cfg(all( + not(feature = "glow"), + feature = "wgpu", + not(feature = "wayland") +))] use iced_winit as runtime; -#[cfg(feature = "glow")] +#[cfg(all(feature = "glow", not(feature = "wayland")))] use iced_glutin as runtime; -#[cfg(all(not(feature = "glow"), feature = "wgpu"))] +#[cfg(all(not(feature = "iced_glow"), feature = "wgpu"))] use iced_wgpu as renderer; -#[cfg(feature = "glow")] +#[cfg(any(feature = "glow", feature = "wayland"))] use iced_glow as renderer; pub use iced_native::theme; pub use runtime::event; pub use runtime::subscription; -pub use application::Application; pub use element::Element; pub use error::Error; pub use event::Event; @@ -213,7 +247,7 @@ pub use runtime::alignment; pub use runtime::futures; pub use runtime::{ color, Alignment, Background, Color, Command, ContentFit, Font, Length, - Padding, Point, Rectangle, Size, Vector, + Padding, Point, Rectangle, Size, Vector, settings as sctk_settings }; #[cfg(feature = "system")] diff --git a/src/settings.rs b/src/settings.rs index d31448fbd7..d3fc0751ea 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,4 +1,5 @@ //! Configure your application. +#[cfg(not(feature = "wayland"))] use crate::window; /// The settings of an application. @@ -13,8 +14,14 @@ pub struct Settings { /// The window settings. /// /// They will be ignored on the Web. + #[cfg(not(feature = "wayland"))] pub window: window::Settings, + + /// the initial surface to be created + #[cfg(feature = "wayland")] + pub initial_surface: iced_sctk::settings::InitialSurface, + /// The data needed to initialize the [`Application`]. /// /// [`Application`]: crate::Application @@ -76,7 +83,10 @@ impl Settings { Self { flags, id: default_settings.id, + #[cfg(not(feature = "wayland"))] window: default_settings.window, + #[cfg(feature = "wayland")] + initial_surface: default_settings.initial_surface, default_font: default_settings.default_font, default_text_size: default_settings.default_text_size, text_multithreading: default_settings.text_multithreading, @@ -94,7 +104,10 @@ where fn default() -> Self { Self { id: None, + #[cfg(not(feature = "wayland"))] window: Default::default(), + #[cfg(feature = "wayland")] + initial_surface: Default::default(), flags: Default::default(), default_font: Default::default(), default_text_size: 20, @@ -106,6 +119,7 @@ where } } +#[cfg(not(any(target_arch = "wasm32", feature = "wayland")))] impl From> for iced_winit::Settings { fn from(settings: Settings) -> iced_winit::Settings { iced_winit::Settings { @@ -117,3 +131,15 @@ impl From> for iced_winit::Settings { } } } +#[cfg(feature = "wayland")] +impl From> for iced_sctk::Settings { + fn from(settings: Settings) -> Self { + Self { + flags: settings.flags, + kbd_repeat: Default::default(), + ptr_theme: None, + surface: settings.initial_surface, + exit_on_close_request: settings.exit_on_close_request, + } + } +} diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs new file mode 100644 index 0000000000..167123dde7 --- /dev/null +++ b/src/wayland/mod.rs @@ -0,0 +1,207 @@ +use crate::{Command, Element, Executor, Settings, Subscription}; + +/// wayland sandbox +pub mod sandbox; +pub use iced_native::application::{Appearance, StyleSheet}; +pub use iced_native::command::platform_specific::wayland as actions; +pub use iced_sctk::{application::SurfaceIdWrapper, commands::*, command::*, settings::*}; + +/// A pure version of [`Application`]. +/// +/// Unlike the impure version, the `view` method of this trait takes an +/// immutable reference to `self` and returns a pure [`Element`]. +/// +/// [`Application`]: crate::Application +/// [`Element`]: pure::Element +pub trait Application: Sized { + /// The [`Executor`] that will run commands and subscriptions. + /// + /// The [default executor] can be a good starting point! + /// + /// [`Executor`]: Self::Executor + /// [default executor]: crate::executor::Default + type Executor: Executor; + + /// The type of __messages__ your [`Application`] will produce. + type Message: std::fmt::Debug + Send; + + /// The theme of your [`Application`]. + type Theme: Default + StyleSheet; + + /// The data needed to initialize your [`Application`]. + type Flags: Clone; + + /// Initializes the [`Application`] with the flags provided to + /// [`run`] as part of the [`Settings`]. + /// + /// Here is where you should return the initial state of your app. + /// + /// Additionally, you can return a [`Command`] if you need to perform some + /// async action in the background on startup. This is useful if you want to + /// load state from a file, perform an initial HTTP request, etc. + /// + /// [`run`]: Self::run + fn new(flags: Self::Flags) -> (Self, Command); + + /// Returns the current title of the [`Application`]. + /// + /// This title can be dynamic! The runtime will automatically update the + /// title of your application when necessary. + fn title(&self) -> String; + + /// Handles a __message__ and updates the state of the [`Application`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by either user interactions or commands, will be handled by + /// this method. + /// + /// Any [`Command`] returned will be executed immediately in the background. + fn update(&mut self, message: Self::Message) -> Command; + + /// Returns the current [`Theme`] of the [`Application`]. + /// + /// [`Theme`]: Self::Theme + fn theme(&self) -> Self::Theme { + Self::Theme::default() + } + + /// Returns the current [`Style`] of the [`Theme`]. + /// + /// [`Style`]: ::Style + /// [`Theme`]: Self::Theme + fn style(&self) -> ::Style { + ::Style::default() + } + + /// Returns the event [`Subscription`] for the current state of the + /// application. + /// + /// A [`Subscription`] will be kept alive as long as you keep returning it, + /// and the __messages__ produced will be handled by + /// [`update`](#tymethod.update). + /// + /// By default, this method returns an empty [`Subscription`]. + fn subscription(&self) -> Subscription { + Subscription::none() + } + + /// Returns the widgets to display in the [`Application`]. + /// + /// These widgets can produce __messages__ based on user interaction. + fn view( + &self, + id: SurfaceIdWrapper, + ) -> Element<'_, Self::Message, crate::Renderer>; + + /// Returns the scale factor of the [`Application`]. + /// + /// It can be used to dynamically control the size of the UI at runtime + /// (i.e. zooming). + /// + /// For instance, a scale factor of `2.0` will make widgets twice as big, + /// while a scale factor of `0.5` will shrink them to half their size. + /// + /// By default, it returns `1.0`. + fn scale_factor(&self) -> f64 { + 1.0 + } + + /// Returns whether the [`Application`] should be terminated. + /// + /// By default, it returns `false`. + fn should_exit(&self) -> bool { + false + } + + /// window was requested to close + fn close_requested(&self, id: SurfaceIdWrapper) -> Self::Message; + + /// Runs the [`Application`]. + /// + /// On native platforms, this method will take control of the current thread + /// until the [`Application`] exits. + /// + /// On the web platform, this method __will NOT return__ unless there is an + /// [`Error`] during startup. + /// + /// [`Error`]: crate::Error + fn run(settings: Settings) -> crate::Result + where + Self: 'static, + { + #[allow(clippy::needless_update)] + let renderer_settings = crate::renderer::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + text_multithreading: settings.text_multithreading, + antialiasing: if settings.antialiasing { + Some(crate::renderer::settings::Antialiasing::MSAAx4) + } else { + None + }, + ..crate::renderer::Settings::from_env() + }; + + Ok(crate::runtime::run::< + Instance, + Self::Executor, + crate::renderer::window::Compositor, + >(settings.into(), renderer_settings)?) + } +} + +struct Instance(A); + +impl crate::runtime::Application for Instance +where + A: Application, +{ + type Flags = A::Flags; + type Renderer = crate::Renderer; + type Message = A::Message; + + fn new(flags: Self::Flags) -> (Self, Command) { + let (app, command) = A::new(flags); + + (Instance(app), command) + } + + fn title(&self) -> String { + self.0.title() + } + + fn update(&mut self, message: Self::Message) -> Command { + self.0.update(message) + } + + fn view( + &self, + id: SurfaceIdWrapper, + ) -> Element<'_, Self::Message, Self::Renderer> { + self.0.view(id) + } + + fn theme(&self) -> A::Theme { + self.0.theme() + } + + fn style(&self) -> ::Style { + self.0.style() + } + + fn subscription(&self) -> Subscription { + self.0.subscription() + } + + fn scale_factor(&self) -> f64 { + self.0.scale_factor() + } + + fn should_exit(&self) -> bool { + self.0.should_exit() + } + + fn close_requested(&self, id: SurfaceIdWrapper) -> Self::Message { + self.0.close_requested(id) + } +} diff --git a/src/wayland/sandbox.rs b/src/wayland/sandbox.rs new file mode 100644 index 0000000000..416d3826ef --- /dev/null +++ b/src/wayland/sandbox.rs @@ -0,0 +1,229 @@ +use iced_sctk::application::SurfaceIdWrapper; + +use crate::theme::{self, Theme}; +use crate::{Application, Command, Element, Error, Settings, Subscription}; + +/// A sandboxed [`Application`]. +/// +/// If you are a just getting started with the library, this trait offers a +/// simpler interface than [`Application`]. +/// +/// Unlike an [`Application`], a [`Sandbox`] cannot run any asynchronous +/// actions or be initialized with some external flags. However, both traits +/// are very similar and upgrading from a [`Sandbox`] is very straightforward. +/// +/// Therefore, it is recommended to always start by implementing this trait and +/// upgrade only once necessary. +/// +/// # Examples +/// [The repository has a bunch of examples] that use the [`Sandbox`] trait: +/// +/// - [`bezier_tool`], a Paint-like tool for drawing Bézier curves using the +/// [`Canvas widget`]. +/// - [`counter`], the classic counter example explained in [the overview]. +/// - [`custom_widget`], a demonstration of how to build a custom widget that +/// draws a circle. +/// - [`geometry`], a custom widget showcasing how to draw geometry with the +/// `Mesh2D` primitive in [`iced_wgpu`]. +/// - [`pane_grid`], a grid of panes that can be split, resized, and +/// reorganized. +/// - [`progress_bar`], a simple progress bar that can be filled by using a +/// slider. +/// - [`styling`], an example showcasing custom styling with a light and dark +/// theme. +/// - [`svg`], an application that renders the [Ghostscript Tiger] by leveraging +/// the [`Svg` widget]. +/// - [`tour`], a simple UI tour that can run both on native platforms and the +/// web! +/// +/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.4/examples +/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.4/examples/bezier_tool +/// [`counter`]: https://github.com/iced-rs/iced/tree/0.4/examples/counter +/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.4/examples/custom_widget +/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.4/examples/geometry +/// [`pane_grid`]: https://github.com/iced-rs/iced/tree/0.4/examples/pane_grid +/// [`progress_bar`]: https://github.com/iced-rs/iced/tree/0.4/examples/progress_bar +/// [`styling`]: https://github.com/iced-rs/iced/tree/0.4/examples/styling +/// [`svg`]: https://github.com/iced-rs/iced/tree/0.4/examples/svg +/// [`tour`]: https://github.com/iced-rs/iced/tree/0.4/examples/tour +/// [`Canvas widget`]: crate::widget::Canvas +/// [the overview]: index.html#overview +/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.4/wgpu +/// [`Svg` widget]: crate::widget::Svg +/// [Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg +/// +/// ## A simple "Hello, world!" +/// +/// If you just want to get started, here is a simple [`Sandbox`] that +/// says "Hello, world!": +/// +/// ```no_run +/// use iced::{Element, Sandbox, Settings}; +/// +/// pub fn main() -> iced::Result { +/// Hello::run(Settings::default()) +/// } +/// +/// struct Hello; +/// +/// impl Sandbox for Hello { +/// type Message = (); +/// +/// fn new() -> Hello { +/// Hello +/// } +/// +/// fn title(&self) -> String { +/// String::from("A cool application") +/// } +/// +/// fn update(&mut self, _message: Self::Message) { +/// // This application has no interactions +/// } +/// +/// fn view(&self) -> Element { +/// "Hello, world!".into() +/// } +/// } +/// ``` +pub trait Sandbox { + /// The type of __messages__ your [`Sandbox`] will produce. + type Message: std::fmt::Debug + Send; + + /// Initializes the [`Sandbox`]. + /// + /// Here is where you should return the initial state of your app. + fn new() -> Self; + + /// Returns the current title of the [`Sandbox`]. + /// + /// This title can be dynamic! The runtime will automatically update the + /// title of your application when necessary. + fn title(&self) -> String; + + /// Handles a __message__ and updates the state of the [`Sandbox`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by user interactions, will be handled by this method. + fn update(&mut self, message: Self::Message); + + /// Returns the widgets to display in the [`Sandbox`] window. + /// + /// These widgets can produce __messages__ based on user interaction. + fn view( + &self, + id: SurfaceIdWrapper, + ) -> Element<'_, Self::Message>; + + /// window was requested to close + fn close_requested(&self, id: SurfaceIdWrapper) -> Self::Message; + + /// Returns the current [`Theme`] of the [`Sandbox`]. + /// + /// If you want to use your own custom theme type, you will have to use an + /// [`Application`]. + /// + /// By default, it returns [`Theme::default`]. + fn theme(&self) -> Theme { + Theme::default() + } + + /// Returns the current style variant of [`theme::Application`]. + /// + /// By default, it returns [`theme::Application::default`]. + fn style(&self) -> theme::Application { + theme::Application::default() + } + + /// Returns the scale factor of the [`Sandbox`]. + /// + /// It can be used to dynamically control the size of the UI at runtime + /// (i.e. zooming). + /// + /// For instance, a scale factor of `2.0` will make widgets twice as big, + /// while a scale factor of `0.5` will shrink them to half their size. + /// + /// By default, it returns `1.0`. + fn scale_factor(&self) -> f64 { + 1.0 + } + + /// Returns whether the [`Sandbox`] should be terminated. + /// + /// By default, it returns `false`. + fn should_exit(&self) -> bool { + false + } + + /// Runs the [`Sandbox`]. + /// + /// On native platforms, this method will take control of the current thread + /// and __will NOT return__. + /// + /// It should probably be that last thing you call in your `main` function. + fn run(settings: Settings<()>) -> Result<(), Error> + where + Self: 'static + Sized, + { + ::run(settings) + } +} + +impl Application for T +where + T: Sandbox, +{ + type Executor = iced_futures::backend::null::Executor; + type Flags = (); + type Message = T::Message; + type Theme = Theme; + + fn new(_flags: ()) -> (Self, Command) { + (T::new(), Command::none()) + } + + fn title(&self) -> String { + T::title(self) + } + + fn update(&mut self, message: T::Message) -> Command { + T::update(self, message); + + Command::none() + } + + fn theme(&self) -> Self::Theme { + T::theme(self) + } + + fn style(&self) -> theme::Application { + T::style(self) + } + + fn subscription(&self) -> Subscription { + Subscription::none() + } + + fn scale_factor(&self) -> f64 { + T::scale_factor(self) + } + + fn should_exit(&self) -> bool { + T::should_exit(self) + } + + /// Returns the widgets to display in the [`Sandbox`] window. + /// + /// These widgets can produce __messages__ based on user interaction. + fn view( + &self, + id: SurfaceIdWrapper, + ) -> Element<'_, Self::Message> { + T::view(self, id) + } + + /// window was requested to close + fn close_requested(&self, id: SurfaceIdWrapper) -> Self::Message { + T::close_requested(&self, id) + } +} diff --git a/src/window.rs b/src/window.rs index 2018053fbb..4afb11f702 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,12 +1,17 @@ //! Configure the window of your application in native platforms. mod position; mod settings; +#[cfg(feature = "winit")] pub mod icon; - +#[cfg(feature = "winit")] pub use icon::Icon; pub use position::Position; pub use settings::Settings; #[cfg(not(target_arch = "wasm32"))] -pub use crate::runtime::window::*; +pub use crate::runtime::window::resize; +#[cfg(not(any(target_arch = "wasm32", feature = "wayland")))] +pub use crate::runtime::window::move_to; + +pub use iced_native::window::Id; diff --git a/src/window/position.rs b/src/window/position.rs index 6b9fac417b..539804cbf0 100644 --- a/src/window/position.rs +++ b/src/window/position.rs @@ -21,6 +21,7 @@ impl Default for Position { } } +#[cfg(feature = "winit")] impl From for iced_winit::Position { fn from(position: Position) -> Self { match position { diff --git a/src/window/settings.rs b/src/window/settings.rs index 83e67257ca..bed957ed73 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -1,4 +1,6 @@ -use crate::window::{Icon, Position}; +use crate::window::Position; +#[cfg(feature = "winit")] +use crate::window::Icon; /// The window settings of an application. #[derive(Debug, Clone)] @@ -34,6 +36,7 @@ pub struct Settings { pub always_on_top: bool, /// The icon of the window. + #[cfg(feature = "winit")] pub icon: Option, } @@ -50,11 +53,13 @@ impl Default for Settings { decorations: true, transparent: false, always_on_top: false, + #[cfg(feature = "winit")] icon: None, } } } +#[cfg(feature = "winit")] impl From for iced_winit::settings::Window { fn from(settings: Settings) -> Self { Self { diff --git a/winit/src/application.rs b/winit/src/application.rs index 4d2fba3ac9..42cab0c6b2 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -22,6 +22,7 @@ use iced_native::user_interface::{self, UserInterface}; pub use iced_native::application::{Appearance, StyleSheet}; +use std::collections::HashMap; use std::mem::ManuallyDrop; use winit::window::{CursorIcon, ResizeDirection}; @@ -296,6 +297,9 @@ async fn run_instance( border_size as f64 * window.scale_factor(), ); + let window_ids = + HashMap::from([(window.id(), iced_native::window::Id::MAIN)]); + let mut mouse_interaction = mouse::Interaction::default(); let mut events = Vec::new(); let mut messages = Vec::new(); @@ -477,6 +481,7 @@ async fn run_instance( } } event::Event::WindowEvent { + window_id, event: window_event, .. } => { @@ -495,6 +500,7 @@ async fn run_instance( state.update(&window, &window_event, &mut debug); if let Some(event) = conversion::window_event( + *window_ids.get(&window_id).unwrap(), &window_event, state.scale_factor(), state.modifiers(), @@ -642,7 +648,7 @@ pub fn run_command( clipboard.write(contents); } }, - command::Action::Window(action) => match action { + command::Action::Window(_, action) => match action { window::Action::Close => { *should_exit = true; } @@ -691,6 +697,7 @@ pub fn run_command( .send_event(tag(mode)) .expect("Send message to event loop"); } + window::Action::Spawn { .. } => {} }, command::Action::System(action) => match action { system::Action::QueryInformation(_tag) => { @@ -743,6 +750,7 @@ pub fn run_command( current_cache = user_interface.into_cache(); *cache = current_cache; } + command::Action::PlatformSpecific(_) => {} } } } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index b1076afe7c..013964a02e 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -10,6 +10,7 @@ use crate::{Event, Point, Position}; /// Converts a winit window event into an iced event. pub fn window_event( + id: window::Id, event: &winit::event::WindowEvent<'_>, scale_factor: f64, modifiers: winit::event::ModifiersState, @@ -20,21 +21,27 @@ pub fn window_event( WindowEvent::Resized(new_size) => { let logical_size = new_size.to_logical(scale_factor); - Some(Event::Window(window::Event::Resized { - width: logical_size.width, - height: logical_size.height, - })) + Some(Event::Window( + id, + window::Event::Resized { + width: logical_size.width, + height: logical_size.height, + }, + )) } WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { let logical_size = new_inner_size.to_logical(scale_factor); - Some(Event::Window(window::Event::Resized { - width: logical_size.width, - height: logical_size.height, - })) + Some(Event::Window( + id, + window::Event::Resized { + width: logical_size.width, + height: logical_size.height, + }, + )) } WindowEvent::CloseRequested => { - Some(Event::Window(window::Event::CloseRequested)) + Some(Event::Window(id, window::Event::CloseRequested)) } WindowEvent::CursorMoved { position, .. } => { let position = position.to_logical::(scale_factor); @@ -112,19 +119,22 @@ pub fn window_event( WindowEvent::ModifiersChanged(new_modifiers) => Some(Event::Keyboard( keyboard::Event::ModifiersChanged(self::modifiers(*new_modifiers)), )), - WindowEvent::Focused(focused) => Some(Event::Window(if *focused { - window::Event::Focused - } else { - window::Event::Unfocused - })), + WindowEvent::Focused(focused) => Some(Event::Window( + id, + if *focused { + window::Event::Focused + } else { + window::Event::Unfocused + }, + )), WindowEvent::HoveredFile(path) => { - Some(Event::Window(window::Event::FileHovered(path.clone()))) + Some(Event::Window(id, window::Event::FileHovered(path.clone()))) } WindowEvent::DroppedFile(path) => { - Some(Event::Window(window::Event::FileDropped(path.clone()))) + Some(Event::Window(id, window::Event::FileDropped(path.clone()))) } WindowEvent::HoveredFileCancelled => { - Some(Event::Window(window::Event::FilesHoveredLeft)) + Some(Event::Window(id, window::Event::FilesHoveredLeft)) } WindowEvent::Touch(touch) => { Some(Event::Touch(touch_event(*touch, scale_factor))) @@ -133,7 +143,7 @@ pub fn window_event( let winit::dpi::LogicalPosition { x, y } = position.to_logical(scale_factor); - Some(Event::Window(window::Event::Moved { x, y })) + Some(Event::Window(id, window::Event::Moved { x, y })) } _ => None, } diff --git a/winit/src/window.rs b/winit/src/window.rs index f6b43a0f96..6150427b50 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -2,56 +2,69 @@ use crate::command::{self, Command}; use iced_native::window; +pub use window::Id; pub use window::{Event, Mode}; /// Closes the current window and exits the application. -pub fn close() -> Command { - Command::single(command::Action::Window(window::Action::Close)) +pub fn close(id: window::Id) -> Command { + Command::single(command::Action::Window(id, window::Action::Close)) } /// Begins dragging the window while the left mouse button is held. -pub fn drag() -> Command { - Command::single(command::Action::Window(window::Action::Drag)) +pub fn drag(id: window::Id) -> Command { + Command::single(command::Action::Window(id, window::Action::Drag)) } /// Resizes the window to the given logical dimensions. -pub fn resize(width: u32, height: u32) -> Command { - Command::single(command::Action::Window(window::Action::Resize { - width, - height, - })) +pub fn resize( + id: window::Id, + width: u32, + height: u32, +) -> Command { + Command::single(command::Action::Window( + id, + window::Action::Resize { width, height }, + )) } /// Sets the window to maximized or back. -pub fn maximize(value: bool) -> Command { - Command::single(command::Action::Window(window::Action::Maximize(value))) +pub fn maximize(id: window::Id, value: bool) -> Command { + Command::single(command::Action::Window( + id, + window::Action::Maximize(value), + )) } /// Set the window to minimized or back. -pub fn minimize(value: bool) -> Command { - Command::single(command::Action::Window(window::Action::Minimize(value))) +pub fn minimize(id: window::Id, value: bool) -> Command { + Command::single(command::Action::Window( + id, + window::Action::Minimize(value), + )) } /// Moves a window to the given logical coordinates. -pub fn move_to(x: i32, y: i32) -> Command { - Command::single(command::Action::Window(window::Action::Move { x, y })) +pub fn move_to(id: window::Id, x: i32, y: i32) -> Command { + Command::single(command::Action::Window(id, window::Action::Move { x, y })) } /// Sets the [`Mode`] of the window. -pub fn set_mode(mode: Mode) -> Command { - Command::single(command::Action::Window(window::Action::SetMode(mode))) +pub fn set_mode(id: window::Id, mode: Mode) -> Command { + Command::single(command::Action::Window(id, window::Action::SetMode(mode))) } /// Sets the window to maximized or back. -pub fn toggle_maximize() -> Command { - Command::single(command::Action::Window(window::Action::ToggleMaximize)) +pub fn toggle_maximize(id: window::Id) -> Command { + Command::single(command::Action::Window(id, window::Action::ToggleMaximize)) } /// Fetches the current [`Mode`] of the window. pub fn fetch_mode( + id: window::Id, f: impl FnOnce(Mode) -> Message + 'static, ) -> Command { - Command::single(command::Action::Window(window::Action::FetchMode( - Box::new(f), - ))) + Command::single(command::Action::Window( + id, + window::Action::FetchMode(Box::new(f)), + )) } From c9b891c84d6383f401fff6470118d788ba357eb1 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 15 Dec 2022 11:04:58 -0500 Subject: [PATCH 05/56] fix: button conversion --- sctk/src/conversion.rs | 15 +++++++++------ sctk/src/handlers/seat/pointer.rs | 1 - 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/sctk/src/conversion.rs b/sctk/src/conversion.rs index 0481c370a5..c33a3e1bd8 100644 --- a/sctk/src/conversion.rs +++ b/sctk/src/conversion.rs @@ -4,7 +4,7 @@ use iced_native::{ }; use sctk::{ reexports::client::protocol::wl_pointer::AxisSource, - seat::{keyboard::Modifiers, pointer::AxisScroll}, + seat::{keyboard::Modifiers, pointer::{AxisScroll, BTN_LEFT, BTN_RIGHT, BTN_MIDDLE}}, }; /// An error that occurred while running an application. #[derive(Debug, thiserror::Error)] @@ -12,11 +12,14 @@ use sctk::{ pub struct KeyCodeError(u32); pub fn pointer_button_to_native(button: u32) -> Option { - match button { - BTN_LEFT => Some(mouse::Button::Left), - BTN_MIDDLE => Some(mouse::Button::Middle), - BTN_RIGHT => Some(mouse::Button::Right), - b => b.try_into().ok().map(|b| mouse::Button::Other(b)), + if button == BTN_LEFT { + Some(mouse::Button::Left) + } else if button == BTN_RIGHT { + Some(mouse::Button::Right) + } else if button == BTN_MIDDLE { + Some(mouse::Button::Right) + } else { + button.try_into().ok().map(|b| mouse::Button::Other(b)) } } diff --git a/sctk/src/handlers/seat/pointer.rs b/sctk/src/handlers/seat/pointer.rs index eb07f85d7a..b5900e7b1e 100644 --- a/sctk/src/handlers/seat/pointer.rs +++ b/sctk/src/handlers/seat/pointer.rs @@ -1,7 +1,6 @@ use crate::{event_loop::state::SctkState, sctk_event::SctkEvent}; use sctk::{ delegate_pointer, - reexports::client::Proxy, seat::pointer::{PointerEventKind, PointerHandler}, }; use std::fmt::Debug; From 4c92b828a3866d524d31738dd68e5dd84f208d0d Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 19 Dec 2022 16:20:12 +0100 Subject: [PATCH 06/56] Revert "Allow &mut self in overlay" This reverts commit f1ada7a803998ac3fb2c1bedc6d6650264f3e603. Fixes panic when using lazy widgets --- lazy/src/component.rs | 31 ++++++++++++++---------- lazy/src/lazy.rs | 27 ++++++++++++++------- lazy/src/responsive.rs | 22 +++++++---------- native/src/element.rs | 4 +-- native/src/overlay.rs | 6 ++--- native/src/user_interface.rs | 18 +++++++------- native/src/widget.rs | 2 +- native/src/widget/button.rs | 4 +-- native/src/widget/column.rs | 4 +-- native/src/widget/container.rs | 4 +-- native/src/widget/mouse_listener.rs | 4 +-- native/src/widget/pane_grid.rs | 4 +-- native/src/widget/pane_grid/content.rs | 8 +++--- native/src/widget/pane_grid/title_bar.rs | 8 +++--- native/src/widget/pick_list.rs | 2 +- native/src/widget/row.rs | 4 +-- native/src/widget/scrollable.rs | 4 +-- native/src/widget/tooltip.rs | 4 +-- 18 files changed, 85 insertions(+), 75 deletions(-) diff --git a/lazy/src/component.rs b/lazy/src/component.rs index 3d7b8758bc..4f1df6504c 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -11,7 +11,7 @@ use iced_native::{ }; use ouroboros::self_referencing; -use std::cell::RefCell; +use std::cell::{Ref, RefCell}; use std::marker::PhantomData; /// A reusable, custom widget that uses The Elm Architecture. @@ -322,25 +322,25 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { let overlay = OverlayBuilder { instance: self, + instance_ref_builder: |instance| instance.state.borrow(), tree, types: PhantomData, overlay_builder: |instance, tree| { - instance.state.get_mut().as_mut().unwrap().with_element_mut( - move |element| { - element.as_mut().unwrap().as_widget_mut().overlay( - &mut tree.children[0], - layout, - renderer, - ) - }, - ) + instance + .as_ref() + .unwrap() + .borrow_element() + .as_ref() + .unwrap() + .as_widget() + .overlay(&mut tree.children[0], layout, renderer) }, } .build(); @@ -362,11 +362,15 @@ where #[self_referencing] struct Overlay<'a, 'b, Message, Renderer, Event, S> { - instance: &'a mut Instance<'b, Message, Renderer, Event, S>, + instance: &'a Instance<'b, Message, Renderer, Event, S>, tree: &'a mut Tree, types: PhantomData<(Message, Event, S)>, - #[borrows(mut instance, mut tree)] + #[borrows(instance)] + #[covariant] + instance_ref: Ref<'this, Option>>, + + #[borrows(instance_ref, mut tree)] #[covariant] overlay: Option>, } @@ -510,6 +514,7 @@ where self.overlay = Some( OverlayBuilder { instance: overlay.instance, + instance_ref_builder: |instance| instance.state.borrow(), tree: overlay.tree, types: PhantomData, overlay_builder: |_, _| None, diff --git a/lazy/src/lazy.rs b/lazy/src/lazy.rs index 2611dd1092..d61cc77e46 100644 --- a/lazy/src/lazy.rs +++ b/lazy/src/lazy.rs @@ -207,7 +207,7 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -216,12 +216,12 @@ where cached: self, tree: &mut tree.children[0], types: PhantomData, - overlay_builder: |cached, tree| { - Rc::get_mut(cached.element.get_mut().as_mut().unwrap()) - .unwrap() - .get_mut() - .as_widget_mut() - .overlay(tree, layout, renderer) + element_ref_builder: |cached| cached.element.borrow(), + element_builder: |element_ref| { + element_ref.as_ref().unwrap().borrow() + }, + overlay_builder: |element, tree| { + element.as_widget().overlay(tree, layout, renderer) }, } .build(); @@ -237,11 +237,20 @@ where #[self_referencing] struct Overlay<'a, 'b, Message, Renderer, Dependency, View> { - cached: &'a mut Lazy<'b, Message, Renderer, Dependency, View>, + cached: &'a Lazy<'b, Message, Renderer, Dependency, View>, tree: &'a mut Tree, types: PhantomData<(Message, Dependency, View)>, - #[borrows(mut cached, mut tree)] + #[borrows(cached)] + #[covariant] + element_ref: + Ref<'this, Option>>>>, + + #[borrows(element_ref)] + #[covariant] + element: Ref<'this, Element<'static, Message, Renderer>>, + + #[borrows(element, mut tree)] #[covariant] overlay: Option>, } diff --git a/lazy/src/responsive.rs b/lazy/src/responsive.rs index 5e1b5dff1c..0b7ae6de10 100644 --- a/lazy/src/responsive.rs +++ b/lazy/src/responsive.rs @@ -235,20 +235,18 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - use std::ops::DerefMut; - let state = tree.state.downcast_ref::(); let overlay = OverlayBuilder { content: self.content.borrow_mut(), tree: state.tree.borrow_mut(), types: PhantomData, - overlay_builder: |content: &mut RefMut>, tree| { + overlay_builder: |content, tree| { content.update( tree, renderer, @@ -256,18 +254,16 @@ where &self.view, ); - let Content { - element, layout, .. - } = content.deref_mut(); - let content_layout = Layout::with_offset( - layout.bounds().position() - Point::ORIGIN, - layout, + layout.position() - Point::ORIGIN, + &content.layout, ); - element - .as_widget_mut() - .overlay(tree, content_layout, renderer) + content.element.as_widget().overlay( + tree, + content_layout, + renderer, + ) }, } .build(); diff --git a/native/src/element.rs b/native/src/element.rs index 2f1adeff59..72cd06d8de 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -405,7 +405,7 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -560,7 +560,7 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, state: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, diff --git a/native/src/overlay.rs b/native/src/overlay.rs index 0b05b058e1..c4a80a99c8 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -93,7 +93,7 @@ where /// This method will generally only be used by advanced users that are /// implementing the [`Widget`](crate::Widget) trait. pub fn from_children<'a, Message, Renderer>( - children: &'a mut [crate::Element<'_, Message, Renderer>], + children: &'a [crate::Element<'_, Message, Renderer>], tree: &'a mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -102,11 +102,11 @@ where Renderer: crate::Renderer, { children - .iter_mut() + .iter() .zip(&mut tree.children) .zip(layout.children()) .filter_map(|((child, state), layout)| { - child.as_widget_mut().overlay(state, layout, renderer) + child.as_widget().overlay(state, layout, renderer) }) .next() } diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 376ce568f8..a48d498393 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -190,7 +190,7 @@ where let mut state = State::Updated; let mut manual_overlay = - ManuallyDrop::new(self.root.as_widget_mut().overlay( + ManuallyDrop::new(self.root.as_widget().overlay( &mut self.state, Layout::new(&self.base), renderer, @@ -226,7 +226,7 @@ where ); manual_overlay = - ManuallyDrop::new(self.root.as_widget_mut().overlay( + ManuallyDrop::new(self.root.as_widget().overlay( &mut self.state, Layout::new(&self.base), renderer, @@ -395,11 +395,11 @@ where let viewport = Rectangle::with_size(self.bounds); - let base_cursor = if let Some(overlay) = self - .root - .as_widget_mut() - .overlay(&mut self.state, Layout::new(&self.base), renderer) - { + let base_cursor = if let Some(overlay) = self.root.as_widget().overlay( + &mut self.state, + Layout::new(&self.base), + renderer, + ) { let overlay_layout = self .overlay .take() @@ -452,7 +452,7 @@ where overlay .as_ref() .and_then(|layout| { - root.as_widget_mut() + root.as_widget() .overlay(&mut self.state, Layout::new(base), renderer) .map(|overlay| { let overlay_interaction = overlay.mouse_interaction( @@ -496,7 +496,7 @@ where operation, ); - if let Some(mut overlay) = self.root.as_widget_mut().overlay( + if let Some(mut overlay) = self.root.as_widget().overlay( &mut self.state, Layout::new(&self.base), renderer, diff --git a/native/src/widget.rs b/native/src/widget.rs index ea9a0574d5..be7de3565d 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -214,7 +214,7 @@ where /// Returns the overlay of the [`Widget`], if there is any. fn overlay<'a>( - &'a mut self, + &'a self, _state: &'a mut Tree, _layout: Layout<'_>, _renderer: &Renderer, diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index bbd9451ce3..caf16ea049 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -260,12 +260,12 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - self.content.as_widget_mut().overlay( + self.content.as_widget().overlay( &mut tree.children[0], layout.children().next().unwrap(), renderer, diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 8030778bff..a8b0f18307 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -242,12 +242,12 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - overlay::from_children(&mut self.children, tree, layout, renderer) + overlay::from_children(&self.children, tree, layout, renderer) } } diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 16d0cb61bd..06ae1bc9e2 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -248,12 +248,12 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - self.content.as_widget_mut().overlay( + self.content.as_widget().overlay( &mut tree.children[0], layout.children().next().unwrap(), renderer, diff --git a/native/src/widget/mouse_listener.rs b/native/src/widget/mouse_listener.rs index 78f416fc50..59281cdca6 100644 --- a/native/src/widget/mouse_listener.rs +++ b/native/src/widget/mouse_listener.rs @@ -249,12 +249,12 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - self.content.as_widget_mut().overlay( + self.content.as_widget().overlay( &mut tree.children[0], layout.children().next().unwrap(), renderer, diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 5de95c651f..cd941c607b 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -444,13 +444,13 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { self.contents - .iter_mut() + .iter() .zip(&mut tree.children) .zip(layout.children()) .filter_map(|(((_, pane), tree), layout)| { diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 5f269d1f60..5e843cff74 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -305,12 +305,12 @@ where } pub(crate) fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - if let Some(title_bar) = self.title_bar.as_mut() { + if let Some(title_bar) = self.title_bar.as_ref() { let mut children = layout.children(); let title_bar_layout = children.next()?; @@ -321,14 +321,14 @@ where match title_bar.overlay(title_bar_state, title_bar_layout, renderer) { Some(overlay) => Some(overlay), - None => self.body.as_widget_mut().overlay( + None => self.body.as_widget().overlay( body_state, children.next()?, renderer, ), } } else { - self.body.as_widget_mut().overlay( + self.body.as_widget().overlay( &mut tree.children[0], layout, renderer, diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index 28e4670f4f..115f6270da 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -395,7 +395,7 @@ where } pub(crate) fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -415,13 +415,13 @@ where let controls_state = states.next().unwrap(); content - .as_widget_mut() + .as_widget() .overlay(title_state, title_layout, renderer) .or_else(move || { - controls.as_mut().and_then(|controls| { + controls.as_ref().and_then(|controls| { let controls_layout = children.next()?; - controls.as_widget_mut().overlay( + controls.as_widget().overlay( controls_state, controls_layout, renderer, diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 52cb1ad189..04a554371e 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -219,7 +219,7 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, _renderer: &Renderer, diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index c689ac13f2..eda7c2d355 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -229,12 +229,12 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - overlay::from_children(&mut self.children, tree, layout, renderer) + overlay::from_children(&self.children, tree, layout, renderer) } } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index a5e0e0e30c..ce29c184ed 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -276,13 +276,13 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { self.content - .as_widget_mut() + .as_widget() .overlay( &mut tree.children[0], layout.children().next().unwrap(), diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index 084dc269ee..9347a8868e 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -221,12 +221,12 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - self.content.as_widget_mut().overlay( + self.content.as_widget().overlay( &mut tree.children[0], layout, renderer, From fc12676ecb55c87fcccc8d9ec8714d649cb0b856 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 19 Dec 2022 13:19:53 -0500 Subject: [PATCH 07/56] feat: various improvements to window commands and event handling --- .../platform_specific/wayland/popup.rs | 2 +- .../platform_specific/wayland/window.rs | 43 ++++++++++++ native/src/event/wayland/layer.rs | 2 - native/src/event/wayland/popup.rs | 2 - native/src/event/wayland/seat.rs | 2 - sctk/src/application.rs | 4 +- sctk/src/commands/window.rs | 41 ++++++++---- sctk/src/event_loop/mod.rs | 66 +++++++++++++++++-- 8 files changed, 135 insertions(+), 27 deletions(-) diff --git a/native/src/command/platform_specific/wayland/popup.rs b/native/src/command/platform_specific/wayland/popup.rs index 2f3ff46218..837920cee3 100644 --- a/native/src/command/platform_specific/wayland/popup.rs +++ b/native/src/command/platform_specific/wayland/popup.rs @@ -122,7 +122,7 @@ impl Action { /// Maps the output of a window [`Action`] using the provided closure. pub fn map( self, - f: impl Fn(T) -> A + 'static + MaybeSend + Sync, + _: impl Fn(T) -> A + 'static + MaybeSend + Sync, ) -> Action where T: 'static, diff --git a/native/src/command/platform_specific/wayland/window.rs b/native/src/command/platform_specific/wayland/window.rs index 233a6ace03..532589c160 100644 --- a/native/src/command/platform_specific/wayland/window.rs +++ b/native/src/command/platform_specific/wayland/window.rs @@ -81,6 +81,11 @@ pub enum Action { /// id of the window id: window::Id, }, + /// Toggle maximization of the window. + ToggleMaximized { + /// id of the window + id: window::Id, + }, /// Maximize the window. Maximize { /// id of the window @@ -91,6 +96,11 @@ pub enum Action { /// id of the window id: window::Id, }, + /// Toggle fullscreen of the window. + ToggleFullscreen { + /// id of the window + id: window::Id, + }, /// Fullscreen the window. Fullscreen { /// id of the window @@ -122,6 +132,15 @@ pub enum Action { /// y location of popup y: i32, }, + /// Set the mode of the window + Mode(window::Id, window::Mode), + /// Set the app id of the window + AppId { + /// id of the window + id: window::Id, + /// app id of the window + app_id: String, + }, } impl Action { @@ -157,6 +176,10 @@ impl Action { Action::InteractiveResize { id, edge } } Action::Destroy(id) => Action::Destroy(id), + Action::Mode(id, m) => Action::Mode(id, m), + Action::ToggleMaximized { id } => Action::ToggleMaximized { id }, + Action::ToggleFullscreen { id } => Action::ToggleFullscreen { id }, + Action::AppId { id, app_id } => Action::AppId { id, app_id }, } } } @@ -234,6 +257,26 @@ impl fmt::Debug for Action { "Action::Window::Destroy {{ id: {:?} }}", id ), + Action::Mode(id, m) => write!( + f, + "Action::Window::Mode {{ id: {:?}, mode: {:?} }}", + id, m + ), + Action::ToggleMaximized { id } => write!( + f, + "Action::Window::Maximized {{ id: {:?} }}", + id + ), + Action::ToggleFullscreen { id } => write!( + f, + "Action::Window::ToggleFullscreen {{ id: {:?} }}", + id + ), + Action::AppId { id, app_id } => write!( + f, + "Action::Window::Mode {{ id: {:?}, app_id: {:?} }}", + id, app_id + ), } } } diff --git a/native/src/event/wayland/layer.rs b/native/src/event/wayland/layer.rs index 70c40f1e0e..c1928ad36e 100644 --- a/native/src/event/wayland/layer.rs +++ b/native/src/event/wayland/layer.rs @@ -1,5 +1,3 @@ -use crate::window; - /// layer surface events #[derive(Debug, Clone, PartialEq, Eq)] pub enum LayerEvent { diff --git a/native/src/event/wayland/popup.rs b/native/src/event/wayland/popup.rs index d70d617bc6..ff925870b2 100644 --- a/native/src/event/wayland/popup.rs +++ b/native/src/event/wayland/popup.rs @@ -1,5 +1,3 @@ -use crate::window; - /// popup events #[derive(Debug, Clone, PartialEq, Eq)] pub enum PopupEvent { diff --git a/native/src/event/wayland/seat.rs b/native/src/event/wayland/seat.rs index 28143fa231..3da4374e71 100644 --- a/native/src/event/wayland/seat.rs +++ b/native/src/event/wayland/seat.rs @@ -1,5 +1,3 @@ -use sctk::reexports::client::protocol::wl_seat::WlSeat; - /// seat events /// Only one seat can interact with an iced_sctk application at a time, but many may interact with the application over the lifetime of the application #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/sctk/src/application.rs b/sctk/src/application.rs index 1eac4d40d4..b391808e96 100644 --- a/sctk/src/application.rs +++ b/sctk/src/application.rs @@ -1158,8 +1158,8 @@ fn run_command( todo!(); } }, - command::Action::Window(id, action) => { - todo!() + command::Action::Window(..) => { + unimplemented!("Use platform specific events instead") } command::Action::System(action) => match action { system::Action::QueryInformation(_tag) => { diff --git a/sctk/src/commands/window.rs b/sctk/src/commands/window.rs index e038c8b4e0..92f9fbd6df 100644 --- a/sctk/src/commands/window.rs +++ b/sctk/src/commands/window.rs @@ -45,18 +45,35 @@ pub fn resize_window( )) } -/// Sets the [`Mode`] of the window. -pub fn set_mode_window( - id: window::Id, - mode: Mode, -) -> Command { - Command::single(command::Action::Window(id, Action::SetMode(mode))) +pub fn start_drag_window(id: window::Id) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Window( + wayland::window::Action::InteractiveMove { id }, + )), + )) } -/// Fetches the current [`Mode`] of the window. -pub fn fetch_mode_window( - id: window::Id, - f: impl FnOnce(Mode) -> Message + 'static, -) -> Command { - Command::single(command::Action::Window(id, Action::FetchMode(Box::new(f)))) +pub fn toggle_maximize(id: window::Id) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Window( + wayland::window::Action::ToggleMaximized { id }, + )), + )) +} + +pub fn set_app_id_window(id: window::Id, app_id: String) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Window( + wayland::window::Action::AppId { id, app_id }, + )), + )) +} + +/// Sets the [`Mode`] of the window. +pub fn set_mode_window(id: window::Id, mode: Mode) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Window( + wayland::window::Action::Mode(id, mode), + )), + )) } diff --git a/sctk/src/event_loop/mod.rs b/sctk/src/event_loop/mod.rs index bce96ed183..cff519716e 100644 --- a/sctk/src/event_loop/mod.rs +++ b/sctk/src/event_loop/mod.rs @@ -414,10 +414,11 @@ where variant: WindowEventVariant::Close, id, } => { - if let Some(i) = - self.state.layer_surfaces.iter().position(|l| { - l.surface.wl_surface().id() == id.id() - }) + if let Some(i) = self + .state + .windows + .iter() + .position(|l| l.window.wl_surface().id() == id.id()) { let w = self.state.windows.remove(i); w.window.xdg_toplevel().destroy(); @@ -627,11 +628,30 @@ where } }, platform_specific::wayland::window::Action::InteractiveMove { id } => { + if let (Some(window), Some((seat, last_press))) = (self.state.windows.iter_mut().find(|w| w.id == id), self.state.seats.first().and_then(|seat| seat.last_ptr_press.map(|p| (&seat.seat, p.2)))) { + window.window.xdg_toplevel()._move(seat, last_press); + to_commit.insert(id, window.window.wl_surface().clone()); + } + }, + platform_specific::wayland::window::Action::InteractiveResize { id, edge } => { + if let (Some(window), Some((seat, last_press))) = (self.state.windows.iter_mut().find(|w| w.id == id), self.state.seats.first().and_then(|seat| seat.last_ptr_press.map(|p| (&seat.seat, p.2)))) { + window.window.xdg_toplevel().resize(seat, last_press, edge); + to_commit.insert(id, window.window.wl_surface().clone()); + } + }, + platform_specific::wayland::window::Action::ToggleMaximized { id } => { if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { - todo!(); + if let Some(c) = &window.last_configure { + dbg!(c); + if c.is_maximized() { + window.window.unset_maximized(); + } else { + window.window.set_maximized(); + } + to_commit.insert(id, window.window.wl_surface().clone()); + } } }, - platform_specific::wayland::window::Action::InteractiveResize { id, edge } => todo!(), platform_specific::wayland::window::Action::ShowWindowMenu { id, x, y } => todo!(), platform_specific::wayland::window::Action::Destroy(id) => { if let Some(i) = self.state.windows.iter().position(|l| &l.id == &id) { @@ -648,6 +668,40 @@ where ); } }, + platform_specific::wayland::window::Action::Mode(id, mode) => { + if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { + match mode { + iced_native::window::Mode::Windowed => { + window.window.unset_fullscreen(); + }, + iced_native::window::Mode::Fullscreen => { + window.window.set_fullscreen(None); + }, + iced_native::window::Mode::Hidden => { + window.window.set_mimimized(); + }, + } + to_commit.insert(id, window.window.wl_surface().clone()); + } + }, + platform_specific::wayland::window::Action::ToggleFullscreen { id } => { + if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { + if let Some(c) = &window.last_configure { + if c.is_fullscreen() { + window.window.unset_fullscreen(); + } else { + window.window.set_fullscreen(None); + } + to_commit.insert(id, window.window.wl_surface().clone()); + } + } + }, + platform_specific::wayland::window::Action::AppId { id, app_id } => { + if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { + window.window.set_app_id(app_id); + to_commit.insert(id, window.window.wl_surface().clone()); + } + }, }, Event::Popup(action) => match action { platform_specific::wayland::popup::Action::Popup { popup, .. } => { From dd6929d8fa0465801d56d9a87c5089d4a4d1d9a1 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 7 Dec 2022 17:14:14 -0700 Subject: [PATCH 08/56] feat: Add softbuffer renderer backend --- Cargo.toml | 11 +- examples/styling/Cargo.toml | 2 +- examples/styling/src/main.rs | 11 +- softbuffer/Cargo.toml | 33 ++ softbuffer/README.md | 3 + softbuffer/src/backend.rs | 288 +++++++++++ softbuffer/src/lib.rs | 21 + softbuffer/src/settings.rs | 47 ++ softbuffer/src/surface.rs | 707 ++++++++++++++++++++++++++++ softbuffer/src/window.rs | 4 + softbuffer/src/window/compositor.rs | 82 ++++ src/lib.rs | 13 +- 12 files changed, 1212 insertions(+), 10 deletions(-) create mode 100644 softbuffer/Cargo.toml create mode 100644 softbuffer/README.md create mode 100644 softbuffer/src/backend.rs create mode 100644 softbuffer/src/lib.rs create mode 100644 softbuffer/src/settings.rs create mode 100644 softbuffer/src/surface.rs create mode 100644 softbuffer/src/window.rs create mode 100644 softbuffer/src/window/compositor.rs diff --git a/Cargo.toml b/Cargo.toml index 194ddb8277..ebd3c47bfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,17 +12,19 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] -default = ["wgpu", "winit"] +default = ["softbuffer", "winit"] # Enables the `Image` widget -image = ["iced_wgpu?/image", "iced_glow?/image", "image_rs"] +image = ["iced_wgpu?/image", "iced_glow?/image", "iced_softbuffer?/image", "image_rs"] # Enables the `Svg` widget -svg = ["iced_wgpu?/svg", "iced_glow?/svg"] +svg = ["iced_wgpu?/svg", "iced_glow?/svg", "iced_softbuffer?/svg"] # Enables the `Canvas` widget canvas = ["iced_graphics/canvas"] # Enables the `QRCode` widget qr_code = ["iced_graphics/qr_code"] # Enables the `iced_wgpu` renderer wgpu = ["iced_wgpu"] +# Enables the `iced_softbuffer` renderer +softbuffer = ["iced_softbuffer"] # Enables using system fonts default_system_font = ["iced_wgpu?/default_system_font", "iced_glow?/default_system_font"] # Enables the `iced_glow` renderer. Overrides `iced_wgpu` @@ -55,6 +57,7 @@ members = [ "glutin", "lazy", "native", + "softbuffer", "style", "wgpu", "winit", @@ -71,6 +74,7 @@ iced_winit = { version = "0.6", path = "winit", features = ["application"], opti iced_glutin = { version = "0.5", path = "glutin", optional = true } iced_glow = { version = "0.5", path = "glow", optional = true } iced_sctk = { path = "./sctk", optional = true } +iced_softbuffer = { version = "0.1", path = "softbuffer", optional = true } thiserror = "1.0" [dependencies.image_rs] @@ -97,3 +101,4 @@ incremental = false opt-level = 3 overflow-checks = false strip = "debuginfo" + diff --git a/examples/styling/Cargo.toml b/examples/styling/Cargo.toml index f771708c8e..3524f0d862 100644 --- a/examples/styling/Cargo.toml +++ b/examples/styling/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } +iced = { path = "../..", features = ["debug"] } diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index e16860adc9..fdbf614c49 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -75,7 +75,7 @@ impl Sandbox for Styling { [ThemeType::Light, ThemeType::Dark, ThemeType::Custom] .iter() .fold( - column![text("Choose a theme:")].spacing(10), + column![text("Choose a theme: 😊")].spacing(10), |column, theme| { column.push(radio( format!("{:?}", theme), @@ -150,12 +150,17 @@ impl Sandbox for Styling { .padding(20) .max_width(600); - container(content) + let element: Element<_> = container(content) .width(Length::Fill) .height(Length::Fill) .center_x() .center_y() - .into() + .into(); + if self.toggler_value { + element.explain(Color::BLACK) + } else { + element + } } fn theme(&self) -> Theme { diff --git a/softbuffer/Cargo.toml b/softbuffer/Cargo.toml new file mode 100644 index 0000000000..c75311b856 --- /dev/null +++ b/softbuffer/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "iced_softbuffer" +version = "0.1.0" +authors = ["Jeremy Soller "] +edition = "2021" +description = "A softbuffer renderer for Iced" +license = "MIT AND OFL-1.1" +repository = "https://github.com/iced-rs/iced" + +[dependencies] +cosmic-text = "0.5.3" +lazy_static = "1.4" +log = "0.4" +raw-window-handle = "0.5" +raqote = { version = "0.8", default-features = false } +# Patched version for raw-window-handle 0.5 and Redox support +softbuffer = { git = "https://github.com/jackpot51/softbuffer", branch = "redox-0.1.1" } + +[dependencies.iced_native] +path = "../native" + +[dependencies.iced_graphics] +path = "../graphics" +features = ["font-fallback", "font-icons"] + +[features] +default = [] +svg = ["iced_graphics/svg"] +image = ["iced_graphics/image"] + +[package.metadata.docs.rs] +rustdoc-args = ["--cfg", "docsrs"] +all-features = true diff --git a/softbuffer/README.md b/softbuffer/README.md new file mode 100644 index 0000000000..e656fd2e4c --- /dev/null +++ b/softbuffer/README.md @@ -0,0 +1,3 @@ +# `iced_softbuffer` + +Software rendering for Iced \ No newline at end of file diff --git a/softbuffer/src/backend.rs b/softbuffer/src/backend.rs new file mode 100644 index 0000000000..02c17ee0c9 --- /dev/null +++ b/softbuffer/src/backend.rs @@ -0,0 +1,288 @@ +use cosmic_text::{Attrs, AttrsList, BufferLine, FontSystem, Metrics, SwashCache, Weight}; +#[cfg(feature = "image")] +use iced_graphics::image::raster; +use iced_graphics::image::storage; +#[cfg(feature = "svg")] +use iced_graphics::image::vector; +use iced_graphics::{Primitive, Vector}; +#[cfg(feature = "image")] +use iced_native::image; +use iced_native::layout; +use iced_native::renderer; +#[cfg(feature = "svg")] +use iced_native::svg; +use iced_native::text::{self, Text}; +use iced_native::{Background, Element, Font, Point, Rectangle, Size}; +use std::cell::RefCell; +use std::fmt; +use std::marker::PhantomData; + +lazy_static::lazy_static! { + pub(crate) static ref FONT_SYSTEM: FontSystem = FontSystem::new(); +} + +/// An entry in some [`Storage`], +pub(crate) struct CpuEntry { + pub(crate) size: Size, + pub(crate) data: Vec, +} + +impl fmt::Debug for CpuEntry { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CpuEntry") + .field("size", &self.size) + .finish() + } +} + +impl storage::Entry for CpuEntry { + /// The [`Size`] of the [`Entry`]. + fn size(&self) -> Size { + self.size + } +} + +/// Stores cached image data for use in rendering +#[derive(Debug)] +pub(crate) struct CpuStorage; + +impl storage::Storage for CpuStorage { + /// The type of an [`Entry`] in the [`Storage`]. + type Entry = CpuEntry; + + /// State provided to upload or remove a [`Self::Entry`]. + type State<'a> = (); + + /// Upload the image data of a [`Self::Entry`]. + fn upload( + &mut self, + width: u32, + height: u32, + data_u8: &[u8], + state: &mut Self::State<'_>, + ) -> Option { + let mut data = Vec::with_capacity(data_u8.len() / 4); + for chunk in data_u8.chunks_exact(4) { + data.push( + raqote::SolidSource::from_unpremultiplied_argb( + chunk[3], + chunk[0], + chunk[1], + chunk[2], + ).to_u32() + ); + } + Some(Self::Entry { + size: Size::new(width, height), + data, + }) + } + + /// Romve a [`Self::Entry`] from the [`Storage`]. + fn remove(&mut self, entry: &Self::Entry, state: &mut Self::State<'_>) { + // no-op + } +} + +pub struct Backend { + pub(crate) swash_cache: SwashCache<'static>, + #[cfg(feature = "image")] + pub(crate) raster_cache: RefCell>, + #[cfg(feature = "svg")] + pub(crate) vector_cache: RefCell>, +} + +impl Backend { + pub(crate) fn new() -> Self { + Self { + swash_cache: SwashCache::new(&FONT_SYSTEM), + #[cfg(feature = "image")] + raster_cache: RefCell::new(raster::Cache::default()), + #[cfg(feature = "svg")] + vector_cache: RefCell::new(vector::Cache::default()), + } + } + + pub(crate) fn cosmic_metrics_attrs(&self, size: f32, font: &Font) -> (Metrics, Attrs) { + //TODO: why is this conversion necessary? + let font_size = (size * 5.0 / 6.0) as i32; + + //TODO: how to properly calculate line height? + let line_height = size as i32; + + let attrs = match font { + Font::Default => Attrs::new().weight(Weight::NORMAL), + //TODO: support using the bytes field. Right now this is just a hack for libcosmic + Font::External { + name, + bytes, + } => match *name { + "Fira Sans Regular" => Attrs::new().weight(Weight::NORMAL), + "Fira Sans Light" => Attrs::new().weight(Weight::LIGHT), + "Fira Sans SemiBold" => Attrs::new().weight(Weight::SEMIBOLD), + _ => { + log::warn!("Unsupported font name {:?}", name); + Attrs::new() + } + } + }; + + ( + Metrics::new(font_size, line_height), + attrs + ) + } +} + +impl iced_graphics::backend::Backend for Backend { + fn trim_measurements(&mut self) { + // no-op + } +} + +impl iced_graphics::backend::Text for Backend { + const ICON_FONT: Font = Font::Default; + const CHECKMARK_ICON: char = '✓'; + const ARROW_DOWN_ICON: char = '⌄'; + + fn default_size(&self) -> u16 { + //TODO: get from settings + 16 + } + + fn measure( + &self, + content: &str, + size: f32, + font: Font, + bounds: Size, + ) -> (f32, f32) { + let (metrics, attrs) = self.cosmic_metrics_attrs(size, &font); + + //TODO: improve implementation + let mut buffer_line = BufferLine::new(content, AttrsList::new(attrs)); + let layout = buffer_line.layout(&FONT_SYSTEM, metrics.font_size, bounds.width as i32); + + let mut width = 0.0; + let mut height = 0.0; + for layout_line in layout.iter() { + for glyph in layout_line.glyphs.iter() { + let max_x = if glyph.rtl { + glyph.x - glyph.w + } else { + glyph.x + glyph.w + }; + if max_x + 1.0 > width { + width = max_x + 1.0; + } + } + + height += metrics.line_height as f32; + } + (width, height) + } + + fn hit_test( + &self, + content: &str, + size: f32, + font: Font, + bounds: Size, + point: Point, + nearest_only: bool, + ) -> Option { + let (metrics, attrs) = self.cosmic_metrics_attrs(size, &font); + + //TODO: improve implementation + let mut buffer_line = BufferLine::new(content, AttrsList::new(attrs)); + let layout = buffer_line.layout(&FONT_SYSTEM, metrics.font_size, bounds.width as i32); + + // Find exact hit + if ! nearest_only { + let mut line_y = 0.0; + for layout_line in layout.iter() { + if point.y > line_y && point.y < line_y + metrics.line_height as f32 { + for glyph in layout_line.glyphs.iter() { + let (min_x, max_x) = if glyph.rtl { + (glyph.x - glyph.w, glyph.x) + } else { + (glyph.x, glyph.x + glyph.w) + }; + + if point.x > min_x && point.x < max_x { + println!("EXACT HIT {:?}", glyph); + return Some(text::Hit::CharOffset( + glyph.start + )); + } + } + } + + line_y += metrics.line_height as f32; + } + } + + // Find nearest + let mut nearest_opt = None; + let mut line_y = 0.0; + for layout_line in layout.iter() { + let center_y = line_y + metrics.line_height as f32 / 2.0; + + for glyph in layout_line.glyphs.iter() { + let (min_x, max_x) = if glyph.rtl { + (glyph.x - glyph.w, glyph.x) + } else { + (glyph.x, glyph.x + glyph.w) + }; + + let center_x = (min_x + max_x) / 2.0; + let center = Point::new(center_x, center_y); + + let distance = center.distance(point); + let vector = point - center; + nearest_opt = match nearest_opt { + Some((nearest_offset, nearest_vector, nearest_distance)) => { + if distance < nearest_distance { + Some((glyph.start, vector, distance)) + } else { + Some((nearest_offset, nearest_vector, nearest_distance)) + } + }, + None => { + Some((glyph.start, vector, distance)) + } + }; + } + + line_y += metrics.line_height as f32; + } + + match nearest_opt { + Some((offset, vector, distance)) => Some(text::Hit::NearestCharOffset( + offset, + vector + )), + None => None, + } + } +} + +#[cfg(feature = "image")] +impl iced_graphics::backend::Image for Backend { + fn dimensions(&self, handle: &image::Handle) -> Size { + let mut cache = self.raster_cache.borrow_mut(); + let memory = cache.load(handle); + + memory.dimensions() + } +} + +#[cfg(feature = "svg")] +impl iced_graphics::backend::Svg for Backend { + fn viewport_dimensions(&self, handle: &svg::Handle) -> Size { + let mut cache = self.vector_cache.borrow_mut(); + let svg = cache.load(handle); + + svg.viewport_dimensions() + } +} diff --git a/softbuffer/src/lib.rs b/softbuffer/src/lib.rs new file mode 100644 index 0000000000..2774ed1216 --- /dev/null +++ b/softbuffer/src/lib.rs @@ -0,0 +1,21 @@ +//! A [`softbuffer`] renderer for [`iced_native`]. +#![doc( + html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" +)] +#![cfg_attr(docsrs, feature(doc_cfg))] + +mod backend; +pub use self::backend::Backend; + +//pub mod renderer; +//pub use self::renderer::Renderer; + +pub mod settings; +pub use self::settings::Settings; + +pub(crate) mod surface; + +pub mod window; + +pub type Renderer = + iced_graphics::Renderer; diff --git a/softbuffer/src/settings.rs b/softbuffer/src/settings.rs new file mode 100644 index 0000000000..ac4a09c7e1 --- /dev/null +++ b/softbuffer/src/settings.rs @@ -0,0 +1,47 @@ +//! Configure a renderer. +pub use iced_graphics::Antialiasing; + +/// The settings of a [`Backend`]. +/// +/// [`Backend`]: crate::Backend +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Settings { + /// The bytes of the font that will be used by default. + /// + /// If `None` is provided, a default system font will be chosen. + pub default_font: Option<&'static [u8]>, + + /// The default size of text. + /// + /// By default, it will be set to 20. + pub default_text_size: u16, + + /// If enabled, spread text workload in multiple threads when multiple cores + /// are available. + /// + /// By default, it is disabled. + pub text_multithreading: bool, + + /// The antialiasing strategy that will be used for triangle primitives. + /// + /// By default, it is `None`. + pub antialiasing: Option, +} + +impl Settings { + /// Creates new [`Settings`] using environment configuration. + pub fn from_env() -> Self { + Settings::default() + } +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + default_font: None, + default_text_size: 20, + text_multithreading: false, + antialiasing: None, + } + } +} diff --git a/softbuffer/src/surface.rs b/softbuffer/src/surface.rs new file mode 100644 index 0000000000..5101b35701 --- /dev/null +++ b/softbuffer/src/surface.rs @@ -0,0 +1,707 @@ +use crate::backend::{Backend, CpuStorage, FONT_SYSTEM}; + +use cosmic_text::{Attrs, AttrsList, BufferLine, SwashCache, SwashContent}; +use iced_graphics::{Background, Gradient, Primitive}; +use iced_graphics::alignment::{Horizontal, Vertical}; +#[cfg(feature = "image")] +use iced_graphics::image::raster; +#[cfg(feature = "svg")] +use iced_graphics::image::vector; +use iced_graphics::triangle; +use iced_native::Font; +use raqote::{DrawOptions, DrawTarget, Image, IntPoint, IntRect, PathBuilder, SolidSource, StrokeStyle, Source, Transform}; +use raw_window_handle::{ + HasRawDisplayHandle, + HasRawWindowHandle, + RawDisplayHandle, + RawWindowHandle +}; +use softbuffer::GraphicsContext; +use std::cmp; +use std::cell::RefMut; +use std::slice; + +// Wrapper to get around lifetimes in GraphicsContext +#[derive(Debug)] +struct RawWindow { + display_handle: RawDisplayHandle, + window_handle: RawWindowHandle, +} + +unsafe impl HasRawDisplayHandle for RawWindow { + fn raw_display_handle(&self) -> RawDisplayHandle { + self.display_handle + } +} + +unsafe impl HasRawWindowHandle for RawWindow { + fn raw_window_handle(&self) -> RawWindowHandle { + self.window_handle + } +} + +// A software rendering surface +pub struct Surface { + context: GraphicsContext, + width: u32, + height: u32, + buffer: Vec, +} + +impl Surface { + pub(crate) fn new(window: &W) -> Self { + let raw_window = crate::surface::RawWindow { + display_handle: window.raw_display_handle(), + window_handle: window.raw_window_handle(), + }; + + let context = match unsafe { GraphicsContext::new(raw_window) } { + Ok(ok) => ok, + Err(err) => panic!("failed to create softbuffer context: {}", err), + }; + Surface { + context, + width: 0, + height: 0, + buffer: Vec::new(), + } + } + + pub(crate) fn configure(&mut self, width: u32, height: u32) { + self.width = width; + self.height = height; + self.buffer = vec![ + 0; + self.width as usize * self.height as usize + ]; + } + + pub(crate) fn present(&mut self, renderer: &mut crate::Renderer, background: iced_graphics::Color) { + { + let mut draw_target = DrawTarget::from_backing( + self.width as i32, + self.height as i32, + self.buffer.as_mut_slice() + ); + + draw_target.clear({ + let rgba = background.into_rgba8(); + SolidSource::from_unpremultiplied_argb( + rgba[3], + rgba[0], + rgba[1], + rgba[2], + ) + }); + + let draw_options = DrawOptions { + // Default to antialiasing off, enable it when necessary + antialias: raqote::AntialiasMode::None, + ..Default::default() + }; + + // Having at least one clip fixes some font rendering issues + draw_target.push_clip_rect(IntRect::new( + IntPoint::new(0, 0), + IntPoint::new(self.width as i32, self.height as i32) + )); + + renderer.with_primitives(|backend, primitives| { + for primitive in primitives.iter() { + draw_primitive( + &mut draw_target, + &draw_options, + backend, + primitive + ); + } + }); + + draw_target.pop_clip(); + } + + self.context.set_buffer( + &self.buffer, + self.width as u16, + self.height as u16 + ); + } +} + +fn draw_primitive( + draw_target: &mut DrawTarget<&mut [u32]>, + draw_options: &DrawOptions, + backend: &mut Backend, + primitive: &Primitive +) { + match primitive { + Primitive::None => (), + Primitive::Group { primitives } => { + for child in primitives.iter() { + draw_primitive(draw_target, draw_options, backend, child); + } + }, + Primitive::Text { + content, + bounds, + color, + size, + font, + horizontal_alignment, + vertical_alignment, + } => { + let cosmic_color = { + let rgba8 = color.into_rgba8(); + cosmic_text::Color::rgba( + rgba8[0], + rgba8[1], + rgba8[2], + rgba8[3], + ) + }; + + let (metrics, attrs) = backend.cosmic_metrics_attrs(*size, &font); + + /* + // Debug bounds in green + let mut pb = PathBuilder::new(); + pb.move_to(bounds.x, bounds.y); + pb.line_to(bounds.x + bounds.width, bounds.y); + pb.line_to(bounds.x + bounds.width, bounds.y + bounds.height); + pb.line_to(bounds.x, bounds.y + bounds.height); + pb.close(); + let path = pb.finish(); + draw_target.stroke( + &path, + &Source::Solid(SolidSource::from_unpremultiplied_argb(0xFF, 0, 0xFF, 0)), + &StrokeStyle::default(), + draw_options + ); + */ + + //TODO: improve implementation + let mut buffer_line = BufferLine::new(content, AttrsList::new(attrs)); + let layout = buffer_line.layout(&FONT_SYSTEM, metrics.font_size, bounds.width as i32); + + let mut line_y = match vertical_alignment { + Vertical::Top => bounds.y as i32 + metrics.font_size, + Vertical::Center => { + //TODO: why is this so weird? + bounds.y as i32 + metrics.font_size - metrics.line_height * layout.len() as i32 / 2 + } + Vertical::Bottom => { + //TODO: why is this so weird? + bounds.y as i32 + metrics.font_size - metrics.line_height * layout.len() as i32 + }, + }; + + let mut line_width = 0.0; + for layout_line in layout.iter() { + for glyph in layout_line.glyphs.iter() { + let max_x = if glyph.rtl { + glyph.x - glyph.w + } else { + glyph.x + glyph.w + }; + if max_x + 1.0 > line_width { + line_width = max_x + 1.0; + } + } + } + + let line_x = match horizontal_alignment { + Horizontal::Left => bounds.x as i32, + Horizontal::Center => { + //TODO: why is this so weird? + bounds.x as i32 - (line_width / 2.0) as i32 + }, + Horizontal::Right => { + //TODO: why is this so weird? + bounds.x as i32 - line_width as i32 + } + }; + + /* + eprintln!( + "{:?}: {}, {}, {}, {} in {:?} from font size {}, {:?}, {:?}", + content, + line_x, line_y, + line_width, metrics.line_height, + bounds, + *size, + horizontal_alignment, + vertical_alignment + ); + */ + + for layout_line in layout.iter() { + /* + // Debug line placement in blue + let mut pb = PathBuilder::new(); + pb.move_to(line_x as f32, line_y as f32 - metrics.font_size as f32); + pb.line_to(line_x as f32 + line_width, line_y as f32 - metrics.font_size as f32); + pb.line_to(line_x as f32 + line_width, line_y as f32 + metrics.line_height as f32 - metrics.font_size as f32); + pb.line_to(line_x as f32, line_y as f32 + metrics.line_height as f32 - metrics.font_size as f32); + pb.close(); + let path = pb.finish(); + draw_target.stroke( + &path, + &Source::Solid(SolidSource::from_unpremultiplied_argb(0xFF, 0, 0, 0xFF)), + &StrokeStyle::default(), + draw_options + ); + */ + + + + //TODO: also clip y, it does not seem to work though because + // bounds.height < metrics.line_height * layout_lines.len() + draw_target.push_clip_rect(IntRect::new( + IntPoint::new( + line_x, + 0, + ), + IntPoint::new( + line_x.checked_add(bounds.width as i32).unwrap_or_else(i32::max_value), + i32::max_value(), + ) + )); + + for glyph in layout_line.glyphs.iter() { + let (cache_key, x_int, y_int) = (glyph.cache_key, glyph.x_int, glyph.y_int); + + let glyph_color = match glyph.color_opt { + Some(some) => some, + None => cosmic_color, + }; + + if let Some(image) = backend.swash_cache.get_image(cache_key) { + let x = line_x + x_int + image.placement.left; + let y = line_y + y_int + -image.placement.top; + + /* + // Debug glyph placement in red + let mut pb = PathBuilder::new(); + pb.move_to(x as f32, y as f32); + pb.line_to(x as f32 + image.placement.width as f32, y as f32); + pb.line_to(x as f32 + image.placement.width as f32, y as f32 + image.placement.height as f32); + pb.line_to(x as f32, y as f32 + image.placement.height as f32); + pb.close(); + let path = pb.finish(); + draw_target.stroke( + &path, + &Source::Solid(SolidSource::from_unpremultiplied_argb(0xFF, 0xFF, 0, 0)), + &StrokeStyle::default(), + draw_options + ); + */ + + let mut image_data = Vec::with_capacity( + image.placement.height as usize * image.placement.width as usize + ); + match image.content { + SwashContent::Mask => { + let mut i = 0; + for _off_y in 0..image.placement.height as i32 { + for _off_x in 0..image.placement.width as i32 { + //TODO: blend base alpha? + image_data.push( + SolidSource::from_unpremultiplied_argb( + image.data[i], + glyph_color.r(), + glyph_color.g(), + glyph_color.b(), + ).to_u32() + ); + i += 1; + } + } + }, + SwashContent::Color => { + let mut i = 0; + for _off_y in 0..image.placement.height as i32 { + for _off_x in 0..image.placement.width as i32 { + //TODO: blend base alpha? + image_data.push( + SolidSource::from_unpremultiplied_argb( + image.data[i + 3], + image.data[i + 0], + image.data[i + 1], + image.data[i + 2], + ).to_u32() + ); + i += 4; + } + } + + }, + SwashContent::SubpixelMask => { + eprintln!("Content::SubpixelMask"); + } + } + + if ! image_data.is_empty() { + draw_target.draw_image_at( + x as f32, + y as f32, + &Image { + width: image.placement.width as i32, + height: image.placement.height as i32, + data: &image_data + }, + &draw_options + ); + } + } + } + + draw_target.pop_clip(); + + line_y += metrics.line_height; + } + }, + Primitive::Quad { + bounds, + background, + border_radius, + border_width, + border_color, + } => { + // Ensure radius is not too large + let clamp_radius = |radius: f32| -> f32 { + if radius > bounds.width / 2.0 { + return bounds.width / 2.0; + } + + if radius > bounds.height / 2.0 { + return bounds.height / 2.0; + } + + radius + }; + + let mut pb = PathBuilder::new(); + + let top_left = clamp_radius(border_radius[0]); + let top_right = clamp_radius(border_radius[1]); + let bottom_right = clamp_radius(border_radius[2]); + let bottom_left = clamp_radius(border_radius[3]); + + // Move to top left corner at start of clockwise arc + pb.move_to(bounds.x, bounds.y + top_left); + pb.arc( + bounds.x + top_left, + bounds.y + top_left, + top_left, + 180.0f32.to_radians(), + 90.0f32.to_radians() + ); + + // Move to top right corner at start of clockwise arc + pb.line_to(bounds.x + bounds.width - top_right, bounds.y); + pb.arc( + bounds.x + bounds.width - top_right, + bounds.y + top_right, + top_right, + 270.0f32.to_radians(), + 90.0f32.to_radians() + ); + + // Move to bottom right corner at start of clockwise arc + pb.line_to(bounds.x + bounds.width, bounds.y + bounds.height - bottom_right); + pb.arc( + bounds.x + bounds.width - bottom_right, + bounds.y + bounds.height - bottom_right, + bottom_right, + 0.0f32.to_radians(), + 90.0f32.to_radians() + ); + + // Move to bottom left corner at start of clockwise arc + pb.line_to(bounds.x + bottom_left, bounds.y + bounds.height); + pb.arc( + bounds.x + bottom_left, + bounds.y + bounds.height - bottom_left, + bottom_left, + 90.0f32.to_radians(), + 90.0f32.to_radians() + ); + + // Close and finish path + pb.close(); + let path = pb.finish(); + + let background_source = match background { + Background::Color(color) => { + let rgba = color.into_rgba8(); + Source::Solid(SolidSource::from_unpremultiplied_argb( + rgba[3], + rgba[0], + rgba[1], + rgba[2], + )) + } + }; + + draw_target.fill( + &path, + &background_source, + &DrawOptions { + // Anti-alias rounded rectangles + antialias: raqote::AntialiasMode::Gray, + ..*draw_options + } + ); + + let border_source = { + let rgba = border_color.into_rgba8(); + Source::Solid( + SolidSource::from_unpremultiplied_argb( + rgba[3], + rgba[0], + rgba[1], + rgba[2], + ) + ) + }; + + let style = StrokeStyle { + width: *border_width, + ..Default::default() + }; + + draw_target.stroke( + &path, + &border_source, + &style, + &DrawOptions { + // Anti-alias rounded rectangles + antialias: raqote::AntialiasMode::Gray, + ..*draw_options + } + ); + }, + Primitive::Image { + handle, + bounds + } => { + #[cfg(feature = "image")] + match backend.raster_cache.borrow_mut().upload( + handle, + &mut (), + &mut CpuStorage + ) { + Some(entry) => { + draw_target.draw_image_with_size_at( + bounds.width, + bounds.height, + bounds.x, + bounds.y, + &Image { + width: entry.size.width as i32, + height: entry.size.height as i32, + data: &entry.data + }, + draw_options + ); + }, + None => (), + } + } + Primitive::Svg { + handle, + bounds, + color, + } => { + #[cfg(feature = "svg")] + match backend.vector_cache.borrow_mut().upload( + handle, + color.clone(), + [bounds.width, bounds.height], + 1.0, /*TODO: what should scale be?*/ + &mut (), + &mut CpuStorage, + ) { + Some(entry) => { + draw_target.draw_image_with_size_at( + bounds.width, + bounds.height, + bounds.x, + bounds.y, + &Image { + width: entry.size.width as i32, + height: entry.size.height as i32, + data: &entry.data + }, + draw_options + ); + }, + None => (), + } + }, + Primitive::Clip { + bounds, + content, + } => { + draw_target.push_clip_rect(IntRect::new( + IntPoint::new( + bounds.x as i32, + bounds.y as i32 + ), + IntPoint::new( + (bounds.x + bounds.width) as i32, + (bounds.y + bounds.height) as i32 + ) + )); + draw_primitive(draw_target, draw_options, backend, &content); + draw_target.pop_clip(); + }, + Primitive::Translate { + translation, + content, + } => { + draw_target.set_transform(&Transform::translation( + translation.x, + translation.y + )); + draw_primitive(draw_target, draw_options, backend, &content); + draw_target.set_transform(&Transform::identity()); + }, + Primitive::GradientMesh { + buffers, + size, + gradient + } => { + let source = match gradient { + Gradient::Linear(linear) => { + let mut stops = Vec::new(); + for stop in linear.color_stops.iter() { + let rgba8 = stop.color.into_rgba8(); + stops.push(raqote::GradientStop { + position: stop.offset, + color: raqote::Color::new( + rgba8[3], + rgba8[0], + rgba8[1], + rgba8[2] + ) + }); + } + Source::new_linear_gradient( + raqote::Gradient { stops }, + raqote::Point::new(linear.start.x, linear.start.y), + raqote::Point::new(linear.end.x, linear.end.y), + raqote::Spread::Pad /*TODO: which spread?*/ + ) + }, + }; + + /* + draw_target.push_clip_rect(IntRect::new( + IntPoint::new(0, 0), + IntPoint::new(size.width as i32, size.height as i32), + )); + */ + + let mut pb = PathBuilder::new(); + + for indices in buffers.indices.chunks_exact(3) { + let a = &buffers.vertices[indices[0] as usize]; + let b = &buffers.vertices[indices[1] as usize]; + let c = &buffers.vertices[indices[2] as usize]; + + pb.move_to(a.position[0], a.position[1]); + pb.line_to(b.position[0], b.position[1]); + pb.line_to(c.position[0], c.position[1]); + pb.close(); + + } + + let path = pb.finish(); + draw_target.fill( + &path, + &source, + draw_options + ); + + /* + draw_target.pop_clip(); + */ + } + Primitive::SolidMesh { + buffers, + size, + } => { + /*TODO: will this be needed? + fn undo_linear_component(linear: f32) -> f32 { + if linear < 0.0031308 { + linear * 12.92 + } else { + 1.055 * linear.powf(1.0 / 2.4) - 0.055 + } + } + + fn linear_to_rgba8(color: &[f32; 4]) -> [u8; 4] { + let r = undo_linear_component(color[0]) * 255.0; + let g = undo_linear_component(color[1]) * 255.0; + let b = undo_linear_component(color[2]) * 255.0; + let a = color[3] * 255.0; + [ + cmp::max(0, cmp::min(255, r.round() as i32)) as u8, + cmp::max(0, cmp::min(255, g.round() as i32)) as u8, + cmp::max(0, cmp::min(255, b.round() as i32)) as u8, + cmp::max(0, cmp::min(255, a.round() as i32)) as u8, + ] + } + + let rgba8 = linear_to_rgba8(&color); + */ + + // TODO: Each vertice has its own separate color. + let rgba8 = iced_graphics::Color::from(buffers.vertices[0].color).into_rgba8(); + let source = Source::Solid(SolidSource::from_unpremultiplied_argb( + rgba8[3], + rgba8[0], + rgba8[1], + rgba8[2], + )); + + /* + draw_target.push_clip_rect(IntRect::new( + IntPoint::new(0, 0), + IntPoint::new(size.width as i32, size.height as i32), + )); + */ + + let mut pb = PathBuilder::new(); + + for indices in buffers.indices.chunks_exact(3) { + let a = &buffers.vertices[indices[0] as usize]; + let b = &buffers.vertices[indices[1] as usize]; + let c = &buffers.vertices[indices[2] as usize]; + + pb.move_to(a.position[0], a.position[1]); + pb.line_to(b.position[0], b.position[1]); + pb.line_to(c.position[0], c.position[1]); + pb.close(); + + } + + let path = pb.finish(); + draw_target.fill( + &path, + &source, + draw_options + ); + + /* + draw_target.pop_clip(); + */ + }, + Primitive::Cached { + cache + } => { + draw_primitive(draw_target, draw_options, backend, &cache); + }, + } +} \ No newline at end of file diff --git a/softbuffer/src/window.rs b/softbuffer/src/window.rs new file mode 100644 index 0000000000..aac5fb9ed8 --- /dev/null +++ b/softbuffer/src/window.rs @@ -0,0 +1,4 @@ +//! Display rendering results on windows. +mod compositor; + +pub use compositor::Compositor; diff --git a/softbuffer/src/window/compositor.rs b/softbuffer/src/window/compositor.rs new file mode 100644 index 0000000000..1400ce8b10 --- /dev/null +++ b/softbuffer/src/window/compositor.rs @@ -0,0 +1,82 @@ +use crate::{Backend, surface::Surface}; + +use iced_graphics::{ + Color, Error, Viewport, + compositor::{self, Information, SurfaceError}, +}; +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use std::marker::PhantomData; + +/// A window graphics backend for iced powered by `glow`. +pub struct Compositor { + theme: PhantomData, +} + +/// A graphics compositor that can draw to windows. +impl compositor::Compositor for Compositor { + /// The settings of the backend. + type Settings = crate::Settings; + + /// The iced renderer of the backend. + type Renderer = crate::Renderer; + + /// The surface of the backend. + type Surface = Surface; + + /// Creates a new [`Compositor`]. + fn new( + settings: Self::Settings, + compatible_window: Option<&W>, + ) -> Result<(Self, Self::Renderer), Error> { + let compositor = Self { + theme: PhantomData, + }; + + let renderer = Self::Renderer::new(Backend::new()); + + Ok((compositor, renderer)) + } + + /// Crates a new [`Surface`] for the given window. + /// + /// [`Surface`]: Self::Surface + fn create_surface( + &mut self, + window: &W, + ) -> Self::Surface { + Self::Surface::new(window) + } + + /// Configures a new [`Surface`] with the given dimensions. + /// + /// [`Surface`]: Self::Surface + fn configure_surface( + &mut self, + surface: &mut Self::Surface, + width: u32, + height: u32, + ) { + surface.configure(width, height); + } + + /// Returns [`Information`] used by this [`Compositor`]. + fn fetch_information(&self) -> Information { + todo!("Compositor::fetch_information"); + } + + /// Presents the [`Renderer`] primitives to the next frame of the given [`Surface`]. + /// + /// [`Renderer`]: Self::Renderer + /// [`Surface`]: Self::Surface + fn present>( + &mut self, + renderer: &mut Self::Renderer, + surface: &mut Self::Surface, + viewport: &Viewport, + background: Color, + overlay: &[T], + ) -> Result<(), SurfaceError> { + surface.present(renderer, background); + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index ef0fe7765c..cc6c816ec2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -164,7 +164,11 @@ #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] -#[cfg(all(not(feature = "glow"), feature = "wgpu", not(feature = "wayland")))] +#[cfg(all( + not(feature = "glow"), + any(feature = "wgpu", feature = "softbuffer"), + not(feature = "wayland") +))] pub mod application; mod element; @@ -203,7 +207,7 @@ pub mod window; #[cfg(all( not(feature = "glow"), - feature = "wgpu", + any(feature = "wgpu", feature = "softbuffer"), not(feature = "wayland"), feature = "multi_window" ))] @@ -214,7 +218,7 @@ use iced_sctk as runtime; #[cfg(all( not(feature = "glow"), - feature = "wgpu", + any(feature = "wgpu", feature = "softbuffer"), not(feature = "wayland") ))] use iced_winit as runtime; @@ -228,6 +232,9 @@ use iced_wgpu as renderer; #[cfg(any(feature = "glow", feature = "wayland"))] use iced_glow as renderer; +#[cfg(all(not(feature = "iced_glow"), feature = "softbuffer"))] +use iced_softbuffer as renderer; + pub use iced_native::theme; pub use runtime::event; pub use runtime::subscription; From 81ff2dd2d8d84f416ac96823e12841b1253c242d Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Mon, 19 Dec 2022 15:36:33 -0700 Subject: [PATCH 09/56] iced_softbuffer: Improvements for solid mesh --- softbuffer/src/surface.rs | 41 +++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/softbuffer/src/surface.rs b/softbuffer/src/surface.rs index 5101b35701..52826a6712 100644 --- a/softbuffer/src/surface.rs +++ b/softbuffer/src/surface.rs @@ -632,7 +632,6 @@ fn draw_primitive( buffers, size, } => { - /*TODO: will this be needed? fn undo_linear_component(linear: f32) -> f32 { if linear < 0.0031308 { linear * 12.92 @@ -654,18 +653,6 @@ fn draw_primitive( ] } - let rgba8 = linear_to_rgba8(&color); - */ - - // TODO: Each vertice has its own separate color. - let rgba8 = iced_graphics::Color::from(buffers.vertices[0].color).into_rgba8(); - let source = Source::Solid(SolidSource::from_unpremultiplied_argb( - rgba8[3], - rgba8[0], - rgba8[1], - rgba8[2], - )); - /* draw_target.push_clip_rect(IntRect::new( IntPoint::new(0, 0), @@ -673,26 +660,34 @@ fn draw_primitive( )); */ - let mut pb = PathBuilder::new(); - for indices in buffers.indices.chunks_exact(3) { let a = &buffers.vertices[indices[0] as usize]; let b = &buffers.vertices[indices[1] as usize]; let c = &buffers.vertices[indices[2] as usize]; + + let mut pb = PathBuilder::new(); pb.move_to(a.position[0], a.position[1]); pb.line_to(b.position[0], b.position[1]); pb.line_to(c.position[0], c.position[1]); pb.close(); - } + // TODO: Each vertice has its own separate color. + let rgba8 = linear_to_rgba8(&a.color); + let source = Source::Solid(SolidSource::from_unpremultiplied_argb( + rgba8[3], + rgba8[0], + rgba8[1], + rgba8[2], + )); - let path = pb.finish(); - draw_target.fill( - &path, - &source, - draw_options - ); + let path = pb.finish(); + draw_target.fill( + &path, + &source, + draw_options + ); + } /* draw_target.pop_clip(); @@ -704,4 +699,4 @@ fn draw_primitive( draw_primitive(draw_target, draw_options, backend, &cache); }, } -} \ No newline at end of file +} From 79267843e03a5b016a8d3abf8b961279fee43818 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 19 Dec 2022 17:56:16 -0500 Subject: [PATCH 10/56] fix: windows should not be removed from the window list until after they have been handled in the event loop --- sctk/src/handlers/shell/xdg_window.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sctk/src/handlers/shell/xdg_window.rs b/sctk/src/handlers/shell/xdg_window.rs index bf0bcac453..f435854ebf 100644 --- a/sctk/src/handlers/shell/xdg_window.rs +++ b/sctk/src/handlers/shell/xdg_window.rs @@ -3,7 +3,7 @@ use crate::{ sctk_event::{SctkEvent, WindowEventVariant}, }; use sctk::{ - delegate_xdg_shell, delegate_xdg_window, reexports::client::Proxy, + delegate_xdg_shell, delegate_xdg_window, shell::xdg::window::WindowHandler, }; use std::fmt::Debug; @@ -18,9 +18,9 @@ impl WindowHandler for SctkState { let window = match self .windows .iter() - .position(|s| s.window.wl_surface() == window.wl_surface()) + .find(|s| s.window.wl_surface() == window.wl_surface()) { - Some(w) => self.windows.remove(w), + Some(w) => w, None => return, }; From f7cc7045995caed32272e3f1be5a0fb3baa1a041 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 20 Dec 2022 10:04:30 -0500 Subject: [PATCH 11/56] update: update sctk, glutin, & wayland-* --- examples/clock_sctk_layer_surface/Cargo.toml | 2 +- examples/clock_sctk_window/Cargo.toml | 2 +- examples/todos_sctk/Cargo.toml | 2 +- native/Cargo.toml | 2 +- sctk/Cargo.toml | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/clock_sctk_layer_surface/Cargo.toml b/examples/clock_sctk_layer_surface/Cargo.toml index 04e10eb561..f1bb003e89 100644 --- a/examples/clock_sctk_layer_surface/Cargo.toml +++ b/examples/clock_sctk_layer_surface/Cargo.toml @@ -9,4 +9,4 @@ publish = false iced = { path = "../..", default-features = false, features = ["canvas", "tokio", "debug", "wayland"] } time = { version = "0.3.5", features = ["local-offset"] } iced_native = { path = "../../native" } -sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "f1d9c3e" } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "73346019952f82ec7e4d4d15f5d66841b54e8b61" } diff --git a/examples/clock_sctk_window/Cargo.toml b/examples/clock_sctk_window/Cargo.toml index 21ff84a05d..a59dc554ca 100644 --- a/examples/clock_sctk_window/Cargo.toml +++ b/examples/clock_sctk_window/Cargo.toml @@ -9,4 +9,4 @@ publish = false iced = { path = "../..", default-features = false, features = ["canvas", "tokio", "debug", "wayland"] } time = { version = "0.3.5", features = ["local-offset"] } iced_native = { path = "../../native" } -sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "f1d9c3e" } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "73346019952f82ec7e4d4d15f5d66841b54e8b61" } diff --git a/examples/todos_sctk/Cargo.toml b/examples/todos_sctk/Cargo.toml index 715bbb8047..d7735ae5d4 100644 --- a/examples/todos_sctk/Cargo.toml +++ b/examples/todos_sctk/Cargo.toml @@ -10,7 +10,7 @@ iced = { path = "../..", default-features=false, features = ["async-std", "wayla serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" once_cell = "1.15" -sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "f1d9c3e" } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "73346019952f82ec7e4d4d15f5d66841b54e8b61" } iced_native = { path = "../../native" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/native/Cargo.toml b/native/Cargo.toml index 9589edc738..42e82c57e6 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -16,7 +16,7 @@ debug = [] twox-hash = { version = "1.5", default-features = false } unicode-segmentation = "1.6" num-traits = "0.2" -sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "f1d9c3e", optional = true } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "73346019952f82ec7e4d4d15f5d66841b54e8b61", optional = true } [dependencies.iced_core] version = "0.6" diff --git a/sctk/Cargo.toml b/sctk/Cargo.toml index 306505194d..3c1ed58659 100644 --- a/sctk/Cargo.toml +++ b/sctk/Cargo.toml @@ -14,13 +14,13 @@ multi_window = [] [dependencies] log = "0.4" thiserror = "1.0" -sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "f1d9c3e" } -glutin = "0.30.0-beta.2" +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "73346019952f82ec7e4d4d15f5d66841b54e8b61" } +glutin = "0.30.0-beta.3" glow = "0.11.2" raw-window-handle = "0.5.0" enum-repr = "0.2.6" futures = "0.3" -wayland-backend = {version = "=0.1.0-beta.13", features = ["client_system"]} +wayland-backend = {version = "=0.1.0-beta.14", features = ["client_system"]} [dependencies.iced_native] features = ["wayland"] From 53a84da301fc418680a26ab3672325abb7edbc52 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 20 Dec 2022 09:06:25 -0700 Subject: [PATCH 12/56] Use swbuf instead of softbuffer --- Cargo.toml | 14 +++++++------- src/lib.rs | 10 +++++----- {softbuffer => swbuf}/Cargo.toml | 8 ++++---- {softbuffer => swbuf}/README.md | 2 +- {softbuffer => swbuf}/src/backend.rs | 7 +++---- {softbuffer => swbuf}/src/lib.rs | 2 +- {softbuffer => swbuf}/src/settings.rs | 0 {softbuffer => swbuf}/src/surface.rs | 6 +++--- {softbuffer => swbuf}/src/window.rs | 0 {softbuffer => swbuf}/src/window/compositor.rs | 0 10 files changed, 24 insertions(+), 25 deletions(-) rename {softbuffer => swbuf}/Cargo.toml (78%) rename {softbuffer => swbuf}/README.md (58%) rename {softbuffer => swbuf}/src/backend.rs (97%) rename {softbuffer => swbuf}/src/lib.rs (90%) rename {softbuffer => swbuf}/src/settings.rs (100%) rename {softbuffer => swbuf}/src/surface.rs (99%) rename {softbuffer => swbuf}/src/window.rs (100%) rename {softbuffer => swbuf}/src/window/compositor.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index ebd3c47bfc..c5dbdcef25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,19 +12,19 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] -default = ["softbuffer", "winit"] +default = ["swbuf", "winit"] # Enables the `Image` widget -image = ["iced_wgpu?/image", "iced_glow?/image", "iced_softbuffer?/image", "image_rs"] +image = ["iced_wgpu?/image", "iced_glow?/image", "iced_swbuf?/image", "image_rs"] # Enables the `Svg` widget -svg = ["iced_wgpu?/svg", "iced_glow?/svg", "iced_softbuffer?/svg"] +svg = ["iced_wgpu?/svg", "iced_glow?/svg", "iced_swbuf?/svg"] # Enables the `Canvas` widget canvas = ["iced_graphics/canvas"] # Enables the `QRCode` widget qr_code = ["iced_graphics/qr_code"] # Enables the `iced_wgpu` renderer wgpu = ["iced_wgpu"] -# Enables the `iced_softbuffer` renderer -softbuffer = ["iced_softbuffer"] +# Enables the `iced_swbuf` renderer +swbuf = ["iced_swbuf"] # Enables using system fonts default_system_font = ["iced_wgpu?/default_system_font", "iced_glow?/default_system_font"] # Enables the `iced_glow` renderer. Overrides `iced_wgpu` @@ -57,7 +57,7 @@ members = [ "glutin", "lazy", "native", - "softbuffer", + "swbuf", "style", "wgpu", "winit", @@ -74,7 +74,7 @@ iced_winit = { version = "0.6", path = "winit", features = ["application"], opti iced_glutin = { version = "0.5", path = "glutin", optional = true } iced_glow = { version = "0.5", path = "glow", optional = true } iced_sctk = { path = "./sctk", optional = true } -iced_softbuffer = { version = "0.1", path = "softbuffer", optional = true } +iced_swbuf = { version = "0.1", path = "swbuf", optional = true } thiserror = "1.0" [dependencies.image_rs] diff --git a/src/lib.rs b/src/lib.rs index cc6c816ec2..9756c4be6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -166,7 +166,7 @@ #[cfg(all( not(feature = "glow"), - any(feature = "wgpu", feature = "softbuffer"), + any(feature = "wgpu", feature = "swbuf"), not(feature = "wayland") ))] pub mod application; @@ -207,7 +207,7 @@ pub mod window; #[cfg(all( not(feature = "glow"), - any(feature = "wgpu", feature = "softbuffer"), + any(feature = "wgpu", feature = "swbuf"), not(feature = "wayland"), feature = "multi_window" ))] @@ -218,7 +218,7 @@ use iced_sctk as runtime; #[cfg(all( not(feature = "glow"), - any(feature = "wgpu", feature = "softbuffer"), + any(feature = "wgpu", feature = "swbuf"), not(feature = "wayland") ))] use iced_winit as runtime; @@ -232,8 +232,8 @@ use iced_wgpu as renderer; #[cfg(any(feature = "glow", feature = "wayland"))] use iced_glow as renderer; -#[cfg(all(not(feature = "iced_glow"), feature = "softbuffer"))] -use iced_softbuffer as renderer; +#[cfg(all(not(feature = "iced_glow"), feature = "swbuf"))] +use iced_swbuf as renderer; pub use iced_native::theme; pub use runtime::event; diff --git a/softbuffer/Cargo.toml b/swbuf/Cargo.toml similarity index 78% rename from softbuffer/Cargo.toml rename to swbuf/Cargo.toml index c75311b856..f2e9863e95 100644 --- a/softbuffer/Cargo.toml +++ b/swbuf/Cargo.toml @@ -1,20 +1,20 @@ [package] -name = "iced_softbuffer" +name = "iced_swbuf" version = "0.1.0" authors = ["Jeremy Soller "] edition = "2021" -description = "A softbuffer renderer for Iced" +description = "A swbuf renderer for Iced" license = "MIT AND OFL-1.1" repository = "https://github.com/iced-rs/iced" [dependencies] -cosmic-text = "0.5.3" +cosmic-text = "0.6" lazy_static = "1.4" log = "0.4" raw-window-handle = "0.5" raqote = { version = "0.8", default-features = false } # Patched version for raw-window-handle 0.5 and Redox support -softbuffer = { git = "https://github.com/jackpot51/softbuffer", branch = "redox-0.1.1" } +swbuf = { git = "https://github.com/rust-windowing/swbuf", branch = "main" } [dependencies.iced_native] path = "../native" diff --git a/softbuffer/README.md b/swbuf/README.md similarity index 58% rename from softbuffer/README.md rename to swbuf/README.md index e656fd2e4c..0c1d530c83 100644 --- a/softbuffer/README.md +++ b/swbuf/README.md @@ -1,3 +1,3 @@ -# `iced_softbuffer` +# `iced_swbuf` Software rendering for Iced \ No newline at end of file diff --git a/softbuffer/src/backend.rs b/swbuf/src/backend.rs similarity index 97% rename from softbuffer/src/backend.rs rename to swbuf/src/backend.rs index 02c17ee0c9..327cf4e07c 100644 --- a/softbuffer/src/backend.rs +++ b/swbuf/src/backend.rs @@ -167,7 +167,7 @@ impl iced_graphics::backend::Text for Backend { let mut height = 0.0; for layout_line in layout.iter() { for glyph in layout_line.glyphs.iter() { - let max_x = if glyph.rtl { + let max_x = if glyph.level.is_rtl() { glyph.x - glyph.w } else { glyph.x + glyph.w @@ -203,14 +203,13 @@ impl iced_graphics::backend::Text for Backend { for layout_line in layout.iter() { if point.y > line_y && point.y < line_y + metrics.line_height as f32 { for glyph in layout_line.glyphs.iter() { - let (min_x, max_x) = if glyph.rtl { + let (min_x, max_x) = if glyph.level.is_rtl() { (glyph.x - glyph.w, glyph.x) } else { (glyph.x, glyph.x + glyph.w) }; if point.x > min_x && point.x < max_x { - println!("EXACT HIT {:?}", glyph); return Some(text::Hit::CharOffset( glyph.start )); @@ -229,7 +228,7 @@ impl iced_graphics::backend::Text for Backend { let center_y = line_y + metrics.line_height as f32 / 2.0; for glyph in layout_line.glyphs.iter() { - let (min_x, max_x) = if glyph.rtl { + let (min_x, max_x) = if glyph.level.is_rtl() { (glyph.x - glyph.w, glyph.x) } else { (glyph.x, glyph.x + glyph.w) diff --git a/softbuffer/src/lib.rs b/swbuf/src/lib.rs similarity index 90% rename from softbuffer/src/lib.rs rename to swbuf/src/lib.rs index 2774ed1216..936e50e3d2 100644 --- a/softbuffer/src/lib.rs +++ b/swbuf/src/lib.rs @@ -1,4 +1,4 @@ -//! A [`softbuffer`] renderer for [`iced_native`]. +//! A [`swbuf`] renderer for [`iced_native`]. #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] diff --git a/softbuffer/src/settings.rs b/swbuf/src/settings.rs similarity index 100% rename from softbuffer/src/settings.rs rename to swbuf/src/settings.rs diff --git a/softbuffer/src/surface.rs b/swbuf/src/surface.rs similarity index 99% rename from softbuffer/src/surface.rs rename to swbuf/src/surface.rs index 52826a6712..7035c35e81 100644 --- a/softbuffer/src/surface.rs +++ b/swbuf/src/surface.rs @@ -16,7 +16,7 @@ use raw_window_handle::{ RawDisplayHandle, RawWindowHandle }; -use softbuffer::GraphicsContext; +use swbuf::GraphicsContext; use std::cmp; use std::cell::RefMut; use std::slice; @@ -57,7 +57,7 @@ impl Surface { let context = match unsafe { GraphicsContext::new(raw_window) } { Ok(ok) => ok, - Err(err) => panic!("failed to create softbuffer context: {}", err), + Err(err) => panic!("failed to create swbuf context: {}", err), }; Surface { context, @@ -198,7 +198,7 @@ fn draw_primitive( let mut line_width = 0.0; for layout_line in layout.iter() { for glyph in layout_line.glyphs.iter() { - let max_x = if glyph.rtl { + let max_x = if glyph.level.is_rtl() { glyph.x - glyph.w } else { glyph.x + glyph.w diff --git a/softbuffer/src/window.rs b/swbuf/src/window.rs similarity index 100% rename from softbuffer/src/window.rs rename to swbuf/src/window.rs diff --git a/softbuffer/src/window/compositor.rs b/swbuf/src/window/compositor.rs similarity index 100% rename from softbuffer/src/window/compositor.rs rename to swbuf/src/window/compositor.rs From 19d28ba298069a69840987543f443b4e83260bac Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 20 Dec 2022 19:02:50 +0100 Subject: [PATCH 13/56] fix(native): Panic in downcast of tree state in mouse_listener --- native/src/widget/mouse_listener.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/native/src/widget/mouse_listener.rs b/native/src/widget/mouse_listener.rs index 59281cdca6..23e95c9804 100644 --- a/native/src/widget/mouse_listener.rs +++ b/native/src/widget/mouse_listener.rs @@ -261,6 +261,10 @@ where ) } + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + fn state(&self) -> tree::State { tree::State::new(State::default()) } From 5f1ce368b75e4d242460466a06a558465eb84bb6 Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Tue, 20 Dec 2022 15:33:29 -0500 Subject: [PATCH 14/56] fix(native): mouse listener layout --- native/src/widget/mouse_listener.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/native/src/widget/mouse_listener.rs b/native/src/widget/mouse_listener.rs index 23e95c9804..a79a65a5b5 100644 --- a/native/src/widget/mouse_listener.rs +++ b/native/src/widget/mouse_listener.rs @@ -157,6 +157,7 @@ where Widget::::width(self), Widget::::height(self), u32::MAX, + u32::MAX, |renderer, limits| { self.content.as_widget().layout(renderer, limits) }, @@ -383,16 +384,12 @@ pub fn layout( width: Length, height: Length, max_height: u32, + max_width: u32, layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, ) -> layout::Node { - let limits = limits.max_height(max_height).width(width).height(height); - - let child_limits = layout::Limits::new( - Size::new(limits.min().width, 0.0), - Size::new(limits.max().width, f32::INFINITY), - ); + let limits = limits.loose().max_height(max_height).max_width(max_width).width(width).height(height); - let content = layout_content(renderer, &child_limits); + let content = layout_content(renderer, &limits); let size = limits.resolve(content.size()); layout::Node::with_children(size, vec![content]) From 499367339dbc7036ac1ceaf6378a0cd7e2972158 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 20 Dec 2022 13:46:06 -0700 Subject: [PATCH 15/56] Update swbuf --- swbuf/Cargo.toml | 3 +-- swbuf/src/backend.rs | 10 +++------- swbuf/src/surface.rs | 43 ++++--------------------------------------- 3 files changed, 8 insertions(+), 48 deletions(-) diff --git a/swbuf/Cargo.toml b/swbuf/Cargo.toml index f2e9863e95..d97cd7f2d1 100644 --- a/swbuf/Cargo.toml +++ b/swbuf/Cargo.toml @@ -13,8 +13,7 @@ lazy_static = "1.4" log = "0.4" raw-window-handle = "0.5" raqote = { version = "0.8", default-features = false } -# Patched version for raw-window-handle 0.5 and Redox support -swbuf = { git = "https://github.com/rust-windowing/swbuf", branch = "main" } +swbuf = { git = "https://github.com/rust-windowing/swbuf", rev = "9b8641fc" } [dependencies.iced_native] path = "../native" diff --git a/swbuf/src/backend.rs b/swbuf/src/backend.rs index 327cf4e07c..6c2c6cebae 100644 --- a/swbuf/src/backend.rs +++ b/swbuf/src/backend.rs @@ -4,18 +4,14 @@ use iced_graphics::image::raster; use iced_graphics::image::storage; #[cfg(feature = "svg")] use iced_graphics::image::vector; -use iced_graphics::{Primitive, Vector}; #[cfg(feature = "image")] use iced_native::image; -use iced_native::layout; -use iced_native::renderer; #[cfg(feature = "svg")] use iced_native::svg; -use iced_native::text::{self, Text}; -use iced_native::{Background, Element, Font, Point, Rectangle, Size}; +use iced_native::text; +use iced_native::{Font, Point, Size}; use std::cell::RefCell; use std::fmt; -use std::marker::PhantomData; lazy_static::lazy_static! { pub(crate) static ref FONT_SYSTEM: FontSystem = FontSystem::new(); @@ -257,7 +253,7 @@ impl iced_graphics::backend::Text for Backend { } match nearest_opt { - Some((offset, vector, distance)) => Some(text::Hit::NearestCharOffset( + Some((offset, vector, _)) => Some(text::Hit::NearestCharOffset( offset, vector )), diff --git a/swbuf/src/surface.rs b/swbuf/src/surface.rs index 7035c35e81..93d9a02497 100644 --- a/swbuf/src/surface.rs +++ b/swbuf/src/surface.rs @@ -1,48 +1,18 @@ use crate::backend::{Backend, CpuStorage, FONT_SYSTEM}; -use cosmic_text::{Attrs, AttrsList, BufferLine, SwashCache, SwashContent}; +use cosmic_text::{AttrsList, BufferLine, SwashContent}; use iced_graphics::{Background, Gradient, Primitive}; use iced_graphics::alignment::{Horizontal, Vertical}; -#[cfg(feature = "image")] -use iced_graphics::image::raster; #[cfg(feature = "svg")] use iced_graphics::image::vector; -use iced_graphics::triangle; -use iced_native::Font; use raqote::{DrawOptions, DrawTarget, Image, IntPoint, IntRect, PathBuilder, SolidSource, StrokeStyle, Source, Transform}; -use raw_window_handle::{ - HasRawDisplayHandle, - HasRawWindowHandle, - RawDisplayHandle, - RawWindowHandle -}; +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use swbuf::GraphicsContext; use std::cmp; -use std::cell::RefMut; -use std::slice; - -// Wrapper to get around lifetimes in GraphicsContext -#[derive(Debug)] -struct RawWindow { - display_handle: RawDisplayHandle, - window_handle: RawWindowHandle, -} - -unsafe impl HasRawDisplayHandle for RawWindow { - fn raw_display_handle(&self) -> RawDisplayHandle { - self.display_handle - } -} - -unsafe impl HasRawWindowHandle for RawWindow { - fn raw_window_handle(&self) -> RawWindowHandle { - self.window_handle - } -} // A software rendering surface pub struct Surface { - context: GraphicsContext, + context: GraphicsContext, width: u32, height: u32, buffer: Vec, @@ -50,12 +20,7 @@ pub struct Surface { impl Surface { pub(crate) fn new(window: &W) -> Self { - let raw_window = crate::surface::RawWindow { - display_handle: window.raw_display_handle(), - window_handle: window.raw_window_handle(), - }; - - let context = match unsafe { GraphicsContext::new(raw_window) } { + let context = match unsafe { GraphicsContext::new(window, window) } { Ok(ok) => ok, Err(err) => panic!("failed to create swbuf context: {}", err), }; From e378c13f0ed8bc6f6e65a95081af120252f283f4 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 21 Dec 2022 18:13:14 -0500 Subject: [PATCH 16/56] feat: sent output events --- examples/todos_sctk/src/main.rs | 151 +++++++++------- native/src/event/wayland/output.rs | 17 +- native/src/widget/mouse_listener.rs | 7 +- sctk/src/commands/window.rs | 10 +- sctk/src/conversion.rs | 5 +- sctk/src/event_loop/mod.rs | 9 +- sctk/src/handlers/output.rs | 2 +- sctk/src/handlers/shell/xdg_window.rs | 3 +- sctk/src/sctk_event.rs | 56 +++++- swbuf/src/backend.rs | 79 +++++---- swbuf/src/surface.rs | 237 ++++++++++++-------------- swbuf/src/window/compositor.rs | 8 +- 12 files changed, 324 insertions(+), 260 deletions(-) diff --git a/examples/todos_sctk/src/main.rs b/examples/todos_sctk/src/main.rs index e39ce49724..876e4d1ddc 100644 --- a/examples/todos_sctk/src/main.rs +++ b/examples/todos_sctk/src/main.rs @@ -4,12 +4,16 @@ use iced::keyboard; use iced::sctk_settings::InitialSurface; use iced::subscription; use iced::theme::{self, Theme}; +use iced::wayland::actions::popup::SctkPopupSettings; +use iced::wayland::actions::window::SctkWindowSettings; +use iced::wayland::popup::get_popup; +use iced::wayland::window::get_window; use iced::wayland::SurfaceIdWrapper; use iced::widget::{ self, button, checkbox, column, container, row, scrollable, text, text_input, Text, }; -use iced::{Application, Element}; +use iced::{window, Application, Element}; use iced::{Color, Command, Font, Length, Settings, Subscription}; use once_cell::sync::Lazy; @@ -60,7 +64,13 @@ impl Application for Todos { fn new(_flags: ()) -> (Todos, Command) { ( Todos::Loading, - Command::perform(SavedState::load(), Message::Loaded), + Command::batch(vec![ + Command::perform(SavedState::load(), Message::Loaded), + get_window(SctkWindowSettings { + window_id: window::Id::new(1), + ..Default::default() + }), + ]), ) } @@ -110,7 +120,13 @@ impl Application for Todos { state.input_value.clear(); } - Command::none() + return get_popup(SctkPopupSettings { + parent: window::Id::new(0), + id: window::Id::new(2), + positioner: Default::default(), + parent_size: None, + grab: true, + }); } Message::FilterChanged(filter) => { state.filter = filter; @@ -185,71 +201,78 @@ impl Application for Todos { } fn view(&self, id: SurfaceIdWrapper) -> Element { - match self { - Todos::Loading => loading_message(), - Todos::Loaded(State { - input_value, - filter, - tasks, - .. - }) => { - let title = text("todos") - .width(Length::Fill) - .size(100) - .style(Color::from([0.5, 0.5, 0.5])) - .horizontal_alignment(alignment::Horizontal::Center); - - let input = text_input( - "What needs to be done?", + match id { + SurfaceIdWrapper::LayerSurface(_) => todo!(), + SurfaceIdWrapper::Window(_) => match self { + Todos::Loading => loading_message(), + Todos::Loaded(State { input_value, - Message::InputChanged, - ) - .id(INPUT_ID.clone()) - .padding(15) - .size(30) - .on_submit(Message::CreateTask); - - let controls = view_controls(tasks, *filter); - let filtered_tasks = - tasks.iter().filter(|task| filter.matches(task)); - - let tasks: Element<_> = if filtered_tasks.count() > 0 { - column( - tasks - .iter() - .enumerate() - .filter(|(_, task)| filter.matches(task)) - .map(|(i, task)| { - task.view(i).map(move |message| { - Message::TaskMessage(i, message) + filter, + tasks, + .. + }) => { + let title = text("todos") + .width(Length::Fill) + .size(100) + .style(Color::from([0.5, 0.5, 0.5])) + .horizontal_alignment(alignment::Horizontal::Center); + + let input = text_input( + "What needs to be done?", + input_value, + Message::InputChanged, + ) + .id(INPUT_ID.clone()) + .padding(15) + .size(30) + .on_submit(Message::CreateTask); + + let controls = view_controls(tasks, *filter); + let filtered_tasks = + tasks.iter().filter(|task| filter.matches(task)); + + let tasks: Element<_> = if filtered_tasks.count() > 0 { + column( + tasks + .iter() + .enumerate() + .filter(|(_, task)| filter.matches(task)) + .map(|(i, task)| { + task.view(i).map(move |message| { + Message::TaskMessage(i, message) + }) }) - }) - .collect(), + .collect(), + ) + .spacing(10) + .into() + } else { + empty_message(match filter { + Filter::All => "You have not created a task yet...", + Filter::Active => "All your tasks are done! :D", + Filter::Completed => { + "You have not completed a task yet..." + } + }) + }; + + let content = column![title, input, controls, tasks] + .spacing(20) + .max_width(800); + + scrollable( + container(content) + .width(Length::Fill) + .padding(40) + .center_x(), ) - .spacing(10) .into() - } else { - empty_message(match filter { - Filter::All => "You have not created a task yet...", - Filter::Active => "All your tasks are done! :D", - Filter::Completed => { - "You have not completed a task yet..." - } - }) - }; - - let content = column![title, input, controls, tasks] - .spacing(20) - .max_width(800); - - scrollable( - container(content) - .width(Length::Fill) - .padding(40) - .center_x(), - ) - .into() - } + } + }, + SurfaceIdWrapper::Popup(_) => container(text("hello")) + .width(Length::Fill) + .height(Length::Fill) + .into(), } } diff --git a/native/src/event/wayland/output.rs b/native/src/event/wayland/output.rs index 7980142dc9..9c8a2214a1 100644 --- a/native/src/event/wayland/output.rs +++ b/native/src/event/wayland/output.rs @@ -2,23 +2,20 @@ #[derive(Debug, Clone, PartialEq, Eq)] pub enum OutputEvent { /// created output - Created { - /// make of the output - make: String, - /// model of the output - model: String, - }, + Created, /// removed output - Removed { + Removed, + /// Make and Model + MakeAndModel { /// make of the output make: String, /// model of the output model: String, }, /// name of the output - Name(String), + Name(Option), /// logical size of the output - LogicalSize(u32, u32), + LogicalSize(Option<(i32, i32)>), /// logical position of the output - LogicalPosition(u32, u32), + LogicalPosition(Option<(i32, i32)>), } diff --git a/native/src/widget/mouse_listener.rs b/native/src/widget/mouse_listener.rs index a79a65a5b5..116ef474f8 100644 --- a/native/src/widget/mouse_listener.rs +++ b/native/src/widget/mouse_listener.rs @@ -387,7 +387,12 @@ pub fn layout( max_width: u32, layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, ) -> layout::Node { - let limits = limits.loose().max_height(max_height).max_width(max_width).width(width).height(height); + let limits = limits + .loose() + .max_height(max_height) + .max_width(max_width) + .width(width) + .height(height); let content = layout_content(renderer, &limits); let size = limits.resolve(content.size()); diff --git a/sctk/src/commands/window.rs b/sctk/src/commands/window.rs index 92f9fbd6df..9a5b880d63 100644 --- a/sctk/src/commands/window.rs +++ b/sctk/src/commands/window.rs @@ -61,7 +61,10 @@ pub fn toggle_maximize(id: window::Id) -> Command { )) } -pub fn set_app_id_window(id: window::Id, app_id: String) -> Command { +pub fn set_app_id_window( + id: window::Id, + app_id: String, +) -> Command { Command::single(command::Action::PlatformSpecific( platform_specific::Action::Wayland(wayland::Action::Window( wayland::window::Action::AppId { id, app_id }, @@ -70,7 +73,10 @@ pub fn set_app_id_window(id: window::Id, app_id: String) -> Command(id: window::Id, mode: Mode) -> Command { +pub fn set_mode_window( + id: window::Id, + mode: Mode, +) -> Command { Command::single(command::Action::PlatformSpecific( platform_specific::Action::Wayland(wayland::Action::Window( wayland::window::Action::Mode(id, mode), diff --git a/sctk/src/conversion.rs b/sctk/src/conversion.rs index c33a3e1bd8..8853d13b94 100644 --- a/sctk/src/conversion.rs +++ b/sctk/src/conversion.rs @@ -4,7 +4,10 @@ use iced_native::{ }; use sctk::{ reexports::client::protocol::wl_pointer::AxisSource, - seat::{keyboard::Modifiers, pointer::{AxisScroll, BTN_LEFT, BTN_RIGHT, BTN_MIDDLE}}, + seat::{ + keyboard::Modifiers, + pointer::{AxisScroll, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT}, + }, }; /// An error that occurred while running an application. #[derive(Debug, thiserror::Error)] diff --git a/sctk/src/event_loop/mod.rs b/sctk/src/event_loop/mod.rs index cff519716e..cc14e1a067 100644 --- a/sctk/src/event_loop/mod.rs +++ b/sctk/src/event_loop/mod.rs @@ -414,11 +414,10 @@ where variant: WindowEventVariant::Close, id, } => { - if let Some(i) = self - .state - .windows - .iter() - .position(|l| l.window.wl_surface().id() == id.id()) + if let Some(i) = + self.state.windows.iter().position(|l| { + l.window.wl_surface().id() == id.id() + }) { let w = self.state.windows.remove(i); w.window.xdg_toplevel().destroy(); diff --git a/sctk/src/handlers/output.rs b/sctk/src/handlers/output.rs index ab49a7b0bf..75069c22fb 100644 --- a/sctk/src/handlers/output.rs +++ b/sctk/src/handlers/output.rs @@ -40,7 +40,7 @@ impl OutputHandler for SctkState { _qh: &sctk::reexports::client::QueueHandle, output: sctk::reexports::client::protocol::wl_output::WlOutput, ) { - self.sctk_events.push(SctkEvent::RemovedOutput(output.id())); + self.sctk_events.push(SctkEvent::RemovedOutput(output)); // TODO clean up any layer surfaces on this output? } } diff --git a/sctk/src/handlers/shell/xdg_window.rs b/sctk/src/handlers/shell/xdg_window.rs index f435854ebf..ce2c08a33b 100644 --- a/sctk/src/handlers/shell/xdg_window.rs +++ b/sctk/src/handlers/shell/xdg_window.rs @@ -3,8 +3,7 @@ use crate::{ sctk_event::{SctkEvent, WindowEventVariant}, }; use sctk::{ - delegate_xdg_shell, delegate_xdg_window, - shell::xdg::window::WindowHandler, + delegate_xdg_shell, delegate_xdg_window, shell::xdg::window::WindowHandler, }; use std::fmt::Debug; diff --git a/sctk/src/sctk_event.rs b/sctk/src/sctk_event.rs index bfb7b4fcb4..8d5806562f 100644 --- a/sctk/src/sctk_event.rs +++ b/sctk/src/sctk_event.rs @@ -155,7 +155,7 @@ pub enum SctkEvent { id: WlOutput, info: OutputInfo, }, - RemovedOutput(ObjectId), + RemovedOutput(WlOutput), // // compositor events @@ -604,9 +604,57 @@ impl SctkEvent { } // TODO } } - SctkEvent::NewOutput { id, info } => Default::default(), - SctkEvent::UpdateOutput { id, info } => Default::default(), - SctkEvent::RemovedOutput(_) => Default::default(), + SctkEvent::NewOutput { id, info } => { + Some(iced_native::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::Output( + wayland::OutputEvent::Created, + id, + )), + )) + .into_iter() + .collect() + } + SctkEvent::UpdateOutput { id, info } => vec![ + iced_native::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::Output( + wayland::OutputEvent::Name(info.name.clone()), + id.clone(), + )), + ), + iced_native::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::Output( + wayland::OutputEvent::LogicalSize(info.logical_size), + id.clone(), + )), + ), + iced_native::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::Output( + wayland::OutputEvent::LogicalPosition( + info.logical_position, + ), + id.clone(), + )), + ), + iced_native::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::Output( + wayland::OutputEvent::MakeAndModel { + make: info.make, + model: info.model, + }, + id.clone(), + )), + ), + ], + SctkEvent::RemovedOutput(id) => { + Some(iced_native::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::Output( + wayland::OutputEvent::Removed, + id.clone(), + )), + )) + .into_iter() + .collect() + } SctkEvent::Draw(_) => Default::default(), SctkEvent::ScaleFactorChanged { factor, diff --git a/swbuf/src/backend.rs b/swbuf/src/backend.rs index 6c2c6cebae..3ed3ea11cb 100644 --- a/swbuf/src/backend.rs +++ b/swbuf/src/backend.rs @@ -1,4 +1,6 @@ -use cosmic_text::{Attrs, AttrsList, BufferLine, FontSystem, Metrics, SwashCache, Weight}; +use cosmic_text::{ + Attrs, AttrsList, BufferLine, FontSystem, Metrics, SwashCache, Weight, +}; #[cfg(feature = "image")] use iced_graphics::image::raster; use iced_graphics::image::storage; @@ -26,8 +28,8 @@ pub(crate) struct CpuEntry { impl fmt::Debug for CpuEntry { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("CpuEntry") - .field("size", &self.size) - .finish() + .field("size", &self.size) + .finish() } } @@ -61,11 +63,9 @@ impl storage::Storage for CpuStorage { for chunk in data_u8.chunks_exact(4) { data.push( raqote::SolidSource::from_unpremultiplied_argb( - chunk[3], - chunk[0], - chunk[1], - chunk[2], - ).to_u32() + chunk[3], chunk[0], chunk[1], chunk[2], + ) + .to_u32(), ); } Some(Self::Entry { @@ -99,7 +99,11 @@ impl Backend { } } - pub(crate) fn cosmic_metrics_attrs(&self, size: f32, font: &Font) -> (Metrics, Attrs) { + pub(crate) fn cosmic_metrics_attrs( + &self, + size: f32, + font: &Font, + ) -> (Metrics, Attrs) { //TODO: why is this conversion necessary? let font_size = (size * 5.0 / 6.0) as i32; @@ -109,10 +113,7 @@ impl Backend { let attrs = match font { Font::Default => Attrs::new().weight(Weight::NORMAL), //TODO: support using the bytes field. Right now this is just a hack for libcosmic - Font::External { - name, - bytes, - } => match *name { + Font::External { name, bytes } => match *name { "Fira Sans Regular" => Attrs::new().weight(Weight::NORMAL), "Fira Sans Light" => Attrs::new().weight(Weight::LIGHT), "Fira Sans SemiBold" => Attrs::new().weight(Weight::SEMIBOLD), @@ -120,13 +121,10 @@ impl Backend { log::warn!("Unsupported font name {:?}", name); Attrs::new() } - } + }, }; - ( - Metrics::new(font_size, line_height), - attrs - ) + (Metrics::new(font_size, line_height), attrs) } } @@ -157,7 +155,11 @@ impl iced_graphics::backend::Text for Backend { //TODO: improve implementation let mut buffer_line = BufferLine::new(content, AttrsList::new(attrs)); - let layout = buffer_line.layout(&FONT_SYSTEM, metrics.font_size, bounds.width as i32); + let layout = buffer_line.layout( + &FONT_SYSTEM, + metrics.font_size, + bounds.width as i32, + ); let mut width = 0.0; let mut height = 0.0; @@ -191,13 +193,19 @@ impl iced_graphics::backend::Text for Backend { //TODO: improve implementation let mut buffer_line = BufferLine::new(content, AttrsList::new(attrs)); - let layout = buffer_line.layout(&FONT_SYSTEM, metrics.font_size, bounds.width as i32); + let layout = buffer_line.layout( + &FONT_SYSTEM, + metrics.font_size, + bounds.width as i32, + ); // Find exact hit - if ! nearest_only { + if !nearest_only { let mut line_y = 0.0; for layout_line in layout.iter() { - if point.y > line_y && point.y < line_y + metrics.line_height as f32 { + if point.y > line_y + && point.y < line_y + metrics.line_height as f32 + { for glyph in layout_line.glyphs.iter() { let (min_x, max_x) = if glyph.level.is_rtl() { (glyph.x - glyph.w, glyph.x) @@ -206,9 +214,7 @@ impl iced_graphics::backend::Text for Backend { }; if point.x > min_x && point.x < max_x { - return Some(text::Hit::CharOffset( - glyph.start - )); + return Some(text::Hit::CharOffset(glyph.start)); } } } @@ -236,16 +242,22 @@ impl iced_graphics::backend::Text for Backend { let distance = center.distance(point); let vector = point - center; nearest_opt = match nearest_opt { - Some((nearest_offset, nearest_vector, nearest_distance)) => { + Some(( + nearest_offset, + nearest_vector, + nearest_distance, + )) => { if distance < nearest_distance { Some((glyph.start, vector, distance)) } else { - Some((nearest_offset, nearest_vector, nearest_distance)) + Some(( + nearest_offset, + nearest_vector, + nearest_distance, + )) } - }, - None => { - Some((glyph.start, vector, distance)) } + None => Some((glyph.start, vector, distance)), }; } @@ -253,10 +265,9 @@ impl iced_graphics::backend::Text for Backend { } match nearest_opt { - Some((offset, vector, _)) => Some(text::Hit::NearestCharOffset( - offset, - vector - )), + Some((offset, vector, _)) => { + Some(text::Hit::NearestCharOffset(offset, vector)) + } None => None, } } diff --git a/swbuf/src/surface.rs b/swbuf/src/surface.rs index 93d9a02497..3546d568a1 100644 --- a/swbuf/src/surface.rs +++ b/swbuf/src/surface.rs @@ -1,14 +1,17 @@ use crate::backend::{Backend, CpuStorage, FONT_SYSTEM}; use cosmic_text::{AttrsList, BufferLine, SwashContent}; -use iced_graphics::{Background, Gradient, Primitive}; use iced_graphics::alignment::{Horizontal, Vertical}; #[cfg(feature = "svg")] use iced_graphics::image::vector; -use raqote::{DrawOptions, DrawTarget, Image, IntPoint, IntRect, PathBuilder, SolidSource, StrokeStyle, Source, Transform}; +use iced_graphics::{Background, Gradient, Primitive}; +use raqote::{ + DrawOptions, DrawTarget, Image, IntPoint, IntRect, PathBuilder, + SolidSource, Source, StrokeStyle, Transform, +}; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; -use swbuf::GraphicsContext; use std::cmp; +use swbuf::GraphicsContext; // A software rendering surface pub struct Surface { @@ -19,7 +22,9 @@ pub struct Surface { } impl Surface { - pub(crate) fn new(window: &W) -> Self { + pub(crate) fn new( + window: &W, + ) -> Self { let context = match unsafe { GraphicsContext::new(window, window) } { Ok(ok) => ok, Err(err) => panic!("failed to create swbuf context: {}", err), @@ -35,27 +40,25 @@ impl Surface { pub(crate) fn configure(&mut self, width: u32, height: u32) { self.width = width; self.height = height; - self.buffer = vec![ - 0; - self.width as usize * self.height as usize - ]; + self.buffer = vec![0; self.width as usize * self.height as usize]; } - pub(crate) fn present(&mut self, renderer: &mut crate::Renderer, background: iced_graphics::Color) { + pub(crate) fn present( + &mut self, + renderer: &mut crate::Renderer, + background: iced_graphics::Color, + ) { { let mut draw_target = DrawTarget::from_backing( self.width as i32, self.height as i32, - self.buffer.as_mut_slice() + self.buffer.as_mut_slice(), ); draw_target.clear({ let rgba = background.into_rgba8(); SolidSource::from_unpremultiplied_argb( - rgba[3], - rgba[0], - rgba[1], - rgba[2], + rgba[3], rgba[0], rgba[1], rgba[2], ) }); @@ -68,7 +71,7 @@ impl Surface { // Having at least one clip fixes some font rendering issues draw_target.push_clip_rect(IntRect::new( IntPoint::new(0, 0), - IntPoint::new(self.width as i32, self.height as i32) + IntPoint::new(self.width as i32, self.height as i32), )); renderer.with_primitives(|backend, primitives| { @@ -77,7 +80,7 @@ impl Surface { &mut draw_target, &draw_options, backend, - primitive + primitive, ); } }); @@ -88,7 +91,7 @@ impl Surface { self.context.set_buffer( &self.buffer, self.width as u16, - self.height as u16 + self.height as u16, ); } } @@ -97,7 +100,7 @@ fn draw_primitive( draw_target: &mut DrawTarget<&mut [u32]>, draw_options: &DrawOptions, backend: &mut Backend, - primitive: &Primitive + primitive: &Primitive, ) { match primitive { Primitive::None => (), @@ -105,7 +108,7 @@ fn draw_primitive( for child in primitives.iter() { draw_primitive(draw_target, draw_options, backend, child); } - }, + } Primitive::Text { content, bounds, @@ -117,12 +120,7 @@ fn draw_primitive( } => { let cosmic_color = { let rgba8 = color.into_rgba8(); - cosmic_text::Color::rgba( - rgba8[0], - rgba8[1], - rgba8[2], - rgba8[3], - ) + cosmic_text::Color::rgba(rgba8[0], rgba8[1], rgba8[2], rgba8[3]) }; let (metrics, attrs) = backend.cosmic_metrics_attrs(*size, &font); @@ -145,19 +143,26 @@ fn draw_primitive( */ //TODO: improve implementation - let mut buffer_line = BufferLine::new(content, AttrsList::new(attrs)); - let layout = buffer_line.layout(&FONT_SYSTEM, metrics.font_size, bounds.width as i32); + let mut buffer_line = + BufferLine::new(content, AttrsList::new(attrs)); + let layout = buffer_line.layout( + &FONT_SYSTEM, + metrics.font_size, + bounds.width as i32, + ); let mut line_y = match vertical_alignment { Vertical::Top => bounds.y as i32 + metrics.font_size, Vertical::Center => { //TODO: why is this so weird? - bounds.y as i32 + metrics.font_size - metrics.line_height * layout.len() as i32 / 2 + bounds.y as i32 + metrics.font_size + - metrics.line_height * layout.len() as i32 / 2 } Vertical::Bottom => { //TODO: why is this so weird? - bounds.y as i32 + metrics.font_size - metrics.line_height * layout.len() as i32 - }, + bounds.y as i32 + metrics.font_size + - metrics.line_height * layout.len() as i32 + } }; let mut line_width = 0.0; @@ -179,7 +184,7 @@ fn draw_primitive( Horizontal::Center => { //TODO: why is this so weird? bounds.x as i32 - (line_width / 2.0) as i32 - }, + } Horizontal::Right => { //TODO: why is this so weird? bounds.x as i32 - line_width as i32 @@ -217,30 +222,30 @@ fn draw_primitive( ); */ - - //TODO: also clip y, it does not seem to work though because // bounds.height < metrics.line_height * layout_lines.len() draw_target.push_clip_rect(IntRect::new( + IntPoint::new(line_x, 0), IntPoint::new( - line_x, - 0, - ), - IntPoint::new( - line_x.checked_add(bounds.width as i32).unwrap_or_else(i32::max_value), + line_x + .checked_add(bounds.width as i32) + .unwrap_or_else(i32::max_value), i32::max_value(), - ) + ), )); for glyph in layout_line.glyphs.iter() { - let (cache_key, x_int, y_int) = (glyph.cache_key, glyph.x_int, glyph.y_int); + let (cache_key, x_int, y_int) = + (glyph.cache_key, glyph.x_int, glyph.y_int); let glyph_color = match glyph.color_opt { Some(some) => some, None => cosmic_color, }; - if let Some(image) = backend.swash_cache.get_image(cache_key) { + if let Some(image) = + backend.swash_cache.get_image(cache_key) + { let x = line_x + x_int + image.placement.left; let y = line_y + y_int + -image.placement.top; @@ -262,13 +267,16 @@ fn draw_primitive( */ let mut image_data = Vec::with_capacity( - image.placement.height as usize * image.placement.width as usize + image.placement.height as usize + * image.placement.width as usize, ); match image.content { SwashContent::Mask => { let mut i = 0; for _off_y in 0..image.placement.height as i32 { - for _off_x in 0..image.placement.width as i32 { + for _off_x in + 0..image.placement.width as i32 + { //TODO: blend base alpha? image_data.push( SolidSource::from_unpremultiplied_argb( @@ -281,11 +289,13 @@ fn draw_primitive( i += 1; } } - }, + } SwashContent::Color => { let mut i = 0; for _off_y in 0..image.placement.height as i32 { - for _off_x in 0..image.placement.width as i32 { + for _off_x in + 0..image.placement.width as i32 + { //TODO: blend base alpha? image_data.push( SolidSource::from_unpremultiplied_argb( @@ -298,23 +308,22 @@ fn draw_primitive( i += 4; } } - - }, + } SwashContent::SubpixelMask => { eprintln!("Content::SubpixelMask"); } } - if ! image_data.is_empty() { + if !image_data.is_empty() { draw_target.draw_image_at( x as f32, y as f32, &Image { width: image.placement.width as i32, height: image.placement.height as i32, - data: &image_data + data: &image_data, }, - &draw_options + &draw_options, ); } } @@ -324,7 +333,7 @@ fn draw_primitive( line_y += metrics.line_height; } - }, + } Primitive::Quad { bounds, background, @@ -359,7 +368,7 @@ fn draw_primitive( bounds.y + top_left, top_left, 180.0f32.to_radians(), - 90.0f32.to_radians() + 90.0f32.to_radians(), ); // Move to top right corner at start of clockwise arc @@ -369,17 +378,20 @@ fn draw_primitive( bounds.y + top_right, top_right, 270.0f32.to_radians(), - 90.0f32.to_radians() + 90.0f32.to_radians(), ); // Move to bottom right corner at start of clockwise arc - pb.line_to(bounds.x + bounds.width, bounds.y + bounds.height - bottom_right); + pb.line_to( + bounds.x + bounds.width, + bounds.y + bounds.height - bottom_right, + ); pb.arc( bounds.x + bounds.width - bottom_right, bounds.y + bounds.height - bottom_right, bottom_right, 0.0f32.to_radians(), - 90.0f32.to_radians() + 90.0f32.to_radians(), ); // Move to bottom left corner at start of clockwise arc @@ -389,7 +401,7 @@ fn draw_primitive( bounds.y + bounds.height - bottom_left, bottom_left, 90.0f32.to_radians(), - 90.0f32.to_radians() + 90.0f32.to_radians(), ); // Close and finish path @@ -400,10 +412,7 @@ fn draw_primitive( Background::Color(color) => { let rgba = color.into_rgba8(); Source::Solid(SolidSource::from_unpremultiplied_argb( - rgba[3], - rgba[0], - rgba[1], - rgba[2], + rgba[3], rgba[0], rgba[1], rgba[2], )) } }; @@ -415,19 +424,14 @@ fn draw_primitive( // Anti-alias rounded rectangles antialias: raqote::AntialiasMode::Gray, ..*draw_options - } + }, ); let border_source = { let rgba = border_color.into_rgba8(); - Source::Solid( - SolidSource::from_unpremultiplied_argb( - rgba[3], - rgba[0], - rgba[1], - rgba[2], - ) - ) + Source::Solid(SolidSource::from_unpremultiplied_argb( + rgba[3], rgba[0], rgba[1], rgba[2], + )) }; let style = StrokeStyle { @@ -443,18 +447,15 @@ fn draw_primitive( // Anti-alias rounded rectangles antialias: raqote::AntialiasMode::Gray, ..*draw_options - } + }, ); - }, - Primitive::Image { - handle, - bounds - } => { + } + Primitive::Image { handle, bounds } => { #[cfg(feature = "image")] match backend.raster_cache.borrow_mut().upload( handle, &mut (), - &mut CpuStorage + &mut CpuStorage, ) { Some(entry) => { draw_target.draw_image_with_size_at( @@ -465,11 +466,11 @@ fn draw_primitive( &Image { width: entry.size.width as i32, height: entry.size.height as i32, - data: &entry.data + data: &entry.data, }, - draw_options + draw_options, ); - }, + } None => (), } } @@ -496,46 +497,40 @@ fn draw_primitive( &Image { width: entry.size.width as i32, height: entry.size.height as i32, - data: &entry.data + data: &entry.data, }, - draw_options + draw_options, ); - }, + } None => (), } - }, - Primitive::Clip { - bounds, - content, - } => { + } + Primitive::Clip { bounds, content } => { draw_target.push_clip_rect(IntRect::new( - IntPoint::new( - bounds.x as i32, - bounds.y as i32 - ), + IntPoint::new(bounds.x as i32, bounds.y as i32), IntPoint::new( (bounds.x + bounds.width) as i32, - (bounds.y + bounds.height) as i32 - ) + (bounds.y + bounds.height) as i32, + ), )); draw_primitive(draw_target, draw_options, backend, &content); draw_target.pop_clip(); - }, + } Primitive::Translate { translation, content, } => { draw_target.set_transform(&Transform::translation( translation.x, - translation.y + translation.y, )); draw_primitive(draw_target, draw_options, backend, &content); draw_target.set_transform(&Transform::identity()); - }, + } Primitive::GradientMesh { buffers, size, - gradient + gradient, } => { let source = match gradient { Gradient::Linear(linear) => { @@ -545,20 +540,17 @@ fn draw_primitive( stops.push(raqote::GradientStop { position: stop.offset, color: raqote::Color::new( - rgba8[3], - rgba8[0], - rgba8[1], - rgba8[2] - ) + rgba8[3], rgba8[0], rgba8[1], rgba8[2], + ), }); } Source::new_linear_gradient( raqote::Gradient { stops }, raqote::Point::new(linear.start.x, linear.start.y), raqote::Point::new(linear.end.x, linear.end.y), - raqote::Spread::Pad /*TODO: which spread?*/ + raqote::Spread::Pad, /*TODO: which spread?*/ ) - }, + } }; /* @@ -579,24 +571,16 @@ fn draw_primitive( pb.line_to(b.position[0], b.position[1]); pb.line_to(c.position[0], c.position[1]); pb.close(); - } let path = pb.finish(); - draw_target.fill( - &path, - &source, - draw_options - ); + draw_target.fill(&path, &source, draw_options); /* draw_target.pop_clip(); */ } - Primitive::SolidMesh { - buffers, - size, - } => { + Primitive::SolidMesh { buffers, size } => { fn undo_linear_component(linear: f32) -> f32 { if linear < 0.0031308 { linear * 12.92 @@ -630,7 +614,6 @@ fn draw_primitive( let b = &buffers.vertices[indices[1] as usize]; let c = &buffers.vertices[indices[2] as usize]; - let mut pb = PathBuilder::new(); pb.move_to(a.position[0], a.position[1]); pb.line_to(b.position[0], b.position[1]); @@ -639,29 +622,21 @@ fn draw_primitive( // TODO: Each vertice has its own separate color. let rgba8 = linear_to_rgba8(&a.color); - let source = Source::Solid(SolidSource::from_unpremultiplied_argb( - rgba8[3], - rgba8[0], - rgba8[1], - rgba8[2], - )); + let source = + Source::Solid(SolidSource::from_unpremultiplied_argb( + rgba8[3], rgba8[0], rgba8[1], rgba8[2], + )); let path = pb.finish(); - draw_target.fill( - &path, - &source, - draw_options - ); + draw_target.fill(&path, &source, draw_options); } /* draw_target.pop_clip(); */ - }, - Primitive::Cached { - cache - } => { + } + Primitive::Cached { cache } => { draw_primitive(draw_target, draw_options, backend, &cache); - }, + } } } diff --git a/swbuf/src/window/compositor.rs b/swbuf/src/window/compositor.rs index 1400ce8b10..7fedfc5bd4 100644 --- a/swbuf/src/window/compositor.rs +++ b/swbuf/src/window/compositor.rs @@ -1,8 +1,8 @@ -use crate::{Backend, surface::Surface}; +use crate::{surface::Surface, Backend}; use iced_graphics::{ - Color, Error, Viewport, compositor::{self, Information, SurfaceError}, + Color, Error, Viewport, }; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; @@ -28,9 +28,7 @@ impl compositor::Compositor for Compositor { settings: Self::Settings, compatible_window: Option<&W>, ) -> Result<(Self, Self::Renderer), Error> { - let compositor = Self { - theme: PhantomData, - }; + let compositor = Self { theme: PhantomData }; let renderer = Self::Renderer::new(Backend::new()); From 2d47c7a844cf6bc10df357a8e4334e2c7280f6b0 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 21 Dec 2022 20:17:55 -0500 Subject: [PATCH 17/56] refactor: OutputInfo in output events --- native/src/event/wayland/output.rs | 39 ++++++++++++++++++------------ sctk/src/sctk_event.rs | 27 ++------------------- 2 files changed, 26 insertions(+), 40 deletions(-) diff --git a/native/src/event/wayland/output.rs b/native/src/event/wayland/output.rs index 9c8a2214a1..3c5280dc6c 100644 --- a/native/src/event/wayland/output.rs +++ b/native/src/event/wayland/output.rs @@ -1,21 +1,30 @@ +use sctk::output::OutputInfo; + /// output events -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub enum OutputEvent { /// created output - Created, + Created(Option), /// removed output Removed, - /// Make and Model - MakeAndModel { - /// make of the output - make: String, - /// model of the output - model: String, - }, - /// name of the output - Name(Option), - /// logical size of the output - LogicalSize(Option<(i32, i32)>), - /// logical position of the output - LogicalPosition(Option<(i32, i32)>), + /// Output Info + InfoUpdate(OutputInfo), } + +impl Eq for OutputEvent {} + +impl PartialEq for OutputEvent { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Created(l0), Self::Created(r0)) => { + if let Some((l0, r0)) = l0.as_ref().zip(r0.as_ref()) { + l0.id == r0.id && l0.make == r0.make && l0.model == r0.model + } else { + l0.is_none() && r0.is_none() + } + }, + (Self::InfoUpdate(l0), Self::InfoUpdate(r0)) => l0.id == r0.id && l0.make == r0.make && l0.model == r0.model, + _ => core::mem::discriminant(self) == core::mem::discriminant(other), + } + } +} \ No newline at end of file diff --git a/sctk/src/sctk_event.rs b/sctk/src/sctk_event.rs index 8d5806562f..dc8e82f9fb 100644 --- a/sctk/src/sctk_event.rs +++ b/sctk/src/sctk_event.rs @@ -607,7 +607,7 @@ impl SctkEvent { SctkEvent::NewOutput { id, info } => { Some(iced_native::Event::PlatformSpecific( PlatformSpecific::Wayland(wayland::Event::Output( - wayland::OutputEvent::Created, + wayland::OutputEvent::Created(info), id, )), )) @@ -617,30 +617,7 @@ impl SctkEvent { SctkEvent::UpdateOutput { id, info } => vec![ iced_native::Event::PlatformSpecific( PlatformSpecific::Wayland(wayland::Event::Output( - wayland::OutputEvent::Name(info.name.clone()), - id.clone(), - )), - ), - iced_native::Event::PlatformSpecific( - PlatformSpecific::Wayland(wayland::Event::Output( - wayland::OutputEvent::LogicalSize(info.logical_size), - id.clone(), - )), - ), - iced_native::Event::PlatformSpecific( - PlatformSpecific::Wayland(wayland::Event::Output( - wayland::OutputEvent::LogicalPosition( - info.logical_position, - ), - id.clone(), - )), - ), - iced_native::Event::PlatformSpecific( - PlatformSpecific::Wayland(wayland::Event::Output( - wayland::OutputEvent::MakeAndModel { - make: info.make, - model: info.model, - }, + wayland::OutputEvent::InfoUpdate(info), id.clone(), )), ), From fec3193836d9fbe57fb93d54f56ce5d73b23ee38 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 23 Dec 2022 16:04:14 +0100 Subject: [PATCH 18/56] feat: Quad border radius support for buttons --- core/src/lib.rs | 2 ++ core/src/radius.rs | 22 ++++++++++++++++++++++ native/src/renderer.rs | 25 ++----------------------- native/src/widget/button.rs | 4 ++-- style/src/button.rs | 6 +++--- style/src/theme.rs | 3 ++- 6 files changed, 33 insertions(+), 29 deletions(-) create mode 100644 core/src/radius.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index f95d61f642..7dd9372885 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -36,6 +36,7 @@ mod font; mod length; mod padding; mod point; +mod radius; mod rectangle; mod size; mod vector; @@ -48,6 +49,7 @@ pub use font::Font; pub use length::Length; pub use padding::Padding; pub use point::Point; +pub use radius::BorderRadius; pub use rectangle::Rectangle; pub use size::Size; pub use vector::Vector; diff --git a/core/src/radius.rs b/core/src/radius.rs new file mode 100644 index 0000000000..b9a302c062 --- /dev/null +++ b/core/src/radius.rs @@ -0,0 +1,22 @@ +/// The border radi for the corners of a graphics primitive in the order: +/// top-left, top-right, bottom-right, bottom-left. +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub struct BorderRadius([f32; 4]); + +impl From for BorderRadius { + fn from(w: f32) -> Self { + Self([w; 4]) + } +} + +impl From<[f32; 4]> for BorderRadius { + fn from(radi: [f32; 4]) -> Self { + Self(radi) + } +} + +impl From for [f32; 4] { + fn from(radi: BorderRadius) -> Self { + radi.0 + } +} diff --git a/native/src/renderer.rs b/native/src/renderer.rs index 5e776be6cd..c1060dbd59 100644 --- a/native/src/renderer.rs +++ b/native/src/renderer.rs @@ -4,6 +4,8 @@ mod null; #[cfg(debug_assertions)] pub use null::Null; +pub use iced_core::BorderRadius; + use crate::layout; use crate::{Background, Color, Element, Rectangle, Vector}; @@ -59,29 +61,6 @@ pub struct Quad { pub border_color: Color, } -/// The border radi for the corners of a graphics primitive in the order: -/// top-left, top-right, bottom-right, bottom-left. -#[derive(Debug, Clone, Copy, PartialEq, Default)] -pub struct BorderRadius([f32; 4]); - -impl From for BorderRadius { - fn from(w: f32) -> Self { - Self([w; 4]) - } -} - -impl From<[f32; 4]> for BorderRadius { - fn from(radi: [f32; 4]) -> Self { - Self(radi) - } -} - -impl From for [f32; 4] { - fn from(radi: BorderRadius) -> Self { - radi.0 - } -} - /// The styling attributes of a [`Renderer`]. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Style { diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index caf16ea049..fa5da24bb2 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -393,7 +393,7 @@ where y: bounds.y + styling.shadow_offset.y, ..bounds }, - border_radius: styling.border_radius.into(), + border_radius: styling.border_radius, border_width: 0.0, border_color: Color::TRANSPARENT, }, @@ -404,7 +404,7 @@ where renderer.fill_quad( renderer::Quad { bounds, - border_radius: styling.border_radius.into(), + border_radius: styling.border_radius, border_width: styling.border_width, border_color: styling.border_color, }, diff --git a/style/src/button.rs b/style/src/button.rs index a564a2b7ea..6f9269a0e1 100644 --- a/style/src/button.rs +++ b/style/src/button.rs @@ -1,5 +1,5 @@ //! Change the apperance of a button. -use iced_core::{Background, Color, Vector}; +use iced_core::{Background, BorderRadius, Color, Vector}; /// The appearance of a button. #[derive(Debug, Clone, Copy)] @@ -9,7 +9,7 @@ pub struct Appearance { /// The [`Background`] of the button. pub background: Option, /// The border radius of the button. - pub border_radius: f32, + pub border_radius: BorderRadius, /// The border width of the button. pub border_width: f32, /// The border [`Color`] of the button. @@ -23,7 +23,7 @@ impl std::default::Default for Appearance { Self { shadow_offset: Vector::default(), background: None, - border_radius: 0.0, + border_radius: BorderRadius::from(0.0), border_width: 0.0, border_color: Color::TRANSPARENT, text_color: Color::BLACK, diff --git a/style/src/theme.rs b/style/src/theme.rs index 139addb44d..4749e24fd2 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -21,6 +21,7 @@ use crate::text; use crate::text_input; use crate::toggler; +use iced_core::BorderRadius; use iced_core::{Background, Color, Vector}; use std::rc::Rc; @@ -146,7 +147,7 @@ impl button::StyleSheet for Theme { let palette = self.extended_palette(); let appearance = button::Appearance { - border_radius: 2.0, + border_radius: BorderRadius::from(2.0), ..button::Appearance::default() }; From f11670fa65e1962e154bf5cf1d4226dada608039 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 23 Dec 2022 07:07:54 -0800 Subject: [PATCH 19/56] sctk: Improvements for handling outputs (#14) * sctk: Don't send output events twice With these lines, a subscription seems to receive output events twice. Without it, only once as expected. * sctk: Make `IcedOutput::Output` take a `WlOutput` This is the most flexible solution, and using output events, it is possible to create surfaces for specific outputs or all. Matching by make/model is a bad idea anyway. Matching by name may be appropriate. --- .../platform_specific/wayland/layer_surface.rs | 12 +++++------- sctk/src/application.rs | 3 --- sctk/src/event_loop/state.rs | 16 +++++++++++++--- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/native/src/command/platform_specific/wayland/layer_surface.rs b/native/src/command/platform_specific/wayland/layer_surface.rs index 582381b4a8..0b07602b6f 100644 --- a/native/src/command/platform_specific/wayland/layer_surface.rs +++ b/native/src/command/platform_specific/wayland/layer_surface.rs @@ -3,7 +3,10 @@ use std::marker::PhantomData; use std::{collections::hash_map::DefaultHasher, fmt}; use iced_futures::MaybeSend; -use sctk::shell::layer::{Anchor, KeyboardInteractivity, Layer}; +use sctk::{ + reexports::client::protocol::wl_output::WlOutput, + shell::layer::{Anchor, KeyboardInteractivity, Layer}, +}; use crate::window; @@ -15,12 +18,7 @@ pub enum IcedOutput { /// show on active output Active, /// show on a specific output - Output { - /// make - make: String, - /// model - model: String, - }, + Output(WlOutput), } impl Default for IcedOutput { diff --git a/sctk/src/application.rs b/sctk/src/application.rs index b391808e96..60efde2dd4 100644 --- a/sctk/src/application.rs +++ b/sctk/src/application.rs @@ -581,13 +581,10 @@ where }, // TODO forward these events to an application which requests them? SctkEvent::NewOutput { id, info } => { - events.push(SctkEvent::NewOutput { id, info }); } SctkEvent::UpdateOutput { id, info } => { - events.push(SctkEvent::UpdateOutput { id, info }); } SctkEvent::RemovedOutput(id) => { - events.push(SctkEvent::RemovedOutput(id)); } SctkEvent::Draw(_) => unimplemented!(), // probably should never be forwarded here... SctkEvent::ScaleFactorChanged { diff --git a/sctk/src/event_loop/state.rs b/sctk/src/event_loop/state.rs index 9e9fc36a0f..159bc69a4b 100644 --- a/sctk/src/event_loop/state.rs +++ b/sctk/src/event_loop/state.rs @@ -10,7 +10,7 @@ use iced_native::{ command::platform_specific::{ self, wayland::{ - layer_surface::{IcedMargin, SctkLayerSurfaceSettings}, + layer_surface::{IcedMargin, IcedOutput, SctkLayerSurfaceSettings}, popup::SctkPopupSettings, window::SctkWindowSettings, }, @@ -460,6 +460,12 @@ where }: SctkLayerSurfaceSettings, ) -> Result<(iced_native::window::Id, WlSurface), LayerSurfaceCreationError> { + let wl_output = match output { + IcedOutput::All => None, // TODO + IcedOutput::Active => None, + IcedOutput::Output(output) => Some(output), + }; + let layer_shell = self .layer_shell .as_ref() @@ -467,13 +473,17 @@ where let wl_surface = self.compositor_state.create_surface(&self.queue_handle); - let layer_surface = LayerSurface::builder() + let mut builder = LayerSurface::builder() .anchor(anchor) .keyboard_interactivity(keyboard_interactivity) .margin(margin.top, margin.right, margin.bottom, margin.left) .size((size.0.unwrap_or_default(), size.1.unwrap_or_default())) .namespace(namespace) - .exclusive_zone(exclusive_zone) + .exclusive_zone(exclusive_zone); + if let Some(wl_output) = wl_output { + builder = builder.output(&wl_output); + } + let layer_surface = builder .map(&self.queue_handle, layer_shell, wl_surface.clone(), layer) .map_err(|g_err| { LayerSurfaceCreationError::LayerSurfaceCreationFailed(g_err) From babe9e16c67427a2712a69520d3ca527d4cd6256 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 23 Dec 2022 12:24:06 -0500 Subject: [PATCH 20/56] fix: use softbuffer instead of swbuf, which is now archived --- swbuf/Cargo.toml | 2 +- swbuf/src/surface.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/swbuf/Cargo.toml b/swbuf/Cargo.toml index d97cd7f2d1..e50c5cafc6 100644 --- a/swbuf/Cargo.toml +++ b/swbuf/Cargo.toml @@ -13,7 +13,7 @@ lazy_static = "1.4" log = "0.4" raw-window-handle = "0.5" raqote = { version = "0.8", default-features = false } -swbuf = { git = "https://github.com/rust-windowing/swbuf", rev = "9b8641fc" } +softbuffer = { git = "https://github.com/rust-windowing/softbuffer"} [dependencies.iced_native] path = "../native" diff --git a/swbuf/src/surface.rs b/swbuf/src/surface.rs index 3546d568a1..4237c07e3b 100644 --- a/swbuf/src/surface.rs +++ b/swbuf/src/surface.rs @@ -11,7 +11,7 @@ use raqote::{ }; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::cmp; -use swbuf::GraphicsContext; +use softbuffer::GraphicsContext; // A software rendering surface pub struct Surface { From ed48d14822f0c33ca13e04edc01e1e8c659cf112 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 23 Dec 2022 12:32:58 -0500 Subject: [PATCH 21/56] cleanup: add rev --- swbuf/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swbuf/Cargo.toml b/swbuf/Cargo.toml index e50c5cafc6..36ef755415 100644 --- a/swbuf/Cargo.toml +++ b/swbuf/Cargo.toml @@ -13,7 +13,7 @@ lazy_static = "1.4" log = "0.4" raw-window-handle = "0.5" raqote = { version = "0.8", default-features = false } -softbuffer = { git = "https://github.com/rust-windowing/softbuffer"} +softbuffer = { git = "https://github.com/rust-windowing/softbuffer", rev = "d5bb2c1" } [dependencies.iced_native] path = "../native" From 1afda2b12674e6054480c041ea6474e4cbc9b471 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Sun, 25 Dec 2022 22:28:48 -0500 Subject: [PATCH 22/56] feat: auto-size popups --- examples/clock_sctk_layer_surface/src/main.rs | 43 ++- examples/clock_sctk_window/src/main.rs | 19 +- .../wayland/layer_surface.rs | 13 +- .../platform_specific/wayland/popup.rs | 14 +- .../platform_specific/wayland/window.rs | 7 + sctk/src/application.rs | 113 +++++--- sctk/src/event_loop/mod.rs | 9 +- sctk/src/event_loop/state.rs | 256 +++++++++++------- sctk/src/handlers/shell/layer.rs | 19 +- sctk/src/handlers/shell/xdg_popup.rs | 10 +- sctk/src/sctk_event.rs | 1 + 11 files changed, 311 insertions(+), 193 deletions(-) diff --git a/examples/clock_sctk_layer_surface/src/main.rs b/examples/clock_sctk_layer_surface/src/main.rs index 23f7df127c..fe6c914f0f 100644 --- a/examples/clock_sctk_layer_surface/src/main.rs +++ b/examples/clock_sctk_layer_surface/src/main.rs @@ -18,7 +18,6 @@ pub fn main() -> iced::Result { antialiasing: true, initial_surface: InitialSurface::LayerSurface( SctkLayerSurfaceSettings { - size: (None, Some(200)), anchor: Anchor::LEFT.union(Anchor::RIGHT).union(Anchor::TOP), exclusive_zone: 200, ..Default::default() @@ -59,8 +58,8 @@ impl Application for Clock { get_layer_surface(SctkLayerSurfaceSettings { // XXX id must be unique! id: to_destroy, - size: (None, Some(100)), - anchor: Anchor::LEFT.union(Anchor::RIGHT).union(Anchor::BOTTOM), + size: None, + anchor: Anchor::BOTTOM, exclusive_zone: 100, ..Default::default() }), @@ -74,22 +73,22 @@ impl Application for Clock { fn update(&mut self, message: Message) -> Command { match message { Message::Tick(local_time) => { - let now = local_time; - - if now != self.now { - self.now = now; - self.clock.clear(); - } - // destroy the second layer surface after counting to 10. - self.count += 1; - if self.count == 10 { - println!("time to remove the bottom clock!"); - return set_size::( - self.to_destroy, - None, - Some(200), - ); - } + // let now = local_time; + + // if now != self.now { + // self.now = now; + // self.clock.clear(); + // } + // // destroy the second layer surface after counting to 10. + // self.count += 1; + // if self.count == 10 { + // println!("time to remove the bottom clock!"); + // return set_size::( + // self.to_destroy, + // None, + // Some(200), + // ); + // } } } @@ -109,13 +108,9 @@ impl Application for Clock { &self, _id: SurfaceIdWrapper, ) -> Element<'_, Self::Message, iced::Renderer> { - let canvas = canvas(self as &Self) - .width(Length::Fill) - .height(Length::Fill); + let canvas = canvas(self as &Self).height(Length::Units(200)).width(Length::Units(200)); container(canvas) - .width(Length::Fill) - .height(Length::Fill) .padding(20) .into() } diff --git a/examples/clock_sctk_window/src/main.rs b/examples/clock_sctk_window/src/main.rs index 696ef78a23..557eb9d497 100644 --- a/examples/clock_sctk_window/src/main.rs +++ b/examples/clock_sctk_window/src/main.rs @@ -22,7 +22,10 @@ pub fn main() -> iced::Result { Clock::run(Settings { antialiasing: true, initial_surface: InitialSurface::XdgWindow( - SctkWindowSettings::default(), + SctkWindowSettings { + autosize: true, + ..Default::default() + }, ), ..Settings::default() }) @@ -107,6 +110,7 @@ impl Application for Clock { width: 160, height: 260, }, + // size: Some((100, 200)), ..Default::default() }, parent_size: None, @@ -139,24 +143,21 @@ impl Application for Clock { SurfaceIdWrapper::LayerSurface(_) => unimplemented!(), SurfaceIdWrapper::Window(_) => { let canvas = canvas(self as &Self) - .width(Length::Fill) - .height(Length::Fill); + .width(Length::Units(100)) + .height(Length::Units(100)); container(column![ button("Popup").on_press(Message::Click(id.inner())), canvas, ]) - .width(Length::Fill) - .height(Length::Fill) .padding(20) .into() } - SurfaceIdWrapper::Popup(_) => button("Hi!, I'm a popup!") + SurfaceIdWrapper::Popup(_) => { + button("Hi!, I'm a popup!") .on_press(Message::Click(id.inner())) - .width(Length::Fill) - .height(Length::Fill) .padding(20) - .into(), + .into()}, } } diff --git a/native/src/command/platform_specific/wayland/layer_surface.rs b/native/src/command/platform_specific/wayland/layer_surface.rs index 0b07602b6f..7a34b5d138 100644 --- a/native/src/command/platform_specific/wayland/layer_surface.rs +++ b/native/src/command/platform_specific/wayland/layer_surface.rs @@ -8,6 +8,7 @@ use sctk::{ shell::layer::{Anchor, KeyboardInteractivity, Layer}, }; +use crate::layout::Limits; use crate::window; /// output for layer surface @@ -49,7 +50,7 @@ pub struct SctkLayerSurfaceSettings { pub layer: Layer, /// interactivity pub keyboard_interactivity: KeyboardInteractivity, - /// anchor + /// anchor, if a surface is anchored to two opposite edges, it will be stretched to fit between those edges, regardless of the specified size in that dimension. pub anchor: Anchor, /// output pub output: IcedOutput, @@ -57,10 +58,13 @@ pub struct SctkLayerSurfaceSettings { pub namespace: String, /// margin pub margin: IcedMargin, - /// size, None in a given dimension lets the compositor decide, usually this would be done with a layer surface that is anchored to left & right or top & bottom - pub size: (Option, Option), + /// XXX size, providing None will autosize the layer surface to its contents + /// If Some size is provided, None in a given dimension lets the compositor decide for that dimension, usually this would be done with a layer surface that is anchored to left & right or top & bottom + pub size: Option<(Option, Option)>, /// exclusive zone pub exclusive_zone: i32, + /// Limits of the popup size + pub size_limits: Limits, } impl Default for SctkLayerSurfaceSettings { @@ -73,8 +77,9 @@ impl Default for SctkLayerSurfaceSettings { output: Default::default(), namespace: Default::default(), margin: Default::default(), - size: (Some(200), Some(200)), + size: Default::default(), exclusive_zone: Default::default(), + size_limits: Limits::NONE, } } } diff --git a/native/src/command/platform_specific/wayland/popup.rs b/native/src/command/platform_specific/wayland/popup.rs index 837920cee3..50641b69e1 100644 --- a/native/src/command/platform_specific/wayland/popup.rs +++ b/native/src/command/platform_specific/wayland/popup.rs @@ -8,6 +8,7 @@ use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{ Anchor, Gravity, }; +use crate::layout::Limits; use crate::window; /// Popup creation details #[derive(Debug, Clone)] @@ -31,10 +32,12 @@ impl Hash for SctkPopupSettings { } /// Positioner of a popup -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct SctkPositioner { - /// size of the popup - pub size: (u32, u32), + /// size of the popup (if it is None, the popup will be autosized) + pub size: Option<(u32, u32)>, + /// Limits of the popup size + pub size_limits: Limits, /// the rectangle which the popup will be anchored to pub anchor_rect: Rectangle, /// the anchor location on the popup @@ -73,7 +76,8 @@ impl Hash for SctkPositioner { impl Default for SctkPositioner { fn default() -> Self { Self { - size: (200, 100), + size: None, + size_limits: Limits::NONE, anchor_rect: Rectangle { x: 0, y: 0, @@ -82,7 +86,7 @@ impl Default for SctkPositioner { }, anchor: Anchor::None, gravity: Gravity::None, - constraint_adjustment: Default::default(), + constraint_adjustment: 15, offset: Default::default(), reactive: true, } diff --git a/native/src/command/platform_specific/wayland/window.rs b/native/src/command/platform_specific/wayland/window.rs index 532589c160..c88709ab98 100644 --- a/native/src/command/platform_specific/wayland/window.rs +++ b/native/src/command/platform_specific/wayland/window.rs @@ -5,6 +5,7 @@ use std::{collections::hash_map::DefaultHasher, fmt}; use iced_futures::MaybeSend; use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge; +use crate::layout::Limits; use crate::window; /// window settings @@ -20,6 +21,10 @@ pub struct SctkWindowSettings { pub title: Option, /// optional window parent pub parent: Option, + /// autosize the window to fit its contents + pub autosize: bool, + /// Limits of the popup size + pub size_limits: Limits, } impl Default for SctkWindowSettings { @@ -30,6 +35,8 @@ impl Default for SctkWindowSettings { app_id: Default::default(), title: Default::default(), parent: Default::default(), + autosize: Default::default(), + size_limits: Limits::NONE, } } } diff --git a/sctk/src/application.rs b/sctk/src/application.rs index 60efde2dd4..8be9ece1a5 100644 --- a/sctk/src/application.rs +++ b/sctk/src/application.rs @@ -18,10 +18,10 @@ use futures::{channel::mpsc, task, Future, FutureExt, StreamExt}; use iced_native::{ application::{self, StyleSheet}, clipboard::{self, Null}, - command::platform_specific, + command::platform_specific::{self, wayland::popup}, mouse::{self, Interaction}, widget::operation, - Element, Renderer, + Element, Renderer, Widget, }; use sctk::{ @@ -34,7 +34,7 @@ use std::{ }; use wayland_backend::client::ObjectId; -use glutin::{api::egl, prelude::*, surface::WindowSurface}; +use glutin::{api::egl, prelude::*, surface::WindowSurface, platform}; use iced_graphics::{compositor, renderer, window, Color, Point, Viewport}; use iced_native::user_interface::{self, UserInterface}; use iced_native::window::Id as SurfaceId; @@ -54,7 +54,8 @@ pub enum Event { Window(platform_specific::wayland::window::Action), /// popup requests from the client Popup(platform_specific::wayland::popup::Action), - + /// init size of surface + InitSurfaceSize(SurfaceIdWrapper, (u32, u32)), /// request sctk to set the cursor of the active pointer SetCursor(Interaction), } @@ -169,7 +170,7 @@ where /// Runs an [`Application`] with an executor, compositor, and the provided /// settings. pub fn run( - settings: settings::Settings, + mut settings: settings::Settings, compositor_settings: C::Settings, ) -> Result<(), error::Error> where @@ -189,25 +190,6 @@ where let mut event_loop = SctkEventLoop::::new(&settings) .expect("Failed to initialize the event loop"); - let (object_id, native_id, wl_surface) = match &settings.surface { - settings::InitialSurface::LayerSurface(l) => { - // TODO ASHLEY should an application panic if it's initial surface can't be created? - let (native_id, surface) = - event_loop.get_layer_surface(l.clone()).unwrap(); - ( - surface.id(), - SurfaceIdWrapper::LayerSurface(native_id), - surface, - ) - } - settings::InitialSurface::XdgWindow(w) => { - let (native_id, surface) = event_loop.get_window(w.clone()); - (surface.id(), SurfaceIdWrapper::Window(native_id), surface) - } - }; - - let surface_ids = HashMap::from([(object_id.clone(), native_id)]); - let (runtime, ev_proxy) = { let ev_proxy = event_loop.proxy(); let executor = E::new().map_err(Error::ExecutorCreationFailed)?; @@ -220,12 +202,10 @@ where runtime.enter(|| A::new(flags)) }; - - let (display, context, config, surface) = init_egl(&wl_surface, 100, 100); + let wl_surface = event_loop.state.compositor_state.create_surface(&event_loop.state.queue_handle); + let (display, context, config, surface) = init_egl(&wl_surface, 1, 1); let context = context.make_current(&surface).unwrap(); - let egl_surfaces = HashMap::from([(native_id.inner(), surface)]); - #[allow(unsafe_code)] let (compositor, renderer) = unsafe { C::new(compositor_settings, |name| { @@ -233,6 +213,47 @@ where display.get_proc_address(name.as_c_str()) })? }; + + let (object_id, native_id, wl_surface, (w, h)) = match &mut settings.surface { + settings::InitialSurface::LayerSurface(builder) => { + // TODO ASHLEY should an application panic if it's initial surface can't be created? + if builder.size.is_none() { + let e = application.view(SurfaceIdWrapper::LayerSurface(builder.id)); + let node = Widget::layout(e.as_widget(), &renderer, &builder.size_limits); + let bounds = node.bounds(); + builder.size = Some((Some(bounds.width as u32), Some(bounds.height as u32))); + }; + let (native_id, surface) = + event_loop.get_layer_surface(builder.clone()).unwrap(); + ( + surface.id(), + SurfaceIdWrapper::LayerSurface(native_id), + surface, + builder.size.map(|(w, h)| (w.unwrap_or_default().max(1), h.unwrap_or_default().max(1))).unwrap_or((1, 1)), + ) + } + settings::InitialSurface::XdgWindow(builder) => { + if builder.autosize { + let e = application.view(SurfaceIdWrapper::Window(builder.window_id)); + let node = Widget::layout(e.as_widget(), &renderer, &builder.size_limits); + let bounds = node.bounds(); + builder.iced_settings.size = (bounds.width as u32, bounds.height as u32); + }; + let (native_id, surface) = event_loop.get_window(builder.clone()); + (surface.id(), SurfaceIdWrapper::Window(native_id), surface, builder.iced_settings.size) + } + }; + let surface = get_surface( + &display, + &config, + &wl_surface, + w, + h, + ); + + let surface_ids = HashMap::from([(object_id.clone(), native_id)]); + let egl_surfaces = HashMap::from([(native_id.inner(), surface)]); + let (mut sender, receiver) = mpsc::unbounded::>(); let mut instance = Box::pin(run_instance::( @@ -1223,21 +1244,51 @@ fn run_command( ), ), ) => { - proxy.send_event(Event::LayerSurface(layer_surface_action)); + if let platform_specific::wayland::layer_surface::Action::LayerSurface{ mut builder, _phantom } = layer_surface_action { + if builder.size.is_none() { + let e = application.view(SurfaceIdWrapper::LayerSurface(builder.id)); + let node = Widget::layout(e.as_widget(), renderer, &builder.size_limits); + let bounds = node.bounds(); + builder.size = Some((Some(bounds.width as u32), Some(bounds.height as u32))); + } + proxy.send_event(Event::LayerSurface(platform_specific::wayland::layer_surface::Action::LayerSurface {builder, _phantom})); + } else { + proxy.send_event(Event::LayerSurface(layer_surface_action)); + } } command::Action::PlatformSpecific( platform_specific::Action::Wayland( platform_specific::wayland::Action::Window(window_action), ), ) => { - proxy.send_event(Event::Window(window_action)); + if let platform_specific::wayland::window::Action::Window{ mut builder, _phantom } = window_action { + if builder.autosize { + let e = application.view(SurfaceIdWrapper::Window(builder.window_id)); + let node = Widget::layout(e.as_widget(), renderer, &builder.size_limits); + let bounds = node.bounds(); + builder.iced_settings.size = (bounds.width as u32, bounds.height as u32); + } + proxy.send_event(Event::Window(platform_specific::wayland::window::Action::Window{builder, _phantom})); + } else { + proxy.send_event(Event::Window(window_action)); + } } command::Action::PlatformSpecific( platform_specific::Action::Wayland( platform_specific::wayland::Action::Popup(popup_action), ), ) => { - proxy.send_event(Event::Popup(popup_action)); + if let popup::Action::Popup { mut popup, _phantom } = popup_action { + if popup.positioner.size.is_none() { + let e = application.view(SurfaceIdWrapper::Popup(popup.id)); + let node = Widget::layout(e.as_widget(), renderer, &popup.positioner.size_limits); + let bounds = node.bounds(); + popup.positioner.size = Some((bounds.width as u32, bounds.height as u32)); + } + proxy.send_event(Event::Popup(popup::Action::Popup{popup, _phantom})); + } else { + proxy.send_event(Event::Popup(popup_action)); + } } _ => {} } diff --git a/sctk/src/event_loop/mod.rs b/sctk/src/event_loop/mod.rs index cc14e1a067..b8d2d7e1eb 100644 --- a/sctk/src/event_loop/mod.rs +++ b/sctk/src/event_loop/mod.rs @@ -720,14 +720,14 @@ where let sctk_popup = match self.state .popups .iter() - .position(|s| s.id == id) + .position(|s| s.data.id == id) { Some(p) => self.state.popups.remove(p), None => continue, }; let mut to_destroy = vec![sctk_popup]; while let Some(popup_to_destroy) = to_destroy.last() { - match popup_to_destroy.parent.clone() { + match popup_to_destroy.data.parent.clone() { state::SctkSurface::LayerSurface(_) | state::SctkSurface::Window(_) => { break; } @@ -746,8 +746,8 @@ where for popup in to_destroy.into_iter().rev() { sticky_exit_callback(IcedSctkEvent::SctkEvent(SctkEvent::PopupEvent { variant: PopupEventVariant::Done, - toplevel_id: popup.toplevel.clone(), - parent_id: popup.parent.wl_surface().clone(), + toplevel_id: popup.data.toplevel.clone(), + parent_id: popup.data.parent.wl_surface().clone(), id: popup.popup.wl_surface().clone(), }), &self.state, @@ -759,6 +759,7 @@ where platform_specific::wayland::popup::Action::Reposition { id, positioner } => todo!(), platform_specific::wayland::popup::Action::Grab { id } => todo!(), }, + Event::InitSurfaceSize(id, requested_size) => todo!(), } } diff --git a/sctk/src/event_loop/state.rs b/sctk/src/event_loop/state.rs index 159bc69a4b..61eeb9e2a5 100644 --- a/sctk/src/event_loop/state.rs +++ b/sctk/src/event_loop/state.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, fmt::Debug, sync::Arc}; use crate::{ application::Event, dpi::LogicalSize, - sctk_event::{SctkEvent, SurfaceCompositorUpdate, SurfaceUserRequest}, + sctk_event::{SctkEvent, SurfaceCompositorUpdate, SurfaceUserRequest}, commands::popup, }; use iced_native::{ @@ -16,7 +16,7 @@ use iced_native::{ }, }, keyboard::Modifiers, - window, + window, layout::Limits, }; use sctk::{ compositor::CompositorState, @@ -117,15 +117,21 @@ impl SctkSurface { #[derive(Debug, Clone)] pub struct SctkPopup { - pub(crate) id: iced_native::window::Id, pub(crate) popup: Popup, - pub(crate) parent: SctkSurface, - pub(crate) toplevel: WlSurface, - pub(crate) requested_size: (u32, u32), pub(crate) last_configure: Option, // pub(crate) positioner: XdgPositioner, pub(crate) pending_requests: Vec>, + pub(crate) data: SctkPopupData +} + +#[derive(Debug, Clone)] +pub struct SctkPopupData { + pub(crate) id: iced_native::window::Id, + pub(crate) parent: SctkSurface, + pub(crate) toplevel: WlSurface, + pub(crate) requested_size: (u32, u32), + pub(crate) limits: Limits, } /// Wrapper to carry sctk state. @@ -214,6 +220,10 @@ pub enum PopupCreationError { #[error("The specified parent is missing")] ParentMissing, + /// The specified size is missing + #[error("The specified size is missing")] + SizeMissing, + /// Popup creation failed #[error("Popup creation failed")] PopupCreationFailed(GlobalError), @@ -244,8 +254,38 @@ where settings: SctkPopupSettings, ) -> Result<(window::Id, WlSurface, WlSurface, WlSurface), PopupCreationError> { + let limits = settings.positioner.size_limits; + + let (parent, toplevel) = if let Some(parent) = + self.layer_surfaces.iter().find(|l| l.id == settings.parent) + { + (SctkSurface::LayerSurface( + parent.surface.wl_surface().clone(), + ), parent.surface.wl_surface().clone()) + } else if let Some(parent) = + self.windows.iter().find(|w| w.id == settings.parent) + { + (SctkSurface::Window(parent.window.wl_surface().clone()),parent.window.wl_surface().clone()) + } else if let Some(i) = + self.popups.iter().position(|p| p.data.id == settings.parent) + { + let parent = &self.popups[i]; + ( + SctkSurface::Popup(parent.popup.wl_surface().clone()), + parent.data.toplevel.clone(), + ) + } else { + return Err(PopupCreationError::ParentMissing); + }; + + let size = if settings.positioner.size.is_none() { + return Err(PopupCreationError::SizeMissing); + } else { + settings.positioner.size.unwrap() + }; + let positioner = XdgPositioner::new(&self.xdg_shell_state) - .map_err(|e| PopupCreationError::PositionerCreationFailed(e))?; + .map_err(|e| PopupCreationError::PositionerCreationFailed(e))?; positioner.set_anchor(settings.positioner.anchor); positioner.set_anchor_rect( settings.positioner.anchor_rect.x, @@ -265,99 +305,106 @@ where positioner.set_reactive(); } positioner.set_size( - settings.positioner.size.0 as i32, - settings.positioner.size.1 as i32, + size.0 as i32, + size.1 as i32, ); - if let Some(parent) = - self.layer_surfaces.iter().find(|l| l.id == settings.parent) - { - let wl_surface = - self.compositor_state.create_surface(&self.queue_handle); - let popup = Popup::from_surface( - None, - &positioner, - &self.queue_handle, - wl_surface.clone(), - &self.xdg_shell_state, - ) - .map_err(|e| PopupCreationError::PopupCreationFailed(e))?; - - parent.surface.get_popup(popup.xdg_popup()); - wl_surface.commit(); - self.popups.push(SctkPopup { - id: settings.id, - popup: popup.clone(), - parent: SctkSurface::LayerSurface( - parent.surface.wl_surface().clone(), - ), - toplevel: parent.surface.wl_surface().clone(), - requested_size: settings.positioner.size, - last_configure: None, - pending_requests: Default::default(), - }); - Ok(( - settings.id, - parent.surface.wl_surface().clone(), - parent.surface.wl_surface().clone(), - popup.wl_surface().clone(), - )) - } else if let Some(parent) = - self.windows.iter().find(|w| w.id == settings.parent) - { - let popup = Popup::new( - parent.window.xdg_surface(), - &positioner, - &self.queue_handle, - &self.compositor_state, - &self.xdg_shell_state, - ) - .map_err(|e| PopupCreationError::PopupCreationFailed(e))?; - self.popups.push(SctkPopup { - id: settings.id, - popup: popup.clone(), - parent: SctkSurface::Window(parent.window.wl_surface().clone()), - toplevel: parent.window.wl_surface().clone(), - requested_size: settings.positioner.size, - last_configure: None, - pending_requests: Default::default(), - }); - Ok(( - settings.id, - parent.window.wl_surface().clone(), - parent.window.wl_surface().clone(), - popup.wl_surface().clone(), - )) - } else if let Some(i) = - self.popups.iter().position(|p| p.id == settings.parent) - { - let (popup, parent, toplevel) = { - let parent = &self.popups[i]; - ( - Popup::new( - parent.popup.xdg_surface(), - &positioner, - &self.queue_handle, - &self.compositor_state, - &self.xdg_shell_state, - ) - .map_err(|e| PopupCreationError::PopupCreationFailed(e))?, - parent.popup.wl_surface().clone(), - parent.toplevel.clone(), + match parent { + SctkSurface::LayerSurface(parent) => { + let parent_layer_surface = self.layer_surfaces.iter().find(|w| w.surface.wl_surface() == &parent).unwrap(); + + let wl_surface = + self.compositor_state.create_surface(&self.queue_handle); + let popup = Popup::from_surface( + None, + &positioner, + &self.queue_handle, + wl_surface.clone(), + &self.xdg_shell_state, ) - }; - self.popups.push(SctkPopup { - id: settings.id, - popup: popup.clone(), - parent: SctkSurface::Popup(parent.clone()), - toplevel: toplevel.clone(), - requested_size: settings.positioner.size, - last_configure: None, - pending_requests: Default::default(), - }); - Ok((settings.id, parent, toplevel, popup.wl_surface().clone())) - } else { - Err(PopupCreationError::ParentMissing) + .map_err(|e| PopupCreationError::PopupCreationFailed(e))?; + + parent_layer_surface.surface.get_popup(popup.xdg_popup()); + wl_surface.commit(); + self.popups.push(SctkPopup { + data: SctkPopupData { + id: settings.id, + parent: SctkSurface::LayerSurface( + parent.clone(), + ), + toplevel: parent.clone(), + requested_size: size, + limits, + }, + popup: popup.clone(), + last_configure: None, + pending_requests: Default::default(), + }); + Ok(( + settings.id, + parent.clone(), + parent.clone(), + wl_surface.clone(), + )) + }, + SctkSurface::Window(parent) => { + let parent_window = self.windows.iter().find(|w| w.window.wl_surface() == &parent).unwrap(); + let popup = Popup::new( + parent_window.window.xdg_surface(), + &positioner, + &self.queue_handle, + &self.compositor_state, + &self.xdg_shell_state, + ) + .map_err(|e| PopupCreationError::PopupCreationFailed(e))?; + self.popups.push(SctkPopup { + popup: popup.clone(), + data: SctkPopupData { + id: settings.id, + parent: SctkSurface::Window(parent.clone()), + toplevel: parent.clone(), + requested_size: size, + limits + }, + last_configure: None, + pending_requests: Default::default(), + }); + Ok(( + settings.id, + parent.clone(), + parent.clone(), + popup.wl_surface().clone(), + )) + }, + SctkSurface::Popup(parent) => { + let parent_xdg = self.windows.iter().find_map(|w| if w.window.wl_surface() == &parent { + Some(w.window.xdg_surface()) + } else { + None + }).unwrap(); + + let popup = Popup::new( + parent_xdg, + &positioner, + &self.queue_handle, + &self.compositor_state, + &self.xdg_shell_state, + ) + .map_err(|e| PopupCreationError::PopupCreationFailed(e))?; + self.popups.push(SctkPopup { + popup: popup.clone(), + data: SctkPopupData { + id: settings.id, + parent: SctkSurface::Popup(parent.clone()), + toplevel: toplevel.clone(), + requested_size: size, + limits, + }, + last_configure: None, + pending_requests: Default::default(), + }); + Ok((settings.id, parent, toplevel, popup.wl_surface().clone())) + }, } } @@ -380,6 +427,7 @@ where app_id, title, parent, + .. } = settings; // TODO Ashley: set window as opaque if transparency is false // TODO Ashley: set icon @@ -426,7 +474,6 @@ where wl_surface.clone(), ) .expect("failed to create window"); - window.xdg_surface().set_window_geometry( 0, 0, @@ -456,8 +503,7 @@ where namespace, margin, size, - exclusive_zone, - }: SctkLayerSurfaceSettings, + exclusive_zone, .. }: SctkLayerSurfaceSettings, ) -> Result<(iced_native::window::Id, WlSurface), LayerSurfaceCreationError> { let wl_output = match output { @@ -472,7 +518,13 @@ where .ok_or(LayerSurfaceCreationError::LayerShellNotSupported)?; let wl_surface = self.compositor_state.create_surface(&self.queue_handle); - + let mut size = size.unwrap(); + if anchor.contains(Anchor::BOTTOM.union(Anchor::TOP)) { + size.1 = None; + } + if anchor.contains(Anchor::LEFT.union(Anchor::RIGHT)) { + size.0 = None; + } let mut builder = LayerSurface::builder() .anchor(anchor) .keyboard_interactivity(keyboard_interactivity) @@ -491,7 +543,7 @@ where self.layer_surfaces.push(SctkLayerSurface { id, surface: layer_surface, - requested_size: (None, None), + requested_size: size, current_size: None, layer, // builder needs to be refactored such that these fields are accessible diff --git a/sctk/src/handlers/shell/layer.rs b/sctk/src/handlers/shell/layer.rs index 57a13fa960..90ee88c35e 100644 --- a/sctk/src/handlers/shell/layer.rs +++ b/sctk/src/handlers/shell/layer.rs @@ -46,17 +46,18 @@ impl LayerShellHandler for SctkState { Some(l) => l, None => return, }; - let id = layer.surface.wl_surface().id(); - configure.new_size.0 = if configure.new_size.0 > 0 { - configure.new_size.0 - } else { - layer.requested_size.0.unwrap_or(1) + + configure.new_size.0 = if let Some(w) = layer.requested_size.0 { + w + } else { + configure.new_size.0.max(1) }; - configure.new_size.1 = if configure.new_size.1 > 0 { - configure.new_size.1 - } else { - layer.requested_size.1.unwrap_or(1) + configure.new_size.1 = if let Some(h) = layer.requested_size.1 { + h + } else { + configure.new_size.1.max(1) }; + layer.current_size.replace(LogicalSize::new( configure.new_size.0, configure.new_size.1, diff --git a/sctk/src/handlers/shell/xdg_popup.rs b/sctk/src/handlers/shell/xdg_popup.rs index 5e30ba167f..d1521d5f0a 100644 --- a/sctk/src/handlers/shell/xdg_popup.rs +++ b/sctk/src/handlers/shell/xdg_popup.rs @@ -33,8 +33,8 @@ impl PopupHandler for SctkState { first, ), id: popup.wl_surface().clone(), - toplevel_id: sctk_popup.toplevel.clone(), - parent_id: match &sctk_popup.parent { + toplevel_id: sctk_popup.data.toplevel.clone(), + parent_id: match &sctk_popup.data.parent { SctkSurface::LayerSurface(s) => s.clone(), SctkSurface::Window(s) => s.clone(), SctkSurface::Popup(s) => s.clone(), @@ -56,7 +56,7 @@ impl PopupHandler for SctkState { }; let mut to_destroy = vec![sctk_popup]; while let Some(popup_to_destroy) = to_destroy.last() { - match popup_to_destroy.parent.clone() { + match popup_to_destroy.data.parent.clone() { state::SctkSurface::LayerSurface(_) | state::SctkSurface::Window(_) => { break; @@ -78,8 +78,8 @@ impl PopupHandler for SctkState { for popup in to_destroy.into_iter().rev() { self.sctk_events.push(SctkEvent::PopupEvent { variant: PopupEventVariant::Done, - toplevel_id: popup.toplevel.clone(), - parent_id: popup.parent.wl_surface().clone(), + toplevel_id: popup.data.toplevel.clone(), + parent_id: popup.data.parent.wl_surface().clone(), id: popup.popup.wl_surface().clone(), }); self.popups.push(popup); diff --git a/sctk/src/sctk_event.rs b/sctk/src/sctk_event.rs index dc8e82f9fb..b7a1efa5dc 100644 --- a/sctk/src/sctk_event.rs +++ b/sctk/src/sctk_event.rs @@ -204,6 +204,7 @@ pub enum WindowEventVariant { #[derive(Debug, Clone)] pub enum PopupEventVariant { + /// Popup Created Created(ObjectId, SurfaceId), /// Done, From 59c96d7b7216d8231a9277ce02c333a55927732f Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 27 Dec 2022 10:45:00 -0500 Subject: [PATCH 23/56] fix: make initial surface current --- sctk/src/application.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/sctk/src/application.rs b/sctk/src/application.rs index 8be9ece1a5..a3faaefad6 100644 --- a/sctk/src/application.rs +++ b/sctk/src/application.rs @@ -250,6 +250,7 @@ where w, h, ); + context.make_current(&surface).unwrap(); let surface_ids = HashMap::from([(object_id.clone(), native_id)]); let egl_surfaces = HashMap::from([(native_id.inner(), surface)]); From bed36f6f3be12f9839016eb30d415124428c4eba Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 27 Dec 2022 14:28:40 -0500 Subject: [PATCH 24/56] fix: applet feature --- native/src/command/platform_specific/wayland/layer_surface.rs | 2 +- native/src/command/platform_specific/wayland/popup.rs | 2 +- native/src/command/platform_specific/wayland/window.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/native/src/command/platform_specific/wayland/layer_surface.rs b/native/src/command/platform_specific/wayland/layer_surface.rs index 7a34b5d138..c8078ae3e8 100644 --- a/native/src/command/platform_specific/wayland/layer_surface.rs +++ b/native/src/command/platform_specific/wayland/layer_surface.rs @@ -79,7 +79,7 @@ impl Default for SctkLayerSurfaceSettings { margin: Default::default(), size: Default::default(), exclusive_zone: Default::default(), - size_limits: Limits::NONE, + size_limits: Limits::NONE.min_height(1).min_width(1).max_width(1920).max_height(1080), } } } diff --git a/native/src/command/platform_specific/wayland/popup.rs b/native/src/command/platform_specific/wayland/popup.rs index 50641b69e1..70f3dce494 100644 --- a/native/src/command/platform_specific/wayland/popup.rs +++ b/native/src/command/platform_specific/wayland/popup.rs @@ -77,7 +77,7 @@ impl Default for SctkPositioner { fn default() -> Self { Self { size: None, - size_limits: Limits::NONE, + size_limits: Limits::NONE.min_height(1).min_width(1).max_width(300).max_height(1080), anchor_rect: Rectangle { x: 0, y: 0, diff --git a/native/src/command/platform_specific/wayland/window.rs b/native/src/command/platform_specific/wayland/window.rs index c88709ab98..47a6d86fd7 100644 --- a/native/src/command/platform_specific/wayland/window.rs +++ b/native/src/command/platform_specific/wayland/window.rs @@ -36,7 +36,7 @@ impl Default for SctkWindowSettings { title: Default::default(), parent: Default::default(), autosize: Default::default(), - size_limits: Limits::NONE, + size_limits: Limits::NONE.min_height(1).min_width(1).max_width(1920).max_height(1080), } } } From a8382585a28399d879e0c64598fb956862623572 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 27 Dec 2022 16:33:17 -0500 Subject: [PATCH 25/56] fix: initialize state & diff to create actual element for Lazy widgets --- sctk/src/application.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/sctk/src/application.rs b/sctk/src/application.rs index a3faaefad6..6e43a9df4d 100644 --- a/sctk/src/application.rs +++ b/sctk/src/application.rs @@ -20,7 +20,7 @@ use iced_native::{ clipboard::{self, Null}, command::platform_specific::{self, wayland::popup}, mouse::{self, Interaction}, - widget::operation, + widget::{operation, Tree}, Element, Renderer, Widget, }; @@ -219,6 +219,8 @@ where // TODO ASHLEY should an application panic if it's initial surface can't be created? if builder.size.is_none() { let e = application.view(SurfaceIdWrapper::LayerSurface(builder.id)); + let _state = Widget::state(e.as_widget()); + e.as_widget().diff(&mut Tree::empty()); let node = Widget::layout(e.as_widget(), &renderer, &builder.size_limits); let bounds = node.bounds(); builder.size = Some((Some(bounds.width as u32), Some(bounds.height as u32))); @@ -235,6 +237,8 @@ where settings::InitialSurface::XdgWindow(builder) => { if builder.autosize { let e = application.view(SurfaceIdWrapper::Window(builder.window_id)); + let _state = Widget::state(e.as_widget()); + e.as_widget().diff(&mut Tree::empty()); let node = Widget::layout(e.as_widget(), &renderer, &builder.size_limits); let bounds = node.bounds(); builder.iced_settings.size = (bounds.width as u32, bounds.height as u32); @@ -1248,6 +1252,8 @@ fn run_command( if let platform_specific::wayland::layer_surface::Action::LayerSurface{ mut builder, _phantom } = layer_surface_action { if builder.size.is_none() { let e = application.view(SurfaceIdWrapper::LayerSurface(builder.id)); + let _state = Widget::state(e.as_widget()); + e.as_widget().diff(&mut Tree::empty()); let node = Widget::layout(e.as_widget(), renderer, &builder.size_limits); let bounds = node.bounds(); builder.size = Some((Some(bounds.width as u32), Some(bounds.height as u32))); @@ -1265,6 +1271,8 @@ fn run_command( if let platform_specific::wayland::window::Action::Window{ mut builder, _phantom } = window_action { if builder.autosize { let e = application.view(SurfaceIdWrapper::Window(builder.window_id)); + let _state = Widget::state(e.as_widget()); + e.as_widget().diff(&mut Tree::empty()); let node = Widget::layout(e.as_widget(), renderer, &builder.size_limits); let bounds = node.bounds(); builder.iced_settings.size = (bounds.width as u32, bounds.height as u32); @@ -1282,6 +1290,8 @@ fn run_command( if let popup::Action::Popup { mut popup, _phantom } = popup_action { if popup.positioner.size.is_none() { let e = application.view(SurfaceIdWrapper::Popup(popup.id)); + let _state = Widget::state(e.as_widget()); + e.as_widget().diff(&mut Tree::empty()); let node = Widget::layout(e.as_widget(), renderer, &popup.positioner.size_limits); let bounds = node.bounds(); popup.positioner.size = Some((bounds.width as u32, bounds.height as u32)); From dd72d66c8f55290d9359bcca2be76b4a9a11b28c Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 28 Dec 2022 09:44:01 -0500 Subject: [PATCH 26/56] feat: resize auto-resized surfaces if layout bounds change after creation --- examples/clock_sctk_window/src/main.rs | 4 +- .../platform_specific/wayland/popup.rs | 26 ++--- sctk/src/application.rs | 107 ++++++++++++++++-- sctk/src/commands/popup.rs | 9 +- sctk/src/event_loop/mod.rs | 21 +++- sctk/src/sctk_event.rs | 5 +- 6 files changed, 144 insertions(+), 28 deletions(-) diff --git a/examples/clock_sctk_window/src/main.rs b/examples/clock_sctk_window/src/main.rs index 557eb9d497..5dc8b5bf6c 100644 --- a/examples/clock_sctk_window/src/main.rs +++ b/examples/clock_sctk_window/src/main.rs @@ -7,7 +7,7 @@ use iced::wayland::{ use iced::widget::canvas::{ stroke, Cache, Cursor, Geometry, LineCap, Path, Stroke, }; -use iced::widget::{button, canvas, column, container}; +use iced::widget::{button, canvas, column, container, text}; use iced::{ sctk_settings::InitialSurface, Application, Color, Command, Element, Length, Point, Rectangle, Settings, Subscription, Theme, Vector, @@ -154,7 +154,7 @@ impl Application for Clock { .into() } SurfaceIdWrapper::Popup(_) => { - button("Hi!, I'm a popup!") + button(text(format!("{}", self.count))) .on_press(Message::Click(id.inner())) .padding(20) .into()}, diff --git a/native/src/command/platform_specific/wayland/popup.rs b/native/src/command/platform_specific/wayland/popup.rs index 70f3dce494..d17c78da00 100644 --- a/native/src/command/platform_specific/wayland/popup.rs +++ b/native/src/command/platform_specific/wayland/popup.rs @@ -108,18 +108,20 @@ pub enum Action { /// id of the popup id: window::Id, }, - /// request that the popup be repositioned - Reposition { - /// id of the popup - id: window::Id, - /// the positioner - positioner: SctkPositioner, - }, /// request that the popup make an explicit grab Grab { /// id of the popup id: window::Id, }, + /// set the size of the popup + Size { + /// id of the popup + id: window::Id, + /// width + width: u32, + /// height + height: u32, + } } impl Action { @@ -137,10 +139,8 @@ impl Action { _phantom: PhantomData::default(), }, Action::Destroy { id } => Action::Destroy { id }, - Action::Reposition { id, positioner } => { - Action::Reposition { id, positioner } - } Action::Grab { id } => Action::Grab { id }, + Action::Size { id, width, height } => Action::Size { id, width, height }, } } } @@ -158,10 +158,10 @@ impl fmt::Debug for Action { "Action::PopupAction::Destroy {{ id: {:?} }}", id ), - Action::Reposition { id, positioner } => write!( + Action::Size { id, width, height } => write!( f, - "Action::PopupAction::Reposition {{ id: {:?}, positioner: {:?} }}", - id, positioner + "Action::PopupAction::Size {{ id: {:?}, width: {:?}, height: {:?} }}", + id, width, height ), Action::Grab { id } => write!( f, diff --git a/sctk/src/application.rs b/sctk/src/application.rs index 6e43a9df4d..49d42289fa 100644 --- a/sctk/src/application.rs +++ b/sctk/src/application.rs @@ -21,7 +21,7 @@ use iced_native::{ command::platform_specific::{self, wayland::popup}, mouse::{self, Interaction}, widget::{operation, Tree}, - Element, Renderer, Widget, + Element, Renderer, Widget, layout::Limits, }; use sctk::{ @@ -30,7 +30,7 @@ use sctk::{ }; use std::{ collections::HashMap, ffi::CString, fmt, marker::PhantomData, - num::NonZeroU32, + num::NonZeroU32, hash::Hash, }; use wayland_backend::client::ObjectId; @@ -214,6 +214,8 @@ where })? }; + let mut auto_size_surfaces = HashMap::new(); + let (object_id, native_id, wl_surface, (w, h)) = match &mut settings.surface { settings::InitialSurface::LayerSurface(builder) => { // TODO ASHLEY should an application panic if it's initial surface can't be created? @@ -223,7 +225,10 @@ where e.as_widget().diff(&mut Tree::empty()); let node = Widget::layout(e.as_widget(), &renderer, &builder.size_limits); let bounds = node.bounds(); - builder.size = Some((Some(bounds.width as u32), Some(bounds.height as u32))); + let w = bounds.width as u32; + let h = bounds.height as u32; + builder.size = Some((Some(w), Some(h))); + auto_size_surfaces.insert(SurfaceIdWrapper::LayerSurface(builder.id), (w, h, builder.size_limits, false)); }; let (native_id, surface) = event_loop.get_layer_surface(builder.clone()).unwrap(); @@ -241,7 +246,11 @@ where e.as_widget().diff(&mut Tree::empty()); let node = Widget::layout(e.as_widget(), &renderer, &builder.size_limits); let bounds = node.bounds(); - builder.iced_settings.size = (bounds.width as u32, bounds.height as u32); + let w = bounds.width as u32; + let h = bounds.height as u32; + builder.iced_settings.size = (w, h); + auto_size_surfaces.insert(SurfaceIdWrapper::LayerSurface(builder.window_id), (w, h, builder.size_limits, false)); + }; let (native_id, surface) = event_loop.get_window(builder.clone()); (surface.id(), SurfaceIdWrapper::Window(native_id), surface, builder.iced_settings.size) @@ -271,6 +280,7 @@ where receiver, egl_surfaces, surface_ids, + auto_size_surfaces, display, context, config, @@ -327,6 +337,7 @@ async fn run_instance( glutin::api::egl::surface::Surface, >, mut surface_ids: HashMap, + mut auto_size_surfaces: HashMap, mut egl_display: egl::display::Display, mut egl_context: egl::context::PossiblyCurrentContext, mut egl_config: glutin::api::egl::config::Config, @@ -352,6 +363,7 @@ where state.logical_size(), &mut debug, init_id, + &mut auto_size_surfaces ); let mut states = HashMap::from([(init_id_inner, state)]); let mut interfaces = @@ -370,6 +382,7 @@ where &mut ev_proxy, &mut debug, || compositor.fetch_information(), + &mut auto_size_surfaces, ); } runtime.track(application.subscription().map(subscription_map::)); @@ -493,6 +506,7 @@ where state.logical_size(), &mut debug, *id, + &mut auto_size_surfaces ); states.insert(id.inner(), state); interfaces.insert(id.inner(), user_interface); @@ -539,6 +553,7 @@ where state.logical_size(), &mut debug, *id, + &mut auto_size_surfaces ); states.insert(id.inner(), state); interfaces.insert(id.inner(), user_interface); @@ -591,6 +606,7 @@ where state.logical_size(), &mut debug, *id, + &mut auto_size_surfaces ); states.insert(id.inner(), state); interfaces.insert(id.inner(), user_interface); @@ -604,6 +620,16 @@ where } } PopupEventVariant::RepositionionedPopup { .. } => {} + PopupEventVariant::Size(width, height) => { + if let Some(id) = surface_ids.get(&id.id()) { + if let Some(state) = states.get_mut(&id.inner()) { + state.set_logical_size( + width as f64, + height as f64, + ); + } + } + }, }, // TODO forward these events to an application which requests them? SctkEvent::NewOutput { id, info } => { @@ -647,6 +673,7 @@ where &mut debug, &mut messages, || compositor.fetch_information(), + &mut auto_size_surfaces ); interfaces = ManuallyDrop::new(build_user_interfaces( @@ -655,6 +682,7 @@ where &mut debug, &states, pure_states, + &mut auto_size_surfaces )); if application.should_exit() { @@ -799,6 +827,7 @@ where &mut debug, &mut messages, || compositor.fetch_information(), + &mut auto_size_surfaces, ); // Update state @@ -814,6 +843,7 @@ where &mut debug, &states, pure_states, + &mut auto_size_surfaces )); } } @@ -833,6 +863,38 @@ where let state = states.get_mut(&id.inner()); (*id, surface, interface, state) }) { + if let Some((w, h, limits, dirty)) = auto_size_surfaces.remove(&native_id) { + if dirty { + match native_id { + SurfaceIdWrapper::LayerSurface(inner) => { + ev_proxy.send_event( + Event::LayerSurface( + iced_native::command::platform_specific::wayland::layer_surface::Action::Size { id: inner, width: Some(w), height: Some(h) }, + ) + ); + }, + SurfaceIdWrapper::Window(inner) => { + ev_proxy.send_event( + Event::Window( + iced_native::command::platform_specific::wayland::window::Action::Size { id: inner, width: w, height: h }, + ) + ); + }, + SurfaceIdWrapper::Popup(inner) => { + ev_proxy.send_event( + Event::Popup( + iced_native::command::platform_specific::wayland::popup::Action::Size { id: inner, width: w, height: h }, + ) + ); + }, + }; + interfaces.insert(native_id.inner(), user_interface); + auto_size_surfaces.insert(native_id, (w, h, limits, false)); + continue; + } else { + auto_size_surfaces.insert(native_id, (w, h, limits, false)); + } + } debug.render_started(); if current_context_window != native_id.inner() { @@ -850,10 +912,11 @@ where let logical_size = state.logical_size(); debug.layout_started(); - user_interface = user_interface - .relayout(logical_size, &mut renderer); + user_interface = user_interface.relayout(logical_size, &mut renderer); debug.layout_finished(); + + debug.draw_started(); let new_mouse_interaction = user_interface.draw( &mut renderer, @@ -903,7 +966,7 @@ where Ok(()) } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum SurfaceIdWrapper { LayerSurface(SurfaceId), Window(SurfaceId), @@ -929,6 +992,7 @@ pub fn build_user_interface<'a, A: Application>( size: Size, debug: &mut Debug, id: SurfaceIdWrapper, + auto_size_surfaces: &mut HashMap, ) -> UserInterface<'a, A::Message, A::Renderer> where ::Theme: StyleSheet, @@ -937,6 +1001,19 @@ where let view = application.view(id); debug.view_finished(); + let size = if let Some((prev_w, prev_h, limits, dirty)) = auto_size_surfaces.remove(&id) { + let view = view.as_widget(); + let _state = view.state(); + let _ = view.diff(&mut Tree::empty()); + let bounds = view.layout(renderer, &limits).bounds().size(); + let (w, h) = (bounds.width as u32, bounds.height as u32); + let dirty = dirty || w != prev_w || h != prev_h; + auto_size_surfaces.insert(id, (w, h, limits, dirty)); + Size::new(w as f32, h as f32) + } else { + size + }; + debug.layout_started(); let user_interface = UserInterface::build(view, size, cache, renderer); debug.layout_finished(); @@ -1113,6 +1190,7 @@ pub(crate) fn update( debug: &mut Debug, messages: &mut Vec, graphics_info: impl FnOnce() -> compositor::Information + Copy, + auto_size_surfaces: &mut HashMap, ) where A: Application + 'static, E: Executor + 'static, @@ -1136,6 +1214,7 @@ pub(crate) fn update( proxy, debug, graphics_info, + auto_size_surfaces ); } @@ -1157,6 +1236,7 @@ fn run_command( proxy: &mut proxy::Proxy>, debug: &mut Debug, _graphics_info: impl FnOnce() -> compositor::Information + Copy, + auto_size_surfaces: &mut HashMap, ) where A: Application, E: Executor, @@ -1221,6 +1301,7 @@ fn run_command( state.logical_size(), debug, id.clone(), // TODO: run the operation on every widget tree + auto_size_surfaces ); while let Some(mut operation) = current_operation.take() { @@ -1256,7 +1337,11 @@ fn run_command( e.as_widget().diff(&mut Tree::empty()); let node = Widget::layout(e.as_widget(), renderer, &builder.size_limits); let bounds = node.bounds(); + let w = bounds.width as u32; + let h = bounds.height as u32; + auto_size_surfaces.insert(SurfaceIdWrapper::LayerSurface(builder.id), (w, h, builder.size_limits, false)); builder.size = Some((Some(bounds.width as u32), Some(bounds.height as u32))); + } proxy.send_event(Event::LayerSurface(platform_specific::wayland::layer_surface::Action::LayerSurface {builder, _phantom})); } else { @@ -1275,6 +1360,9 @@ fn run_command( e.as_widget().diff(&mut Tree::empty()); let node = Widget::layout(e.as_widget(), renderer, &builder.size_limits); let bounds = node.bounds(); + let w = bounds.width as u32; + let h = bounds.height as u32; + auto_size_surfaces.insert(SurfaceIdWrapper::Window(builder.window_id), (w, h, builder.size_limits, false)); builder.iced_settings.size = (bounds.width as u32, bounds.height as u32); } proxy.send_event(Event::Window(platform_specific::wayland::window::Action::Window{builder, _phantom})); @@ -1294,6 +1382,9 @@ fn run_command( e.as_widget().diff(&mut Tree::empty()); let node = Widget::layout(e.as_widget(), renderer, &popup.positioner.size_limits); let bounds = node.bounds(); + let w = bounds.width as u32; + let h = bounds.height as u32; + auto_size_surfaces.insert(SurfaceIdWrapper::Popup(popup.id), (w, h, popup.positioner.size_limits, false)); popup.positioner.size = Some((bounds.width as u32, bounds.height as u32)); } proxy.send_event(Event::Popup(popup::Action::Popup{popup, _phantom})); @@ -1312,6 +1403,7 @@ pub fn build_user_interfaces<'a, A>( debug: &mut Debug, states: &HashMap>, mut pure_states: HashMap, + auto_size_surfaces: &mut HashMap, ) -> HashMap< SurfaceId, UserInterface< @@ -1336,6 +1428,7 @@ where state.logical_size(), debug, state.id, + auto_size_surfaces, ); let _ = interfaces.insert(id, user_interface); diff --git a/sctk/src/commands/popup.rs b/sctk/src/commands/popup.rs index 3f21a1a00f..cc7a4f02d6 100644 --- a/sctk/src/commands/popup.rs +++ b/sctk/src/commands/popup.rs @@ -5,7 +5,7 @@ use iced_native::command::{ self, wayland::{ self, - popup::{SctkPopupSettings, SctkPositioner}, + popup::SctkPopupSettings, }, }, }; @@ -27,13 +27,14 @@ pub fn get_popup(popup: SctkPopupSettings) -> Command { } /// -pub fn reposition_popup( +pub fn set_size( id: SurfaceId, - positioner: SctkPositioner, + width: u32, + height: u32, ) -> Command { Command::single(command::Action::PlatformSpecific( platform_specific::Action::Wayland(wayland::Action::Popup( - wayland::popup::Action::Reposition { id, positioner }, + wayland::popup::Action::Size { id, width, height }, )), )) } diff --git a/sctk/src/event_loop/mod.rs b/sctk/src/event_loop/mod.rs index b8d2d7e1eb..0882563ec2 100644 --- a/sctk/src/event_loop/mod.rs +++ b/sctk/src/event_loop/mod.rs @@ -756,7 +756,26 @@ where ); } }, - platform_specific::wayland::popup::Action::Reposition { id, positioner } => todo!(), + platform_specific::wayland::popup::Action::Size { id, width, height } => { + if let Some(sctk_popup) = self.state + .popups + .iter() + .find(|s| s.data.id == id) + { + sctk_popup.popup.xdg_surface().set_window_geometry(0, 0, width as i32, height as i32); + sctk_popup.popup.wl_surface().commit(); + sticky_exit_callback(IcedSctkEvent::SctkEvent(SctkEvent::PopupEvent { + variant: PopupEventVariant::Size(width, height), + toplevel_id: sctk_popup.data.toplevel.clone(), + parent_id: sctk_popup.data.parent.wl_surface().clone(), + id: sctk_popup.popup.wl_surface().clone(), + }), + &self.state, + &mut control_flow, + &mut callback, + ); + } + }, platform_specific::wayland::popup::Action::Grab { id } => todo!(), }, Event::InitSurfaceSize(id, requested_size) => todo!(), diff --git a/sctk/src/sctk_event.rs b/sctk/src/sctk_event.rs index b7a1efa5dc..03f35ad6d5 100644 --- a/sctk/src/sctk_event.rs +++ b/sctk/src/sctk_event.rs @@ -216,6 +216,8 @@ pub enum PopupEventVariant { RepositionionedPopup { token: u32, }, + /// size + Size(u32, u32) } #[derive(Debug, Clone)] @@ -602,7 +604,8 @@ impl SctkEvent { PopupEventVariant::Configure(_, _, _) => Default::default(), // TODO PopupEventVariant::RepositionionedPopup { token } => { Default::default() - } // TODO + } + PopupEventVariant::Size(_, _) => Default::default(), // TODO } } SctkEvent::NewOutput { id, info } => { From 39dca51ae15aaabc270559a7fd6f57b9fa93802f Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 28 Dec 2022 10:31:59 -0500 Subject: [PATCH 27/56] fix: don't skip render after sending message for resize --- sctk/src/application.rs | 3 +-- sctk/src/event_loop/mod.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/sctk/src/application.rs b/sctk/src/application.rs index 49d42289fa..128ab983c3 100644 --- a/sctk/src/application.rs +++ b/sctk/src/application.rs @@ -888,9 +888,7 @@ where ); }, }; - interfaces.insert(native_id.inner(), user_interface); auto_size_surfaces.insert(native_id, (w, h, limits, false)); - continue; } else { auto_size_surfaces.insert(native_id, (w, h, limits, false)); } @@ -1004,6 +1002,7 @@ where let size = if let Some((prev_w, prev_h, limits, dirty)) = auto_size_surfaces.remove(&id) { let view = view.as_widget(); let _state = view.state(); + // TODO would it be ok to diff against the current cache? let _ = view.diff(&mut Tree::empty()); let bounds = view.layout(renderer, &limits).bounds().size(); let (w, h) = (bounds.width as u32, bounds.height as u32); diff --git a/sctk/src/event_loop/mod.rs b/sctk/src/event_loop/mod.rs index 0882563ec2..6a7d07a1bc 100644 --- a/sctk/src/event_loop/mod.rs +++ b/sctk/src/event_loop/mod.rs @@ -763,7 +763,7 @@ where .find(|s| s.data.id == id) { sctk_popup.popup.xdg_surface().set_window_geometry(0, 0, width as i32, height as i32); - sctk_popup.popup.wl_surface().commit(); + to_commit.insert(id, sctk_popup.popup.wl_surface().clone()); sticky_exit_callback(IcedSctkEvent::SctkEvent(SctkEvent::PopupEvent { variant: PopupEventVariant::Size(width, height), toplevel_id: sctk_popup.data.toplevel.clone(), From a1e5adc30dac3491f882df3eb000af56161ddc9a Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 29 Dec 2022 09:29:57 -0800 Subject: [PATCH 28/56] Update wayland-rs, sctk, glutin --- examples/clock_sctk_layer_surface/Cargo.toml | 2 +- examples/clock_sctk_window/Cargo.toml | 2 +- examples/todos_sctk/Cargo.toml | 2 +- native/Cargo.toml | 2 +- sctk/Cargo.toml | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/clock_sctk_layer_surface/Cargo.toml b/examples/clock_sctk_layer_surface/Cargo.toml index f1bb003e89..4c47b9d482 100644 --- a/examples/clock_sctk_layer_surface/Cargo.toml +++ b/examples/clock_sctk_layer_surface/Cargo.toml @@ -9,4 +9,4 @@ publish = false iced = { path = "../..", default-features = false, features = ["canvas", "tokio", "debug", "wayland"] } time = { version = "0.3.5", features = ["local-offset"] } iced_native = { path = "../../native" } -sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "73346019952f82ec7e4d4d15f5d66841b54e8b61" } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "a257bf7" } diff --git a/examples/clock_sctk_window/Cargo.toml b/examples/clock_sctk_window/Cargo.toml index a59dc554ca..bf9d3e49cf 100644 --- a/examples/clock_sctk_window/Cargo.toml +++ b/examples/clock_sctk_window/Cargo.toml @@ -9,4 +9,4 @@ publish = false iced = { path = "../..", default-features = false, features = ["canvas", "tokio", "debug", "wayland"] } time = { version = "0.3.5", features = ["local-offset"] } iced_native = { path = "../../native" } -sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "73346019952f82ec7e4d4d15f5d66841b54e8b61" } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "a257bf7" } diff --git a/examples/todos_sctk/Cargo.toml b/examples/todos_sctk/Cargo.toml index d7735ae5d4..0710e673c1 100644 --- a/examples/todos_sctk/Cargo.toml +++ b/examples/todos_sctk/Cargo.toml @@ -10,7 +10,7 @@ iced = { path = "../..", default-features=false, features = ["async-std", "wayla serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" once_cell = "1.15" -sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "73346019952f82ec7e4d4d15f5d66841b54e8b61" } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "a257bf7" } iced_native = { path = "../../native" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/native/Cargo.toml b/native/Cargo.toml index 42e82c57e6..22c48ad5c4 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -16,7 +16,7 @@ debug = [] twox-hash = { version = "1.5", default-features = false } unicode-segmentation = "1.6" num-traits = "0.2" -sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "73346019952f82ec7e4d4d15f5d66841b54e8b61", optional = true } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "a257bf7", optional = true } [dependencies.iced_core] version = "0.6" diff --git a/sctk/Cargo.toml b/sctk/Cargo.toml index 3c1ed58659..5267abe179 100644 --- a/sctk/Cargo.toml +++ b/sctk/Cargo.toml @@ -14,13 +14,13 @@ multi_window = [] [dependencies] log = "0.4" thiserror = "1.0" -sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "73346019952f82ec7e4d4d15f5d66841b54e8b61" } -glutin = "0.30.0-beta.3" +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "a257bf7" } +glutin = "0.30.0" glow = "0.11.2" raw-window-handle = "0.5.0" enum-repr = "0.2.6" futures = "0.3" -wayland-backend = {version = "=0.1.0-beta.14", features = ["client_system"]} +wayland-backend = {version = "0.1.0", features = ["client_system"]} [dependencies.iced_native] features = ["wayland"] From 81dae1f0a3c54731646afe45eb120e64130eddb3 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 3 Jan 2023 10:33:54 -0500 Subject: [PATCH 29/56] update sctk --- native/Cargo.toml | 2 +- sctk/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/native/Cargo.toml b/native/Cargo.toml index 22c48ad5c4..2206789b26 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -16,7 +16,7 @@ debug = [] twox-hash = { version = "1.5", default-features = false } unicode-segmentation = "1.6" num-traits = "0.2" -sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "a257bf7", optional = true } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "3776d4a", optional = true } [dependencies.iced_core] version = "0.6" diff --git a/sctk/Cargo.toml b/sctk/Cargo.toml index 5267abe179..802587cd81 100644 --- a/sctk/Cargo.toml +++ b/sctk/Cargo.toml @@ -14,7 +14,7 @@ multi_window = [] [dependencies] log = "0.4" thiserror = "1.0" -sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "a257bf7" } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "3776d4a" } glutin = "0.30.0" glow = "0.11.2" raw-window-handle = "0.5.0" From cf731afe88d89de0567c03ec19965b439f9e0c9b Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld <4404502+Drakulix@users.noreply.github.com> Date: Tue, 3 Jan 2023 21:44:03 +0100 Subject: [PATCH 30/56] features: Don't always build sctk (#17) --- Cargo.toml | 2 +- native/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c5dbdcef25..51115c0716 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ palette = ["iced_core/palette"] # Enables querying system information system = ["iced_winit/system"] # Enables wayland shell -wayland = ["iced_sctk", "iced_glow", "iced_native/sctk"] +wayland = ["iced_sctk", "iced_glow"] winit = ["iced_winit"] [badges] diff --git a/native/Cargo.toml b/native/Cargo.toml index 2206789b26..2555f2c1c2 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" repository = "https://github.com/iced-rs/iced" [features] -default = ["wayland"] +default = [] wayland = ["sctk"] debug = [] From 2e52613c21c6fba314c3a06bdf6e34eb10125eb7 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 3 Jan 2023 22:59:27 +0100 Subject: [PATCH 31/56] features: Explicit backend/renderer combinations --- Cargo.toml | 24 ++++++---- native/src/event.rs | 1 + src/clipboard.rs | 3 +- src/element.rs | 1 + src/error.rs | 8 ++-- src/executor.rs | 1 + src/keyboard.rs | 1 + src/lib.rs | 49 ++++++++------------ src/mouse.rs | 1 + src/overlay.rs | 10 ++++ src/settings.rs | 3 +- src/touch.rs | 1 + src/wayland/mod.rs | 4 +- src/widget.rs | 108 ++++++++++++++++++++++++++++++++++++++++++++ src/window.rs | 14 +++--- 15 files changed, 178 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 51115c0716..7a8f4610dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,16 +21,16 @@ svg = ["iced_wgpu?/svg", "iced_glow?/svg", "iced_swbuf?/svg"] canvas = ["iced_graphics/canvas"] # Enables the `QRCode` widget qr_code = ["iced_graphics/qr_code"] -# Enables the `iced_wgpu` renderer +# Enables the `iced_wgpu` renderer. Conflicts with `iced_glow` and `iced_swbuf` wgpu = ["iced_wgpu"] -# Enables the `iced_swbuf` renderer +# Enables the `iced_swbuf` renderer. Conflicts with `iced_wgpu` and `iced_glow` swbuf = ["iced_swbuf"] +# Enables the `iced_glow` renderer. Conflicts with `iced_wgpu` and `iced_swbuf` +glow = ["iced_glow"] # Enables using system fonts default_system_font = ["iced_wgpu?/default_system_font", "iced_glow?/default_system_font"] -# Enables the `iced_glow` renderer. Overrides `iced_wgpu` -glow = ["iced_glow", "iced_glutin"] # Enables a debug view in native platforms (press F12) -debug = ["iced_winit/debug"] +debug = ["iced_winit?/debug"] # Enables `tokio` as the `executor::Default` on native platforms tokio = ["iced_futures/tokio"] # Enables `async-std` as the `executor::Default` on native platforms @@ -40,9 +40,14 @@ smol = ["iced_futures/smol"] # Enables advanced color conversion via `palette` palette = ["iced_core/palette"] # Enables querying system information -system = ["iced_winit/system"] -# Enables wayland shell -wayland = ["iced_sctk", "iced_glow"] +system = ["iced_winit?/system"] +# Enables the glutin shell. Conflicts with `sctk` and `winit`. +# Forces the `glow` renderer, further conflicting with `wgpu` and `swbuf`. +glutin = ["iced_glutin", "iced_glow"] +# Enables the wayland shell. Conflicts with `winit` and `glutin`. +# Incompatible with the `swbuf` and `wgpu` renderer. +wayland = ["iced_sctk"] +# Enables the winit shell. Conflicts with `wayland` and `glutin`. winit = ["iced_winit"] [badges] @@ -102,3 +107,6 @@ opt-level = 3 overflow-checks = false strip = "debuginfo" +[patch."https://github.com/iced-rs/winit.git".winit] +git = "https://github.com/pop-os/winit.git" +branch = "iced" \ No newline at end of file diff --git a/native/src/event.rs b/native/src/event.rs index 6f68cc065b..a7d04bbda6 100644 --- a/native/src/event.rs +++ b/native/src/event.rs @@ -35,6 +35,7 @@ pub enum Event { #[derive(Debug, Clone, PartialEq, Eq)] pub enum PlatformSpecific { /// A Wayland specific event + #[cfg(feature = "wayland")] Wayland(wayland::Event), /// A MacOS specific event MacOS(MacOS), diff --git a/src/clipboard.rs b/src/clipboard.rs index 4b2dd4ea66..27ac211040 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -1,3 +1,4 @@ //! Access the clipboard. -#[cfg(all(not(target_arch = "wasm32"), not(feature = "wayland")))] // TODO support in wayland +#[cfg(any(feature = "winit"))] +// TODO support in wayland pub use crate::runtime::clipboard::{read, write}; diff --git a/src/element.rs b/src/element.rs index 2eb1bb4db3..e014251ba5 100644 --- a/src/element.rs +++ b/src/element.rs @@ -1,5 +1,6 @@ /// A generic widget. /// /// This is an alias of an `iced_native` element with a default `Renderer`. +#[cfg(any(feature = "winit", feature = "wayland"))] pub type Element<'a, Message, Renderer = crate::Renderer> = crate::runtime::Element<'a, Message, Renderer>; diff --git a/src/error.rs b/src/error.rs index efd19c5621..c6d617b2c9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -22,18 +22,18 @@ impl From for Error { match error { iced_sctk::Error::ExecutorCreationFailed(error) => { Error::ExecutorCreationFailed(error) - }, + } iced_sctk::Error::WindowCreationFailed(error) => { Error::WindowCreationFailed(error) - }, + } iced_sctk::Error::GraphicsCreationFailed(error) => { Error::GraphicsCreationFailed(error) - }, + } } } } -#[cfg(not(feature = "wayland"))] +#[cfg(feature = "winit")] impl From for Error { fn from(error: iced_winit::Error) -> Error { match error { diff --git a/src/executor.rs b/src/executor.rs index 36ae274ec0..e9853391cc 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -1,4 +1,5 @@ //! Choose your preferred executor to power your application. +#[cfg(any(feature = "winit", feature = "wayland"))] pub use crate::runtime::Executor; /// A default cross-platform executor. diff --git a/src/keyboard.rs b/src/keyboard.rs index 2134a66bc3..c565a8dab5 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -1,2 +1,3 @@ //! Listen and react to keyboard events. +#[cfg(any(feature = "winit", feature = "wayland"))] pub use crate::runtime::keyboard::{Event, KeyCode, Modifiers}; diff --git a/src/lib.rs b/src/lib.rs index 9756c4be6f..671c8691ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -164,35 +164,26 @@ #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] -#[cfg(all( - not(feature = "glow"), - any(feature = "wgpu", feature = "swbuf"), - not(feature = "wayland") -))] +#[cfg(feature = "winit")] pub mod application; mod element; mod error; mod result; -#[cfg(all( - not(feature = "wayland") -))] +#[cfg(feature = "winit")] mod sandbox; -#[cfg(all( - not(feature = "wayland") -))] +#[cfg(feature = "winit")] pub use application::Application; /// wayland application #[cfg(feature = "wayland")] pub mod wayland; #[cfg(feature = "wayland")] -pub use wayland::Application; -#[cfg(feature = "wayland")] pub use wayland::sandbox; - +#[cfg(feature = "wayland")] +pub use wayland::Application; pub mod clipboard; pub mod executor; @@ -216,45 +207,45 @@ pub mod multi_window; #[cfg(feature = "wayland")] use iced_sctk as runtime; -#[cfg(all( - not(feature = "glow"), - any(feature = "wgpu", feature = "swbuf"), - not(feature = "wayland") -))] +#[cfg(feature = "winit")] use iced_winit as runtime; -#[cfg(all(feature = "glow", not(feature = "wayland")))] +#[cfg(feature = "glutin")] use iced_glutin as runtime; -#[cfg(all(not(feature = "iced_glow"), feature = "wgpu"))] +#[cfg(feature = "wgpu")] use iced_wgpu as renderer; -#[cfg(any(feature = "glow", feature = "wayland"))] +#[cfg(any(feature = "glow", feature = "glutin"))] use iced_glow as renderer; -#[cfg(all(not(feature = "iced_glow"), feature = "swbuf"))] +#[cfg(feature = "swbuf")] use iced_swbuf as renderer; pub use iced_native::theme; -pub use runtime::event; -pub use runtime::subscription; +#[cfg(any(feature = "winit", feature = "wayland"))] pub use element::Element; pub use error::Error; +#[cfg(any(feature = "winit", feature = "wayland"))] pub use event::Event; +#[cfg(any(feature = "winit", feature = "wayland"))] pub use executor::Executor; +#[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub use renderer::Renderer; pub use result::Result; +#[cfg(any(feature = "winit", feature = "wayland"))] pub use sandbox::Sandbox; pub use settings::Settings; +#[cfg(any(feature = "winit", feature = "wayland"))] pub use subscription::Subscription; pub use theme::Theme; -pub use runtime::alignment; -pub use runtime::futures; +#[cfg(any(feature = "winit", feature = "wayland"))] pub use runtime::{ - color, Alignment, Background, Color, Command, ContentFit, Font, Length, - Padding, Point, Rectangle, Size, Vector, settings as sctk_settings + alignment, color, event, futures, settings as sctk_settings, subscription, + Alignment, Background, Color, Command, ContentFit, Font, Length, Padding, + Point, Rectangle, Size, Vector, }; #[cfg(feature = "system")] diff --git a/src/mouse.rs b/src/mouse.rs index d61ed09a06..037a0bd491 100644 --- a/src/mouse.rs +++ b/src/mouse.rs @@ -1,2 +1,3 @@ //! Listen and react to mouse events. +#[cfg(any(feature = "winit", feature = "wayland"))] pub use crate::runtime::mouse::{Button, Event, Interaction, ScrollDelta}; diff --git a/src/overlay.rs b/src/overlay.rs index c0f4c49202..821bebba85 100644 --- a/src/overlay.rs +++ b/src/overlay.rs @@ -5,14 +5,24 @@ /// This is an alias of an `iced_native` element with a default `Renderer`. /// /// [`Overlay`]: iced_native::Overlay +#[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type Element<'a, Message, Renderer = crate::Renderer> = iced_native::overlay::Element<'a, Message, Renderer>; +#[cfg(not(any(feature = "swbuf", feature = "glow", feature = "wgpu")))] +pub use iced_native::overlay::Element; pub mod menu { //! Build and show dropdown menus. pub use iced_native::overlay::menu::{Appearance, State, StyleSheet}; /// A widget that produces a message when clicked. + #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type Menu<'a, Message, Renderer = crate::Renderer> = iced_native::overlay::Menu<'a, Message, Renderer>; + #[cfg(not(any( + feature = "swbuf", + feature = "glow", + feature = "wgpu" + )))] + pub use iced_native::overlay::Menu; } diff --git a/src/settings.rs b/src/settings.rs index d3fc0751ea..82c8831ef9 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -17,7 +17,6 @@ pub struct Settings { #[cfg(not(feature = "wayland"))] pub window: window::Settings, - /// the initial surface to be created #[cfg(feature = "wayland")] pub initial_surface: iced_sctk::settings::InitialSurface, @@ -119,7 +118,7 @@ where } } -#[cfg(not(any(target_arch = "wasm32", feature = "wayland")))] +#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))] impl From> for iced_winit::Settings { fn from(settings: Settings) -> iced_winit::Settings { iced_winit::Settings { diff --git a/src/touch.rs b/src/touch.rs index 0b77c386ee..2e74bb83db 100644 --- a/src/touch.rs +++ b/src/touch.rs @@ -1,2 +1,3 @@ //! Listen and react to touch events. +#[cfg(any(feature = "winit", feature = "wayland"))] pub use crate::runtime::touch::{Event, Finger}; diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 167123dde7..65f6260913 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -4,7 +4,9 @@ use crate::{Command, Element, Executor, Settings, Subscription}; pub mod sandbox; pub use iced_native::application::{Appearance, StyleSheet}; pub use iced_native::command::platform_specific::wayland as actions; -pub use iced_sctk::{application::SurfaceIdWrapper, commands::*, command::*, settings::*}; +pub use iced_sctk::{ + application::SurfaceIdWrapper, command::*, commands::*, settings::*, +}; /// A pure version of [`Application`]. /// diff --git a/src/widget.rs b/src/widget.rs index 4a7d353908..f67143ab29 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -4,20 +4,33 @@ pub use iced_native::widget::helpers::*; pub use iced_native::{column, row}; /// A container that distributes its contents vertically. +#[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type Column<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Column<'a, Message, Renderer>; +#[cfg(not(any(feature = "swbuf", feature = "glow", feature = "wgpu")))] +pub use iced_native::widget::Column; /// A container that distributes its contents horizontally. +#[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type Row<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Row<'a, Message, Renderer>; +#[cfg(not(any(feature = "swbuf", feature = "glow", feature = "wgpu")))] +pub use iced_native::widget::Row; pub mod text { //! Write some text for your users to read. pub use iced_native::widget::text::{Appearance, StyleSheet}; /// A paragraph of text. + #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type Text<'a, Renderer = crate::Renderer> = iced_native::widget::Text<'a, Renderer>; + #[cfg(not(any( + feature = "swbuf", + feature = "glow", + feature = "wgpu" + )))] + pub use iced_native::widget::Text; } pub mod button { @@ -25,8 +38,15 @@ pub mod button { pub use iced_native::widget::button::{Appearance, StyleSheet}; /// A widget that produces a message when clicked. + #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type Button<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Button<'a, Message, Renderer>; + #[cfg(not(any( + feature = "swbuf", + feature = "glow", + feature = "wgpu" + )))] + pub use iced_native::widget::Button; } pub mod checkbox { @@ -34,8 +54,15 @@ pub mod checkbox { pub use iced_native::widget::checkbox::{Appearance, StyleSheet}; /// A box that can be checked. + #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type Checkbox<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Checkbox<'a, Message, Renderer>; + #[cfg(not(any( + feature = "swbuf", + feature = "glow", + feature = "wgpu" + )))] + pub use iced_native::widget::Checkbox; } pub mod container { @@ -43,16 +70,30 @@ pub mod container { pub use iced_native::widget::container::{Appearance, StyleSheet}; /// An element decorating some content. + #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type Container<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Container<'a, Message, Renderer>; + #[cfg(not(any( + feature = "swbuf", + feature = "glow", + feature = "wgpu" + )))] + pub use iced_native::widget::Container; } pub mod mouse_listener { //! Intercept mouse events on a widget. /// A container intercepting mouse events. + #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type MouseListener<'a, Message, Renderer = crate::Renderer> = iced_native::widget::MouseListener<'a, Message, Renderer>; + #[cfg(not(any( + feature = "swbuf", + feature = "glow", + feature = "wgpu" + )))] + pub use iced_native::widget::MouseListener; } pub mod pane_grid { @@ -74,16 +115,37 @@ pub mod pane_grid { /// to completely fill the space available. /// /// [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) + #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type PaneGrid<'a, Message, Renderer = crate::Renderer> = iced_native::widget::PaneGrid<'a, Message, Renderer>; + #[cfg(not(any( + feature = "swbuf", + feature = "glow", + feature = "wgpu" + )))] + pub use iced_native::widget::PaneGrid; /// The content of a [`Pane`]. + #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type Content<'a, Message, Renderer = crate::Renderer> = iced_native::widget::pane_grid::Content<'a, Message, Renderer>; + #[cfg(not(any( + feature = "swbuf", + feature = "glow", + feature = "wgpu" + )))] + pub use iced_native::widget::pane_grid::Content; /// The title bar of a [`Pane`]. + #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type TitleBar<'a, Message, Renderer = crate::Renderer> = iced_native::widget::pane_grid::TitleBar<'a, Message, Renderer>; + #[cfg(not(any( + feature = "swbuf", + feature = "glow", + feature = "wgpu" + )))] + pub use iced_native::widget::pane_grid::TitleBar; } pub mod pick_list { @@ -91,8 +153,15 @@ pub mod pick_list { pub use iced_native::widget::pick_list::{Appearance, StyleSheet}; /// A widget allowing the selection of a single value from a list of options. + #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type PickList<'a, T, Message, Renderer = crate::Renderer> = iced_native::widget::PickList<'a, T, Message, Renderer>; + #[cfg(not(any( + feature = "swbuf", + feature = "glow", + feature = "wgpu" + )))] + pub use iced_native::widget::PickList; } pub mod radio { @@ -100,8 +169,15 @@ pub mod radio { pub use iced_native::widget::radio::{Appearance, StyleSheet}; /// A circular button representing a choice. + #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type Radio = iced_native::widget::Radio; + #[cfg(not(any( + feature = "swbuf", + feature = "glow", + feature = "wgpu" + )))] + pub use iced_native::widget::Radio; } pub mod scrollable { @@ -112,8 +188,15 @@ pub mod scrollable { /// A widget that can vertically display an infinite amount of content /// with a scrollbar. + #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type Scrollable<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Scrollable<'a, Message, Renderer>; + #[cfg(not(any( + feature = "swbuf", + feature = "glow", + feature = "wgpu" + )))] + pub use iced_native::widget::Scrollable; } pub mod toggler { @@ -121,8 +204,15 @@ pub mod toggler { pub use iced_native::widget::toggler::{Appearance, StyleSheet}; /// A toggler widget. + #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type Toggler<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Toggler<'a, Message, Renderer>; + #[cfg(not(any( + feature = "swbuf", + feature = "glow", + feature = "wgpu" + )))] + pub use iced_native::widget::Toggler; } pub mod text_input { @@ -133,8 +223,15 @@ pub mod text_input { }; /// A field that can be filled with text. + #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type TextInput<'a, Message, Renderer = crate::Renderer> = iced_native::widget::TextInput<'a, Message, Renderer>; + #[cfg(not(any( + feature = "swbuf", + feature = "glow", + feature = "wgpu" + )))] + pub use iced_native::widget::TextInput; } pub mod tooltip { @@ -142,8 +239,15 @@ pub mod tooltip { pub use iced_native::widget::tooltip::Position; /// A widget allowing the selection of a single value from a list of options. + #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type Tooltip<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Tooltip<'a, Message, Renderer>; + #[cfg(not(any( + feature = "swbuf", + feature = "glow", + feature = "wgpu" + )))] + pub use iced_native::widget::Tooltip; } pub use iced_native::widget::progress_bar; @@ -223,10 +327,13 @@ pub use qr_code::QRCode; #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] pub use svg::Svg; +#[cfg(any(feature = "winit", feature = "wayland"))] use crate::Command; +#[cfg(any(feature = "winit", feature = "wayland"))] use iced_native::widget::operation; /// Focuses the previous focusable widget. +#[cfg(any(feature = "winit", feature = "wayland"))] pub fn focus_previous() -> Command where Message: 'static, @@ -235,6 +342,7 @@ where } /// Focuses the next focusable widget. +#[cfg(any(feature = "winit", feature = "wayland"))] pub fn focus_next() -> Command where Message: 'static, diff --git a/src/window.rs b/src/window.rs index 4afb11f702..1fa1b60fe5 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,17 +1,19 @@ //! Configure the window of your application in native platforms. -mod position; -mod settings; #[cfg(feature = "winit")] - pub mod icon; +mod position; +mod settings; #[cfg(feature = "winit")] pub use icon::Icon; pub use position::Position; pub use settings::Settings; -#[cfg(not(target_arch = "wasm32"))] -pub use crate::runtime::window::resize; -#[cfg(not(any(target_arch = "wasm32", feature = "wayland")))] +#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))] pub use crate::runtime::window::move_to; +#[cfg(all( + not(target_arch = "wasm32"), + any(feature = "wayland", feature = "winit") +))] +pub use crate::runtime::window::resize; pub use iced_native::window::Id; From c1096f9f4a4e3eaae1a6a7d43c65e8f49906daaf Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld <4404502+Drakulix@users.noreply.github.com> Date: Wed, 4 Jan 2023 16:47:57 +0100 Subject: [PATCH 32/56] features: Use iced_native types without a runtime (#19) --- src/element.rs | 4 ++-- src/lib.rs | 16 ++++++++++------ src/mouse.rs | 3 +-- src/touch.rs | 3 +-- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/element.rs b/src/element.rs index e014251ba5..19627fd627 100644 --- a/src/element.rs +++ b/src/element.rs @@ -1,6 +1,6 @@ /// A generic widget. /// /// This is an alias of an `iced_native` element with a default `Renderer`. -#[cfg(any(feature = "winit", feature = "wayland"))] +#[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub type Element<'a, Message, Renderer = crate::Renderer> = - crate::runtime::Element<'a, Message, Renderer>; + iced_native::Element<'a, Message, Renderer>; diff --git a/src/lib.rs b/src/lib.rs index 671c8691ec..61a3457b4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -224,10 +224,9 @@ use iced_swbuf as renderer; pub use iced_native::theme; -#[cfg(any(feature = "winit", feature = "wayland"))] +#[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] pub use element::Element; pub use error::Error; -#[cfg(any(feature = "winit", feature = "wayland"))] pub use event::Event; #[cfg(any(feature = "winit", feature = "wayland"))] pub use executor::Executor; @@ -237,15 +236,20 @@ pub use result::Result; #[cfg(any(feature = "winit", feature = "wayland"))] pub use sandbox::Sandbox; pub use settings::Settings; -#[cfg(any(feature = "winit", feature = "wayland"))] pub use subscription::Subscription; pub use theme::Theme; +#[cfg(not(any(feature = "winit", feature = "wayland")))] +pub use iced_native::{ + alignment, color, event, futures, subscription, Alignment, Background, + Color, Command, ContentFit, Font, Length, Padding, Point, Rectangle, Size, + Vector, +}; #[cfg(any(feature = "winit", feature = "wayland"))] pub use runtime::{ - alignment, color, event, futures, settings as sctk_settings, subscription, - Alignment, Background, Color, Command, ContentFit, Font, Length, Padding, - Point, Rectangle, Size, Vector, + alignment, color, event, futures, subscription, Alignment, Background, + Color, Command, ContentFit, Font, Length, Padding, Point, Rectangle, Size, + Vector, }; #[cfg(feature = "system")] diff --git a/src/mouse.rs b/src/mouse.rs index 037a0bd491..9b2d368dc5 100644 --- a/src/mouse.rs +++ b/src/mouse.rs @@ -1,3 +1,2 @@ //! Listen and react to mouse events. -#[cfg(any(feature = "winit", feature = "wayland"))] -pub use crate::runtime::mouse::{Button, Event, Interaction, ScrollDelta}; +pub use iced_native::mouse::{Button, Event, Interaction, ScrollDelta}; diff --git a/src/touch.rs b/src/touch.rs index 2e74bb83db..e8f21af87a 100644 --- a/src/touch.rs +++ b/src/touch.rs @@ -1,3 +1,2 @@ //! Listen and react to touch events. -#[cfg(any(feature = "winit", feature = "wayland"))] -pub use crate::runtime::touch::{Event, Finger}; +pub use iced_native::touch::{Event, Finger}; From b5d8058be87a31f3141961786bd2c927c6716d29 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld <4404502+Drakulix@users.noreply.github.com> Date: Wed, 4 Jan 2023 21:03:30 +0100 Subject: [PATCH 33/56] swbuf: Make backend constructor visible (#20) --- swbuf/src/backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swbuf/src/backend.rs b/swbuf/src/backend.rs index 3ed3ea11cb..9d3e1684ec 100644 --- a/swbuf/src/backend.rs +++ b/swbuf/src/backend.rs @@ -89,7 +89,7 @@ pub struct Backend { } impl Backend { - pub(crate) fn new() -> Self { + pub fn new() -> Self { Self { swash_cache: SwashCache::new(&FONT_SYSTEM), #[cfg(feature = "image")] From 9984f4459e594f139015e1aee31b28730b97c610 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 5 Jan 2023 14:27:24 -0500 Subject: [PATCH 34/56] fix: create popups with grab when a grab is requested --- examples/clock_sctk_window/Cargo.toml | 2 +- examples/clock_sctk_window/src/main.rs | 55 +++++-- sctk/src/event_loop/state.rs | 209 +++++++++++++------------ sctk/src/handlers/seat/keyboard.rs | 4 +- 4 files changed, 152 insertions(+), 118 deletions(-) diff --git a/examples/clock_sctk_window/Cargo.toml b/examples/clock_sctk_window/Cargo.toml index bf9d3e49cf..cfe40dfacd 100644 --- a/examples/clock_sctk_window/Cargo.toml +++ b/examples/clock_sctk_window/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", default-features = false, features = ["canvas", "tokio", "debug", "wayland"] } +iced = { path = "../..", default-features = false, features = ["canvas", "tokio", "debug", "wayland", "glow"] } time = { version = "0.3.5", features = ["local-offset"] } iced_native = { path = "../../native" } sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "a257bf7" } diff --git a/examples/clock_sctk_window/src/main.rs b/examples/clock_sctk_window/src/main.rs index 5dc8b5bf6c..bec2d08602 100644 --- a/examples/clock_sctk_window/src/main.rs +++ b/examples/clock_sctk_window/src/main.rs @@ -1,4 +1,6 @@ use iced::executor; +use iced::wayland::actions::layer_surface::SctkLayerSurfaceSettings; +use iced::wayland::layer_surface::KeyboardInteractivity; use iced::wayland::{ popup::{destroy_popup, get_popup}, window::{close_window, get_window}, @@ -7,26 +9,25 @@ use iced::wayland::{ use iced::widget::canvas::{ stroke, Cache, Cursor, Geometry, LineCap, Path, Stroke, }; -use iced::widget::{button, canvas, column, container, text}; +use iced::widget::{button, canvas, column, container, text, text_input}; use iced::{ - sctk_settings::InitialSurface, Application, Color, Command, Element, - Length, Point, Rectangle, Settings, Subscription, Theme, Vector, + wayland::InitialSurface, Application, Color, Command, Element, Length, + Point, Rectangle, Settings, Subscription, Theme, Vector, }; use iced_native::command::platform_specific::wayland::popup::{ SctkPopupSettings, SctkPositioner, }; use iced_native::command::platform_specific::wayland::window::SctkWindowSettings; use iced_native::window::{self, Id}; +use iced_native::Widget; pub fn main() -> iced::Result { Clock::run(Settings { antialiasing: true, - initial_surface: InitialSurface::XdgWindow( - SctkWindowSettings { - autosize: true, - ..Default::default() - }, - ), + initial_surface: InitialSurface::XdgWindow(SctkWindowSettings { + autosize: true, + ..Default::default() + }), ..Settings::default() }) } @@ -38,13 +39,15 @@ struct Clock { to_destroy: Id, id_ctr: u32, popup: Option, + input: String, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] enum Message { Tick(time::OffsetDateTime), Click(window::Id), SurfaceClosed, + Input(String), } impl Application for Clock { @@ -64,6 +67,7 @@ impl Application for Clock { popup: None, to_destroy, id_ctr: 2, + input: String::new(), }, get_window(SctkWindowSettings { window_id: to_destroy, @@ -121,6 +125,9 @@ impl Application for Clock { Message::SurfaceClosed => { // ignored } + Message::Input(input) => { + self.input = input; + } } Command::none() @@ -140,24 +147,40 @@ impl Application for Clock { id: SurfaceIdWrapper, ) -> Element<'_, Self::Message, iced::Renderer> { match id { - SurfaceIdWrapper::LayerSurface(_) => unimplemented!(), - SurfaceIdWrapper::Window(_) => { + SurfaceIdWrapper::LayerSurface(_) => { let canvas = canvas(self as &Self) .width(Length::Units(100)) .height(Length::Units(100)); container(column![ + text_input("hello", &self.input, Message::Input) + .width(Length::Fill), button("Popup").on_press(Message::Click(id.inner())), canvas, ]) .padding(20) .into() } - SurfaceIdWrapper::Popup(_) => { - button(text(format!("{}", self.count))) - .on_press(Message::Click(id.inner())) + SurfaceIdWrapper::Window(_) => { + let canvas = canvas(self as &Self) + .width(Length::Units(100)) + .height(Length::Units(100)); + + container(column![ + text_input("hello", &self.input, Message::Input) + .width(Length::Fill), + button("Popup").on_press(Message::Click(id.inner())), + canvas, + ]) .padding(20) - .into()}, + .into() + } + SurfaceIdWrapper::Popup(_) => container( + text_input("hello", &self.input, Message::Input) + .width(Length::Fill), + ) + .width(Length::Units(300)) + .into(), } } diff --git a/sctk/src/event_loop/state.rs b/sctk/src/event_loop/state.rs index 61eeb9e2a5..9f35a9517e 100644 --- a/sctk/src/event_loop/state.rs +++ b/sctk/src/event_loop/state.rs @@ -1,9 +1,9 @@ -use std::{collections::HashMap, fmt::Debug, sync::Arc}; +use std::{collections::HashMap, fmt::Debug}; use crate::{ application::Event, dpi::LogicalSize, - sctk_event::{SctkEvent, SurfaceCompositorUpdate, SurfaceUserRequest}, commands::popup, + sctk_event::{SctkEvent, SurfaceCompositorUpdate, SurfaceUserRequest}, }; use iced_native::{ @@ -16,7 +16,8 @@ use iced_native::{ }, }, keyboard::Modifiers, - window, layout::Limits, + layout::Limits, + window, }; use sctk::{ compositor::CompositorState, @@ -61,7 +62,7 @@ pub(crate) struct SctkSeat { pub(crate) seat: WlSeat, pub(crate) kbd: Option, pub(crate) kbd_focus: Option, - pub(crate) last_kbd_press: Option, + pub(crate) last_kbd_press: Option<(KeyEvent, u32)>, pub(crate) ptr: Option, pub(crate) ptr_focus: Option, pub(crate) last_ptr_press: Option<(u32, u32, u32)>, // (time, button, serial) @@ -122,7 +123,7 @@ pub struct SctkPopup { // pub(crate) positioner: XdgPositioner, pub(crate) pending_requests: Vec>, - pub(crate) data: SctkPopupData + pub(crate) data: SctkPopupData, } #[derive(Debug, Clone)] @@ -132,6 +133,7 @@ pub struct SctkPopupData { pub(crate) toplevel: WlSurface, pub(crate) requested_size: (u32, u32), pub(crate) limits: Limits, + pub(crate) grab: bool, } /// Wrapper to carry sctk state. @@ -255,19 +257,25 @@ where ) -> Result<(window::Id, WlSurface, WlSurface, WlSurface), PopupCreationError> { let limits = settings.positioner.size_limits; - + let (parent, toplevel) = if let Some(parent) = self.layer_surfaces.iter().find(|l| l.id == settings.parent) { - (SctkSurface::LayerSurface( + ( + SctkSurface::LayerSurface(parent.surface.wl_surface().clone()), parent.surface.wl_surface().clone(), - ), parent.surface.wl_surface().clone()) + ) } else if let Some(parent) = self.windows.iter().find(|w| w.id == settings.parent) { - (SctkSurface::Window(parent.window.wl_surface().clone()),parent.window.wl_surface().clone()) - } else if let Some(i) = - self.popups.iter().position(|p| p.data.id == settings.parent) + ( + SctkSurface::Window(parent.window.wl_surface().clone()), + parent.window.wl_surface().clone(), + ) + } else if let Some(i) = self + .popups + .iter() + .position(|p| p.data.id == settings.parent) { let parent = &self.popups[i]; ( @@ -285,7 +293,7 @@ where }; let positioner = XdgPositioner::new(&self.xdg_shell_state) - .map_err(|e| PopupCreationError::PositionerCreationFailed(e))?; + .map_err(|e| PopupCreationError::PositionerCreationFailed(e))?; positioner.set_anchor(settings.positioner.anchor); positioner.set_anchor_rect( settings.positioner.anchor_rect.x, @@ -304,17 +312,20 @@ where if settings.positioner.reactive { positioner.set_reactive(); } - positioner.set_size( - size.0 as i32, - size.1 as i32, - ); + positioner.set_size(size.0 as i32, size.1 as i32); - match parent { - SctkSurface::LayerSurface(parent) => { - let parent_layer_surface = self.layer_surfaces.iter().find(|w| w.surface.wl_surface() == &parent).unwrap(); + let grab = settings.grab; - let wl_surface = - self.compositor_state.create_surface(&self.queue_handle); + let wl_surface = + self.compositor_state.create_surface(&self.queue_handle); + + let (toplevel, popup) = match &parent { + SctkSurface::LayerSurface(parent) => { + let parent_layer_surface = self + .layer_surfaces + .iter() + .find(|w| w.surface.wl_surface() == parent) + .unwrap(); let popup = Popup::from_surface( None, &positioner, @@ -323,89 +334,87 @@ where &self.xdg_shell_state, ) .map_err(|e| PopupCreationError::PopupCreationFailed(e))?; - parent_layer_surface.surface.get_popup(popup.xdg_popup()); - wl_surface.commit(); - self.popups.push(SctkPopup { - data: SctkPopupData { - id: settings.id, - parent: SctkSurface::LayerSurface( - parent.clone(), - ), - toplevel: parent.clone(), - requested_size: size, - limits, - }, - popup: popup.clone(), - last_configure: None, - pending_requests: Default::default(), - }); - Ok(( - settings.id, - parent.clone(), - parent.clone(), - wl_surface.clone(), - )) - }, + (parent_layer_surface.surface.wl_surface(), popup) + } SctkSurface::Window(parent) => { - let parent_window = self.windows.iter().find(|w| w.window.wl_surface() == &parent).unwrap(); - let popup = Popup::new( - parent_window.window.xdg_surface(), - &positioner, - &self.queue_handle, - &self.compositor_state, - &self.xdg_shell_state, + let parent_window = self + .windows + .iter() + .find(|w| w.window.wl_surface() == parent) + .unwrap(); + ( + parent_window.window.wl_surface(), + Popup::from_surface( + Some(parent_window.window.xdg_surface()), + &positioner, + &self.queue_handle, + wl_surface.clone(), + &self.xdg_shell_state, + ) + .map_err(|e| PopupCreationError::PopupCreationFailed(e))?, ) - .map_err(|e| PopupCreationError::PopupCreationFailed(e))?; - self.popups.push(SctkPopup { - popup: popup.clone(), - data: SctkPopupData { - id: settings.id, - parent: SctkSurface::Window(parent.clone()), - toplevel: parent.clone(), - requested_size: size, - limits - }, - last_configure: None, - pending_requests: Default::default(), - }); - Ok(( - settings.id, - parent.clone(), - parent.clone(), - popup.wl_surface().clone(), - )) - }, + } SctkSurface::Popup(parent) => { - let parent_xdg = self.windows.iter().find_map(|w| if w.window.wl_surface() == &parent { - Some(w.window.xdg_surface()) - } else { - None - }).unwrap(); - - let popup = Popup::new( - parent_xdg, - &positioner, - &self.queue_handle, - &self.compositor_state, - &self.xdg_shell_state, + let parent_xdg = self + .windows + .iter() + .find_map(|w| { + if w.window.wl_surface() == parent { + Some(w.window.xdg_surface()) + } else { + None + } + }) + .unwrap(); + + ( + &toplevel, + Popup::from_surface( + Some(parent_xdg), + &positioner, + &self.queue_handle, + wl_surface.clone(), + &self.xdg_shell_state, + ) + .map_err(|e| PopupCreationError::PopupCreationFailed(e))?, ) - .map_err(|e| PopupCreationError::PopupCreationFailed(e))?; - self.popups.push(SctkPopup { - popup: popup.clone(), - data: SctkPopupData { - id: settings.id, - parent: SctkSurface::Popup(parent.clone()), - toplevel: toplevel.clone(), - requested_size: size, - limits, - }, - last_configure: None, - pending_requests: Default::default(), - }); - Ok((settings.id, parent, toplevel, popup.wl_surface().clone())) - }, + } + }; + if grab { + if let Some(s) = self.seats.first() { + popup.xdg_popup().grab( + &s.seat, + s.last_ptr_press.map(|p| p.2).unwrap_or_else(|| { + s.last_kbd_press + .as_ref() + .map(|p| p.1) + .unwrap_or_default() + }), + ) + } } + wl_surface.commit(); + self.popups.push(SctkPopup { + popup: popup.clone(), + data: SctkPopupData { + id: settings.id, + parent: parent.clone(), + toplevel: toplevel.clone(), + requested_size: size, + limits, + grab, + }, + last_configure: None, + pending_requests: Default::default(), + }); + + Ok(( + settings.id, + parent.wl_surface().clone(), + toplevel.clone(), + popup.wl_surface().clone(), + )) } pub fn get_window( @@ -503,7 +512,9 @@ where namespace, margin, size, - exclusive_zone, .. }: SctkLayerSurfaceSettings, + exclusive_zone, + .. + }: SctkLayerSurfaceSettings, ) -> Result<(iced_native::window::Id, WlSurface), LayerSurfaceCreationError> { let wl_output = match output { diff --git a/sctk/src/handlers/seat/keyboard.rs b/sctk/src/handlers/seat/keyboard.rs index 5bf3879ac4..974065be7f 100644 --- a/sctk/src/handlers/seat/keyboard.rs +++ b/sctk/src/handlers/seat/keyboard.rs @@ -108,7 +108,7 @@ impl KeyboardHandler for SctkState { _conn: &sctk::reexports::client::Connection, _qh: &sctk::reexports::client::QueueHandle, keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, - _serial: u32, + serial: u32, event: sctk::seat::keyboard::KeyEvent, ) { let (is_active, my_seat) = @@ -124,7 +124,7 @@ impl KeyboardHandler for SctkState { }; let seat_id = my_seat.seat.clone(); let kbd_id = keyboard.clone(); - my_seat.last_kbd_press.replace(event.clone()); + my_seat.last_kbd_press.replace((event.clone(), serial)); if is_active { self.sctk_events.push(SctkEvent::KeyboardEvent { variant: KeyboardEventVariant::Press(event), From 8658bb3827f8ed7f89817256b54d21da78f16663 Mon Sep 17 00:00:00 2001 From: 13r0ck Date: Thu, 5 Jan 2023 12:04:44 -0700 Subject: [PATCH 35/56] Add focus operation and style for button --- native/src/widget/button.rs | 94 +++++++++++++++++++++++++++++++++++-- src/widget.rs | 2 +- style/src/button.rs | 10 ++++ style/src/theme.rs | 19 ++++++++ 4 files changed, 119 insertions(+), 6 deletions(-) diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index fa5da24bb2..3c0d6e453d 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -2,16 +2,18 @@ //! //! A [`Button`] has some local [`State`]. use crate::event::{self, Event}; +use crate::keyboard; use crate::layout; use crate::mouse; use crate::overlay; use crate::renderer; use crate::touch; +use crate::widget; +use crate::widget::operation::{self, Operation}; use crate::widget::tree::{self, Tree}; -use crate::widget::Operation; use crate::{ - Background, Clipboard, Color, Element, Layout, Length, Padding, Point, - Rectangle, Shell, Vector, Widget, + Background, Clipboard, Color, Command, Element, Layout, Length, Padding, + Point, Rectangle, Shell, Vector, Widget, }; pub use iced_style::button::{Appearance, StyleSheet}; @@ -56,6 +58,7 @@ where Renderer: crate::Renderer, Renderer::Theme: StyleSheet, { + id: Option, content: Element<'a, Message, Renderer>, on_press: Option, width: Length, @@ -72,6 +75,7 @@ where /// Creates a new [`Button`] with the given content. pub fn new(content: impl Into>) -> Self { Button { + id: None, content: content.into(), on_press: None, width: Length::Shrink, @@ -107,6 +111,12 @@ where self } + /// Sets the [`Id`] of the [`Button`]. + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } + /// Sets the style variant of this [`Button`]. pub fn style( mut self, @@ -178,6 +188,9 @@ where operation, ); }); + + let state = tree.state.downcast_mut::(); + operation.focusable(state, self.id.as_ref().map(|id| &id.0)); } fn on_event( @@ -289,6 +302,7 @@ where #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct State { is_pressed: bool, + is_focused: bool, } impl State { @@ -296,6 +310,21 @@ impl State { pub fn new() -> State { State::default() } + + /// Returns whether the [`Button`] is currently focused or not. + pub fn is_focused(&self) -> bool { + self.is_focused + } + + /// Focuses the [`Button`]. + pub fn focus(&mut self) { + self.is_focused = true; + } + + /// Unfocuses the [`Button`]. + pub fn unfocus(&mut self) { + self.is_focused = false; + } } /// Processes the given [`Event`] and updates the [`State`] of a [`Button`] @@ -341,6 +370,17 @@ pub fn update<'a, Message: Clone>( } } } + + Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => { + if let Some(on_press) = on_press.clone() { + let state = state(); + if state.is_focused && key_code == keyboard::KeyCode::Enter { + state.is_pressed = true; + shell.publish(on_press); + return event::Status::Captured; + } + } + } Event::Touch(touch::Event::FingerLost { .. }) => { let state = state(); @@ -368,17 +408,18 @@ where Renderer::Theme: StyleSheet, { let is_mouse_over = bounds.contains(cursor_position); + let state = state(); let styling = if !is_enabled { style_sheet.disabled(style) } else if is_mouse_over { - let state = state(); - if state.is_pressed { style_sheet.pressed(style) } else { style_sheet.hovered(style) } + } else if state.is_focused { + style_sheet.focused(style) } else { style_sheet.active(style) }; @@ -451,3 +492,46 @@ pub fn mouse_interaction( mouse::Interaction::default() } } + +/// The identifier of a [`Button`]. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Id(widget::Id); + +impl Id { + /// Creates a custom [`Id`]. + pub fn new(id: impl Into>) -> Self { + Self(widget::Id::new(id)) + } + + /// Creates a unique [`Id`]. + /// + /// This function produces a different [`Id`] every time it is called. + pub fn unique() -> Self { + Self(widget::Id::unique()) + } +} + +impl From for widget::Id { + fn from(id: Id) -> Self { + id.0 + } +} + +/// Produces a [`Command`] that focuses the [`Button`] with the given [`Id`]. +pub fn focus(id: Id) -> Command { + Command::widget(operation::focusable::focus(id.0)) +} + +impl operation::Focusable for State { + fn is_focused(&self) -> bool { + State::is_focused(self) + } + + fn focus(&mut self) { + State::focus(self) + } + + fn unfocus(&mut self) { + State::unfocus(self) + } +} diff --git a/src/widget.rs b/src/widget.rs index f67143ab29..6712397805 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -35,7 +35,7 @@ pub mod text { pub mod button { //! Allow your users to perform actions by pressing a button. - pub use iced_native::widget::button::{Appearance, StyleSheet}; + pub use iced_native::widget::button::{focus, Appearance, Id, StyleSheet}; /// A widget that produces a message when clicked. #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] diff --git a/style/src/button.rs b/style/src/button.rs index 6f9269a0e1..93e13e6b40 100644 --- a/style/src/button.rs +++ b/style/src/button.rs @@ -39,6 +39,16 @@ pub trait StyleSheet { /// Produces the active [`Appearance`] of a button. fn active(&self, style: &Self::Style) -> Appearance; + /// Produces the focused [`Appearance`] of a button. + fn focused(&self, style: &Self::Style) -> Appearance { + let active = self.active(style); + + Appearance { + shadow_offset: active.shadow_offset + Vector::new(0.0, 1.0), + ..active + } + } + /// Produces the hovered [`Appearance`] of a button. fn hovered(&self, style: &Self::Style) -> Appearance { let active = self.active(style); diff --git a/style/src/theme.rs b/style/src/theme.rs index 4749e24fd2..a427904c03 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -193,6 +193,25 @@ impl button::StyleSheet for Theme { } } + fn focused(&self, style: &Self::Style) -> button::Appearance { + let palette = self.extended_palette(); + + let background = match style { + Button::Primary => Some(palette.primary.base.color), + Button::Secondary => Some(palette.background.strong.color), + Button::Positive => Some(palette.success.strong.color), + Button::Destructive => Some(palette.danger.strong.color), + Button::Text | Button::Custom(_) => None, + }; + + let active = self.active(style); + + button::Appearance { + background: background.map(Background::from), + ..active + } + } + fn pressed(&self, style: &Self::Style) -> button::Appearance { if let Button::Custom(custom) = style { return custom.pressed(self); From c4fba7e07bc1ff9f1b9226663a7025429b65d6cc Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 6 Jan 2023 10:55:24 -0500 Subject: [PATCH 36/56] fix: reposition popup when resizing --- examples/clock_sctk_window/src/main.rs | 18 +++++++++++------- sctk/src/event_loop/mod.rs | 10 +++++++++- sctk/src/event_loop/state.rs | 13 +++++-------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/examples/clock_sctk_window/src/main.rs b/examples/clock_sctk_window/src/main.rs index bec2d08602..553df8bbbb 100644 --- a/examples/clock_sctk_window/src/main.rs +++ b/examples/clock_sctk_window/src/main.rs @@ -134,7 +134,7 @@ impl Application for Clock { } fn subscription(&self) -> Subscription { - iced::time::every(std::time::Duration::from_millis(500)).map(|_| { + iced::time::every(std::time::Duration::from_millis(2000)).map(|_| { Message::Tick( time::OffsetDateTime::now_local() .unwrap_or_else(|_| time::OffsetDateTime::now_utc()), @@ -175,12 +175,16 @@ impl Application for Clock { .padding(20) .into() } - SurfaceIdWrapper::Popup(_) => container( - text_input("hello", &self.input, Message::Input) - .width(Length::Fill), - ) - .width(Length::Units(300)) - .into(), + SurfaceIdWrapper::Popup(_) => { + let mut s = String::with_capacity(self.count as usize); + for i in 0..self.count { + s.push('X'); + } + button(text(format!("{}", s))) + .on_press(Message::Click(id.inner())) + .padding(20) + .into() + } } } diff --git a/sctk/src/event_loop/mod.rs b/sctk/src/event_loop/mod.rs index 6a7d07a1bc..978e81cb62 100644 --- a/sctk/src/event_loop/mod.rs +++ b/sctk/src/event_loop/mod.rs @@ -169,6 +169,7 @@ where layer_surface_user_requests: Default::default(), popup_user_requests: Default::default(), pending_user_events: Vec::new(), + token_ctr: 0, }, features: Default::default(), event_loop_awakener: ping, @@ -762,7 +763,13 @@ where .iter() .find(|s| s.data.id == id) { + // update geometry sctk_popup.popup.xdg_surface().set_window_geometry(0, 0, width as i32, height as i32); + // update positioner + self.state.token_ctr += 1; + sctk_popup.data.positioner.set_size(width as i32, height as i32); + sctk_popup.popup.reposition(&sctk_popup.data.positioner, self.state.token_ctr); + to_commit.insert(id, sctk_popup.popup.wl_surface().clone()); sticky_exit_callback(IcedSctkEvent::SctkEvent(SctkEvent::PopupEvent { variant: PopupEventVariant::Size(width, height), @@ -776,7 +783,8 @@ where ); } }, - platform_specific::wayland::popup::Action::Grab { id } => todo!(), + // TODO probably remove this? + platform_specific::wayland::popup::Action::Grab { id } => {}, }, Event::InitSurfaceSize(id, requested_size) => todo!(), } diff --git a/sctk/src/event_loop/state.rs b/sctk/src/event_loop/state.rs index 9f35a9517e..22814f09ee 100644 --- a/sctk/src/event_loop/state.rs +++ b/sctk/src/event_loop/state.rs @@ -116,7 +116,7 @@ impl SctkSurface { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct SctkPopup { pub(crate) popup: Popup, pub(crate) last_configure: Option, @@ -126,14 +126,12 @@ pub struct SctkPopup { pub(crate) data: SctkPopupData, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct SctkPopupData { pub(crate) id: iced_native::window::Id, pub(crate) parent: SctkSurface, pub(crate) toplevel: WlSurface, - pub(crate) requested_size: (u32, u32), - pub(crate) limits: Limits, - pub(crate) grab: bool, + pub(crate) positioner: XdgPositioner, } /// Wrapper to carry sctk state. @@ -209,6 +207,7 @@ pub struct SctkState { pub(crate) layer_shell: Option, pub(crate) connection: Connection, + pub(crate) token_ctr: u32, } /// An error that occurred while running an application. @@ -401,9 +400,7 @@ where id: settings.id, parent: parent.clone(), toplevel: toplevel.clone(), - requested_size: size, - limits, - grab, + positioner, }, last_configure: None, pending_requests: Default::default(), From be09b346b7e63413c90ea5f1d47efcfa480ec6c3 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 6 Jan 2023 08:38:47 -0700 Subject: [PATCH 37/56] Go back to using softbuffer --- Cargo.toml | 22 +++---- {swbuf => softbuffer}/Cargo.toml | 6 +- {swbuf => softbuffer}/README.md | 2 +- {swbuf => softbuffer}/src/backend.rs | 0 {swbuf => softbuffer}/src/lib.rs | 2 +- {swbuf => softbuffer}/src/settings.rs | 0 {swbuf => softbuffer}/src/surface.rs | 2 +- {swbuf => softbuffer}/src/window.rs | 0 .../src/window/compositor.rs | 0 src/element.rs | 2 +- src/lib.rs | 10 +-- src/overlay.rs | 8 +-- src/widget.rs | 64 +++++++++---------- 13 files changed, 59 insertions(+), 59 deletions(-) rename {swbuf => softbuffer}/Cargo.toml (89%) rename {swbuf => softbuffer}/README.md (58%) rename {swbuf => softbuffer}/src/backend.rs (100%) rename {swbuf => softbuffer}/src/lib.rs (90%) rename {swbuf => softbuffer}/src/settings.rs (100%) rename {swbuf => softbuffer}/src/surface.rs (99%) rename {swbuf => softbuffer}/src/window.rs (100%) rename {swbuf => softbuffer}/src/window/compositor.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 7a8f4610dc..5cfac768e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,20 +12,20 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] -default = ["swbuf", "winit"] +default = ["softbuffer", "winit"] # Enables the `Image` widget -image = ["iced_wgpu?/image", "iced_glow?/image", "iced_swbuf?/image", "image_rs"] +image = ["iced_wgpu?/image", "iced_glow?/image", "iced_softbuffer?/image", "image_rs"] # Enables the `Svg` widget -svg = ["iced_wgpu?/svg", "iced_glow?/svg", "iced_swbuf?/svg"] +svg = ["iced_wgpu?/svg", "iced_glow?/svg", "iced_softbuffer?/svg"] # Enables the `Canvas` widget canvas = ["iced_graphics/canvas"] # Enables the `QRCode` widget qr_code = ["iced_graphics/qr_code"] -# Enables the `iced_wgpu` renderer. Conflicts with `iced_glow` and `iced_swbuf` +# Enables the `iced_wgpu` renderer. Conflicts with `iced_glow` and `iced_softbuffer` wgpu = ["iced_wgpu"] -# Enables the `iced_swbuf` renderer. Conflicts with `iced_wgpu` and `iced_glow` -swbuf = ["iced_swbuf"] -# Enables the `iced_glow` renderer. Conflicts with `iced_wgpu` and `iced_swbuf` +# Enables the `iced_softbuffer` renderer. Conflicts with `iced_wgpu` and `iced_glow` +softbuffer = ["iced_softbuffer"] +# Enables the `iced_glow` renderer. Conflicts with `iced_wgpu` and `iced_softbuffer` glow = ["iced_glow"] # Enables using system fonts default_system_font = ["iced_wgpu?/default_system_font", "iced_glow?/default_system_font"] @@ -42,10 +42,10 @@ palette = ["iced_core/palette"] # Enables querying system information system = ["iced_winit?/system"] # Enables the glutin shell. Conflicts with `sctk` and `winit`. -# Forces the `glow` renderer, further conflicting with `wgpu` and `swbuf`. +# Forces the `glow` renderer, further conflicting with `wgpu` and `softbuffer`. glutin = ["iced_glutin", "iced_glow"] # Enables the wayland shell. Conflicts with `winit` and `glutin`. -# Incompatible with the `swbuf` and `wgpu` renderer. +# Incompatible with the `softbuffer` and `wgpu` renderer. wayland = ["iced_sctk"] # Enables the winit shell. Conflicts with `wayland` and `glutin`. winit = ["iced_winit"] @@ -62,7 +62,7 @@ members = [ "glutin", "lazy", "native", - "swbuf", + "softbuffer", "style", "wgpu", "winit", @@ -79,7 +79,7 @@ iced_winit = { version = "0.6", path = "winit", features = ["application"], opti iced_glutin = { version = "0.5", path = "glutin", optional = true } iced_glow = { version = "0.5", path = "glow", optional = true } iced_sctk = { path = "./sctk", optional = true } -iced_swbuf = { version = "0.1", path = "swbuf", optional = true } +iced_softbuffer = { version = "0.1", path = "softbuffer", optional = true } thiserror = "1.0" [dependencies.image_rs] diff --git a/swbuf/Cargo.toml b/softbuffer/Cargo.toml similarity index 89% rename from swbuf/Cargo.toml rename to softbuffer/Cargo.toml index 36ef755415..516b19f196 100644 --- a/swbuf/Cargo.toml +++ b/softbuffer/Cargo.toml @@ -1,9 +1,9 @@ [package] -name = "iced_swbuf" +name = "iced_softbuffer" version = "0.1.0" authors = ["Jeremy Soller "] edition = "2021" -description = "A swbuf renderer for Iced" +description = "A softbuffer renderer for Iced" license = "MIT AND OFL-1.1" repository = "https://github.com/iced-rs/iced" @@ -13,7 +13,7 @@ lazy_static = "1.4" log = "0.4" raw-window-handle = "0.5" raqote = { version = "0.8", default-features = false } -softbuffer = { git = "https://github.com/rust-windowing/softbuffer", rev = "d5bb2c1" } +softbuffer = { git = "https://github.com/rust-windowing/softbuffer" } [dependencies.iced_native] path = "../native" diff --git a/swbuf/README.md b/softbuffer/README.md similarity index 58% rename from swbuf/README.md rename to softbuffer/README.md index 0c1d530c83..e656fd2e4c 100644 --- a/swbuf/README.md +++ b/softbuffer/README.md @@ -1,3 +1,3 @@ -# `iced_swbuf` +# `iced_softbuffer` Software rendering for Iced \ No newline at end of file diff --git a/swbuf/src/backend.rs b/softbuffer/src/backend.rs similarity index 100% rename from swbuf/src/backend.rs rename to softbuffer/src/backend.rs diff --git a/swbuf/src/lib.rs b/softbuffer/src/lib.rs similarity index 90% rename from swbuf/src/lib.rs rename to softbuffer/src/lib.rs index 936e50e3d2..2774ed1216 100644 --- a/swbuf/src/lib.rs +++ b/softbuffer/src/lib.rs @@ -1,4 +1,4 @@ -//! A [`swbuf`] renderer for [`iced_native`]. +//! A [`softbuffer`] renderer for [`iced_native`]. #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] diff --git a/swbuf/src/settings.rs b/softbuffer/src/settings.rs similarity index 100% rename from swbuf/src/settings.rs rename to softbuffer/src/settings.rs diff --git a/swbuf/src/surface.rs b/softbuffer/src/surface.rs similarity index 99% rename from swbuf/src/surface.rs rename to softbuffer/src/surface.rs index 4237c07e3b..cdf7d0bede 100644 --- a/swbuf/src/surface.rs +++ b/softbuffer/src/surface.rs @@ -27,7 +27,7 @@ impl Surface { ) -> Self { let context = match unsafe { GraphicsContext::new(window, window) } { Ok(ok) => ok, - Err(err) => panic!("failed to create swbuf context: {}", err), + Err(err) => panic!("failed to create softbuffer context: {}", err), }; Surface { context, diff --git a/swbuf/src/window.rs b/softbuffer/src/window.rs similarity index 100% rename from swbuf/src/window.rs rename to softbuffer/src/window.rs diff --git a/swbuf/src/window/compositor.rs b/softbuffer/src/window/compositor.rs similarity index 100% rename from swbuf/src/window/compositor.rs rename to softbuffer/src/window/compositor.rs diff --git a/src/element.rs b/src/element.rs index 19627fd627..be28d32734 100644 --- a/src/element.rs +++ b/src/element.rs @@ -1,6 +1,6 @@ /// A generic widget. /// /// This is an alias of an `iced_native` element with a default `Renderer`. -#[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type Element<'a, Message, Renderer = crate::Renderer> = iced_native::Element<'a, Message, Renderer>; diff --git a/src/lib.rs b/src/lib.rs index 61a3457b4f..9601162e48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -198,7 +198,7 @@ pub mod window; #[cfg(all( not(feature = "glow"), - any(feature = "wgpu", feature = "swbuf"), + any(feature = "wgpu", feature = "softbuffer"), not(feature = "wayland"), feature = "multi_window" ))] @@ -219,18 +219,18 @@ use iced_wgpu as renderer; #[cfg(any(feature = "glow", feature = "glutin"))] use iced_glow as renderer; -#[cfg(feature = "swbuf")] -use iced_swbuf as renderer; +#[cfg(feature = "softbuffer")] +use iced_softbuffer as renderer; pub use iced_native::theme; -#[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub use element::Element; pub use error::Error; pub use event::Event; #[cfg(any(feature = "winit", feature = "wayland"))] pub use executor::Executor; -#[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub use renderer::Renderer; pub use result::Result; #[cfg(any(feature = "winit", feature = "wayland"))] diff --git a/src/overlay.rs b/src/overlay.rs index 821bebba85..46aea11dc1 100644 --- a/src/overlay.rs +++ b/src/overlay.rs @@ -5,10 +5,10 @@ /// This is an alias of an `iced_native` element with a default `Renderer`. /// /// [`Overlay`]: iced_native::Overlay -#[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type Element<'a, Message, Renderer = crate::Renderer> = iced_native::overlay::Element<'a, Message, Renderer>; -#[cfg(not(any(feature = "swbuf", feature = "glow", feature = "wgpu")))] +#[cfg(not(any(feature = "softbuffer", feature = "glow", feature = "wgpu")))] pub use iced_native::overlay::Element; pub mod menu { @@ -16,11 +16,11 @@ pub mod menu { pub use iced_native::overlay::menu::{Appearance, State, StyleSheet}; /// A widget that produces a message when clicked. - #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type Menu<'a, Message, Renderer = crate::Renderer> = iced_native::overlay::Menu<'a, Message, Renderer>; #[cfg(not(any( - feature = "swbuf", + feature = "softbuffer", feature = "glow", feature = "wgpu" )))] diff --git a/src/widget.rs b/src/widget.rs index 6712397805..bbc5e4f4a0 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -4,17 +4,17 @@ pub use iced_native::widget::helpers::*; pub use iced_native::{column, row}; /// A container that distributes its contents vertically. -#[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type Column<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Column<'a, Message, Renderer>; -#[cfg(not(any(feature = "swbuf", feature = "glow", feature = "wgpu")))] +#[cfg(not(any(feature = "softbuffer", feature = "glow", feature = "wgpu")))] pub use iced_native::widget::Column; /// A container that distributes its contents horizontally. -#[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type Row<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Row<'a, Message, Renderer>; -#[cfg(not(any(feature = "swbuf", feature = "glow", feature = "wgpu")))] +#[cfg(not(any(feature = "softbuffer", feature = "glow", feature = "wgpu")))] pub use iced_native::widget::Row; pub mod text { @@ -22,11 +22,11 @@ pub mod text { pub use iced_native::widget::text::{Appearance, StyleSheet}; /// A paragraph of text. - #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type Text<'a, Renderer = crate::Renderer> = iced_native::widget::Text<'a, Renderer>; #[cfg(not(any( - feature = "swbuf", + feature = "softbuffer", feature = "glow", feature = "wgpu" )))] @@ -38,11 +38,11 @@ pub mod button { pub use iced_native::widget::button::{focus, Appearance, Id, StyleSheet}; /// A widget that produces a message when clicked. - #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type Button<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Button<'a, Message, Renderer>; #[cfg(not(any( - feature = "swbuf", + feature = "softbuffer", feature = "glow", feature = "wgpu" )))] @@ -54,11 +54,11 @@ pub mod checkbox { pub use iced_native::widget::checkbox::{Appearance, StyleSheet}; /// A box that can be checked. - #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type Checkbox<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Checkbox<'a, Message, Renderer>; #[cfg(not(any( - feature = "swbuf", + feature = "softbuffer", feature = "glow", feature = "wgpu" )))] @@ -70,11 +70,11 @@ pub mod container { pub use iced_native::widget::container::{Appearance, StyleSheet}; /// An element decorating some content. - #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type Container<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Container<'a, Message, Renderer>; #[cfg(not(any( - feature = "swbuf", + feature = "softbuffer", feature = "glow", feature = "wgpu" )))] @@ -85,11 +85,11 @@ pub mod mouse_listener { //! Intercept mouse events on a widget. /// A container intercepting mouse events. - #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type MouseListener<'a, Message, Renderer = crate::Renderer> = iced_native::widget::MouseListener<'a, Message, Renderer>; #[cfg(not(any( - feature = "swbuf", + feature = "softbuffer", feature = "glow", feature = "wgpu" )))] @@ -115,33 +115,33 @@ pub mod pane_grid { /// to completely fill the space available. /// /// [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) - #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type PaneGrid<'a, Message, Renderer = crate::Renderer> = iced_native::widget::PaneGrid<'a, Message, Renderer>; #[cfg(not(any( - feature = "swbuf", + feature = "softbuffer", feature = "glow", feature = "wgpu" )))] pub use iced_native::widget::PaneGrid; /// The content of a [`Pane`]. - #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type Content<'a, Message, Renderer = crate::Renderer> = iced_native::widget::pane_grid::Content<'a, Message, Renderer>; #[cfg(not(any( - feature = "swbuf", + feature = "softbuffer", feature = "glow", feature = "wgpu" )))] pub use iced_native::widget::pane_grid::Content; /// The title bar of a [`Pane`]. - #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type TitleBar<'a, Message, Renderer = crate::Renderer> = iced_native::widget::pane_grid::TitleBar<'a, Message, Renderer>; #[cfg(not(any( - feature = "swbuf", + feature = "softbuffer", feature = "glow", feature = "wgpu" )))] @@ -153,11 +153,11 @@ pub mod pick_list { pub use iced_native::widget::pick_list::{Appearance, StyleSheet}; /// A widget allowing the selection of a single value from a list of options. - #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type PickList<'a, T, Message, Renderer = crate::Renderer> = iced_native::widget::PickList<'a, T, Message, Renderer>; #[cfg(not(any( - feature = "swbuf", + feature = "softbuffer", feature = "glow", feature = "wgpu" )))] @@ -169,11 +169,11 @@ pub mod radio { pub use iced_native::widget::radio::{Appearance, StyleSheet}; /// A circular button representing a choice. - #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type Radio = iced_native::widget::Radio; #[cfg(not(any( - feature = "swbuf", + feature = "softbuffer", feature = "glow", feature = "wgpu" )))] @@ -188,11 +188,11 @@ pub mod scrollable { /// A widget that can vertically display an infinite amount of content /// with a scrollbar. - #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type Scrollable<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Scrollable<'a, Message, Renderer>; #[cfg(not(any( - feature = "swbuf", + feature = "softbuffer", feature = "glow", feature = "wgpu" )))] @@ -204,11 +204,11 @@ pub mod toggler { pub use iced_native::widget::toggler::{Appearance, StyleSheet}; /// A toggler widget. - #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type Toggler<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Toggler<'a, Message, Renderer>; #[cfg(not(any( - feature = "swbuf", + feature = "softbuffer", feature = "glow", feature = "wgpu" )))] @@ -223,11 +223,11 @@ pub mod text_input { }; /// A field that can be filled with text. - #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type TextInput<'a, Message, Renderer = crate::Renderer> = iced_native::widget::TextInput<'a, Message, Renderer>; #[cfg(not(any( - feature = "swbuf", + feature = "softbuffer", feature = "glow", feature = "wgpu" )))] @@ -239,11 +239,11 @@ pub mod tooltip { pub use iced_native::widget::tooltip::Position; /// A widget allowing the selection of a single value from a list of options. - #[cfg(any(feature = "swbuf", feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] pub type Tooltip<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Tooltip<'a, Message, Renderer>; #[cfg(not(any( - feature = "swbuf", + feature = "softbuffer", feature = "glow", feature = "wgpu" )))] From fb31dfb6cacad5fb26773f82069f1b65bbd7c99d Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 6 Jan 2023 09:25:02 -0700 Subject: [PATCH 38/56] Use released version of softbuffer --- softbuffer/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/softbuffer/Cargo.toml b/softbuffer/Cargo.toml index 516b19f196..ec775c88ee 100644 --- a/softbuffer/Cargo.toml +++ b/softbuffer/Cargo.toml @@ -13,7 +13,7 @@ lazy_static = "1.4" log = "0.4" raw-window-handle = "0.5" raqote = { version = "0.8", default-features = false } -softbuffer = { git = "https://github.com/rust-windowing/softbuffer" } +softbuffer = "0.2" [dependencies.iced_native] path = "../native" From c9ee0428aca30fd431f6a4e0e7aedc21ff6f8a33 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 6 Jan 2023 17:59:40 +0100 Subject: [PATCH 39/56] softbuffer: export draw_primitives --- softbuffer/src/lib.rs | 5 +++++ softbuffer/src/surface.rs | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/softbuffer/src/lib.rs b/softbuffer/src/lib.rs index 2774ed1216..69b97be07b 100644 --- a/softbuffer/src/lib.rs +++ b/softbuffer/src/lib.rs @@ -15,6 +15,11 @@ pub use self::settings::Settings; pub(crate) mod surface; +pub mod native { + pub use crate::surface::draw_primitive; + pub use raqote; +} + pub mod window; pub type Renderer = diff --git a/softbuffer/src/surface.rs b/softbuffer/src/surface.rs index cdf7d0bede..a942f7463a 100644 --- a/softbuffer/src/surface.rs +++ b/softbuffer/src/surface.rs @@ -10,8 +10,8 @@ use raqote::{ SolidSource, Source, StrokeStyle, Transform, }; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; -use std::cmp; use softbuffer::GraphicsContext; +use std::cmp; // A software rendering surface pub struct Surface { @@ -96,7 +96,7 @@ impl Surface { } } -fn draw_primitive( +pub fn draw_primitive( draw_target: &mut DrawTarget<&mut [u32]>, draw_options: &DrawOptions, backend: &mut Backend, From 4019d1b1f3037facbbec50c8dcaae228b217ee02 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 6 Jan 2023 11:41:57 -0700 Subject: [PATCH 40/56] HiDPI support for softbuffer --- softbuffer/src/surface.rs | 141 ++++++++++++++++++++++++---- softbuffer/src/window/compositor.rs | 2 +- 2 files changed, 122 insertions(+), 21 deletions(-) diff --git a/softbuffer/src/surface.rs b/softbuffer/src/surface.rs index a942f7463a..2b4301d6a1 100644 --- a/softbuffer/src/surface.rs +++ b/softbuffer/src/surface.rs @@ -4,10 +4,10 @@ use cosmic_text::{AttrsList, BufferLine, SwashContent}; use iced_graphics::alignment::{Horizontal, Vertical}; #[cfg(feature = "svg")] use iced_graphics::image::vector; -use iced_graphics::{Background, Gradient, Primitive}; +use iced_graphics::{Background, Gradient, Point, Primitive, Rectangle, Size}; use raqote::{ DrawOptions, DrawTarget, Image, IntPoint, IntRect, PathBuilder, - SolidSource, Source, StrokeStyle, Transform, + SolidSource, Source, StrokeStyle, Transform, Vector, }; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use softbuffer::GraphicsContext; @@ -46,6 +46,7 @@ impl Surface { pub(crate) fn present( &mut self, renderer: &mut crate::Renderer, + scale_factor: f32, background: iced_graphics::Color, ) { { @@ -80,6 +81,7 @@ impl Surface { &mut draw_target, &draw_options, backend, + scale_factor, primitive, ); } @@ -100,13 +102,37 @@ pub fn draw_primitive( draw_target: &mut DrawTarget<&mut [u32]>, draw_options: &DrawOptions, backend: &mut Backend, + scale_factor: f32, primitive: &Primitive, ) { + let scale_size = |size: f32, align: bool| -> f32 { + if align { + (size * scale_factor).round() + } else { + size * scale_factor + } + }; + let scale_rect = |rect: &Rectangle, align: bool| -> Rectangle { + Rectangle::new( + Point::new(scale_size(rect.x, align), scale_size(rect.y, align)), + Size::new( + scale_size(rect.width, align), + scale_size(rect.height, align), + ), + ) + }; + match primitive { Primitive::None => (), Primitive::Group { primitives } => { for child in primitives.iter() { - draw_primitive(draw_target, draw_options, backend, child); + draw_primitive( + draw_target, + draw_options, + backend, + scale_factor, + child, + ); } } Primitive::Text { @@ -118,12 +144,17 @@ pub fn draw_primitive( horizontal_alignment, vertical_alignment, } => { + // Apply scaling + //TODO: align to integers? + let bounds = scale_rect(bounds, false); + let size = scale_size(*size, false); + let cosmic_color = { let rgba8 = color.into_rgba8(); cosmic_text::Color::rgba(rgba8[0], rgba8[1], rgba8[2], rgba8[3]) }; - let (metrics, attrs) = backend.cosmic_metrics_attrs(*size, &font); + let (metrics, attrs) = backend.cosmic_metrics_attrs(size, &font); /* // Debug bounds in green @@ -198,7 +229,7 @@ pub fn draw_primitive( line_x, line_y, line_width, metrics.line_height, bounds, - *size, + size, horizontal_alignment, vertical_alignment ); @@ -341,6 +372,17 @@ pub fn draw_primitive( border_width, border_color, } => { + // Apply scaling + //TODO: align to integers? + let bounds = scale_rect(bounds, false); + let border_radius = [ + scale_size(border_radius[0], false), + scale_size(border_radius[1], false), + scale_size(border_radius[2], false), + scale_size(border_radius[3], false), + ]; + let border_width = scale_size(*border_width, false); + // Ensure radius is not too large let clamp_radius = |radius: f32| -> f32 { if radius > bounds.width / 2.0 { @@ -435,7 +477,7 @@ pub fn draw_primitive( }; let style = StrokeStyle { - width: *border_width, + width: border_width, ..Default::default() }; @@ -451,6 +493,10 @@ pub fn draw_primitive( ); } Primitive::Image { handle, bounds } => { + // Apply scaling + //TODO: align to integers? + let bounds = scale_rect(bounds, false); + #[cfg(feature = "image")] match backend.raster_cache.borrow_mut().upload( handle, @@ -479,6 +525,10 @@ pub fn draw_primitive( bounds, color, } => { + // Apply scaling + //TODO: align to integers? + let bounds = scale_rect(bounds, false); + #[cfg(feature = "svg")] match backend.vector_cache.borrow_mut().upload( handle, @@ -506,6 +556,10 @@ pub fn draw_primitive( } } Primitive::Clip { bounds, content } => { + // Apply scaling + //TODO: align to integers? + let bounds = scale_rect(bounds, false); + draw_target.push_clip_rect(IntRect::new( IntPoint::new(bounds.x as i32, bounds.y as i32), IntPoint::new( @@ -513,19 +567,38 @@ pub fn draw_primitive( (bounds.y + bounds.height) as i32, ), )); - draw_primitive(draw_target, draw_options, backend, &content); + draw_primitive( + draw_target, + draw_options, + backend, + scale_factor, + &content, + ); draw_target.pop_clip(); } Primitive::Translate { translation, content, } => { - draw_target.set_transform(&Transform::translation( - translation.x, - translation.y, - )); - draw_primitive(draw_target, draw_options, backend, &content); - draw_target.set_transform(&Transform::identity()); + // Apply scaling + //TODO: align to integers? + let translation = Vector::new( + scale_size(translation.x, false), + scale_size(translation.y, false), + ); + + let transform = draw_target.get_transform().clone(); + draw_target.set_transform(&transform.then_translate(translation)); + + draw_primitive( + draw_target, + draw_options, + backend, + scale_factor, + &content, + ); + + draw_target.set_transform(&transform); } Primitive::GradientMesh { buffers, @@ -567,9 +640,20 @@ pub fn draw_primitive( let b = &buffers.vertices[indices[1] as usize]; let c = &buffers.vertices[indices[2] as usize]; - pb.move_to(a.position[0], a.position[1]); - pb.line_to(b.position[0], b.position[1]); - pb.line_to(c.position[0], c.position[1]); + // Scaling is applied here + //TODO: align to integers? + pb.move_to( + scale_size(a.position[0], false), + scale_size(a.position[1], false), + ); + pb.line_to( + scale_size(b.position[0], false), + scale_size(b.position[1], false), + ); + pb.line_to( + scale_size(c.position[0], false), + scale_size(c.position[1], false), + ); pb.close(); } @@ -614,10 +698,21 @@ pub fn draw_primitive( let b = &buffers.vertices[indices[1] as usize]; let c = &buffers.vertices[indices[2] as usize]; + // Scaling is applied here + //TODO: align to integers? let mut pb = PathBuilder::new(); - pb.move_to(a.position[0], a.position[1]); - pb.line_to(b.position[0], b.position[1]); - pb.line_to(c.position[0], c.position[1]); + pb.move_to( + scale_size(a.position[0], false), + scale_size(a.position[1], false), + ); + pb.line_to( + scale_size(b.position[0], false), + scale_size(b.position[1], false), + ); + pb.line_to( + scale_size(c.position[0], false), + scale_size(c.position[1], false), + ); pb.close(); // TODO: Each vertice has its own separate color. @@ -636,7 +731,13 @@ pub fn draw_primitive( */ } Primitive::Cached { cache } => { - draw_primitive(draw_target, draw_options, backend, &cache); + draw_primitive( + draw_target, + draw_options, + backend, + scale_factor, + &cache, + ); } } } diff --git a/softbuffer/src/window/compositor.rs b/softbuffer/src/window/compositor.rs index 7fedfc5bd4..2b0ce9db65 100644 --- a/softbuffer/src/window/compositor.rs +++ b/softbuffer/src/window/compositor.rs @@ -74,7 +74,7 @@ impl compositor::Compositor for Compositor { background: Color, overlay: &[T], ) -> Result<(), SurfaceError> { - surface.present(renderer, background); + surface.present(renderer, viewport.scale_factor() as f32, background); Ok(()) } } From 205fa6d38d65390d5d84efaff379e56d6f6cb990 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 6 Jan 2023 13:08:53 -0700 Subject: [PATCH 41/56] Fix softbuffer text wrapping when scaled --- softbuffer/src/surface.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/softbuffer/src/surface.rs b/softbuffer/src/surface.rs index 2b4301d6a1..cddf3322b2 100644 --- a/softbuffer/src/surface.rs +++ b/softbuffer/src/surface.rs @@ -1,6 +1,6 @@ use crate::backend::{Backend, CpuStorage, FONT_SYSTEM}; -use cosmic_text::{AttrsList, BufferLine, SwashContent}; +use cosmic_text::{AttrsList, BufferLine, Metrics, SwashContent}; use iced_graphics::alignment::{Horizontal, Vertical}; #[cfg(feature = "svg")] use iced_graphics::image::vector; @@ -147,14 +147,20 @@ pub fn draw_primitive( // Apply scaling //TODO: align to integers? let bounds = scale_rect(bounds, false); - let size = scale_size(*size, false); let cosmic_color = { let rgba8 = color.into_rgba8(); cosmic_text::Color::rgba(rgba8[0], rgba8[1], rgba8[2], rgba8[3]) }; - let (metrics, attrs) = backend.cosmic_metrics_attrs(size, &font); + let (metrics_unscaled, attrs) = + backend.cosmic_metrics_attrs(*size, &font); + // Scale metrics separately to avoid errors + //TODO: fix this by knowing correct scale when measuring text and doing hit test + let metrics = Metrics::new( + ((metrics_unscaled.font_size as f32) * scale_factor) as i32, + ((metrics_unscaled.line_height as f32) * scale_factor) as i32, + ); /* // Debug bounds in green @@ -229,7 +235,7 @@ pub fn draw_primitive( line_x, line_y, line_width, metrics.line_height, bounds, - size, + *size, horizontal_alignment, vertical_alignment ); From f3519149ad2410bd0b9f43496a5bfd699be25091 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 12 Jan 2023 00:33:45 -0500 Subject: [PATCH 42/56] fix typo --- sctk/src/application.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sctk/src/application.rs b/sctk/src/application.rs index 128ab983c3..f43d5d44f5 100644 --- a/sctk/src/application.rs +++ b/sctk/src/application.rs @@ -249,7 +249,7 @@ where let w = bounds.width as u32; let h = bounds.height as u32; builder.iced_settings.size = (w, h); - auto_size_surfaces.insert(SurfaceIdWrapper::LayerSurface(builder.window_id), (w, h, builder.size_limits, false)); + auto_size_surfaces.insert(SurfaceIdWrapper::Window(builder.window_id), (w, h, builder.size_limits, false)); }; let (native_id, surface) = event_loop.get_window(builder.clone()); From 47e52f1ddfeb1c756e0cf2277006a72fe4524ec2 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld <4404502+Drakulix@users.noreply.github.com> Date: Mon, 16 Jan 2023 18:09:11 +0100 Subject: [PATCH 43/56] iced: Use iced_native for keyboard types (#25) --- src/keyboard.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/keyboard.rs b/src/keyboard.rs index c565a8dab5..a1e45851f8 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -1,3 +1,2 @@ //! Listen and react to keyboard events. -#[cfg(any(feature = "winit", feature = "wayland"))] -pub use crate::runtime::keyboard::{Event, KeyCode, Modifiers}; +pub use iced_native::keyboard::{Event, KeyCode, Modifiers}; From f1310e47617c3046a3cd98e20e373247f19327af Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 20 Dec 2022 12:53:49 -0700 Subject: [PATCH 44/56] Dynamic renderer --- Cargo.toml | 12 +- dyrend/Cargo.toml | 47 +++++ dyrend/README.md | 3 + dyrend/src/lib.rs | 13 ++ dyrend/src/renderer.rs | 274 +++++++++++++++++++++++++++++ dyrend/src/settings.rs | 47 +++++ dyrend/src/window.rs | 4 + dyrend/src/window/compositor.rs | 295 ++++++++++++++++++++++++++++++++ graphics/src/renderer.rs | 5 + softbuffer/src/lib.rs | 3 - src/element.rs | 7 +- src/lib.rs | 19 +- src/overlay.rs | 24 ++- src/widget.rs | 168 ++++++++++++++---- 14 files changed, 874 insertions(+), 47 deletions(-) create mode 100644 dyrend/Cargo.toml create mode 100644 dyrend/README.md create mode 100644 dyrend/src/lib.rs create mode 100644 dyrend/src/renderer.rs create mode 100644 dyrend/src/settings.rs create mode 100644 dyrend/src/window.rs create mode 100644 dyrend/src/window/compositor.rs diff --git a/Cargo.toml b/Cargo.toml index 5cfac768e0..e94d77702a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,15 +12,17 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] -default = ["softbuffer", "winit"] +default = ["dyrend", "winit"] # Enables the `Image` widget -image = ["iced_wgpu?/image", "iced_glow?/image", "iced_softbuffer?/image", "image_rs"] +image = ["iced_wgpu?/image", "iced_glow?/image", "iced_softbuffer?/image", "iced_dyrend?/image", "image_rs"] # Enables the `Svg` widget -svg = ["iced_wgpu?/svg", "iced_glow?/svg", "iced_softbuffer?/svg"] +svg = ["iced_wgpu?/svg", "iced_glow?/svg", "iced_softbuffer?/svg", "iced_dyrend?/svg"] # Enables the `Canvas` widget canvas = ["iced_graphics/canvas"] # Enables the `QRCode` widget qr_code = ["iced_graphics/qr_code"] +# Enables the `iced_dyrend` renderer +dyrend = ["iced_dyrend"] # Enables the `iced_wgpu` renderer. Conflicts with `iced_glow` and `iced_softbuffer` wgpu = ["iced_wgpu"] # Enables the `iced_softbuffer` renderer. Conflicts with `iced_wgpu` and `iced_glow` @@ -56,6 +58,7 @@ maintenance = { status = "actively-developed" } [workspace] members = [ "core", + "dyrend", "futures", "graphics", "glow", @@ -72,6 +75,7 @@ members = [ [dependencies] iced_core = { version = "0.6", path = "core" } +iced_dyrend = { version = "0.1", path = "dyrend", optional = true } iced_futures = { version = "0.5", path = "futures" } iced_native = { version = "0.7", path = "native" } iced_graphics = { version = "0.5", path = "graphics" } @@ -109,4 +113,4 @@ strip = "debuginfo" [patch."https://github.com/iced-rs/winit.git".winit] git = "https://github.com/pop-os/winit.git" -branch = "iced" \ No newline at end of file +branch = "iced" diff --git a/dyrend/Cargo.toml b/dyrend/Cargo.toml new file mode 100644 index 0000000000..50986a5e8d --- /dev/null +++ b/dyrend/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "iced_dyrend" +version = "0.1.0" +authors = ["Jeremy Soller "] +edition = "2021" +description = "A dynamicly chosen renderer for Iced" +license = "MIT AND OFL-1.1" +repository = "https://github.com/iced-rs/iced" + +[dependencies] +log = "0.4" +raw-window-handle = "0.5" + +[dependencies.iced_native] +path = "../native" + +[dependencies.iced_graphics] +path = "../graphics" +features = ["font-fallback", "font-icons"] + +[dependencies.iced_glow] +path = "../glow" +default-features = false +optional = true + +[dependencies.iced_softbuffer] +path = "../softbuffer" +default-features = false +optional = true + +[dependencies.iced_wgpu] +path = "../wgpu" +default-features = false +optional = true + +[features] +default = ["softbuffer", "wgpu"] +image = ["iced_graphics/image", "iced_glow?/image", "iced_softbuffer?/image", "iced_wgpu?/image"] +svg = ["iced_graphics/svg", "iced_glow?/svg", "iced_softbuffer?/svg", "iced_wgpu?/svg"] +#TODO: implement Compositor for glow Backend +#glow = ["iced_glow"] +softbuffer = ["iced_softbuffer"] +wgpu = ["iced_wgpu"] + +[package.metadata.docs.rs] +rustdoc-args = ["--cfg", "docsrs"] +all-features = true diff --git a/dyrend/README.md b/dyrend/README.md new file mode 100644 index 0000000000..e656fd2e4c --- /dev/null +++ b/dyrend/README.md @@ -0,0 +1,3 @@ +# `iced_softbuffer` + +Software rendering for Iced \ No newline at end of file diff --git a/dyrend/src/lib.rs b/dyrend/src/lib.rs new file mode 100644 index 0000000000..f541fb5cb3 --- /dev/null +++ b/dyrend/src/lib.rs @@ -0,0 +1,13 @@ +//! A [`softbuffer`] renderer for [`iced_native`]. +#![doc( + html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" +)] +#![cfg_attr(docsrs, feature(doc_cfg))] + +mod renderer; +pub use self::renderer::Renderer; + +pub mod settings; +pub use self::settings::Settings; + +pub mod window; diff --git a/dyrend/src/renderer.rs b/dyrend/src/renderer.rs new file mode 100644 index 0000000000..c938fd4cf8 --- /dev/null +++ b/dyrend/src/renderer.rs @@ -0,0 +1,274 @@ +use iced_graphics::{Backend, Vector}; +#[cfg(feature = "image")] +use iced_native::image; +use iced_native::layout; +use iced_native::renderer; +#[cfg(feature = "svg")] +use iced_native::svg; +use iced_native::text::{self, Text}; +use iced_native::{Background, Color, Element, Font, Point, Rectangle, Size}; + +pub enum Renderer { + #[cfg(feature = "glow")] + Glow(iced_glow::Renderer), + #[cfg(feature = "softbuffer")] + softbuffer(iced_softbuffer::Renderer), + #[cfg(feature = "wgpu")] + Wgpu(iced_wgpu::Renderer), +} + +impl iced_native::Renderer for Renderer { + type Theme = T; + + fn layout<'a, Message>( + &mut self, + element: &Element<'a, Message, Self>, + limits: &layout::Limits, + ) -> layout::Node { + let layout = element.as_widget().layout(self, limits); + + match self { + #[cfg(feature = "glow")] + Renderer::Glow(renderer) => { + renderer.backend_mut().trim_measurements(); + } + #[cfg(feature = "softbuffer")] + Renderer::softbuffer(renderer) => { + renderer.backend_mut().trim_measurements(); + } + #[cfg(feature = "wgpu")] + Renderer::Wgpu(renderer) => { + renderer.backend_mut().trim_measurements(); + } + } + + layout + } + + fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { + let self_ptr = self as *mut _; + match self { + #[cfg(feature = "glow")] + Renderer::Glow(renderer) => unsafe { + // TODO: find a way to do this safely + renderer.with_layer(bounds, |_| f(&mut *self_ptr)) + }, + #[cfg(feature = "softbuffer")] + Renderer::softbuffer(renderer) => unsafe { + // TODO: find a way to do this safely + renderer.with_layer(bounds, |_| f(&mut *self_ptr)) + }, + #[cfg(feature = "wgpu")] + Renderer::Wgpu(renderer) => unsafe { + // TODO: find a way to do this safely + renderer.with_layer(bounds, |_| f(&mut *self_ptr)) + }, + } + } + + fn with_translation( + &mut self, + translation: Vector, + f: impl FnOnce(&mut Self), + ) { + let self_ptr = self as *mut _; + match self { + #[cfg(feature = "glow")] + Renderer::Glow(renderer) => unsafe { + // TODO: find a way to do this safely + renderer.with_translation(translation, |_| f(&mut *self_ptr)) + }, + #[cfg(feature = "softbuffer")] + Renderer::softbuffer(renderer) => unsafe { + // TODO: find a way to do this safely + renderer.with_translation(translation, |_| f(&mut *self_ptr)) + }, + #[cfg(feature = "wgpu")] + Renderer::Wgpu(renderer) => unsafe { + // TODO: find a way to do this safely + renderer.with_translation(translation, |_| f(&mut *self_ptr)) + }, + } + } + + fn fill_quad( + &mut self, + quad: renderer::Quad, + background: impl Into, + ) { + match self { + #[cfg(feature = "glow")] + Renderer::Glow(renderer) => renderer.fill_quad(quad, background), + #[cfg(feature = "softbuffer")] + Renderer::softbuffer(renderer) => { + renderer.fill_quad(quad, background) + } + #[cfg(feature = "wgpu")] + Renderer::Wgpu(renderer) => renderer.fill_quad(quad, background), + } + } + + fn clear(&mut self) { + match self { + #[cfg(feature = "glow")] + Renderer::Glow(renderer) => renderer.clear(), + #[cfg(feature = "softbuffer")] + Renderer::softbuffer(renderer) => renderer.clear(), + #[cfg(feature = "wgpu")] + Renderer::Wgpu(renderer) => renderer.clear(), + } + } +} + +impl text::Renderer for Renderer { + type Font = Font; + + //TODO: use the right values here for each backend + const ICON_FONT: Font = Font::Default; + const CHECKMARK_ICON: char = '✓'; + const ARROW_DOWN_ICON: char = '⌄'; + + fn default_size(&self) -> u16 { + match self { + #[cfg(feature = "glow")] + Renderer::Glow(renderer) => renderer.default_size(), + #[cfg(feature = "softbuffer")] + Renderer::softbuffer(renderer) => renderer.default_size(), + #[cfg(feature = "wgpu")] + Renderer::Wgpu(renderer) => renderer.default_size(), + } + } + + fn measure( + &self, + content: &str, + size: u16, + font: Font, + bounds: Size, + ) -> (f32, f32) { + match self { + #[cfg(feature = "glow")] + Renderer::Glow(renderer) => { + renderer.measure(content, size, font, bounds) + } + #[cfg(feature = "softbuffer")] + Renderer::softbuffer(renderer) => { + renderer.measure(content, size, font, bounds) + } + #[cfg(feature = "wgpu")] + Renderer::Wgpu(renderer) => { + renderer.measure(content, size, font, bounds) + } + } + } + + fn hit_test( + &self, + content: &str, + size: f32, + font: Font, + bounds: Size, + point: Point, + nearest_only: bool, + ) -> Option { + match self { + #[cfg(feature = "glow")] + Renderer::Glow(renderer) => renderer.hit_test( + content, + size, + font, + bounds, + point, + nearest_only, + ), + #[cfg(feature = "softbuffer")] + Renderer::softbuffer(renderer) => renderer.hit_test( + content, + size, + font, + bounds, + point, + nearest_only, + ), + #[cfg(feature = "wgpu")] + Renderer::Wgpu(renderer) => renderer.hit_test( + content, + size, + font, + bounds, + point, + nearest_only, + ), + } + } + + fn fill_text(&mut self, text: Text<'_, Self::Font>) { + match self { + #[cfg(feature = "glow")] + Renderer::Glow(renderer) => renderer.fill_text(text), + #[cfg(feature = "softbuffer")] + Renderer::softbuffer(renderer) => renderer.fill_text(text), + #[cfg(feature = "wgpu")] + Renderer::Wgpu(renderer) => renderer.fill_text(text), + } + } +} + +#[cfg(feature = "image")] +impl image::Renderer for Renderer { + type Handle = image::Handle; + + fn dimensions(&self, handle: &image::Handle) -> Size { + match self { + #[cfg(feature = "glow")] + Renderer::Glow(renderer) => renderer.dimensions(handle), + #[cfg(feature = "softbuffer")] + Renderer::softbuffer(renderer) => renderer.dimensions(handle), + #[cfg(feature = "wgpu")] + Renderer::Wgpu(renderer) => renderer.dimensions(handle), + } + } + + fn draw(&mut self, handle: image::Handle, bounds: Rectangle) { + match self { + #[cfg(feature = "glow")] + Renderer::Glow(renderer) => renderer.draw(handle, bounds), + #[cfg(feature = "softbuffer")] + Renderer::softbuffer(renderer) => renderer.draw(handle, bounds), + #[cfg(feature = "wgpu")] + Renderer::Wgpu(renderer) => renderer.draw(handle, bounds), + } + } +} + +#[cfg(feature = "svg")] +impl svg::Renderer for Renderer { + fn dimensions(&self, handle: &svg::Handle) -> Size { + match self { + #[cfg(feature = "glow")] + Renderer::Glow(renderer) => renderer.dimensions(handle), + #[cfg(feature = "softbuffer")] + Renderer::softbuffer(renderer) => renderer.dimensions(handle), + #[cfg(feature = "wgpu")] + Renderer::Wgpu(renderer) => renderer.dimensions(handle), + } + } + + fn draw( + &mut self, + handle: svg::Handle, + color: Option, + bounds: Rectangle, + ) { + match self { + #[cfg(feature = "glow")] + Renderer::Glow(renderer) => renderer.draw(handle, color, bounds), + #[cfg(feature = "softbuffer")] + Renderer::softbuffer(renderer) => { + renderer.draw(handle, color, bounds) + } + #[cfg(feature = "wgpu")] + Renderer::Wgpu(renderer) => renderer.draw(handle, color, bounds), + } + } +} diff --git a/dyrend/src/settings.rs b/dyrend/src/settings.rs new file mode 100644 index 0000000000..ac4a09c7e1 --- /dev/null +++ b/dyrend/src/settings.rs @@ -0,0 +1,47 @@ +//! Configure a renderer. +pub use iced_graphics::Antialiasing; + +/// The settings of a [`Backend`]. +/// +/// [`Backend`]: crate::Backend +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Settings { + /// The bytes of the font that will be used by default. + /// + /// If `None` is provided, a default system font will be chosen. + pub default_font: Option<&'static [u8]>, + + /// The default size of text. + /// + /// By default, it will be set to 20. + pub default_text_size: u16, + + /// If enabled, spread text workload in multiple threads when multiple cores + /// are available. + /// + /// By default, it is disabled. + pub text_multithreading: bool, + + /// The antialiasing strategy that will be used for triangle primitives. + /// + /// By default, it is `None`. + pub antialiasing: Option, +} + +impl Settings { + /// Creates new [`Settings`] using environment configuration. + pub fn from_env() -> Self { + Settings::default() + } +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + default_font: None, + default_text_size: 20, + text_multithreading: false, + antialiasing: None, + } + } +} diff --git a/dyrend/src/window.rs b/dyrend/src/window.rs new file mode 100644 index 0000000000..aac5fb9ed8 --- /dev/null +++ b/dyrend/src/window.rs @@ -0,0 +1,4 @@ +//! Display rendering results on windows. +mod compositor; + +pub use compositor::Compositor; diff --git a/dyrend/src/window/compositor.rs b/dyrend/src/window/compositor.rs new file mode 100644 index 0000000000..4a9ec657d5 --- /dev/null +++ b/dyrend/src/window/compositor.rs @@ -0,0 +1,295 @@ +#[cfg(feature = "glow")] +use iced_glow::window::Compositor as GlowCompositor; +use iced_graphics::{ + compositor::{self, Compositor as _, Information, SurfaceError}, + Color, Error, Viewport, +}; +#[cfg(feature = "softbuffer")] +use iced_softbuffer::window::Compositor as softbufferCompositor; +#[cfg(feature = "wgpu")] +use iced_wgpu::window::Compositor as WgpuCompositor; +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use std::env; + +use crate::Renderer; + +/// A window graphics backend for iced powered by `glow`. +pub enum Compositor { + #[cfg(feature = "glow")] + Glow(GlowCompositor), + #[cfg(feature = "softbuffer")] + softbuffer(softbufferCompositor), + #[cfg(feature = "wgpu")] + Wgpu(WgpuCompositor), +} + +pub enum Surface { + #[cfg(feature = "glow")] + Glow( as compositor::Compositor>::Surface), + #[cfg(feature = "softbuffer")] + softbuffer( + as compositor::Compositor>::Surface, + ), + #[cfg(feature = "wgpu")] + Wgpu( as compositor::Compositor>::Surface), +} + +impl Compositor { + #[cfg(feature = "glow")] + fn new_glow( + settings: crate::Settings, + compatible_window: Option<&W>, + ) -> Result<(Self, Renderer), Error> { + match GlowCompositor::new( + iced_glow::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + text_multithreading: settings.text_multithreading, + antialiasing: settings.antialiasing, + ..iced_glow::Settings::from_env() + }, + compatible_window, + ) { + Ok((compositor, renderer)) => { + Ok((Compositor::Glow(compositor), Renderer::Glow(renderer))) + } + Err(err) => Err(err), + } + } + + #[cfg(feature = "softbuffer")] + fn new_softbuffer( + settings: crate::Settings, + compatible_window: Option<&W>, + ) -> Result<(Self, Renderer), Error> { + match softbufferCompositor::new( + iced_softbuffer::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + text_multithreading: settings.text_multithreading, + antialiasing: settings.antialiasing, + ..iced_softbuffer::Settings::from_env() + }, + compatible_window, + ) { + Ok((compositor, renderer)) => Ok(( + Compositor::softbuffer(compositor), + Renderer::softbuffer(renderer), + )), + Err(err) => Err(err), + } + } + + #[cfg(feature = "wgpu")] + fn new_wgpu( + settings: crate::Settings, + compatible_window: Option<&W>, + ) -> Result<(Self, Renderer), Error> { + match WgpuCompositor::new( + iced_wgpu::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + text_multithreading: settings.text_multithreading, + antialiasing: settings.antialiasing, + ..iced_wgpu::Settings::from_env() + }, + compatible_window, + ) { + Ok((compositor, renderer)) => { + Ok((Compositor::Wgpu(compositor), Renderer::Wgpu(renderer))) + } + Err(err) => Err(err), + } + } +} + +/// A graphics compositor that can draw to windows. +impl compositor::Compositor for Compositor { + /// The settings of the backend. + type Settings = crate::Settings; + + /// The iced renderer of the backend. + type Renderer = Renderer; + + /// The surface of the backend. + type Surface = Surface; + + /// Creates a new [`Compositor`]. + fn new( + settings: Self::Settings, + compatible_window: Option<&W>, + ) -> Result<(Self, Self::Renderer), Error> { + //TODO: move to settings! + if let Ok(var) = env::var("ICED_DYREND") { + return match var.as_str() { + #[cfg(feature = "glow")] + "glow" => Self::new_glow(settings, compatible_window), + #[cfg(feature = "softbuffer")] + "softbuffer" => { + Self::new_softbuffer(settings, compatible_window) + } + #[cfg(feature = "wgpu")] + "wgpu" => Self::new_wgpu(settings, compatible_window), + _ => Err(Error::BackendError(format!( + "ICED_DYREND value {:?} not supported", + var + ))), + }; + } + + #[cfg(feature = "wgpu")] + { + eprintln!("trying wgpu compositor"); + match Self::new_wgpu(settings, compatible_window) { + Ok(ok) => { + eprintln!("initialized wgpu compositor"); + return Ok(ok); + } + Err(err) => { + eprintln!( + "failed to initialize wgpu compositor: {:?}", + err + ); + } + } + } + + #[cfg(feature = "glow")] + { + eprintln!("trying glow compositor"); + match Self::new_glow(settings, compatible_window) { + Ok(ok) => { + eprintln!("initialized glow compositor"); + return Ok(ok); + } + Err(err) => { + eprintln!( + "failed to initialize glow compositor: {:?}", + err + ); + } + } + } + + #[cfg(feature = "softbuffer")] + { + eprintln!("trying softbuffer compositor"); + match Self::new_softbuffer(settings, compatible_window) { + Ok(ok) => { + eprintln!("initialized softbuffer compositor"); + return Ok(ok); + } + Err(err) => { + eprintln!( + "failed to initialize softbuffer compositor: {:?}", + err + ); + } + } + } + + Err(Error::GraphicsAdapterNotFound) + } + + /// Crates a new [`Surface`] for the given window. + /// + /// [`Surface`]: Self::Surface + fn create_surface( + &mut self, + window: &W, + ) -> Self::Surface { + match self { + #[cfg(feature = "glow")] + Compositor::Glow(compositor) => { + Surface::Glow(compositor.create_surface(window)) + } + #[cfg(feature = "softbuffer")] + Compositor::softbuffer(compositor) => { + Surface::softbuffer(compositor.create_surface(window)) + } + #[cfg(feature = "wgpu")] + Compositor::Wgpu(compositor) => { + Surface::Wgpu(compositor.create_surface(window)) + } + } + } + + /// Configures a new [`Surface`] with the given dimensions. + /// + /// [`Surface`]: Self::Surface + fn configure_surface( + &mut self, + surface: &mut Self::Surface, + width: u32, + height: u32, + ) { + match (self, surface) { + #[cfg(feature = "glow")] + (Compositor::Glow(compositor), Surface::Glow(surface)) => { + compositor.configure_surface(surface, width, height) + } + #[cfg(feature = "softbuffer")] + ( + Compositor::softbuffer(compositor), + Surface::softbuffer(surface), + ) => compositor.configure_surface(surface, width, height), + #[cfg(feature = "wgpu")] + (Compositor::Wgpu(compositor), Surface::Wgpu(surface)) => { + compositor.configure_surface(surface, width, height) + } + _ => panic!("dyrand configuring incorrect surface"), + } + } + + /// Returns [`Information`] used by this [`Compositor`]. + fn fetch_information(&self) -> Information { + match self { + #[cfg(feature = "glow")] + Compositor::Glow(compositor) => compositor.fetch_information(), + #[cfg(feature = "softbuffer")] + Compositor::softbuffer(compositor) => { + compositor.fetch_information() + } + #[cfg(feature = "wgpu")] + Compositor::Wgpu(compositor) => compositor.fetch_information(), + } + } + + /// Presents the [`Renderer`] primitives to the next frame of the given [`Surface`]. + /// + /// [`Renderer`]: Self::Renderer + /// [`Surface`]: Self::Surface + fn present>( + &mut self, + renderer: &mut Self::Renderer, + surface: &mut Self::Surface, + viewport: &Viewport, + background: Color, + overlay: &[T], + ) -> Result<(), SurfaceError> { + match (self, renderer, surface) { + #[cfg(feature = "glow")] + ( + Compositor::Glow(compositor), + Renderer::Glow(renderer), + Surface::Glow(surface), + ) => compositor + .present(renderer, surface, viewport, background, overlay), + #[cfg(feature = "softbuffer")] + ( + Compositor::softbuffer(compositor), + Renderer::softbuffer(renderer), + Surface::softbuffer(surface), + ) => compositor + .present(renderer, surface, viewport, background, overlay), + #[cfg(feature = "wgpu")] + ( + Compositor::Wgpu(compositor), + Renderer::Wgpu(renderer), + Surface::Wgpu(surface), + ) => compositor + .present(renderer, surface, viewport, background, overlay), + _ => panic!("dyrand presenting incorrect renderer or surface"), + } + } +} diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index aabdf7fc45..8f94766d11 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -35,6 +35,11 @@ impl Renderer { &self.backend } + /// Returns the [`Backend`] of the [`Renderer`], mutably. + pub fn backend_mut(&mut self) -> &mut B { + &mut self.backend + } + /// Enqueues the given [`Primitive`] in the [`Renderer`] for drawing. pub fn draw_primitive(&mut self, primitive: Primitive) { self.primitives.push(primitive); diff --git a/softbuffer/src/lib.rs b/softbuffer/src/lib.rs index 69b97be07b..d84250353a 100644 --- a/softbuffer/src/lib.rs +++ b/softbuffer/src/lib.rs @@ -7,9 +7,6 @@ mod backend; pub use self::backend::Backend; -//pub mod renderer; -//pub use self::renderer::Renderer; - pub mod settings; pub use self::settings::Settings; diff --git a/src/element.rs b/src/element.rs index be28d32734..0081bae6d2 100644 --- a/src/element.rs +++ b/src/element.rs @@ -1,6 +1,11 @@ /// A generic widget. /// /// This is an alias of an `iced_native` element with a default `Renderer`. -#[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] +#[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" +))] pub type Element<'a, Message, Renderer = crate::Renderer> = iced_native::Element<'a, Message, Renderer>; diff --git a/src/lib.rs b/src/lib.rs index 9601162e48..dfaf53e2b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -198,7 +198,7 @@ pub mod window; #[cfg(all( not(feature = "glow"), - any(feature = "wgpu", feature = "softbuffer"), + any(feature = "wgpu", feature = "softbuffer", feature = "dyrend",), not(feature = "wayland"), feature = "multi_window" ))] @@ -222,15 +222,28 @@ use iced_glow as renderer; #[cfg(feature = "softbuffer")] use iced_softbuffer as renderer; +#[cfg(feature = "dyrend")] +use iced_dyrend as renderer; + pub use iced_native::theme; -#[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] +#[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" +))] pub use element::Element; pub use error::Error; pub use event::Event; #[cfg(any(feature = "winit", feature = "wayland"))] pub use executor::Executor; -#[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] +#[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" +))] pub use renderer::Renderer; pub use result::Result; #[cfg(any(feature = "winit", feature = "wayland"))] diff --git a/src/overlay.rs b/src/overlay.rs index 46aea11dc1..cab951186e 100644 --- a/src/overlay.rs +++ b/src/overlay.rs @@ -5,10 +5,20 @@ /// This is an alias of an `iced_native` element with a default `Renderer`. /// /// [`Overlay`]: iced_native::Overlay -#[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] +#[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" +))] pub type Element<'a, Message, Renderer = crate::Renderer> = iced_native::overlay::Element<'a, Message, Renderer>; -#[cfg(not(any(feature = "softbuffer", feature = "glow", feature = "wgpu")))] +#[cfg(not(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" +)))] pub use iced_native::overlay::Element; pub mod menu { @@ -16,13 +26,19 @@ pub mod menu { pub use iced_native::overlay::menu::{Appearance, State, StyleSheet}; /// A widget that produces a message when clicked. - #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] + #[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" + ))] pub type Menu<'a, Message, Renderer = crate::Renderer> = iced_native::overlay::Menu<'a, Message, Renderer>; #[cfg(not(any( feature = "softbuffer", feature = "glow", - feature = "wgpu" + feature = "wgpu", + feature = "dyrend" )))] pub use iced_native::overlay::Menu; } diff --git a/src/widget.rs b/src/widget.rs index bbc5e4f4a0..a06b8468ac 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -4,17 +4,37 @@ pub use iced_native::widget::helpers::*; pub use iced_native::{column, row}; /// A container that distributes its contents vertically. -#[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] +#[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" +))] pub type Column<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Column<'a, Message, Renderer>; -#[cfg(not(any(feature = "softbuffer", feature = "glow", feature = "wgpu")))] +#[cfg(not(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" +)))] pub use iced_native::widget::Column; /// A container that distributes its contents horizontally. -#[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] +#[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" +))] pub type Row<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Row<'a, Message, Renderer>; -#[cfg(not(any(feature = "softbuffer", feature = "glow", feature = "wgpu")))] +#[cfg(not(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" +)))] pub use iced_native::widget::Row; pub mod text { @@ -22,13 +42,19 @@ pub mod text { pub use iced_native::widget::text::{Appearance, StyleSheet}; /// A paragraph of text. - #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] + #[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" + ))] pub type Text<'a, Renderer = crate::Renderer> = iced_native::widget::Text<'a, Renderer>; #[cfg(not(any( feature = "softbuffer", feature = "glow", - feature = "wgpu" + feature = "wgpu", + feature = "dyrend" )))] pub use iced_native::widget::Text; } @@ -38,13 +64,19 @@ pub mod button { pub use iced_native::widget::button::{focus, Appearance, Id, StyleSheet}; /// A widget that produces a message when clicked. - #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] + #[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" + ))] pub type Button<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Button<'a, Message, Renderer>; #[cfg(not(any( feature = "softbuffer", feature = "glow", - feature = "wgpu" + feature = "wgpu", + feature = "dyrend" )))] pub use iced_native::widget::Button; } @@ -54,13 +86,19 @@ pub mod checkbox { pub use iced_native::widget::checkbox::{Appearance, StyleSheet}; /// A box that can be checked. - #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] + #[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" + ))] pub type Checkbox<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Checkbox<'a, Message, Renderer>; #[cfg(not(any( feature = "softbuffer", feature = "glow", - feature = "wgpu" + feature = "wgpu", + feature = "dyrend" )))] pub use iced_native::widget::Checkbox; } @@ -70,13 +108,19 @@ pub mod container { pub use iced_native::widget::container::{Appearance, StyleSheet}; /// An element decorating some content. - #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] + #[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" + ))] pub type Container<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Container<'a, Message, Renderer>; #[cfg(not(any( feature = "softbuffer", feature = "glow", - feature = "wgpu" + feature = "wgpu", + feature = "dyrend" )))] pub use iced_native::widget::Container; } @@ -85,13 +129,19 @@ pub mod mouse_listener { //! Intercept mouse events on a widget. /// A container intercepting mouse events. - #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] + #[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" + ))] pub type MouseListener<'a, Message, Renderer = crate::Renderer> = iced_native::widget::MouseListener<'a, Message, Renderer>; #[cfg(not(any( feature = "softbuffer", feature = "glow", - feature = "wgpu" + feature = "wgpu", + feature = "dyrend" )))] pub use iced_native::widget::MouseListener; } @@ -115,35 +165,53 @@ pub mod pane_grid { /// to completely fill the space available. /// /// [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) - #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] + #[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" + ))] pub type PaneGrid<'a, Message, Renderer = crate::Renderer> = iced_native::widget::PaneGrid<'a, Message, Renderer>; #[cfg(not(any( feature = "softbuffer", feature = "glow", - feature = "wgpu" + feature = "wgpu", + feature = "dyrend" )))] pub use iced_native::widget::PaneGrid; /// The content of a [`Pane`]. - #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] + #[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" + ))] pub type Content<'a, Message, Renderer = crate::Renderer> = iced_native::widget::pane_grid::Content<'a, Message, Renderer>; #[cfg(not(any( feature = "softbuffer", feature = "glow", - feature = "wgpu" + feature = "wgpu", + feature = "dyrend" )))] pub use iced_native::widget::pane_grid::Content; /// The title bar of a [`Pane`]. - #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] + #[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" + ))] pub type TitleBar<'a, Message, Renderer = crate::Renderer> = iced_native::widget::pane_grid::TitleBar<'a, Message, Renderer>; #[cfg(not(any( feature = "softbuffer", feature = "glow", - feature = "wgpu" + feature = "wgpu", + feature = "dyrend" )))] pub use iced_native::widget::pane_grid::TitleBar; } @@ -153,13 +221,19 @@ pub mod pick_list { pub use iced_native::widget::pick_list::{Appearance, StyleSheet}; /// A widget allowing the selection of a single value from a list of options. - #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] + #[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" + ))] pub type PickList<'a, T, Message, Renderer = crate::Renderer> = iced_native::widget::PickList<'a, T, Message, Renderer>; #[cfg(not(any( feature = "softbuffer", feature = "glow", - feature = "wgpu" + feature = "wgpu", + feature = "dyrend" )))] pub use iced_native::widget::PickList; } @@ -169,13 +243,19 @@ pub mod radio { pub use iced_native::widget::radio::{Appearance, StyleSheet}; /// A circular button representing a choice. - #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] + #[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" + ))] pub type Radio = iced_native::widget::Radio; #[cfg(not(any( feature = "softbuffer", feature = "glow", - feature = "wgpu" + feature = "wgpu", + feature = "dyrend" )))] pub use iced_native::widget::Radio; } @@ -188,13 +268,19 @@ pub mod scrollable { /// A widget that can vertically display an infinite amount of content /// with a scrollbar. - #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] + #[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" + ))] pub type Scrollable<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Scrollable<'a, Message, Renderer>; #[cfg(not(any( feature = "softbuffer", feature = "glow", - feature = "wgpu" + feature = "wgpu", + feature = "dyrend" )))] pub use iced_native::widget::Scrollable; } @@ -204,13 +290,19 @@ pub mod toggler { pub use iced_native::widget::toggler::{Appearance, StyleSheet}; /// A toggler widget. - #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] + #[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" + ))] pub type Toggler<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Toggler<'a, Message, Renderer>; #[cfg(not(any( feature = "softbuffer", feature = "glow", - feature = "wgpu" + feature = "wgpu", + feature = "dyrend" )))] pub use iced_native::widget::Toggler; } @@ -223,13 +315,19 @@ pub mod text_input { }; /// A field that can be filled with text. - #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] + #[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" + ))] pub type TextInput<'a, Message, Renderer = crate::Renderer> = iced_native::widget::TextInput<'a, Message, Renderer>; #[cfg(not(any( feature = "softbuffer", feature = "glow", - feature = "wgpu" + feature = "wgpu", + feature = "dyrend" )))] pub use iced_native::widget::TextInput; } @@ -239,13 +337,19 @@ pub mod tooltip { pub use iced_native::widget::tooltip::Position; /// A widget allowing the selection of a single value from a list of options. - #[cfg(any(feature = "softbuffer", feature = "glow", feature = "wgpu"))] + #[cfg(any( + feature = "softbuffer", + feature = "glow", + feature = "wgpu", + feature = "dyrend" + ))] pub type Tooltip<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Tooltip<'a, Message, Renderer>; #[cfg(not(any( feature = "softbuffer", feature = "glow", - feature = "wgpu" + feature = "wgpu", + feature = "dyrend" )))] pub use iced_native::widget::Tooltip; } From e3155814bd962ca1722eda8e07c92120a73b2751 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 18 Jan 2023 12:52:11 -0500 Subject: [PATCH 45/56] feat: sctk dyrend --- examples/todos_sctk/Cargo.toml | 2 +- examples/todos_sctk/src/main.rs | 10 +- examples/tour_sctk/Cargo.toml | 10 + examples/tour_sctk/README.md | 33 ++ examples/tour_sctk/images/ferris.png | Bin 0 -> 16289 bytes examples/tour_sctk/index.html | 12 + examples/tour_sctk/src/main.rs | 681 ++++++++++++++++++++++++++ sctk/Cargo.toml | 2 - sctk/src/application.rs | 225 +++++---- sctk/src/egl.rs | 96 ---- sctk/src/event_loop/state.rs | 5 - sctk/src/handlers/shell/xdg_popup.rs | 4 +- sctk/src/handlers/shell/xdg_window.rs | 4 +- sctk/src/lib.rs | 1 - 14 files changed, 869 insertions(+), 216 deletions(-) create mode 100644 examples/tour_sctk/Cargo.toml create mode 100644 examples/tour_sctk/README.md create mode 100644 examples/tour_sctk/images/ferris.png create mode 100644 examples/tour_sctk/index.html create mode 100644 examples/tour_sctk/src/main.rs delete mode 100644 sctk/src/egl.rs diff --git a/examples/todos_sctk/Cargo.toml b/examples/todos_sctk/Cargo.toml index 0710e673c1..35d4dac301 100644 --- a/examples/todos_sctk/Cargo.toml +++ b/examples/todos_sctk/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", default-features=false, features = ["async-std", "wayland", "debug"] } +iced = { path = "../..", default-features=false, features = ["async-std", "wayland", "debug", "image", "dyrend"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" once_cell = "1.15" diff --git a/examples/todos_sctk/src/main.rs b/examples/todos_sctk/src/main.rs index 876e4d1ddc..75bbf68056 100644 --- a/examples/todos_sctk/src/main.rs +++ b/examples/todos_sctk/src/main.rs @@ -1,13 +1,13 @@ use iced::alignment::{self, Alignment}; use iced::event::{self, Event}; use iced::keyboard; -use iced::sctk_settings::InitialSurface; use iced::subscription; use iced::theme::{self, Theme}; use iced::wayland::actions::popup::SctkPopupSettings; use iced::wayland::actions::window::SctkWindowSettings; use iced::wayland::popup::get_popup; use iced::wayland::window::get_window; +use iced::wayland::InitialSurface; use iced::wayland::SurfaceIdWrapper; use iced::widget::{ self, button, checkbox, column, container, row, scrollable, text, @@ -23,7 +23,6 @@ static INPUT_ID: Lazy = Lazy::new(text_input::Id::unique); pub fn main() -> iced::Result { Todos::run(Settings { - antialiasing: true, initial_surface: InitialSurface::XdgWindow(Default::default()), ..Settings::default() }) @@ -53,6 +52,7 @@ enum Message { FilterChanged(Filter), TaskMessage(usize, TaskMessage), TabPressed { shift: bool }, + CloseRequested(SurfaceIdWrapper), } impl Application for Todos { @@ -171,6 +171,10 @@ impl Application for Todos { widget::focus_next() } } + Message::CloseRequested(s) => { + dbg!(s); + std::process::exit(0); + } _ => Command::none(), }; @@ -293,7 +297,7 @@ impl Application for Todos { } fn close_requested(&self, id: SurfaceIdWrapper) -> Self::Message { - todo!() + Message::CloseRequested(id) } } diff --git a/examples/tour_sctk/Cargo.toml b/examples/tour_sctk/Cargo.toml new file mode 100644 index 0000000000..fd88fb6707 --- /dev/null +++ b/examples/tour_sctk/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "tour_sctk" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", default-features = false, features = ["wayland", "debug", "image", "dyrend"] } +env_logger = "0.8" diff --git a/examples/tour_sctk/README.md b/examples/tour_sctk/README.md new file mode 100644 index 0000000000..731e7e66f0 --- /dev/null +++ b/examples/tour_sctk/README.md @@ -0,0 +1,33 @@ +## Tour + +A simple UI tour that can run both on native platforms and the web! It showcases different widgets that can be built using Iced. + +The __[`main`]__ file contains all the code of the example! All the cross-platform GUI is defined in terms of __state__, __messages__, __update logic__ and __view logic__. + +

+ +[`main`]: src/main.rs +[`iced_winit`]: ../../winit +[`iced_native`]: ../../native +[`iced_wgpu`]: ../../wgpu +[`iced_web`]: https://github.com/iced-rs/iced_web +[`winit`]: https://github.com/rust-windowing/winit +[`wgpu`]: https://github.com/gfx-rs/wgpu-rs + +You can run the native version with `cargo run`: +``` +cargo run --package tour +``` + +The web version can be run with [`trunk`]: + +``` +cd examples/tour +trunk serve +``` + +[`trunk`]: https://trunkrs.dev/ diff --git a/examples/tour_sctk/images/ferris.png b/examples/tour_sctk/images/ferris.png new file mode 100644 index 0000000000000000000000000000000000000000..9e883834825a83d7b333645b07a0fda659a499bd GIT binary patch literal 16289 zcmdseWmH?i+h=eIlHyV*&{8C!xVyU+3GVLhQnW=2MOq5QH35PacPUmJiWiD|ac9&2 zzF&6t-4FY9=iGDeJkR_dok`BznYkY{)a3DTsc-=R0KTGvj1~ZZjsO7A=&;d{9CR^9 zj(-nZNmWSzpgIY7Z;pYa=`0nrQ~`j1*8o6RBmi)WWQFYj0NzOKo*4ij^ceskcgt$l z6afHGgf&#OWs!g@mH&sUnHx|9{_n3=p8uYY)c;Ew&?E@RSqWo>o^^b8>R7E$Hqt-5!_6IyyQuG&HXDK=-XVw@GGC=f}vRF32(R&J0<0v_Q== zpw;QI8-EE74vxFxu&S!6hk>fc-PIcxj)T0gvEGhTCBU*2;HLnS~I94Ux!py30cnJNFU=#~wc^T1hXH9)1f#s#BDvPy+LWV>5(&d8j5daW< zrYIw+?YD54ZEs03kY4OqHMHPSN5_-$kr`bf3@~BSe`$oaJm(vbNjav{a_mF_#Ov;S zdcr8cc$W?zfvQ=_Sc#3<3M=Z!wtD~kaFu1pgEaI1S&@jhfr)+{6*_cD8bbwy7mI%m zE?V9=1B96=M%M!4uN44v+HF*0Z#dVaQNk3?ar;oiB<#ny=)mY5#MQ@FLMulnol3&KIvQtCm*ca(;+{Ux=}AvxJ5<%#vzw*UuOPd9yS zAzh4otB*`jAMY!e!7sIIOcVzaQ){S*FrWLlU3#1<*<6mfJVq&R4@8IZ>i}C@P+KNG z#G*^(yG+J0DEG~It#zMK^#Vq?wTbW8EV#uD1hB9$>9S`AhoPc!f9I3a!qy<3Y-+Z6 zjdnxoOU*V?ULUL}v3CTxFk{GSPO!nHm|6DJ8f961DZ#cgBIBJ~lCLM{ef7cb2PPmn zdH*lZ&OJ3NPP*g?-xooz0#pf-4Pbo3J06`W@I(3AcEq|9pcG9p8DYmdE1`+-0^ec; z@KT@YpiYavoc>nHk;zh^VP8CS`NWpQ?xcP)Oivcw^>coW4spD-+XIt+E?KDHT$c zwb$hS=wq8%I zOpJ1|$?@?R9!|9s4uVk%J)6!>IXEkLnHJW$Osg}C1Jd65&dq|)lJQ3ycG~6uCkc;z zDrJ^-5?rN1qxj|v{o%Z!siua_415{N_)3<|L4fD!-$q`O#%~fe?v8|iGfJ^Pmwe?s zA6SJ#XZpM!GY$SNx!Fq2vzYX%wK;HjL-X}Z>dC~;Tphr8DH&y96kBb|+qVarO$>Gv zwY#hR+(hlNWB?K(!9yoIj!#$(NanYXCiaO=6dzWkF##DnA81m%3(GeK zJ53O8Rw5kU_RKYE^4fEIP3nXTPgP7oI{c}6^iwKq(>_M*1>ieL_5e#s z6frnhkYF=~hbF<`up!a}`5V5pn^sl#8dj=GmFJ81;voS)r$&}lw?Ys?B0dfl-}Ri%X1%N0MJq=!BKSxW8*F@iuqh^AIoO4FveKR_rR%c z6Yq=iYd?0)w~j7rC03s=j`ojv;Ro;5J@fp;a zk~h-etgBz;;n^)y;5Trf2@v*TD9EZq=-77+*^eoDi=1Cm0S?BwEq^XIMmPXxd&Ow} z$z^_1;bp35Fc}qIRbKEYV+4@oSMbc+uNAI`rx^z;)f<-Uy8-xit|)Dbqq56^vl<(< zUpbj5J25|5+|1M=#9<;IoQ0qotyp0e@l+0#g;70wz}ab`|L7*GH9E2|_~oQyT4j?R zM{Yr#c#!`<*@^n+3y0}c_hTV93@1W&>NQR89Kh^D6U~|cn@VxdLg@_oXy$pslw=|f z3Z$CCVuljm*1M3qdNAxu3ZB_dJIo@fJMJ(+=U0VB&2T}0Wxs+#QytV3(4R}k*uzO5 z#;HnDcwi|dl0cpG!H5InXGNK~6GmlyEH6hyl%w)akgg-wdVp65_(dQ8g33<#Uf0zV_aCel+ z8Q0haUxqvQO;eA)pc#9J29Y*(Y1$SxFxdqYTz^JnEN%(8k0$vI!i!$}XDY+4?%<)`>o2q#!)IdIk^%1kl}V;@@cCY$ww!~Et?tZL=l z-i{brS2!+kwn#PQBSAnq{K&tFtEm;cD_jXU>pw{4{SohYE4sGlWAVJPqp=T)OvG+* zIFrewSRc$yNxy6tBpU<;5pE?$D&}w&bdA?sbAceG5oVII5(q#SDmw5#HxL9#{nrf? zt_t|uX+`m0P5(!`1R{P{_17IXnaq#eL|6!yG-Cag`M_+J&*AL-pj8}gpt#zvYHZyK zfi?}hA3x=hR&Yi01X`ZpWB-#dtZY|1KSKO{hP1i<5jGF+>;DIem9F%t9Kl2fg5Zda z*mw?$#uC`p^qN8UZjnwR4%npe7PG6o*~qvlA(30^ABs+KK<{ZQGC0W$C7hIY6n-IU zv-lDkW)`LAm^0nr6!VWwSs=)yWYqHRU!CHit5y7qagv!NTm{xt8F)s{#|ZyyUv0gw z%6L}r>iAX&*8W`NwEZlo0TQmmJNd5mb4>&zDl$jPT&+}FPknr~cUrIQ>5(RIxY&01 zY8_r31T+jL$>D@!VC&Mv9-1QdF29Y~MD*_a98Me6fFS5FDX+M%Hx)t8Eyv_O{!z9R z`b7I5Y&Uo@v&H+_NXpjks)ps@UJcTRs96tFd)<@-PWpOvRyM&qFhd4>%gakfobv$} z&^3u|rKV?SW>~!ArXiIlzfv$+6`k!<62cMXXIRlP<*G&U>TGHOZ*p;nw>6Xy)qd5Ok?PK>T0EM*2@Yqjht2VWhJ8VgIVx$w|BlLz|$dW z?m`GxO^!cDn3@Yz`Fu^(=3-kt>?|*dFh%$;N&>jDFYtTc2J4Y#sTJE5E&at9C_4LA z2F?C`MYZ|OCWTJ3`;^kc+T}*_)fGV1yS)sY&ub0q-kr3|j7_@&DPPHdXRBWh+BElU z&-5YjU(*P*{zl9uchxoL?S2W&K9|4cwrfegx60Y*&{`awvxc*g59H9E>8HK;qB-=J z&PY&1`IfDgnniJ^WKQAh#d=v@Mra2>>_*;*H7vja1-(>@G zw_YiB#V`pf!N!pMZJR^k@1a%wtAEOvY_zrG&B?lOfjJ%aq}}<@mJa(<;J1YoT+~hU zE>vuFM5YZ3JW9eOB(u2}VT1Cdf@!t)O57XcB0nx4OIWBR7368 zb35yq*ieV}_VhZwC+dSG*1FtF@ey4ivr|@>N#BfgoY%18SlFz-hxtbT-AQY*gtPz^ z7>@V$#1xx^MLVau`4%yPxMS@GzvAkyLk=_TGJ2*DGqQiH6hh^@go}=Fhv{DyZ#I;{ zetYCs-3q1cj~|up)Eam4bJB1t4tQJKRP=M^>23xPrJFH2k)Nmt7r(HxkMm(I)DvPN z4+#0&sdS7~#Lc)M_mxIHocH_rBRnn`bsUCWB>h3e)tS6J^F9yjZd73DX;DsW&$3tjsgRr}=TQ z>8AN-rmhWzo`p;g7_O&Oo4_aBjt;IG%EsMIQ}b69ufSEPMkFFz9Q8jn^7W1U5h4n; z&<%i9vS8BquQ8L&YMv=afZ7Jc19xMD1We3^LnII|K)2yRA=9wDJb{p!YNq#{~c+qD{-J%zt@e^>Ggymnf6 zJ!cakjHU^J7yM54a4JyE`_3yk<)|#~u>$C>bi5&uu5Ns!6wy*!m0!#;BPAWH+om76 z*#Dk=d_&jl)hL8@&zZO{FPJtrlx(E5@rcfV{m=Xgvvq(X*9j9eY9V*=Zkbe}>Jy@a zorzQcfTBbRV%p|a=|h7oTTT|gzx)!4%Jvi<)T}yw5R^Xz=(K)_zc;kfL3vJE7A}kA!3L#@^nq9+8sT{*e^e22Nc?n%qCRKr z+$X8ZpD2WlS7+G5K0H$0#RzQH4CiSN-n+pa%bal8CvLya;7wh>tV%hi38*e&`mk!b zCWSS}cpRA$vKi2J7rWvfom68W-Z5*BBT)1FzEFiF9@5gV(d3g$>CVVDF%4r%~uwyFPJy# zEp^vZ((?4#W4{x4DqpPUhiZj>v{e=x)V?8dot+;WY7Fk)Nj)cV%%Bc~=wGaDc-3F3&mC=eG3E@a2NpKV+E@eAKh%-LNZD+~ z+v}s0s_N#HKR9Pkk#dOW6Bym$nFtz3mndtw6V|7_g6TDwscteN%taK78mZn`!FbtQ zn7lZoRW)w()s@L0FWabu!Sf{1P_25$Dg8m5YNF|AMYnKkoAx)AO<(tv*4p^A`u52J zFn+t1e8ovOuYKR6HC6gblK=UUnizI{{)<$zFMQug_5Q>Y_TdWW6pE1)cuG~0MFwcv zy=Q)lP*J@=%aF$+*3iM}VbAua75|cDuoukaeAS7{Oi(R?t;-N3Vk%Gl#4V_5Y>n#)OvQHC?@xra_?&?e^ zXLjq*UEhVwWasH>2*2J0&Nh86bYNO3?^nM&_`h?F$~GJdPQ!+k|_QC z$u{~(oc=*@&?TStEHNqV)Skrg%eF zjv1#h=AN8Ex5*e0yv}&?F7Dx(<#s#f!&Vk8kiwy_b3k#rbiy^`^3g-px8QdWe(G|b zO{x#uNEFmsWBm^J_$+v!9Wx>s>L;J4#%yFK@ZtUe1H(~`Qh8*~Z2a+HeL*2iL-Q9y z!H-qb(;UOaD2F@bI=MXv)wzTt9NFA zzD(zwj^a7ZIWf5zcq;1=N=Y_ba|&x7PrTnSD!5TQSUuobrYWStA=}a{1y23&BuGHf z^4N-ZUq2w`B8I;PQaW_edBBo>dZBgQS@-Arp8f)oWe`h=Qlw?8-2f3(!5LC4jJwg{ zQMD+0VdcAA)240}e>Fc0hw7IfIWt2)%jXfOtcwZg%^G6qU8^ql<`##~SaV8WX#Oni zhFI7ybX)8A?#bY*V#HNLHG+A3py@P$YDlavqhY{mEdB0SnUd9o5q|M7`QH+;rQUs{&3H_IK5=uGv{ z>ZtcGo>p>_trg=xx68S3UuAPK;J?A$Rk04jFU$VdXR#@fDQao7(E_aJDnvM@(qn1} zjwC7GFEXt({XT&UIyS;&*Iq*D0ew(N>FYK>?!-iA5+a0gnKSsGN>+`1BnS=!D11~-Ya^*dI0u+AbzWrU1bt{39rZ$Blf^|eztKs99H z(rLPk=d1$!v)ara8>t}HK8|iSPUl8CxH$v|g-il-wr|;<_nZ}edj1grqyyezuKOTK z=Ds~*Gn7W0>HyLSm>d_urtmHZN#j<_&5XmsNF1fYFn+W4I>f{B^wyYlKLLQ9`tN7p z?@glep=hWOKn(qCDMJA1dkgQw#io)9zQ3f}sej$3zYj|-AoX#OfU8r=HNW~imuNUu z#}!7hKqSM6jTM>l+~^u!$|B4Mp@;Gv9YAbRc0T`I!P@5{;6+gdn_;4HMs}Sh)rVDc zZs$VWPL+mcdm3-v0g z9VbturjlgMjt;FwEx>@62G(RNE%@qw)%+v)h)jw(l*l-x)0T2>=j)yx^EK=?RA$xc z!m3w?Jk(-e(%Fd5P~o%z7-o_!sZ;gtlPAjaQE*L)x5g;ta^E9cp1*FtD4$S>rbmae z?BjP1t`I@)y{PFmww#5~p#a7hD1P4O!jYpciOFtB(AzVV@*lq{s2!IaBc<%^*LcF{ z2y^q#jiS*$$tk3I8vG)tA*arzI$u6~Q*{J(ZE$ zLS@!Rp!4|vT%zF%Eq~Oqvnp-Tft?9v^Q%$hMnX`nk2B76NZY{l`YF-esV=PuxO*v@ z21>c4&BXF-?X&~B>fF4$In{e#Ql#;Xl0-~PGPZ|C4!u&&MATngAFCShKFM7RP|6DZ zcp9eU-AgfCG;Jikj&oUU_w=>3y?P%gEPQqBB>ual7Gkc?yBlrj+~HOE+l<7EYA^DJ z)wAyD%IE0Nx z=}Eb-%ijtz&h$1uru>s3Nt05y=}yT!Y(kgghaO*fk!76;_#Fy%SLk)p>eETj)#}6N zr}97rT@+QtDLbpRv~&Nwl(dM#@o$SyyX@kk!kag7$J#vmm8$`>;*V3NIoWL|gg2Sq zITpIK#)?dSUTZV+μ0;nkU9TrzaiO!SDS8BTy^GK6&+WmaK~jn_r4utFB@47 z<O;qoZF9-&#mDJoaBpruawnmKB2(IB17*po_7TlZ@B(i zW~&nO+Fv8|KfaUZV5h>Gm(3`4dB=EU(elwM!Taf1dJazL=;G6ui5R|eb;DKYXz;33 zRB$hUNa6t>k#QSUVZsn6@B2`%0+kdgv`4nkC-J*G7ll~wK;Ns9`>SA+LPTKDHilw+ zr3pTDnpL?dW^nnPRq z+&51!4a7Gv?ri1V(A~<>(Nu=)v0+wkKGgqCxg})AS)1JW4t|tS{gNz?_URV>*^~ss z@##Zg+rmTMbudsGvIjM_Q`*sK(X=*#@f(i^+}DPivBW^HmZ-R2&^%GI$F1tEWdVG9 zIA7wl?Y&APgB&(DZsqDU=SG2{1NW*r)kn=ib65irRU!VbiHMF)W^9=v7Xo|;W58#UFpr$ zbVFW$_0WDT>nQG2%*Lhp+L;Ht8nO@dk_4ZHl42-}GI?$F2Lzp-lUp1c`Z`L>RV+zV z$<54x3t9w?aoGctVeDIoalS7czINN+su zu;gDnRJ9lDJunDcS(#xrXK6dmHVD0t#%U|B>|1XCQRschi-e)fiZcUe*{SH$Kkbd% zhh1dv-LX<|9?jgqI<&y*DzBX{sV_Q9fAGu-LJENfESatPB*-V{K&Z>#dL z-J6!jvq|^cXRoyeD(|izi7OM#R*cAa5LK;Daa-$x;$bZ%EKv4VT02a2Ws8n@#{#`# zpYsm^2ESwyN%GS>z`$8`p>Xe2ov`}5+S1P1*?BiXdZ)~TflLwv{9uR59vKd)FAFqq{Xbe_(C@tO*IQ5r zcAL>~9E|%j%V^0{NVckvwGdXrM|UkQ4p3`6Z-PYNGLLGH(s5mMfc0kD9`k0Dm*m)X zVa7F+UO1z}P*Cf>3SuvV*o5O{VYK*eg&dpxf{a8&`yyQ>CZn%!No38R;-iqj15<5N zJ@;0b2aoPDi8S9|r7S*dp@-{QWChh*e z7bt_7Q3^@4r_}_wFjjXj%>tq42A)=Fzb~6g9#xz5??T$zFFmCZ@nEick49yF7IGyr z<1+y4?1Zu&KMeE0$ixV(oH`QwF#s`&aQ@7Vx29fwWW?!|X~_NeDX)Xm{o8_OgT8;b2J`*K>RTHtoDrR1qc^B3m28GI-i1fG_jh0NEm4G-mM-h zQl+?HweYUMWY5zJkL9KIB5ux^xn9UBG3dS3F-1g{AAM&R)n#G2j~mbwFy~f*2ssI> z_9|&r+hha#;4IxdNii(}f@^2SXhx|BAK%c_rt78#Jqh1`bGm(I?t=ooJw}L*WzW^? z@CzBJ;SA&Du~1dhSb#=hl7^rLeBNwYx{5>@8q1rPp{|OqZ1Z|HZQzmlu&c2zT*D$( zrHS<;;obShhWqxC&qH6-sBxu0UGPN{y9^rKAn~(g73y2~d|2i7bD#6XWs^s{S5Sub z=taGpyFbcZxVnd~VY(P-v!spN#gI1#bEZaqRK2&c$5tZOf%9{1!{HKDK_e~z**E%M zW1Ee?iSrUep1qu1hBG}ug!Ijuy#AIPzZFqrbBY)a|HZ6{|^uNzoj3phG zsd5MqrVk+fZBAcWq<==bUs588J9|_$)^}cRGb*@~#^D7>XNP){4$D=&Lf`LNiV+_z zZGF0*&Ur#@%ReV@`LR$pAA8|T-IfA^A7%|ad9eHlxM^DpT(MQm)g!4{CxS0`w2^R| zrM-f-sE;Yno*m}|h|)+@?csg;rA!WAF3E_1=Y%ff8zK^w^;bKtZ{CxyF1CWhqfF)} znb^Ggmzu|u*ni6Ev$roUQNuakO6($db@TaFk)RzFr1N#{1_!%htGHeQ(aeMDRF>b z$W_k6FV8H>ZjB<1&U&Qjp&Wp|vy>h*cWOPsFRLoQO7}Y~xY$=tu|L8J6gsWQ~ z-^WScch#)39K|Yq6nwL!{JHC6okpaXzd1z5x2(l3i@f?03ZleQ0CHN-l)t}_*s;zw zly|MA6JYbwA6jSNaJ=;Wqga;(17DSQN2mMMc ziRAq%8Y`<0{()Wb%tDC;yrb0cJDz1=J5n8rFUaAe&Dw9lh7qjB?0?=MFSoAVqrV$F zs(u9;o|{Bfc^)(|8&!(;doW%>I!9+7Hs+c+d z+BF?Ij!u-e(>_ajaa~IO`$LdPRLXy%_Fo_Dfu0YYmY&ca`E=ENMEaPmj61D_|;^1s-1x9S#fT0)hU}k!7M{U@30)5O-}MPLOQ5r-md&< zgLp4DJvF)%!}N|na?gSu3>ja z910`fv6!l7AAkN_$sSO5xN;8md-zKik#uYt%1obZ_fq%g`CiZ0I8&A{=4zjLnbrcJ z9Vd(%KlXa|dm=={+8+){!%0g_+5&R^Mzue5mgjtRTkfE+6>SZ)rLM+hLfoET{&|P{ zLR@f^Fd58`b|>~Aw!I%D|Cv{uKgjE9rR|T({=_A*Sb`fWO#OJKZqM+|U#xJ_nXqVa z4~Fg4)yF~FFu#qxi({vt z72V$zKVv-mF|&w9{rt0kw708Y`W)Vpthb_+Fc=j%1jDHA>;Y^R?PBU%!%K{QNqxvF|<|p{>6s{LPxR6 zJjMsv9J~Yl2@!RA-bjR!UMyC`oE|mnZf{2h^^*3LboaXh@?}N!Z_UMl-=rL{*G@_t zih>mn2mIS6c9hd%9YfrM8Q^-hmZur@V{aAk>G48@wT)`_sRxvU+pfi1-CL&?xLozl z;+VDg17?142%QIJdEthddIdvb>X~_};tw&i8=wM6Wpj z=0s#{Dq_5&-1oG*rH_>*Vem^9v7s;~>en;vATJ1%8pB9}>CLr6&b?;<$~OHH;btG) z|4JpLp;@j3^`jH6ZH3#;fcxUz_7{s0A;Sx%XN=6y46)&vs0cLb(R_5m&A%nOSUYu)9!X3xxcsf>NYXMPER) zC2N1+D{pl9(AW&_W9n`H#7r9%o?|?td~+>i1G#vx?S;+F&V+m?n=7-u*&4jOJQN<_ z_scek)Vy%iyx4yKW~s5c6WAu~7#_~6paF~i9VMu*iWaLn4x8g6FYUU+E^IhnYN|ck z%K;dL)h~eoEG; zWid_Us;gBe@+WH*j~_47X5ejawF`|qUJE#c-C;*18k~iqq_*NSjRGBG`LKgdnnwr= zw@wRd%6{+j-wuck!{!Z3O66LZ?Wc;@-_TfCs||D%Z#WCE5jvF$eP0=Og?3halGqE; zq~lEr)KSGC#8)fJ;NLpP(xCySA0~^C`nuanGo9@!8IRi@0*6w(Pwosy2)?|VHqMrC zpiNT10B)8eGA!4WW`-?V*-5WF6;x@mGlMEJ!ng79L+=A)PizkkW3@!z_Pof!*XlBA zV&ySsQ);ezYo*kV7GRgYn*)jWr2nF(JSLB8z$8L618XZUn<+`cTKCZLLT`u6WAV!r zMM}*8tzs&aT;ONQ(#kc7Sa!+J#XzKRr7sARw!qNe5|yzkP#`S)6Qz7DMj@vLH5k4E{hv=3zlq(eFr?vwa~7Mx@de#JGjpMP{RaoHc}Dk|+7)H-gA z_CNJFqrzeLHrh6NDX@Im>|{{ps(w7ia2h3;H})I16#S1VV$^%tT6bH*F$SQlR6(FS zOVTjnJsLTF?*@y&fYJMhxhMLcu~!<;eGUpfl0Rs^G=MqhGLl&wu9kl{7C{m>ERv?^5%k<_2cWzM}+ z*|)tXVsl9?b|e-w%FD%r&pY`qgoRq`+4J=X9pSoI`-ZU|X{H`yD%c6iP}SZ+zo|DctSdt}UZM_dC83^@p^ox0(- zsdX-faOehnR<(FcE;3oF5n@|O8G+zFV^E7hfSg*Egd_7nA3pxYOd03js{Oj-m zWtaWTF0uJZHEil&qHYONnDZV%`!uSkc)d||^9f=hzg~~LE#P8tk-x#AKG({A%*;3732v=3Xe-&9o zp}-Vf0v@7IUoJDBQ71gsGpaIat0k-$Vf~i{f1JE8W$z*p&-?biP#sN6!>2sbD{N?5 z%ktIteWEV*dbX3WP?wDA=ILR#IC;rPs7AuH^?MzjMssP`U8l;$s+FF(7v*U$T=ws( zY30WMLQ=43=jL6FY8UCgVs?DTtT!i;(kXr;4G-_Wt2r93WW%wW?HU-<86wQt30OH6 z!#07hJiW=+-B)&;y|a`=hS;3^F%rSkm-#sWS|<&W92L5L2)527d|^jgbJ=q=a3fu4 z=N-V>$czwfzzd4R6poEV$n*{2SI=4@y|-PMt8I@oa9!2OFTa3_vISii5?FY+) zf$T{NoyACKNoKv>c0`9$;Cq95T5{6WD^!u^K^KeLJrNNSGpqwsA3uTiY$J_?H5)88 zW1vk@SJsRC$dkQqn$|6KD-N@|2JfySKEcGr2h-q2r8fF81eS`0`)-n3Nu2{MzRp67yCq>+0D3bk7l;eRGXg?3zM!0{+6l6RP6F!Y3|$6TrQBlIXHpW zTpXb-6qr^81n)?}5{V?qtzn)^JPO#}#A)=+f8?uU1ArbjtyRNMj4(s6i_HG--=k}e z`D{87GfaOMZs*1wJV!5lpu9){?ryd4gKJHAw|WSy4z%yow(O=cDU|W5AG*k@UcMrT z3sb_W@BmibNR@qgcKM9PG{U_t)FxEBwRQfBE?`2#1hiE*6CCCkgdxA19{n`CjGh+z!n;%-@+QyRx2=*YT+X z>z`@D->u!U(eBmmu=U>RPJ4WNVM0_X4x`XQ&V+AI5LxOIF;JRXmvzeN=3omIDdvd3 zeI$LfjjB2HvvV4b?wse zH70v{(|-hk0W(5px!>N;xjxS9ClL;N6=jzG$Gv)|U{|EqtMJj{a#0Kfr{flS>v+bw$=W^6(uLCtgHq_ru*{~MtKReB{pJ@d+wq0XOw6Vp| zL%W#Ml}ZDbr6UrLQkH)ieNbswxAbQbsSq+BWJVdc8_wGnr0CQ7lEG;!Fne1+E{0+? zJR|t~bZgxJ0Tkb#O)Xk~ssQWnMb(ff7pi7joPr-B366|J-S1Ho^q zcg?B8XFhTEJk_EsYtDvzgUAOy80-apD!pQ|L z+pwXBPl(X*#T6aPV@aRP+B=}lj&iK7I7*+MH>SFA`<3 zMI>iPk1`k@QgxHK`ieI9>-EYvH%F-_`m|+HX7SUWs#M23TVspPU#2V<)RNtl)Vq^- zR6yA0-dwdbH8oXhMs2oR=;|MuyC*@br`yvi!XAz;;m!~jTI;ysnc1?<(Sm8$da1bJ z9)t>K_d0E@MVYZ}3sLi`F{`miY%^a5xWHJRtKRFmx-o3sz?4q}o9YK|xWr9tk)Qa+ z1?%)OFL!^!=%-F_16u#Z=4RR-JBNY=aBxrV_RVK8va3eqYpi3+d_k@DSM>{pwuZzF z$4%yI+`n0Wcj{eSXk)af=N!(`Ak>#}_>7?gT@e;#P5#_TxO$GalrM@Bnf}fx-a^}> z*+QR{{^2N>BhmqxmC2^YGWrI5>(PPq;UEm%ZJq^GzYi&BI|$0Wh&dnhjbnrlKY z_|Tu3vGjinh8&SCQ2;k4OoVoum}om$zxJiY5C7=2n0PMf8ITDJs3>Q*dhdEYcG z*_9G=y2HNgOHk^j6~e(7HNH>x%S!p)Nf12ypj3Py_n{>Na*hf-j{KK(s>D> zxWLHRBA2g=uX<4l;h!s;6QKbpYs~5W-y6D(Ak5i7PbxCr#{m{(u2|CpwEU{&VfTLI$1z2bC*n3qU@g@;SRwg| zaz|$Fgo##=Y!Z{YUNc&ZZ6_*Pl`BM}o?f+9!G&}vL8I+*@A?WePr|z0d;My!pT2s? zGaCMI%ESc7=c&%|cI1eB$s!V}?H55vu~cKx_)AWJinRhtW%|3RTlgoY+erBQGj%RA zJJTXkWhR;SuC@yeqPS=#k(bezI!4uAMl8d~b4MHT8dcu)x#3X-d8n~PZ~Jf4Nwj=o zxVXB#+oJfoCp3clCMoKx6{P7?GoS5w>ug{_@eX0p7Q4{UjFDDOfE;C>&D%a~d1)FF zlRdlfbJfVjNiG)?%2+q2pD+iesTE~!!Tu1E8{Ig%azsmwWueQ;4AHzpbERYlb zV=@TR3v5P9PI-oeAszRKEt=47lu^Teqt92?xQzidl7@=*O}dvEF)^Y08gYBvCb;oT zD_8Tp{#x7s*noE^b!FJ!Wn+8+;X#vu;=V)nfP6t@1f>IJojFUOmlk zyV7indR}r;ZktY&IhD$kuSiUug?;?0fRClQ69CKU`RAI;ZzQiaB)iZpfq_iG ze2R`<#hqtbtJ*82n+=mO0_Z}u#3q(3pcH;WfwRKZROEPwrt!Nu#Jz5SH&Vx%HG_n& zf$T;^GAm+#;q1G-0M%YOl*jm;E!lZsyAU!K!@m4D6xP~8cOGri>d!&=)f4>_8kT+e ze{t9IKNRH6#>%b`YI1f>XgKy!o!m&sj8I1+v+^cZwv1@qbXb7Op#Zw^2zutsO6{Gy z`o)|u`g%9{^>lo}1(FrDieyPWBkP-Nfs%Go ze|Zggj*kp6763wd`!ikxlJD?7iarsOBrb>LJ>yHOLc<3V#}VZJhML^_g1plXElar@ zNoy{tbZ28y;1zRk#xt&;745N6cLWy0*vQ;KtFf!;8sZUyF=487rvTvE^R>vbVg z5bQ5vOpDB9^klu=k(cp8;MtELV1B-|6x6Xl5(;{zs)jv?i&ITUCU@UF`@jT~eveUY zgRZ@VQ=^XoazP%^0T$yqhbioEdk|3>Z&^q_ucO$*_cNp;!z3}xbFUYVyY$6Qrmfc% z*s5uI2c?#>QlbTv#Pa@5l*0Z-M(ZoEnL-0RV@Cf$ktA#dCi{tr%}!_r;TkC;)bNS< ztx&zvDbmOAJ9?bLU7aASKz5!+%0{7&=BMY1gwWQXuuckuka?xWgdVPWvfFF)jP%~m z{aV;*sr5K#q(CrPq%1j*Y=fChkRDwY2qZIF`}!`mkvcp-0R!kk5|;%Ew~(+E+h8GE z5y8eGlKPVDowHBLnM4o4S@n|tJ?{YxlFSLsKa$;Y15Rc`DUk~Y*ZyWe=&|DS!^`@e;g z8-5?k=jh4KU#)+JY-041g?d?;dsz!vdRQYL04@$LE;bHMHZCr04nZMKULkHyFb9Va z2ge(~_x1mafU~QWy^a6>UBHx)0|zNU`Om^zR~s*1a}R5Pq?N0MHJzffxvjO9wYinQ V`=B-QyLkXWQC3Z+Qpzm+e*g+Z2ciG~ literal 0 HcmV?d00001 diff --git a/examples/tour_sctk/index.html b/examples/tour_sctk/index.html new file mode 100644 index 0000000000..c64af912c5 --- /dev/null +++ b/examples/tour_sctk/index.html @@ -0,0 +1,12 @@ + + + + + + Tour - Iced + + + + + + diff --git a/examples/tour_sctk/src/main.rs b/examples/tour_sctk/src/main.rs new file mode 100644 index 0000000000..592a0a277b --- /dev/null +++ b/examples/tour_sctk/src/main.rs @@ -0,0 +1,681 @@ +use iced::alignment; +use iced::theme; +use iced::wayland::InitialSurface; +use iced::wayland::SurfaceIdWrapper; +use iced::widget::{ + checkbox, column, container, horizontal_space, image, radio, row, + scrollable, slider, text, text_input, toggler, vertical_space, +}; +use iced::widget::{Button, Column, Container, Slider}; +use iced::{Color, Element, Length, Renderer, Sandbox, Settings}; + +pub fn main() -> iced::Result { + env_logger::init(); + + Tour::run(Settings { + initial_surface: InitialSurface::XdgWindow(Default::default()), + ..Settings::default() + }) +} + +pub struct Tour { + steps: Steps, + debug: bool, +} + +impl Sandbox for Tour { + type Message = Message; + + fn new() -> Tour { + Tour { + steps: Steps::new(), + debug: false, + } + } + + fn title(&self) -> String { + format!("{} - Iced", self.steps.title()) + } + + fn update(&mut self, event: Message) { + match event { + Message::BackPressed => { + self.steps.go_back(); + } + Message::NextPressed => { + self.steps.advance(); + } + Message::StepMessage(step_msg) => { + self.steps.update(step_msg, &mut self.debug); + } + } + } + + fn view(&self, _: SurfaceIdWrapper) -> Element { + let Tour { steps, .. } = self; + + let mut controls = row![]; + + if steps.has_previous() { + controls = controls.push( + button("Back") + .on_press(Message::BackPressed) + .style(theme::Button::Secondary), + ); + } + + controls = controls.push(horizontal_space(Length::Fill)); + + if steps.can_continue() { + controls = controls.push( + button("Next") + .on_press(Message::NextPressed) + .style(theme::Button::Primary), + ); + } + + let content: Element<_> = column![ + steps.view(self.debug).map(Message::StepMessage), + controls, + ] + .max_width(540) + .spacing(20) + .padding(20) + .into(); + + let scrollable = scrollable( + container(if self.debug { + content.explain(Color::BLACK) + } else { + content + }) + .width(Length::Fill) + .center_x(), + ); + + container(scrollable).height(Length::Fill).center_y().into() + } + + fn close_requested(&self, _: SurfaceIdWrapper) -> Message { + todo!() + } +} + +#[derive(Debug, Clone)] +pub enum Message { + BackPressed, + NextPressed, + StepMessage(StepMessage), +} + +struct Steps { + steps: Vec, + current: usize, +} + +impl Steps { + fn new() -> Steps { + Steps { + steps: vec![ + Step::Welcome, + Step::Slider { value: 50 }, + Step::RowsAndColumns { + layout: Layout::Row, + spacing: 20, + }, + Step::Text { + size: 30, + color: Color::BLACK, + }, + Step::Radio { selection: None }, + Step::Toggler { + can_continue: false, + }, + Step::Image { width: 300 }, + Step::Scrollable, + Step::TextInput { + value: String::new(), + is_secure: false, + }, + Step::Debugger, + Step::End, + ], + current: 0, + } + } + + fn update(&mut self, msg: StepMessage, debug: &mut bool) { + self.steps[self.current].update(msg, debug); + } + + fn view(&self, debug: bool) -> Element { + self.steps[self.current].view(debug) + } + + fn advance(&mut self) { + if self.can_continue() { + self.current += 1; + } + } + + fn go_back(&mut self) { + if self.has_previous() { + self.current -= 1; + } + } + + fn has_previous(&self) -> bool { + self.current > 0 + } + + fn can_continue(&self) -> bool { + self.current + 1 < self.steps.len() + && self.steps[self.current].can_continue() + } + + fn title(&self) -> &str { + self.steps[self.current].title() + } +} + +enum Step { + Welcome, + Slider { value: u8 }, + RowsAndColumns { layout: Layout, spacing: u16 }, + Text { size: u16, color: Color }, + Radio { selection: Option }, + Toggler { can_continue: bool }, + Image { width: u16 }, + Scrollable, + TextInput { value: String, is_secure: bool }, + Debugger, + End, +} + +#[derive(Debug, Clone)] +pub enum StepMessage { + SliderChanged(u8), + LayoutChanged(Layout), + SpacingChanged(u16), + TextSizeChanged(u16), + TextColorChanged(Color), + LanguageSelected(Language), + ImageWidthChanged(u16), + InputChanged(String), + ToggleSecureInput(bool), + DebugToggled(bool), + TogglerChanged(bool), +} + +impl<'a> Step { + fn update(&mut self, msg: StepMessage, debug: &mut bool) { + match msg { + StepMessage::DebugToggled(value) => { + if let Step::Debugger = self { + *debug = value; + } + } + StepMessage::LanguageSelected(language) => { + if let Step::Radio { selection } = self { + *selection = Some(language); + } + } + StepMessage::SliderChanged(new_value) => { + if let Step::Slider { value, .. } = self { + *value = new_value; + } + } + StepMessage::TextSizeChanged(new_size) => { + if let Step::Text { size, .. } = self { + *size = new_size; + } + } + StepMessage::TextColorChanged(new_color) => { + if let Step::Text { color, .. } = self { + *color = new_color; + } + } + StepMessage::LayoutChanged(new_layout) => { + if let Step::RowsAndColumns { layout, .. } = self { + *layout = new_layout; + } + } + StepMessage::SpacingChanged(new_spacing) => { + if let Step::RowsAndColumns { spacing, .. } = self { + *spacing = new_spacing; + } + } + StepMessage::ImageWidthChanged(new_width) => { + if let Step::Image { width, .. } = self { + *width = new_width; + } + } + StepMessage::InputChanged(new_value) => { + if let Step::TextInput { value, .. } = self { + *value = new_value; + } + } + StepMessage::ToggleSecureInput(toggle) => { + if let Step::TextInput { is_secure, .. } = self { + *is_secure = toggle; + } + } + StepMessage::TogglerChanged(value) => { + if let Step::Toggler { can_continue, .. } = self { + *can_continue = value; + } + } + }; + } + + fn title(&self) -> &str { + match self { + Step::Welcome => "Welcome", + Step::Radio { .. } => "Radio button", + Step::Toggler { .. } => "Toggler", + Step::Slider { .. } => "Slider", + Step::Text { .. } => "Text", + Step::Image { .. } => "Image", + Step::RowsAndColumns { .. } => "Rows and columns", + Step::Scrollable => "Scrollable", + Step::TextInput { .. } => "Text input", + Step::Debugger => "Debugger", + Step::End => "End", + } + } + + fn can_continue(&self) -> bool { + match self { + Step::Welcome => true, + Step::Radio { selection } => *selection == Some(Language::Rust), + Step::Toggler { can_continue } => *can_continue, + Step::Slider { .. } => true, + Step::Text { .. } => true, + Step::Image { .. } => true, + Step::RowsAndColumns { .. } => true, + Step::Scrollable => true, + Step::TextInput { value, .. } => !value.is_empty(), + Step::Debugger => true, + Step::End => false, + } + } + + fn view(&self, debug: bool) -> Element { + match self { + Step::Welcome => Self::welcome(), + Step::Radio { selection } => Self::radio(*selection), + Step::Toggler { can_continue } => Self::toggler(*can_continue), + Step::Slider { value } => Self::slider(*value), + Step::Text { size, color } => Self::text(*size, *color), + Step::Image { width } => Self::image(*width), + Step::RowsAndColumns { layout, spacing } => { + Self::rows_and_columns(*layout, *spacing) + } + Step::Scrollable => Self::scrollable(), + Step::TextInput { value, is_secure } => { + Self::text_input(value, *is_secure) + } + Step::Debugger => Self::debugger(debug), + Step::End => Self::end(), + } + .into() + } + + fn container(title: &str) -> Column<'a, StepMessage> { + column![text(title).size(50)].spacing(20) + } + + fn welcome() -> Column<'a, StepMessage> { + Self::container("Welcome!") + .push( + "This is a simple tour meant to showcase a bunch of widgets \ + that can be easily implemented on top of Iced.", + ) + .push( + "Iced is a cross-platform GUI library for Rust focused on \ + simplicity and type-safety. It is heavily inspired by Elm.", + ) + .push( + "It was originally born as part of Coffee, an opinionated \ + 2D game engine for Rust.", + ) + .push( + "On native platforms, Iced provides by default a renderer \ + built on top of wgpu, a graphics library supporting Vulkan, \ + Metal, DX11, and DX12.", + ) + .push( + "Additionally, this tour can also run on WebAssembly thanks \ + to dodrio, an experimental VDOM library for Rust.", + ) + .push( + "You will need to interact with the UI in order to reach the \ + end!", + ) + } + + fn slider(value: u8) -> Column<'a, StepMessage> { + Self::container("Slider") + .push( + "A slider allows you to smoothly select a value from a range \ + of values.", + ) + .push( + "The following slider lets you choose an integer from \ + 0 to 100:", + ) + .push(slider(0..=100, value, StepMessage::SliderChanged)) + .push( + text(value.to_string()) + .width(Length::Fill) + .horizontal_alignment(alignment::Horizontal::Center), + ) + } + + fn rows_and_columns( + layout: Layout, + spacing: u16, + ) -> Column<'a, StepMessage> { + let row_radio = + radio("Row", Layout::Row, Some(layout), StepMessage::LayoutChanged); + + let column_radio = radio( + "Column", + Layout::Column, + Some(layout), + StepMessage::LayoutChanged, + ); + + let layout_section: Element<_> = match layout { + Layout::Row => { + row![row_radio, column_radio].spacing(spacing).into() + } + Layout::Column => { + column![row_radio, column_radio].spacing(spacing).into() + } + }; + + let spacing_section = column![ + slider(0..=80, spacing, StepMessage::SpacingChanged), + text(format!("{} px", spacing)) + .width(Length::Fill) + .horizontal_alignment(alignment::Horizontal::Center), + ] + .spacing(10); + + Self::container("Rows and columns") + .spacing(spacing) + .push( + "Iced uses a layout model based on flexbox to position UI \ + elements.", + ) + .push( + "Rows and columns can be used to distribute content \ + horizontally or vertically, respectively.", + ) + .push(layout_section) + .push("You can also easily change the spacing between elements:") + .push(spacing_section) + } + + fn text(size: u16, color: Color) -> Column<'a, StepMessage> { + let size_section = column![ + "You can change its size:", + text(format!("This text is {} pixels", size)).size(size), + slider(10..=70, size, StepMessage::TextSizeChanged), + ] + .padding(20) + .spacing(20); + + let color_sliders = row![ + color_slider(color.r, move |r| Color { r, ..color }), + color_slider(color.g, move |g| Color { g, ..color }), + color_slider(color.b, move |b| Color { b, ..color }), + ] + .spacing(10); + + let color_section = column![ + "And its color:", + text(format!("{:?}", color)).style(color), + color_sliders, + ] + .padding(20) + .spacing(20); + + Self::container("Text") + .push( + "Text is probably the most essential widget for your UI. \ + It will try to adapt to the dimensions of its container.", + ) + .push(size_section) + .push(color_section) + } + + fn radio(selection: Option) -> Column<'a, StepMessage> { + let question = column![ + text("Iced is written in...").size(24), + column( + Language::all() + .iter() + .cloned() + .map(|language| { + radio( + language, + language, + selection, + StepMessage::LanguageSelected, + ) + }) + .map(Element::from) + .collect() + ) + .spacing(10) + ] + .padding(20) + .spacing(10); + + Self::container("Radio button") + .push( + "A radio button is normally used to represent a choice... \ + Surprise test!", + ) + .push(question) + .push( + "Iced works very well with iterators! The list above is \ + basically created by folding a column over the different \ + choices, creating a radio button for each one of them!", + ) + } + + fn toggler(can_continue: bool) -> Column<'a, StepMessage> { + Self::container("Toggler") + .push("A toggler is mostly used to enable or disable something.") + .push( + Container::new(toggler( + "Toggle me to continue...".to_owned(), + can_continue, + StepMessage::TogglerChanged, + )) + .padding([0, 40]), + ) + } + + fn image(width: u16) -> Column<'a, StepMessage> { + Self::container("Image") + .push("An image that tries to keep its aspect ratio.") + .push(ferris(width)) + .push(slider(100..=500, width, StepMessage::ImageWidthChanged)) + .push( + text(format!("Width: {} px", width)) + .width(Length::Fill) + .horizontal_alignment(alignment::Horizontal::Center), + ) + } + + fn scrollable() -> Column<'a, StepMessage> { + Self::container("Scrollable") + .push( + "Iced supports scrollable content. Try it out! Find the \ + button further below.", + ) + .push( + text("Tip: You can use the scrollbar to scroll down faster!") + .size(16), + ) + .push(vertical_space(Length::Units(4096))) + .push( + text("You are halfway there!") + .width(Length::Fill) + .size(30) + .horizontal_alignment(alignment::Horizontal::Center), + ) + .push(vertical_space(Length::Units(4096))) + .push(ferris(300)) + .push( + text("You made it!") + .width(Length::Fill) + .size(50) + .horizontal_alignment(alignment::Horizontal::Center), + ) + } + + fn text_input(value: &str, is_secure: bool) -> Column<'a, StepMessage> { + let text_input = text_input( + "Type something to continue...", + value, + StepMessage::InputChanged, + ) + .padding(10) + .size(30); + + Self::container("Text input") + .push("Use a text input to ask for different kinds of information.") + .push(if is_secure { + text_input.password() + } else { + text_input + }) + .push(checkbox( + "Enable password mode", + is_secure, + StepMessage::ToggleSecureInput, + )) + .push( + "A text input produces a message every time it changes. It is \ + very easy to keep track of its contents:", + ) + .push( + text(if value.is_empty() { + "You have not typed anything yet..." + } else { + value + }) + .width(Length::Fill) + .horizontal_alignment(alignment::Horizontal::Center), + ) + } + + fn debugger(debug: bool) -> Column<'a, StepMessage> { + Self::container("Debugger") + .push( + "You can ask Iced to visually explain the layouting of the \ + different elements comprising your UI!", + ) + .push( + "Give it a shot! Check the following checkbox to be able to \ + see element boundaries.", + ) + .push(if cfg!(target_arch = "wasm32") { + Element::new( + text("Not available on web yet!") + .style(Color::from([0.7, 0.7, 0.7])) + .horizontal_alignment(alignment::Horizontal::Center), + ) + } else { + checkbox("Explain layout", debug, StepMessage::DebugToggled) + .into() + }) + .push("Feel free to go back and take a look.") + } + + fn end() -> Column<'a, StepMessage> { + Self::container("You reached the end!") + .push("This tour will be updated as more features are added.") + .push("Make sure to keep an eye on it!") + } +} + +fn ferris<'a>(width: u16) -> Container<'a, StepMessage> { + container( + // This should go away once we unify resource loading on native + // platforms + if cfg!(target_arch = "wasm32") { + image("tour/images/ferris.png") + } else { + image(format!("{}/images/ferris.png", env!("CARGO_MANIFEST_DIR"))) + } + .width(Length::Units(width)), + ) + .width(Length::Fill) + .center_x() +} + +fn button<'a, Message: Clone>(label: &str) -> Button<'a, Message> { + iced::widget::button( + text(label).horizontal_alignment(alignment::Horizontal::Center), + ) + .padding(12) + .width(Length::Units(100)) +} + +fn color_slider<'a>( + component: f32, + update: impl Fn(f32) -> Color + 'a, +) -> Slider<'a, f64, StepMessage, Renderer> { + slider(0.0..=1.0, f64::from(component), move |c| { + StepMessage::TextColorChanged(update(c as f32)) + }) + .step(0.01) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Language { + Rust, + Elm, + Ruby, + Haskell, + C, + Other, +} + +impl Language { + fn all() -> [Language; 6] { + [ + Language::C, + Language::Elm, + Language::Ruby, + Language::Haskell, + Language::Rust, + Language::Other, + ] + } +} + +impl From for String { + fn from(language: Language) -> String { + String::from(match language { + Language::Rust => "Rust", + Language::Elm => "Elm", + Language::Ruby => "Ruby", + Language::Haskell => "Haskell", + Language::C => "C", + Language::Other => "Other", + }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Layout { + Row, + Column, +} diff --git a/sctk/Cargo.toml b/sctk/Cargo.toml index 802587cd81..9bd00fe26e 100644 --- a/sctk/Cargo.toml +++ b/sctk/Cargo.toml @@ -15,8 +15,6 @@ multi_window = [] log = "0.4" thiserror = "1.0" sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "3776d4a" } -glutin = "0.30.0" -glow = "0.11.2" raw-window-handle = "0.5.0" enum-repr = "0.2.6" futures = "0.3" diff --git a/sctk/src/application.rs b/sctk/src/application.rs index f43d5d44f5..baeb74a0ca 100644 --- a/sctk/src/application.rs +++ b/sctk/src/application.rs @@ -1,5 +1,4 @@ use crate::{ - egl::{get_surface, init_egl}, error::{self, Error}, event_loop::{ self, @@ -25,21 +24,22 @@ use iced_native::{ }; use sctk::{ - reexports::client::Proxy, + reexports::client::{Proxy, protocol::wl_surface::WlSurface}, seat::{keyboard::Modifiers, pointer::PointerEventKind}, }; use std::{ - collections::HashMap, ffi::CString, fmt, marker::PhantomData, - num::NonZeroU32, hash::Hash, + collections::HashMap, fmt, marker::PhantomData, hash::Hash, }; use wayland_backend::client::ObjectId; -use glutin::{api::egl, prelude::*, surface::WindowSurface, platform}; -use iced_graphics::{compositor, renderer, window, Color, Point, Viewport}; +use iced_graphics::{compositor, renderer, window::{self, Compositor}, Color, Point, Viewport}; use iced_native::user_interface::{self, UserInterface}; use iced_native::window::Id as SurfaceId; use std::mem::ManuallyDrop; - +use raw_window_handle::{ + HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, + WaylandDisplayHandle, WaylandWindowHandle +}; #[derive(Debug)] pub enum Event { /// A normal sctk event @@ -167,6 +167,28 @@ where fn close_requested(&self, id: SurfaceIdWrapper) -> Self::Message; } +pub struct SurfaceDisplayWrapper { + comp_surface: Option<::Surface>, + backend: wayland_backend::client::Backend, + wl_surface: WlSurface, +} + +unsafe impl HasRawDisplayHandle for SurfaceDisplayWrapper { + fn raw_display_handle(&self) -> RawDisplayHandle { + let mut display_handle = WaylandDisplayHandle::empty(); + display_handle.display = self.backend.display_ptr() as *mut _; + RawDisplayHandle::Wayland(display_handle) + } +} + +unsafe impl HasRawWindowHandle for SurfaceDisplayWrapper { + fn raw_window_handle(&self) -> RawWindowHandle { + let mut window_handle = WaylandWindowHandle::empty(); + window_handle.surface = self.wl_surface.id().as_ptr() as *mut _; + RawWindowHandle::Wayland(window_handle) + } +} + /// Runs an [`Application`] with an executor, compositor, and the provided /// settings. pub fn run( @@ -176,7 +198,7 @@ pub fn run( where A: Application + 'static, E: Executor + 'static, - C: window::GLCompositor + 'static, + C: window::Compositor + 'static, ::Theme: StyleSheet, A::Flags: Clone, { @@ -203,16 +225,14 @@ where runtime.enter(|| A::new(flags)) }; let wl_surface = event_loop.state.compositor_state.create_surface(&event_loop.state.queue_handle); - let (display, context, config, surface) = init_egl(&wl_surface, 1, 1); - let context = context.make_current(&surface).unwrap(); + // let (display, context, config, surface) = init_egl(&wl_surface, 100, 100); + let backend = event_loop.state.connection.backend(); + let wrapper = SurfaceDisplayWrapper:: { comp_surface: None, backend: backend.clone(), wl_surface }; + + #[allow(unsafe_code)] - let (compositor, renderer) = unsafe { - C::new(compositor_settings, |name| { - let name = CString::new(name).unwrap(); - display.get_proc_address(name.as_c_str()) - })? - }; + let (mut compositor, renderer) = C::new(compositor_settings, Some(&wrapper)).unwrap(); let mut auto_size_surfaces = HashMap::new(); @@ -256,20 +276,17 @@ where (surface.id(), SurfaceIdWrapper::Window(native_id), surface, builder.iced_settings.size) } }; - let surface = get_surface( - &display, - &config, - &wl_surface, - w, - h, - ); - context.make_current(&surface).unwrap(); - let surface_ids = HashMap::from([(object_id.clone(), native_id)]); - let egl_surfaces = HashMap::from([(native_id.inner(), surface)]); + let surface_ids = HashMap::from([(object_id, native_id)]); + let mut wrapper = SurfaceDisplayWrapper { comp_surface: None, backend: backend.clone(), wl_surface }; + let c_surface = compositor.create_surface(&wrapper); + + wrapper.comp_surface.replace(c_surface); let (mut sender, receiver) = mpsc::unbounded::>(); + let compositor_surfaces = HashMap::from([(native_id.inner(), wrapper)]); + let mut instance = Box::pin(run_instance::( application, compositor, @@ -278,12 +295,13 @@ where ev_proxy, debug, receiver, - egl_surfaces, + compositor_surfaces, surface_ids, auto_size_surfaces, - display, - context, - config, + // display, + // context, + // config, + backend, init_command, exit_on_close_request, if is_layer_surface { @@ -317,7 +335,7 @@ fn subscription_map(e: A::Message) -> Event where A: Application + 'static, E: Executor + 'static, - C: window::GLCompositor + 'static, + C: window::Compositor + 'static, ::Theme: StyleSheet, { Event::SctkEvent(IcedSctkEvent::UserEvent(e)) @@ -332,15 +350,13 @@ async fn run_instance( mut ev_proxy: proxy::Proxy>, mut debug: Debug, mut receiver: mpsc::UnboundedReceiver>, - mut egl_surfaces: HashMap< + mut compositor_surfaces: HashMap< SurfaceId, - glutin::api::egl::surface::Surface, + SurfaceDisplayWrapper, >, mut surface_ids: HashMap, mut auto_size_surfaces: HashMap, - mut egl_display: egl::display::Display, - mut egl_context: egl::context::PossiblyCurrentContext, - mut egl_config: glutin::api::egl::config::Config, + mut backend: wayland_backend::client::Backend, init_command: Command, exit_on_close_request: bool, init_id: SurfaceIdWrapper, @@ -348,7 +364,7 @@ async fn run_instance( where A: Application + 'static, E: Executor + 'static, - C: window::GLCompositor + 'static, + C: window::Compositor + 'static, ::Theme: StyleSheet, { let mut cache = user_interface::Cache::default(); @@ -468,7 +484,7 @@ where } crate::sctk_event::WindowEventVariant::Close => { if let Some(surface_id) = surface_ids.remove(&id.id()) { - drop(egl_surfaces.remove(&surface_id.inner())); + // drop(compositor_surfaces.remove(&surface_id.inner())); interfaces.remove(&surface_id.inner()); states.remove(&surface_id.inner()); messages.push(application.close_requested(surface_id)); @@ -488,15 +504,18 @@ where if let Some(id) = surface_ids.get(&id.id()) { let new_size = configure.new_size.unwrap(); - if first && !egl_surfaces.contains_key(&id.inner()) { - let egl_surface = get_surface( - &egl_display, - &egl_config, - &wl_surface, - new_size.0, - new_size.1, - ); - egl_surfaces.insert(id.inner(), egl_surface); + if !compositor_surfaces.contains_key(&id.inner()) { + let mut wrapper = SurfaceDisplayWrapper { + comp_surface: None, + backend: backend.clone(), + wl_surface + }; + let c_surface = compositor.create_surface(&wrapper); + wrapper.comp_surface.replace(c_surface); + compositor_surfaces.insert(id.inner(), wrapper); + + } + if first { let state = State::new(&application, *id); let user_interface = build_user_interface( @@ -523,7 +542,7 @@ where } LayerSurfaceEventVariant::Done => { if let Some(surface_id) = surface_ids.remove(&id.id()) { - drop(egl_surfaces.remove(&surface_id.inner())); + drop(compositor_surfaces.remove(&surface_id.inner())); interfaces.remove(&surface_id.inner()); states.remove(&surface_id.inner()); messages.push(application.close_requested(surface_id)); @@ -535,15 +554,17 @@ where } LayerSurfaceEventVariant::Configure(configure, wl_surface, first) => { if let Some(id) = surface_ids.get(&id.id()) { - if first && !egl_surfaces.contains_key(&id.inner()) { - let egl_surface = get_surface( - &egl_display, - &egl_config, - &wl_surface, - configure.new_size.0, - configure.new_size.1, - ); - egl_surfaces.insert(id.inner(), egl_surface); + if !compositor_surfaces.contains_key(&id.inner()) { + let mut wrapper = SurfaceDisplayWrapper { + comp_surface: None, + backend: backend.clone(), + wl_surface + }; + let c_surface = compositor.create_surface(&wrapper); + wrapper.comp_surface.replace(c_surface); + compositor_surfaces.insert(id.inner(), wrapper); + } + if first { let state = State::new(&application, *id); let user_interface = build_user_interface( @@ -578,7 +599,7 @@ where } PopupEventVariant::Done => { if let Some(surface_id) = surface_ids.remove(&id.id()) { - drop(egl_surfaces.remove(&surface_id.inner())); + drop(compositor_surfaces.remove(&surface_id.inner())); interfaces.remove(&surface_id.inner()); states.remove(&surface_id.inner()); messages.push(application.close_requested(surface_id)); @@ -588,15 +609,17 @@ where PopupEventVariant::WmCapabilities(_) => {} PopupEventVariant::Configure(configure, wl_surface, first) => { if let Some(id) = surface_ids.get(&id.id()) { - if first && !egl_surfaces.contains_key(&id.inner()) { - let egl_surface = get_surface( - &egl_display, - &egl_config, - &wl_surface, - configure.width as u32, - configure.height as u32, - ); - egl_surfaces.insert(id.inner(), egl_surface); + if !compositor_surfaces.contains_key(&id.inner()) { + let mut wrapper = SurfaceDisplayWrapper { + comp_surface: None, + backend: backend.clone(), + wl_surface + }; + let c_surface = compositor.create_surface(&wrapper); + wrapper.comp_surface.replace(c_surface); + compositor_surfaces.insert(id.inner(), wrapper); + } + if first { let state = State::new(&application, *id); let user_interface = build_user_interface( @@ -742,20 +765,13 @@ where i += 1; } } + let has_events = !filtered.is_empty(); + let cursor_position = match states.get(&surface_id.inner()) { Some(s) => s.cursor_position(), None => continue, }; - if filtered.is_empty() && messages.is_empty() { - continue; - } else { - ev_proxy.send_event(Event::SctkEvent( - IcedSctkEvent::RedrawRequested( - object_id.clone(), - ), - )); - } debug.event_processing_started(); let native_events: Vec<_> = filtered .into_iter() @@ -785,7 +801,7 @@ where { runtime.broadcast(event); } - if !messages.is_empty() + if has_events || !messages.is_empty() || matches!( interface_state, user_interface::State::Outdated @@ -793,8 +809,16 @@ where { needs_redraw = true; } + if needs_redraw { + ev_proxy.send_event(Event::SctkEvent( + IcedSctkEvent::RedrawRequested( + object_id.clone(), + ), + )); + } } if needs_redraw { + let mut pure_states: HashMap<_, _> = ManuallyDrop::into_inner(interfaces) .drain() @@ -802,24 +826,24 @@ where (id, interface.into_cache()) }) .collect(); - + for (_object_id, surface_id) in &surface_ids { let state = match states.get_mut(&surface_id.inner()) { Some(s) => s, None => continue, }; - let cache = match pure_states - .get_mut(&surface_id.inner()) + let mut cache = match pure_states + .remove(&surface_id.inner()) { Some(cache) => cache, - None => continue, + None => user_interface::Cache::default(), }; // Update application update::( &mut application, - cache, + &mut cache, Some(state), &mut renderer, &mut runtime, @@ -830,6 +854,8 @@ where &mut auto_size_surfaces, ); + pure_states.insert(surface_id.inner(), cache); + // Update state state.synchronize(&application); @@ -854,11 +880,11 @@ where IcedSctkEvent::RedrawRequested(id) => { if let Some(( native_id, - Some(egl_surface), + Some(wrapper), Some(mut user_interface), Some(state), )) = surface_ids.get(&id).map(|id| { - let surface = egl_surfaces.get_mut(&id.inner()); + let surface = compositor_surfaces.get_mut(&id.inner()); let interface = interfaces.remove(&id.inner()); let state = states.get_mut(&id.inner()); (*id, surface, interface, state) @@ -894,16 +920,10 @@ where } } debug.render_started(); - - if current_context_window != native_id.inner() { - if egl_context.make_current(egl_surface).is_ok() { - current_context_window = native_id.inner(); - } else { - interfaces - .insert(native_id.inner(), user_interface); - continue; - } - } + let comp_surface = match wrapper.comp_surface.as_mut() { + Some(s) => s, + None => continue, + }; if state.viewport_changed() { let physical_size = state.physical_size(); @@ -929,13 +949,8 @@ where new_mouse_interaction, )); - egl_surface.resize( - &egl_context, - NonZeroU32::new(physical_size.width).unwrap(), - NonZeroU32::new(physical_size.height).unwrap(), - ); - - compositor.resize_viewport(physical_size); + compositor.configure_surface(comp_surface, physical_size.width, physical_size.height); + // is resize viewport still necessary? let _ = interfaces .insert(native_id.inner(), user_interface); @@ -943,13 +958,13 @@ where interfaces.insert(native_id.inner(), user_interface); } - compositor.present( + let _ = compositor.present( &mut renderer, + comp_surface, state.viewport(), state.background_color(), &debug.overlay(), ); - let _ = egl_surface.swap_buffers(&egl_context); debug.render_finished(); } @@ -1056,7 +1071,7 @@ where title, scale_factor, viewport, - viewport_changed: false, + viewport_changed: true, // TODO: Encode cursor availability in the type-system cursor_position: Point::new(-1.0, -1.0), modifiers: Modifiers::default(), @@ -1193,7 +1208,7 @@ pub(crate) fn update( ) where A: Application + 'static, E: Executor + 'static, - C: window::GLCompositor + 'static, + C: window::Compositor + 'static, ::Theme: StyleSheet, { for message in messages.drain(..) { diff --git a/sctk/src/egl.rs b/sctk/src/egl.rs deleted file mode 100644 index 05161f4fbb..0000000000 --- a/sctk/src/egl.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::num::NonZeroU32; - -use glutin::{ - api::egl, config::ConfigSurfaceTypes, prelude::GlDisplay, - surface::WindowSurface, -}; -use sctk::reexports::client::{protocol::wl_surface, Proxy}; - -/// helper for initializing egl after creation of the first layer surface / window -pub fn init_egl( - surface: &wl_surface::WlSurface, - width: u32, - height: u32, -) -> ( - egl::display::Display, - egl::context::NotCurrentContext, - glutin::api::egl::config::Config, - egl::surface::Surface, -) { - let mut display_handle = raw_window_handle::WaylandDisplayHandle::empty(); - display_handle.display = surface - .backend() - .upgrade() - .expect("Connection has been closed") - .display_ptr() as *mut _; - let display_handle = - raw_window_handle::RawDisplayHandle::Wayland(display_handle); - let mut window_handle = raw_window_handle::WaylandWindowHandle::empty(); - window_handle.surface = surface.id().as_ptr() as *mut _; - let window_handle = - raw_window_handle::RawWindowHandle::Wayland(window_handle); - - // Initialize the EGL Wayland platform - // - // SAFETY: The connection is valid. - let display = unsafe { egl::display::Display::new(display_handle) } - .expect("Failed to initialize Wayland EGL platform"); - - // Find a suitable config for the window. - let config_template = glutin::config::ConfigTemplateBuilder::default() - .compatible_with_native_window(window_handle) - .with_surface_type(ConfigSurfaceTypes::WINDOW) - .with_api(glutin::config::Api::GLES2) - .build(); - let config = unsafe { display.find_configs(config_template) } - .unwrap() - .next() - .expect("No available configs"); - let gl_attrs = glutin::context::ContextAttributesBuilder::default() - .with_context_api(glutin::context::ContextApi::OpenGl(None)) - .build(Some(window_handle)); - let gles_attrs = glutin::context::ContextAttributesBuilder::default() - .with_context_api(glutin::context::ContextApi::Gles(None)) - .build(Some(window_handle)); - - let context = unsafe { display.create_context(&config, &gl_attrs) } - .or_else(|_| unsafe { display.create_context(&config, &gles_attrs) }) - .expect("Failed to create context"); - - let surface_attrs = - glutin::surface::SurfaceAttributesBuilder::::default() - .build( - window_handle, - NonZeroU32::new(width).unwrap(), - NonZeroU32::new(height).unwrap(), - ); - let surface = - unsafe { display.create_window_surface(&config, &surface_attrs) } - .expect("Failed to create surface"); - - (display, context, config, surface) -} - -pub fn get_surface( - display: &egl::display::Display, - config: &glutin::api::egl::config::Config, - surface: &wl_surface::WlSurface, - width: u32, - height: u32, -) -> egl::surface::Surface { - let mut window_handle = raw_window_handle::WaylandWindowHandle::empty(); - window_handle.surface = surface.id().as_ptr() as *mut _; - let window_handle = - raw_window_handle::RawWindowHandle::Wayland(window_handle); - let surface_attrs = - glutin::surface::SurfaceAttributesBuilder::::default() - .build( - window_handle, - NonZeroU32::new(width).unwrap(), - NonZeroU32::new(height).unwrap(), - ); - let surface = - unsafe { display.create_window_surface(&config, &surface_attrs) } - .expect("Failed to create surface"); - surface -} diff --git a/sctk/src/event_loop/state.rs b/sctk/src/event_loop/state.rs index 22814f09ee..fa6c516ea8 100644 --- a/sctk/src/event_loop/state.rs +++ b/sctk/src/event_loop/state.rs @@ -137,11 +137,6 @@ pub struct SctkPopupData { /// Wrapper to carry sctk state. #[derive(Debug)] pub struct SctkState { - // egl - // pub(crate) context: Option, - // pub(crate) glow: Option, - // pub(crate) display: Option, - // pub(crate) config: Option, /// the cursor wl_surface pub(crate) cursor_surface: Option, /// a memory pool diff --git a/sctk/src/handlers/shell/xdg_popup.rs b/sctk/src/handlers/shell/xdg_popup.rs index d1521d5f0a..534b95d217 100644 --- a/sctk/src/handlers/shell/xdg_popup.rs +++ b/sctk/src/handlers/shell/xdg_popup.rs @@ -39,7 +39,9 @@ impl PopupHandler for SctkState { SctkSurface::Window(s) => s.clone(), SctkSurface::Popup(s) => s.clone(), }, - }) + }); + self.sctk_events + .push(SctkEvent::Draw(popup.wl_surface().clone())); } fn done( diff --git a/sctk/src/handlers/shell/xdg_window.rs b/sctk/src/handlers/shell/xdg_window.rs index ce2c08a33b..4426cb6f90 100644 --- a/sctk/src/handlers/shell/xdg_window.rs +++ b/sctk/src/handlers/shell/xdg_window.rs @@ -51,7 +51,6 @@ impl WindowHandler for SctkState { configure.new_size = Some(window.requested_size.unwrap_or((300, 500))); }; - let wl_surface = window.window.wl_surface(); let id = wl_surface.clone(); let first = window.last_configure.is_none(); @@ -64,7 +63,8 @@ impl WindowHandler for SctkState { first, ), id, - }) + }); + self.sctk_events.push(SctkEvent::Draw(wl_surface.clone())); } } diff --git a/sctk/src/lib.rs b/sctk/src/lib.rs index 35b8c506f7..2fab080ecb 100644 --- a/sctk/src/lib.rs +++ b/sctk/src/lib.rs @@ -4,7 +4,6 @@ pub mod application; pub mod commands; pub mod conversion; pub mod dpi; -pub mod egl; pub mod error; pub mod event_loop; mod handlers; From eafe2248fe2caadd99204136f85f953c9822c5aa Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 18 Jan 2023 13:11:24 -0500 Subject: [PATCH 46/56] refactor: default dyrend to just softbuffer --- dyrend/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dyrend/Cargo.toml b/dyrend/Cargo.toml index 50986a5e8d..29769cb5f0 100644 --- a/dyrend/Cargo.toml +++ b/dyrend/Cargo.toml @@ -34,7 +34,7 @@ default-features = false optional = true [features] -default = ["softbuffer", "wgpu"] +default = ["softbuffer"] image = ["iced_graphics/image", "iced_glow?/image", "iced_softbuffer?/image", "iced_wgpu?/image"] svg = ["iced_graphics/svg", "iced_glow?/svg", "iced_softbuffer?/svg", "iced_wgpu?/svg"] #TODO: implement Compositor for glow Backend From 70aaa7f7e269d31a8e0dc23a2bbe5107205c6a40 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 18 Jan 2023 17:36:05 -0500 Subject: [PATCH 47/56] fix: use softbuffer fork supporting Argb8888 --- examples/todos/Cargo.toml | 1 + examples/todos/src/main.rs | 22 +++++++++++++++++++++- examples/todos_sctk/Cargo.toml | 1 + examples/todos_sctk/src/main.rs | 20 ++++++++++++++++++++ softbuffer/Cargo.toml | 2 +- 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index 7ad4d558bd..c0beb0693d 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -7,6 +7,7 @@ publish = false [dependencies] iced = { path = "../..", features = ["async-std", "debug"] } +iced_style = { path = "../../style" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" once_cell = "1.15" diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 690d9c0979..0a5cd56ea0 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -1,6 +1,5 @@ use iced::alignment::{self, Alignment}; use iced::event::{self, Event}; -use iced::keyboard; use iced::subscription; use iced::theme::{self, Theme}; use iced::widget::{ @@ -8,6 +7,7 @@ use iced::widget::{ text_input, Text, }; use iced::window; +use iced::{application, keyboard}; use iced::{Application, Element}; use iced::{Color, Command, Font, Length, Settings, Subscription}; @@ -378,6 +378,26 @@ impl Task { } } } + + fn style(&self) -> ::Style { + ::Style::Custom(Box::new( + CustomTheme, + )) + } +} + +pub struct CustomTheme; + +impl application::StyleSheet for CustomTheme { + type Style = iced::Theme; + + fn appearance(&self, style: &Self::Style) -> application::Appearance { + dbg!(Color::TRANSPARENT); + application::Appearance { + background_color: Color::TRANSPARENT, + text_color: Color::BLACK, + } + } } fn view_controls(tasks: &[Task], current_filter: Filter) -> Element { diff --git a/examples/todos_sctk/Cargo.toml b/examples/todos_sctk/Cargo.toml index 35d4dac301..ea257c40c1 100644 --- a/examples/todos_sctk/Cargo.toml +++ b/examples/todos_sctk/Cargo.toml @@ -12,6 +12,7 @@ serde_json = "1.0" once_cell = "1.15" sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "a257bf7" } iced_native = { path = "../../native" } +iced_style = { path = "../../style" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] async-std = "1.0" diff --git a/examples/todos_sctk/src/main.rs b/examples/todos_sctk/src/main.rs index 75bbf68056..5bc8652a20 100644 --- a/examples/todos_sctk/src/main.rs +++ b/examples/todos_sctk/src/main.rs @@ -15,6 +15,7 @@ use iced::widget::{ }; use iced::{window, Application, Element}; use iced::{Color, Command, Font, Length, Settings, Subscription}; +use iced_style::application; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; @@ -299,6 +300,25 @@ impl Application for Todos { fn close_requested(&self, id: SurfaceIdWrapper) -> Self::Message { Message::CloseRequested(id) } + + fn style(&self) -> ::Style { + ::Style::Custom(Box::new( + CustomTheme, + )) + } +} + +pub struct CustomTheme; + +impl application::StyleSheet for CustomTheme { + type Style = iced::Theme; + + fn appearance(&self, style: &Self::Style) -> application::Appearance { + application::Appearance { + background_color: Color::TRANSPARENT, + text_color: Color::BLACK, + } + } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/softbuffer/Cargo.toml b/softbuffer/Cargo.toml index ec775c88ee..04077ba264 100644 --- a/softbuffer/Cargo.toml +++ b/softbuffer/Cargo.toml @@ -13,7 +13,7 @@ lazy_static = "1.4" log = "0.4" raw-window-handle = "0.5" raqote = { version = "0.8", default-features = false } -softbuffer = "0.2" +softbuffer = { git = "https://github.com/pop-os/softbuffer", rev = "68240f56b" } [dependencies.iced_native] path = "../native" From 334d5e9e3b8bb5f673bbe21870b4c02455390efb Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 20 Jan 2023 01:47:02 -0500 Subject: [PATCH 48/56] cleanup: fixes visual corruption sometimes present in auto-sized surfaces, and also adds the option for starting with no initial surface --- examples/autosize_layer/Cargo.toml | 13 + examples/autosize_layer/src/main.rs | 168 ++++++++++ .../platform_specific/wayland/popup.rs | 12 +- sctk/src/application.rs | 290 +++++++----------- sctk/src/event_loop/mod.rs | 110 ++----- sctk/src/event_loop/state.rs | 9 - sctk/src/handlers/compositor.rs | 2 +- sctk/src/handlers/shell/layer.rs | 12 +- sctk/src/handlers/shell/xdg_popup.rs | 2 +- sctk/src/handlers/shell/xdg_window.rs | 2 +- sctk/src/sctk_event.rs | 18 +- sctk/src/settings.rs | 1 + 12 files changed, 345 insertions(+), 294 deletions(-) create mode 100644 examples/autosize_layer/Cargo.toml create mode 100644 examples/autosize_layer/src/main.rs diff --git a/examples/autosize_layer/Cargo.toml b/examples/autosize_layer/Cargo.toml new file mode 100644 index 0000000000..9a199d88c2 --- /dev/null +++ b/examples/autosize_layer/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "autosize_layer" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", default-features=false, features = ["async-std", "wayland", "debug", "dyrend"] } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "a257bf7" } +iced_native = { path = "../../native" } +iced_style = { path = "../../style" } + diff --git a/examples/autosize_layer/src/main.rs b/examples/autosize_layer/src/main.rs new file mode 100644 index 0000000000..8eeea7a4fe --- /dev/null +++ b/examples/autosize_layer/src/main.rs @@ -0,0 +1,168 @@ +use iced::alignment::{self, Alignment}; +use iced::event::{self, Event}; +use iced::keyboard; +use iced::subscription; +use iced::theme::{self, Theme}; +use iced::wayland::actions::layer_surface::SctkLayerSurfaceSettings; +use iced::wayland::actions::popup::SctkPopupSettings; +use iced::wayland::actions::popup::SctkPositioner; +use iced::wayland::actions::window::SctkWindowSettings; +use iced::wayland::layer_surface::{get_layer_surface, Anchor}; +use iced::wayland::popup::destroy_popup; +use iced::wayland::popup::get_popup; +use iced::wayland::window::get_window; +use iced::wayland::InitialSurface; +use iced::wayland::SurfaceIdWrapper; +use iced::widget::{ + self, button, checkbox, column, container, row, scrollable, text, + text_input, Column, Row, Text, +}; +use iced::Rectangle; +use iced::{window, Application, Element}; +use iced::{Color, Command, Font, Length, Settings, Subscription}; +use iced_style::application; + +pub fn main() -> iced::Result { + Todos::run(Settings { + initial_surface: InitialSurface::LayerSurface(Default::default()), + ..Default::default() + }) +} + +#[derive(Debug, Default)] +struct Todos { + size: u32, + id_ctr: u32, + popup: Option, +} + +#[derive(Debug, Clone)] +enum Message { + Tick, + Popup, + Ignore, +} + +impl Application for Todos { + type Message = Message; + type Theme = Theme; + type Executor = iced::executor::Default; + type Flags = (); + + fn new(_flags: ()) -> (Todos, Command) { + ( + Todos { + size: 5, + id_ctr: 2, + ..Default::default() + }, + get_layer_surface(SctkLayerSurfaceSettings { + id: window::Id::new(1), + anchor: Anchor::RIGHT, + ..Default::default() + }), + ) + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Tick => { + self.size = (self.size - 1) % 10; + if self.size == 0 { + self.size = 10; + } + } + Message::Popup => { + if let Some(p) = self.popup.take() { + return destroy_popup(p); + } else { + self.id_ctr += 1; + let new_id = window::Id::new(self.id_ctr); + self.popup.replace(new_id); + return get_popup(SctkPopupSettings { + parent: window::Id::new(0), + id: new_id, + positioner: SctkPositioner { + anchor_rect: Rectangle { + x: 10, + y: 10, + width: 1, + height: 1, + }, + ..Default::default() + }, + parent_size: None, + grab: true, + }); + } + } + Message::Ignore => {} + } + Command::none() + } + + fn view(&self, id: SurfaceIdWrapper) -> Element { + match id { + SurfaceIdWrapper::LayerSurface(_) | SurfaceIdWrapper::Popup(_) => { + Column::with_children( + (0..self.size) + .map(|_| { + Row::with_children( + (0..self.size) + .map(|i| { + button(Text::new(format!( + "{}: {}", + i, i + ))) + .on_press(Message::Popup) + .into() + }) + .collect(), + ) + .spacing(12) + .width(Length::Shrink) + .height(Length::Shrink) + .into() + }) + .collect(), + ) + .spacing(12) + .width(Length::Shrink) + .height(Length::Shrink) + .into() + } + SurfaceIdWrapper::Window(_) => unimplemented!(), + } + } + + fn close_requested(&self, id: SurfaceIdWrapper) -> Self::Message { + Message::Ignore + } + + fn style(&self) -> ::Style { + ::Style::Custom(Box::new( + CustomTheme, + )) + } + fn title(&self) -> String { + String::from("autosize") + } + + fn subscription(&self) -> Subscription { + iced::time::every(std::time::Duration::from_millis(1000)) + .map(|_| Message::Tick) + } +} + +pub struct CustomTheme; + +impl application::StyleSheet for CustomTheme { + type Style = iced::Theme; + + fn appearance(&self, style: &Self::Style) -> application::Appearance { + application::Appearance { + background_color: Color::from_rgba(1.0, 1.0, 1.0, 0.8), + text_color: Color::BLACK, + } + } +} diff --git a/native/src/command/platform_specific/wayland/popup.rs b/native/src/command/platform_specific/wayland/popup.rs index d17c78da00..ec45f181fa 100644 --- a/native/src/command/platform_specific/wayland/popup.rs +++ b/native/src/command/platform_specific/wayland/popup.rs @@ -77,7 +77,11 @@ impl Default for SctkPositioner { fn default() -> Self { Self { size: None, - size_limits: Limits::NONE.min_height(1).min_width(1).max_width(300).max_height(1080), + size_limits: Limits::NONE + .min_height(1) + .min_width(1) + .max_width(300) + .max_height(1080), anchor_rect: Rectangle { x: 0, y: 0, @@ -121,7 +125,7 @@ pub enum Action { width: u32, /// height height: u32, - } + }, } impl Action { @@ -140,7 +144,9 @@ impl Action { }, Action::Destroy { id } => Action::Destroy { id }, Action::Grab { id } => Action::Grab { id }, - Action::Size { id, width, height } => Action::Size { id, width, height }, + Action::Size { id, width, height } => { + Action::Size { id, width, height } + } } } } diff --git a/sctk/src/application.rs b/sctk/src/application.rs index baeb74a0ca..dc326a2d5a 100644 --- a/sctk/src/application.rs +++ b/sctk/src/application.rs @@ -11,7 +11,7 @@ use crate::{ IcedSctkEvent, KeyboardEventVariant, LayerSurfaceEventVariant, PopupEventVariant, SctkEvent, }, - settings, Command, Debug, Executor, Runtime, Size, Subscription, + settings, Command, Debug, Executor, Runtime, Size, Subscription, commands::{layer_surface::get_layer_surface, window::get_window}, }; use futures::{channel::mpsc, task, Future, FutureExt, StreamExt}; use iced_native::{ @@ -54,8 +54,6 @@ pub enum Event { Window(platform_specific::wayland::window::Action), /// popup requests from the client Popup(platform_specific::wayland::popup::Action), - /// init size of surface - InitSurfaceSize(SurfaceIdWrapper, (u32, u32)), /// request sctk to set the cursor of the active pointer SetCursor(Interaction), } @@ -192,7 +190,7 @@ unsafe impl HasRawWindowHandle for SurfaceDisplayWrapper { /// Runs an [`Application`] with an executor, compositor, and the provided /// settings. pub fn run( - mut settings: settings::Settings, + settings: settings::Settings, compositor_settings: C::Settings, ) -> Result<(), error::Error> where @@ -207,8 +205,7 @@ where let flags = settings.flags.clone(); let exit_on_close_request = settings.exit_on_close_request; - let is_layer_surface = - matches!(settings.surface, settings::InitialSurface::LayerSurface(_)); + let mut event_loop = SctkEventLoop::::new(&settings) .expect("Failed to initialize the event loop"); @@ -224,6 +221,12 @@ where runtime.enter(|| A::new(flags)) }; + + let init_command = match settings.surface { + settings::InitialSurface::LayerSurface(b) => Command::batch(vec![init_command, get_layer_surface(b)]), + settings::InitialSurface::XdgWindow(b) => Command::batch(vec![init_command, get_window(b)]), + settings::InitialSurface::None => init_command, + }; let wl_surface = event_loop.state.compositor_state.create_surface(&event_loop.state.queue_handle); // let (display, context, config, surface) = init_egl(&wl_surface, 100, 100); @@ -232,61 +235,15 @@ where #[allow(unsafe_code)] - let (mut compositor, renderer) = C::new(compositor_settings, Some(&wrapper)).unwrap(); - - let mut auto_size_surfaces = HashMap::new(); - - let (object_id, native_id, wl_surface, (w, h)) = match &mut settings.surface { - settings::InitialSurface::LayerSurface(builder) => { - // TODO ASHLEY should an application panic if it's initial surface can't be created? - if builder.size.is_none() { - let e = application.view(SurfaceIdWrapper::LayerSurface(builder.id)); - let _state = Widget::state(e.as_widget()); - e.as_widget().diff(&mut Tree::empty()); - let node = Widget::layout(e.as_widget(), &renderer, &builder.size_limits); - let bounds = node.bounds(); - let w = bounds.width as u32; - let h = bounds.height as u32; - builder.size = Some((Some(w), Some(h))); - auto_size_surfaces.insert(SurfaceIdWrapper::LayerSurface(builder.id), (w, h, builder.size_limits, false)); - }; - let (native_id, surface) = - event_loop.get_layer_surface(builder.clone()).unwrap(); - ( - surface.id(), - SurfaceIdWrapper::LayerSurface(native_id), - surface, - builder.size.map(|(w, h)| (w.unwrap_or_default().max(1), h.unwrap_or_default().max(1))).unwrap_or((1, 1)), - ) - } - settings::InitialSurface::XdgWindow(builder) => { - if builder.autosize { - let e = application.view(SurfaceIdWrapper::Window(builder.window_id)); - let _state = Widget::state(e.as_widget()); - e.as_widget().diff(&mut Tree::empty()); - let node = Widget::layout(e.as_widget(), &renderer, &builder.size_limits); - let bounds = node.bounds(); - let w = bounds.width as u32; - let h = bounds.height as u32; - builder.iced_settings.size = (w, h); - auto_size_surfaces.insert(SurfaceIdWrapper::Window(builder.window_id), (w, h, builder.size_limits, false)); - - }; - let (native_id, surface) = event_loop.get_window(builder.clone()); - (surface.id(), SurfaceIdWrapper::Window(native_id), surface, builder.iced_settings.size) - } - }; + let (compositor, renderer) = C::new(compositor_settings, Some(&wrapper)).unwrap(); - let surface_ids = HashMap::from([(object_id, native_id)]); - let mut wrapper = SurfaceDisplayWrapper { comp_surface: None, backend: backend.clone(), wl_surface }; - let c_surface = compositor.create_surface(&wrapper); - - wrapper.comp_surface.replace(c_surface); + let auto_size_surfaces = HashMap::new(); + + let surface_ids = Default::default(); let (mut sender, receiver) = mpsc::unbounded::>(); - let compositor_surfaces = HashMap::from([(native_id.inner(), wrapper)]); - + let compositor_surfaces = HashMap::new(); let mut instance = Box::pin(run_instance::( application, compositor, @@ -304,16 +261,11 @@ where backend, init_command, exit_on_close_request, - if is_layer_surface { - SurfaceIdWrapper::LayerSurface(native_id.inner()) - } else { - SurfaceIdWrapper::Window(native_id.inner()) - }, )); let mut context = task::Context::from_waker(task::noop_waker_ref()); - let _ = event_loop.run_return(move |event, event_loop, control_flow| { + let _ = event_loop.run_return(move |event, _, control_flow| { if let ControlFlow::ExitWithCode(_) = control_flow { return; } @@ -356,10 +308,9 @@ async fn run_instance( >, mut surface_ids: HashMap, mut auto_size_surfaces: HashMap, - mut backend: wayland_backend::client::Backend, + backend: wayland_backend::client::Backend, init_command: Command, - exit_on_close_request: bool, - init_id: SurfaceIdWrapper, + _exit_on_close_request: bool, // TODO Ashley ) -> Result<(), Error> where A: Application + 'static, @@ -369,29 +320,14 @@ where { let mut cache = user_interface::Cache::default(); - let init_id_inner = init_id.inner(); - let state = State::new(&application, init_id); - - let user_interface = build_user_interface( - &application, - user_interface::Cache::default(), - &mut renderer, - state.logical_size(), - &mut debug, - init_id, - &mut auto_size_surfaces - ); - let mut states = HashMap::from([(init_id_inner, state)]); - let mut interfaces = - ManuallyDrop::new(HashMap::from([(init_id_inner, user_interface)])); + let mut states: HashMap> = HashMap::new(); + let mut interfaces = ManuallyDrop::new(HashMap::new()); { - let state = states.get(&init_id_inner).unwrap(); - run_command( &application, &mut cache, - Some(state), + None, &mut renderer, init_command, &mut runtime, @@ -403,12 +339,12 @@ where } runtime.track(application.subscription().map(subscription_map::)); - let mut mouse_interaction = mouse::Interaction::default(); + let _mouse_interaction = mouse::Interaction::default(); let mut events: Vec = Vec::new(); let mut messages: Vec = Vec::new(); debug.startup_finished(); - let mut current_context_window = init_id_inner; + // let mut current_context_window = init_id_inner; let mut kbd_surface_id: Option = None; let mut mods = Modifiers::default(); @@ -427,8 +363,7 @@ where SctkEvent::SeatEvent { .. } => {} // TODO Ashley: handle later possibly if multiseat support is wanted SctkEvent::PointerEvent { variant, - ptr_id, - seat_id, + .. } => { let (state, _native_id) = match surface_ids .get(&variant.surface.id()) @@ -489,9 +424,9 @@ where states.remove(&surface_id.inner()); messages.push(application.close_requested(surface_id)); destroyed_surface_ids.insert(id.id(), surface_id); - if exit_on_close_request && surface_id == init_id { - break 'main; - } + // if exit_on_close_request && surface_id == init_id { + // break 'main; + // } } } crate::sctk_event::WindowEventVariant::WmCapabilities(_) @@ -512,8 +447,7 @@ where }; let c_surface = compositor.create_surface(&wrapper); wrapper.comp_surface.replace(c_surface); - compositor_surfaces.insert(id.inner(), wrapper); - + compositor_surfaces.insert(id.inner(), wrapper); } if first { let state = State::new(&application, *id); @@ -547,9 +481,9 @@ where states.remove(&surface_id.inner()); messages.push(application.close_requested(surface_id)); destroyed_surface_ids.insert(id.id(), surface_id); - if exit_on_close_request && surface_id == init_id { - break 'main; - } + // if exit_on_close_request && surface_id == init_id { + // break 'main; + // } } } LayerSurfaceEventVariant::Configure(configure, wl_surface, first) => { @@ -560,7 +494,8 @@ where backend: backend.clone(), wl_surface }; - let c_surface = compositor.create_surface(&wrapper); + let mut c_surface = compositor.create_surface(&wrapper); + compositor.configure_surface(&mut c_surface, configure.new_size.0, configure.new_size.1); wrapper.comp_surface.replace(c_surface); compositor_surfaces.insert(id.inner(), wrapper); } @@ -655,13 +590,15 @@ where }, }, // TODO forward these events to an application which requests them? - SctkEvent::NewOutput { id, info } => { + SctkEvent::NewOutput { .. } => { } - SctkEvent::UpdateOutput { id, info } => { + SctkEvent::UpdateOutput { .. } => { } - SctkEvent::RemovedOutput(id) => { + SctkEvent::RemovedOutput( ..) => { } - SctkEvent::Draw(_) => unimplemented!(), // probably should never be forwarded here... + SctkEvent::Frame(_) => { + // TODO if animations are running, request redraw here? + }, SctkEvent::ScaleFactorChanged { factor, id, @@ -743,10 +680,10 @@ where SctkEvent::PopupEvent { id, .. } => { (&id.id() == object_id, false) } + SctkEvent::Frame(_) => (false, false), SctkEvent::NewOutput { .. } | SctkEvent::UpdateOutput { .. } | SctkEvent::RemovedOutput(_) => (false, true), - SctkEvent::Draw(_) => unimplemented!(), SctkEvent::ScaleFactorChanged { id, .. } => { (&id.id() == object_id, false) } @@ -801,6 +738,8 @@ where { runtime.broadcast(event); } + + // TODO ASHLEY if event is a configure which isn't a new size and has no other changes, don't redraw if has_events || !messages.is_empty() || matches!( interface_state, @@ -809,7 +748,45 @@ where { needs_redraw = true; } + + if let Some((w, h, limits, dirty)) = auto_size_surfaces.remove(&surface_id) { + if dirty { + match surface_id { + SurfaceIdWrapper::LayerSurface(inner) => { + ev_proxy.send_event( + Event::LayerSurface( + iced_native::command::platform_specific::wayland::layer_surface::Action::Size { id: inner.clone(), width: Some(w), height: Some(h) }, + ) + ); + }, + SurfaceIdWrapper::Window(inner) => { + ev_proxy.send_event( + Event::Window( + iced_native::command::platform_specific::wayland::window::Action::Size { id: inner.clone(), width: w, height: h }, + ) + ); + }, + SurfaceIdWrapper::Popup(inner) => { + ev_proxy.send_event( + Event::Popup( + iced_native::command::platform_specific::wayland::popup::Action::Size { id: inner.clone(), width: w, height: h }, + ) + ); + }, + }; + ev_proxy.send_event( + Event::SctkEvent(IcedSctkEvent::RedrawRequested(object_id.clone())) + ); + auto_size_surfaces.insert(surface_id.clone(), (w, h, limits, false)); + needs_redraw = false; + } else { + auto_size_surfaces.insert(surface_id.clone(), (w, h, limits, false)); + needs_redraw = true; + } + } + if needs_redraw { + ev_proxy.send_event(Event::SctkEvent( IcedSctkEvent::RedrawRequested( object_id.clone(), @@ -817,8 +794,7 @@ where )); } } - if needs_redraw { - + if needs_redraw { let mut pure_states: HashMap<_, _> = ManuallyDrop::into_inner(interfaces) .drain() @@ -877,48 +853,19 @@ where // clear the destroyed surfaces after they have been handled destroyed_surface_ids.clear(); } - IcedSctkEvent::RedrawRequested(id) => { + IcedSctkEvent::RedrawRequested(object_id) => { if let Some(( native_id, Some(wrapper), Some(mut user_interface), Some(state), - )) = surface_ids.get(&id).map(|id| { + )) = surface_ids.get(&object_id).map(|id| { let surface = compositor_surfaces.get_mut(&id.inner()); let interface = interfaces.remove(&id.inner()); let state = states.get_mut(&id.inner()); (*id, surface, interface, state) }) { - if let Some((w, h, limits, dirty)) = auto_size_surfaces.remove(&native_id) { - if dirty { - match native_id { - SurfaceIdWrapper::LayerSurface(inner) => { - ev_proxy.send_event( - Event::LayerSurface( - iced_native::command::platform_specific::wayland::layer_surface::Action::Size { id: inner, width: Some(w), height: Some(h) }, - ) - ); - }, - SurfaceIdWrapper::Window(inner) => { - ev_proxy.send_event( - Event::Window( - iced_native::command::platform_specific::wayland::window::Action::Size { id: inner, width: w, height: h }, - ) - ); - }, - SurfaceIdWrapper::Popup(inner) => { - ev_proxy.send_event( - Event::Popup( - iced_native::command::platform_specific::wayland::popup::Action::Size { id: inner, width: w, height: h }, - ) - ); - }, - }; - auto_size_surfaces.insert(native_id, (w, h, limits, false)); - } else { - auto_size_surfaces.insert(native_id, (w, h, limits, false)); - } - } + debug.render_started(); let comp_surface = match wrapper.comp_surface.as_mut() { Some(s) => s, @@ -928,13 +875,11 @@ where if state.viewport_changed() { let physical_size = state.physical_size(); let logical_size = state.logical_size(); - debug.layout_started(); user_interface = user_interface.relayout(logical_size, &mut renderer); debug.layout_finished(); - + compositor.configure_surface(comp_surface, physical_size.width, physical_size.height); - debug.draw_started(); let new_mouse_interaction = user_interface.draw( &mut renderer, @@ -949,12 +894,23 @@ where new_mouse_interaction, )); - compositor.configure_surface(comp_surface, physical_size.width, physical_size.height); - // is resize viewport still necessary? - let _ = interfaces .insert(native_id.inner(), user_interface); + state.viewport_changed = false; } else { + debug.draw_started(); + let new_mouse_interaction = user_interface.draw( + &mut renderer, + state.theme(), + &renderer::Style { + text_color: state.text_color(), + }, + state.cursor_position(), + ); + debug.draw_finished(); + ev_proxy.send_event(Event::SetCursor( + new_mouse_interaction, + )); interfaces.insert(native_id.inner(), user_interface); } @@ -1020,7 +976,7 @@ where // TODO would it be ok to diff against the current cache? let _ = view.diff(&mut Tree::empty()); let bounds = view.layout(renderer, &limits).bounds().size(); - let (w, h) = (bounds.width as u32, bounds.height as u32); + let (w, h) = (bounds.width.ceil() as u32, bounds.height.ceil() as u32); let dirty = dirty || w != prev_w || h != prev_h; auto_size_surfaces.insert(id, (w, h, limits, dirty)); Size::new(w as f32, h as f32) @@ -1063,7 +1019,6 @@ where let scale_factor = application.scale_factor(); let theme = application.theme(); let appearance = theme.appearance(&application.style()); - let viewport = Viewport::with_physical_size(Size::new(1, 1), 1.0); Self { @@ -1164,22 +1119,6 @@ where self.cursor_position = p; } - /// Synchronizes the [`State`] with its [`Application`] and its respective - /// windows. - /// - /// Normally an [`Application`] should be synchronized with its [`State`] - /// and window after calling [`Application::update`]. - /// - /// [`Application::update`]: crate::Program::update - pub(crate) fn synchronize_window( - &mut self, - application: &A, - window: &SctkWindow, - proxy: &proxy::Proxy>, - ) { - self.synchronize(application); - } - fn synchronize(&mut self, application: &A) { // Update theme and appearance self.theme = application.theme(); @@ -1268,10 +1207,10 @@ fn run_command( }))); } command::Action::Clipboard(action) => match action { - clipboard::Action::Read(tag) => { + clipboard::Action::Read(..) => { todo!(); } - clipboard::Action::Write(contents) => { + clipboard::Action::Write(..) => { todo!(); } }, @@ -1314,7 +1253,7 @@ fn run_command( renderer, state.logical_size(), debug, - id.clone(), // TODO: run the operation on every widget tree + id.clone(), // TODO: run the operation on every widget tree ? auto_size_surfaces ); @@ -1351,8 +1290,8 @@ fn run_command( e.as_widget().diff(&mut Tree::empty()); let node = Widget::layout(e.as_widget(), renderer, &builder.size_limits); let bounds = node.bounds(); - let w = bounds.width as u32; - let h = bounds.height as u32; + let w = bounds.width.ceil() as u32; + let h = bounds.height.ceil() as u32; auto_size_surfaces.insert(SurfaceIdWrapper::LayerSurface(builder.id), (w, h, builder.size_limits, false)); builder.size = Some((Some(bounds.width as u32), Some(bounds.height as u32))); @@ -1374,8 +1313,8 @@ fn run_command( e.as_widget().diff(&mut Tree::empty()); let node = Widget::layout(e.as_widget(), renderer, &builder.size_limits); let bounds = node.bounds(); - let w = bounds.width as u32; - let h = bounds.height as u32; + let w = bounds.width.ceil() as u32; + let h = bounds.height.ceil() as u32; auto_size_surfaces.insert(SurfaceIdWrapper::Window(builder.window_id), (w, h, builder.size_limits, false)); builder.iced_settings.size = (bounds.width as u32, bounds.height as u32); } @@ -1396,8 +1335,8 @@ fn run_command( e.as_widget().diff(&mut Tree::empty()); let node = Widget::layout(e.as_widget(), renderer, &popup.positioner.size_limits); let bounds = node.bounds(); - let w = bounds.width as u32; - let h = bounds.height as u32; + let w = bounds.width.ceil().ceil() as u32; + let h = bounds.height.ceil().ceil() as u32; auto_size_surfaces.insert(SurfaceIdWrapper::Popup(popup.id), (w, h, popup.positioner.size_limits, false)); popup.positioner.size = Some((bounds.width as u32, bounds.height as u32)); } @@ -1450,16 +1389,3 @@ where interfaces } - -pub fn run_event_loop( - mut event_loop: event_loop::SctkEventLoop, - event_handler: F, -) -> Result<(), crate::error::Error> -where - F: 'static + FnMut(IcedSctkEvent, &SctkState, &mut ControlFlow), - T: 'static + fmt::Debug, -{ - let _ = event_loop.run_return(event_handler); - - Ok(()) -} diff --git a/sctk/src/event_loop/mod.rs b/sctk/src/event_loop/mod.rs index 978e81cb62..800b4b3158 100644 --- a/sctk/src/event_loop/mod.rs +++ b/sctk/src/event_loop/mod.rs @@ -5,7 +5,6 @@ pub mod state; use std::{ collections::HashMap, fmt::Debug, - mem, time::{Duration, Instant}, }; @@ -13,7 +12,7 @@ use crate::{ application::Event, sctk_event::{ IcedSctkEvent, LayerSurfaceEventVariant, PopupEventVariant, SctkEvent, - StartCause, SurfaceUserRequest, WindowEventVariant, + StartCause, WindowEventVariant, }, settings, }; @@ -31,7 +30,7 @@ use sctk::{ reexports::{ calloop::{self, EventLoop}, client::{ - backend::ObjectId, globals::registry_queue_init, + globals::registry_queue_init, protocol::wl_surface::WlSurface, ConnectError, Connection, DispatchError, Proxy, }, @@ -161,13 +160,10 @@ where layer_surfaces: Vec::new(), popups: Vec::new(), kbd_focus: None, - window_user_requests: HashMap::new(), window_compositor_updates: HashMap::new(), sctk_events: Vec::new(), popup_compositor_updates: Default::default(), layer_surface_compositor_updates: Default::default(), - layer_surface_user_requests: Default::default(), - popup_user_requests: Default::default(), pending_user_events: Vec::new(), token_ctr: 0, }, @@ -208,9 +204,6 @@ where &mut control_flow, ); - let mut surface_user_requests: Vec<(ObjectId, SurfaceUserRequest)> = - Vec::new(); - let mut event_sink_back_buffer = Vec::new(); // NOTE We break on errors from dispatches, since if we've got protocol error @@ -352,11 +345,14 @@ where ); // Handle pending sctk events. - let mut must_redraw = Vec::new(); - for event in event_sink_back_buffer.drain(..) { match event { - SctkEvent::Draw(id) => must_redraw.push(id), + SctkEvent::Frame(id) => sticky_exit_callback( + IcedSctkEvent::SctkEvent(SctkEvent::Frame(id)), + &self.state, + &mut control_flow, + &mut callback, + ), SctkEvent::PopupEvent { variant: PopupEventVariant::Done, toplevel_id, @@ -451,11 +447,20 @@ where .drain(..) .partition(|e| matches!(e, Event::SctkEvent(_))); let mut to_commit = HashMap::new(); + let mut pending_redraws = Vec::new(); for event in sctk_events.into_iter().chain(user_events.into_iter()) { match event { Event::SctkEvent(event) => { - sticky_exit_callback(event, &self.state, &mut control_flow, &mut callback) + match event { + IcedSctkEvent::RedrawRequested(id) => {pending_redraws.push(id);}, + e => sticky_exit_callback( + e, + &self.state, + &mut control_flow, + &mut callback, + ), + } } Event::LayerSurface(action) => match action { platform_specific::wayland::layer_surface::Action::LayerSurface { @@ -642,7 +647,6 @@ where platform_specific::wayland::window::Action::ToggleMaximized { id } => { if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { if let Some(c) = &window.last_configure { - dbg!(c); if c.is_maximized() { window.window.unset_maximized(); } else { @@ -769,8 +773,6 @@ where self.state.token_ctr += 1; sctk_popup.data.positioner.set_size(width as i32, height as i32); sctk_popup.popup.reposition(&sctk_popup.data.positioner, self.state.token_ctr); - - to_commit.insert(id, sctk_popup.popup.wl_surface().clone()); sticky_exit_callback(IcedSctkEvent::SctkEvent(SctkEvent::PopupEvent { variant: PopupEventVariant::Size(width, height), toplevel_id: sctk_popup.data.toplevel.clone(), @@ -785,16 +787,10 @@ where }, // TODO probably remove this? platform_specific::wayland::popup::Action::Grab { id } => {}, - }, - Event::InitSurfaceSize(id, requested_size) => todo!(), + }, } } - // commit changes made via actions - for s in to_commit { - s.1.commit(); - } - // Send events cleared. sticky_exit_callback( IcedSctkEvent::MainEventsCleared, @@ -803,68 +799,20 @@ where &mut callback, ); - // Apply user requests, so every event required resize and latter surface commit will - // be applied right before drawing. This will also ensure that every `RedrawRequested` - // event will be delivered in time. - // Process 'new' pending updates from compositor. - surface_user_requests.clear(); - surface_user_requests.extend( - self.state.window_user_requests.iter_mut().map( - |(wid, window_request)| { - (wid.clone(), mem::take(window_request)) - }, - ), - ); - - // Handle RedrawRequested requests. - for (surface_id, mut surface_request) in - surface_user_requests.iter() - { - if let Some(i) = - must_redraw.iter().position(|a_id| &a_id.id() == surface_id) - { - must_redraw.remove(i); - } - let wl_suface = self - .state - .windows - .iter() - .map(|w| w.window.wl_surface()) - .chain( - self.state - .layer_surfaces - .iter() - .map(|l| l.surface.wl_surface()), - ) - .find(|s| s.id() == *surface_id) - .unwrap(); - - // Handle refresh of the frame. - if surface_request.refresh_frame { - // In general refreshing the frame requires surface commit, those force user - // to redraw. - surface_request.redraw_requested = true; - } - - // Handle redraw request. - if surface_request.redraw_requested { - sticky_exit_callback( - IcedSctkEvent::RedrawRequested(surface_id.clone()), - &self.state, - &mut control_flow, - &mut callback, - ); - } - wl_suface.commit(); - } - - for id in must_redraw { + // redraw + pending_redraws.dedup(); + for id in pending_redraws { sticky_exit_callback( - IcedSctkEvent::RedrawRequested(id.id()), + IcedSctkEvent::RedrawRequested(id.clone()), &self.state, &mut control_flow, &mut callback, - ); + ) + } + + // commit changes made via actions + for s in to_commit { + s.1.commit(); } // Send RedrawEventCleared. diff --git a/sctk/src/event_loop/state.rs b/sctk/src/event_loop/state.rs index fa6c516ea8..697a450a90 100644 --- a/sctk/src/event_loop/state.rs +++ b/sctk/src/event_loop/state.rs @@ -174,15 +174,6 @@ pub struct SctkState { /// A sink for window and device events that is being filled during dispatching /// event loop and forwarded downstream afterwards. pub(crate) sctk_events: Vec, - /// Window updates comming from the user requests. Those are separatelly dispatched right after - /// `MainEventsCleared`. - pub window_user_requests: HashMap, - /// Layer Surface updates comming from the user requests. Those are separatelly dispatched right after - /// `MainEventsCleared`. - pub layer_surface_user_requests: HashMap, - /// Window updates comming from the user requests. Those are separatelly dispatched right after - /// `MainEventsCleared`. - pub popup_user_requests: HashMap, /// pending user events pub pending_user_events: Vec>, diff --git a/sctk/src/handlers/compositor.rs b/sctk/src/handlers/compositor.rs index b88b884078..f6938d9e01 100644 --- a/sctk/src/handlers/compositor.rs +++ b/sctk/src/handlers/compositor.rs @@ -58,7 +58,7 @@ impl CompositorHandler for SctkState { surface: &wl_surface::WlSurface, _time: u32, ) { - self.sctk_events.push(SctkEvent::Draw(surface.clone())); + self.sctk_events.push(SctkEvent::Frame(surface.clone())); } } diff --git a/sctk/src/handlers/shell/layer.rs b/sctk/src/handlers/shell/layer.rs index 90ee88c35e..6a5b0eff9d 100644 --- a/sctk/src/handlers/shell/layer.rs +++ b/sctk/src/handlers/shell/layer.rs @@ -46,15 +46,15 @@ impl LayerShellHandler for SctkState { Some(l) => l, None => return, }; - - configure.new_size.0 = if let Some(w) = layer.requested_size.0 { + + configure.new_size.0 = if let Some(w) = layer.requested_size.0 { w - } else { + } else { configure.new_size.0.max(1) }; - configure.new_size.1 = if let Some(h) = layer.requested_size.1 { + configure.new_size.1 = if let Some(h) = layer.requested_size.1 { h - } else { + } else { configure.new_size.1.max(1) }; @@ -74,7 +74,7 @@ impl LayerShellHandler for SctkState { id: layer.surface.wl_surface().clone(), }); self.sctk_events - .push(SctkEvent::Draw(layer.surface.wl_surface().clone())); + .push(SctkEvent::Frame(layer.surface.wl_surface().clone())); } } diff --git a/sctk/src/handlers/shell/xdg_popup.rs b/sctk/src/handlers/shell/xdg_popup.rs index 534b95d217..1bf4f0b009 100644 --- a/sctk/src/handlers/shell/xdg_popup.rs +++ b/sctk/src/handlers/shell/xdg_popup.rs @@ -41,7 +41,7 @@ impl PopupHandler for SctkState { }, }); self.sctk_events - .push(SctkEvent::Draw(popup.wl_surface().clone())); + .push(SctkEvent::Frame(popup.wl_surface().clone())); } fn done( diff --git a/sctk/src/handlers/shell/xdg_window.rs b/sctk/src/handlers/shell/xdg_window.rs index 4426cb6f90..34b2dc0735 100644 --- a/sctk/src/handlers/shell/xdg_window.rs +++ b/sctk/src/handlers/shell/xdg_window.rs @@ -64,7 +64,7 @@ impl WindowHandler for SctkState { ), id, }); - self.sctk_events.push(SctkEvent::Draw(wl_surface.clone())); + self.sctk_events.push(SctkEvent::Frame(wl_surface.clone())); } } diff --git a/sctk/src/sctk_event.rs b/sctk/src/sctk_event.rs index 03f35ad6d5..557445d82e 100644 --- a/sctk/src/sctk_event.rs +++ b/sctk/src/sctk_event.rs @@ -160,7 +160,7 @@ pub enum SctkEvent { // // compositor events // - Draw(WlSurface), + Frame(WlSurface), ScaleFactorChanged { factor: f64, id: WlOutput, @@ -213,11 +213,9 @@ pub enum PopupEventVariant { /// Configure(PopupConfigure, WlSurface, bool), /// - RepositionionedPopup { - token: u32, - }, + RepositionionedPopup { token: u32 }, /// size - Size(u32, u32) + Size(u32, u32), } #[derive(Debug, Clone)] @@ -618,14 +616,14 @@ impl SctkEvent { .into_iter() .collect() } - SctkEvent::UpdateOutput { id, info } => vec![ - iced_native::Event::PlatformSpecific( + SctkEvent::UpdateOutput { id, info } => { + vec![iced_native::Event::PlatformSpecific( PlatformSpecific::Wayland(wayland::Event::Output( wayland::OutputEvent::InfoUpdate(info), id.clone(), )), - ), - ], + )] + } SctkEvent::RemovedOutput(id) => { Some(iced_native::Event::PlatformSpecific( PlatformSpecific::Wayland(wayland::Event::Output( @@ -636,7 +634,7 @@ impl SctkEvent { .into_iter() .collect() } - SctkEvent::Draw(_) => Default::default(), + SctkEvent::Frame(_) => Default::default(), SctkEvent::ScaleFactorChanged { factor, id, diff --git a/sctk/src/settings.rs b/sctk/src/settings.rs index 9e7ebb2d1b..0311d13904 100644 --- a/sctk/src/settings.rs +++ b/sctk/src/settings.rs @@ -22,6 +22,7 @@ pub struct Settings { pub enum InitialSurface { LayerSurface(SctkLayerSurfaceSettings), XdgWindow(SctkWindowSettings), + None, } impl Default for InitialSurface { From a6605c2455b353aa092e742f81e0a443d2a3a71b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Sun, 22 Jan 2023 22:21:18 -0500 Subject: [PATCH 49/56] update softbuffer with buffer resize fix --- softbuffer/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/softbuffer/Cargo.toml b/softbuffer/Cargo.toml index 04077ba264..ed5cc01be9 100644 --- a/softbuffer/Cargo.toml +++ b/softbuffer/Cargo.toml @@ -13,8 +13,7 @@ lazy_static = "1.4" log = "0.4" raw-window-handle = "0.5" raqote = { version = "0.8", default-features = false } -softbuffer = { git = "https://github.com/pop-os/softbuffer", rev = "68240f56b" } - +softbuffer = { git = "https://github.com/pop-os/softbuffer", rev = "8dcb6438b" } [dependencies.iced_native] path = "../native" From c4ea30d6caf6d6aceae96dcd92e813e5256bf22e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Sun, 22 Jan 2023 21:05:25 -0500 Subject: [PATCH 50/56] fix: better sync resizes --- examples/autosize_layer/Cargo.toml | 2 +- examples/autosize_layer/src/main.rs | 17 +++-- sctk/Cargo.toml | 1 + sctk/src/application.rs | 113 +++++++++++++++------------- sctk/src/event_loop/mod.rs | 29 ++++++- 5 files changed, 98 insertions(+), 64 deletions(-) diff --git a/examples/autosize_layer/Cargo.toml b/examples/autosize_layer/Cargo.toml index 9a199d88c2..bdc3fb07f2 100644 --- a/examples/autosize_layer/Cargo.toml +++ b/examples/autosize_layer/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", default-features=false, features = ["async-std", "wayland", "debug", "dyrend"] } +iced = { path = "../..", default-features=false, features = ["async-std", "wayland", "debug", "softbuffer"] } sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "a257bf7" } iced_native = { path = "../../native" } iced_style = { path = "../../style" } diff --git a/examples/autosize_layer/src/main.rs b/examples/autosize_layer/src/main.rs index 8eeea7a4fe..c5e97febf5 100644 --- a/examples/autosize_layer/src/main.rs +++ b/examples/autosize_layer/src/main.rs @@ -14,8 +14,8 @@ use iced::wayland::window::get_window; use iced::wayland::InitialSurface; use iced::wayland::SurfaceIdWrapper; use iced::widget::{ - self, button, checkbox, column, container, row, scrollable, text, - text_input, Column, Row, Text, + self, button, checkbox, column, container, horizontal_space, row, + scrollable, text, text_input, Column, Row, Text, }; use iced::Rectangle; use iced::{window, Application, Element}; @@ -52,7 +52,7 @@ impl Application for Todos { fn new(_flags: ()) -> (Todos, Command) { ( Todos { - size: 5, + size: 1, id_ctr: 2, ..Default::default() }, @@ -67,9 +67,9 @@ impl Application for Todos { fn update(&mut self, message: Message) -> Command { match message { Message::Tick => { - self.size = (self.size - 1) % 10; + self.size = (self.size - 1) % 3; if self.size == 0 { - self.size = 10; + self.size = 3; } } Message::Popup => { @@ -110,11 +110,12 @@ impl Application for Todos { Row::with_children( (0..self.size) .map(|i| { - button(Text::new(format!( - "{}: {}", - i, i + button(horizontal_space(Length::Units( + 20, ))) .on_press(Message::Popup) + .width(Length::Units(20)) + .height(Length::Units(20)) .into() }) .collect(), diff --git a/sctk/Cargo.toml b/sctk/Cargo.toml index 9bd00fe26e..ffcae66b1f 100644 --- a/sctk/Cargo.toml +++ b/sctk/Cargo.toml @@ -19,6 +19,7 @@ raw-window-handle = "0.5.0" enum-repr = "0.2.6" futures = "0.3" wayland-backend = {version = "0.1.0", features = ["client_system"]} +float-cmp = "0.9" [dependencies.iced_native] features = ["wayland"] diff --git a/sctk/src/application.rs b/sctk/src/application.rs index dc326a2d5a..5fa80a3c50 100644 --- a/sctk/src/application.rs +++ b/sctk/src/application.rs @@ -13,6 +13,7 @@ use crate::{ }, settings, Command, Debug, Executor, Runtime, Size, Subscription, commands::{layer_surface::get_layer_surface, window::get_window}, }; +use float_cmp::approx_eq; use futures::{channel::mpsc, task, Future, FutureExt, StreamExt}; use iced_native::{ application::{self, StyleSheet}, @@ -459,7 +460,8 @@ where state.logical_size(), &mut debug, *id, - &mut auto_size_surfaces + &mut auto_size_surfaces, + &mut ev_proxy ); states.insert(id.inner(), state); interfaces.insert(id.inner(), user_interface); @@ -509,7 +511,8 @@ where state.logical_size(), &mut debug, *id, - &mut auto_size_surfaces + &mut auto_size_surfaces, + &mut ev_proxy ); states.insert(id.inner(), state); interfaces.insert(id.inner(), user_interface); @@ -564,7 +567,8 @@ where state.logical_size(), &mut debug, *id, - &mut auto_size_surfaces + &mut auto_size_surfaces, + &mut ev_proxy ); states.insert(id.inner(), state); interfaces.insert(id.inner(), user_interface); @@ -633,7 +637,7 @@ where &mut debug, &mut messages, || compositor.fetch_information(), - &mut auto_size_surfaces + &mut auto_size_surfaces, ); interfaces = ManuallyDrop::new(build_user_interfaces( @@ -642,7 +646,8 @@ where &mut debug, &states, pure_states, - &mut auto_size_surfaces + &mut auto_size_surfaces, + &mut ev_proxy )); if application.should_exit() { @@ -739,6 +744,18 @@ where runtime.broadcast(event); } + if let Some((w, h, limits, dirty)) = auto_size_surfaces.remove(&surface_id) { + if dirty { + let state = + match states.get_mut(&surface_id.inner()) { + Some(s) => s, + None => continue, + }; + state.set_logical_size(w as f64, h as f64); + } + auto_size_surfaces.insert(surface_id.clone(), (w, h, limits, false)); + } + // TODO ASHLEY if event is a configure which isn't a new size and has no other changes, don't redraw if has_events || !messages.is_empty() || matches!( @@ -747,46 +764,6 @@ where ) { needs_redraw = true; - } - - if let Some((w, h, limits, dirty)) = auto_size_surfaces.remove(&surface_id) { - if dirty { - match surface_id { - SurfaceIdWrapper::LayerSurface(inner) => { - ev_proxy.send_event( - Event::LayerSurface( - iced_native::command::platform_specific::wayland::layer_surface::Action::Size { id: inner.clone(), width: Some(w), height: Some(h) }, - ) - ); - }, - SurfaceIdWrapper::Window(inner) => { - ev_proxy.send_event( - Event::Window( - iced_native::command::platform_specific::wayland::window::Action::Size { id: inner.clone(), width: w, height: h }, - ) - ); - }, - SurfaceIdWrapper::Popup(inner) => { - ev_proxy.send_event( - Event::Popup( - iced_native::command::platform_specific::wayland::popup::Action::Size { id: inner.clone(), width: w, height: h }, - ) - ); - }, - }; - ev_proxy.send_event( - Event::SctkEvent(IcedSctkEvent::RedrawRequested(object_id.clone())) - ); - auto_size_surfaces.insert(surface_id.clone(), (w, h, limits, false)); - needs_redraw = false; - } else { - auto_size_surfaces.insert(surface_id.clone(), (w, h, limits, false)); - needs_redraw = true; - } - } - - if needs_redraw { - ev_proxy.send_event(Event::SctkEvent( IcedSctkEvent::RedrawRequested( object_id.clone(), @@ -845,7 +822,8 @@ where &mut debug, &states, pure_states, - &mut auto_size_surfaces + &mut auto_size_surfaces, + &mut ev_proxy )); } } @@ -875,10 +853,11 @@ where if state.viewport_changed() { let physical_size = state.physical_size(); let logical_size = state.logical_size(); + compositor.configure_surface(comp_surface, physical_size.width, physical_size.height); + debug.layout_started(); user_interface = user_interface.relayout(logical_size, &mut renderer); debug.layout_finished(); - compositor.configure_surface(comp_surface, physical_size.width, physical_size.height); debug.draw_started(); let new_mouse_interaction = user_interface.draw( @@ -896,6 +875,7 @@ where let _ = interfaces .insert(native_id.inner(), user_interface); + state.viewport_changed = false; } else { debug.draw_started(); @@ -962,6 +942,7 @@ pub fn build_user_interface<'a, A: Application>( debug: &mut Debug, id: SurfaceIdWrapper, auto_size_surfaces: &mut HashMap, + ev_proxy: &mut proxy::Proxy>, ) -> UserInterface<'a, A::Message, A::Renderer> where ::Theme: StyleSheet, @@ -979,6 +960,32 @@ where let (w, h) = (bounds.width.ceil() as u32, bounds.height.ceil() as u32); let dirty = dirty || w != prev_w || h != prev_h; auto_size_surfaces.insert(id, (w, h, limits, dirty)); + if dirty { + match id { + SurfaceIdWrapper::LayerSurface(inner) => { + ev_proxy.send_event( + Event::LayerSurface( + iced_native::command::platform_specific::wayland::layer_surface::Action::Size { id: inner.clone(), width: Some(w), height: Some(h) }, + ) + ); + }, + SurfaceIdWrapper::Window(inner) => { + ev_proxy.send_event( + Event::Window( + iced_native::command::platform_specific::wayland::window::Action::Size { id: inner.clone(), width: w, height: h }, + ) + ); + }, + SurfaceIdWrapper::Popup(inner) => { + ev_proxy.send_event( + Event::Popup( + iced_native::command::platform_specific::wayland::popup::Action::Size { id: inner.clone(), width: w, height: h }, + ) + ); + }, + }; + } + Size::new(w as f32, h as f32) } else { size @@ -1059,7 +1066,7 @@ where /// Sets the logical [`Size`] of the [`Viewport`] of the [`State`]. pub fn set_logical_size(&mut self, w: f64, h: f64) { let old_size = self.viewport.logical_size(); - if w != old_size.width.into() || h != old_size.height.into() { + if !approx_eq!(f32, w as f32, old_size.width, ulps = 2) || !approx_eq!(f32, h as f32, old_size.height, ulps = 2) { self.viewport_changed = true; self.viewport = Viewport::with_physical_size( Size { @@ -1077,7 +1084,7 @@ where } pub fn set_scale_factor(&mut self, scale_factor: f64) { - if scale_factor != self.scale_factor { + if approx_eq!(f64, scale_factor, self.scale_factor , ulps = 2) { self.viewport_changed = true; let logical_size = self.viewport.logical_size(); self.viewport = Viewport::with_physical_size( @@ -1167,7 +1174,7 @@ pub(crate) fn update( proxy, debug, graphics_info, - auto_size_surfaces + auto_size_surfaces, ); } @@ -1190,6 +1197,7 @@ fn run_command( debug: &mut Debug, _graphics_info: impl FnOnce() -> compositor::Information + Copy, auto_size_surfaces: &mut HashMap, + ) where A: Application, E: Executor, @@ -1254,7 +1262,8 @@ fn run_command( state.logical_size(), debug, id.clone(), // TODO: run the operation on every widget tree ? - auto_size_surfaces + auto_size_surfaces, + proxy ); while let Some(mut operation) = current_operation.take() { @@ -1357,6 +1366,7 @@ pub fn build_user_interfaces<'a, A>( states: &HashMap>, mut pure_states: HashMap, auto_size_surfaces: &mut HashMap, + ev_proxy: &mut proxy::Proxy>, ) -> HashMap< SurfaceId, UserInterface< @@ -1382,6 +1392,7 @@ where debug, state.id, auto_size_surfaces, + ev_proxy, ); let _ = interfaces.insert(id, user_interface); diff --git a/sctk/src/event_loop/mod.rs b/sctk/src/event_loop/mod.rs index 800b4b3158..2a05d87e47 100644 --- a/sctk/src/event_loop/mod.rs +++ b/sctk/src/event_loop/mod.rs @@ -453,7 +453,9 @@ where match event { Event::SctkEvent(event) => { match event { - IcedSctkEvent::RedrawRequested(id) => {pending_redraws.push(id);}, + IcedSctkEvent::RedrawRequested(id) => { + pending_redraws.push(id); + }, e => sticky_exit_callback( e, &self.state, @@ -489,8 +491,10 @@ where if let Some(layer_surface) = self.state.layer_surfaces.iter_mut().find(|l| l.id == id) { layer_surface.requested_size = (width, height); layer_surface.surface.set_size(width.unwrap_or_default(), height.unwrap_or_default()); - to_commit.insert(id, layer_surface.surface.wl_surface().clone()); + + pending_redraws.push(layer_surface.surface.wl_surface().id()); } + }, platform_specific::wayland::layer_surface::Action::Destroy(id) => { if let Some(i) = self.state.layer_surfaces.iter().position(|l| &l.id == &id) { @@ -570,8 +574,9 @@ where if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { window.requested_size = Some((width, height)); window.window.xdg_surface().set_window_geometry(0, 0, width.max(1) as i32, height.max(1) as i32); - to_commit.insert(id, window.window.wl_surface().clone()); // TODO Ashley maybe don't force window size? + pending_redraws.push(window.window.wl_surface().id()); + if let Some(mut prev_configure) = window.last_configure.clone() { prev_configure.new_size = Some((width, height)); sticky_exit_callback( @@ -773,6 +778,8 @@ where self.state.token_ctr += 1; sctk_popup.data.positioner.set_size(width as i32, height as i32); sctk_popup.popup.reposition(&sctk_popup.data.positioner, self.state.token_ctr); + pending_redraws.push(sctk_popup.popup.wl_surface().id()); + sticky_exit_callback(IcedSctkEvent::SctkEvent(SctkEvent::PopupEvent { variant: PopupEventVariant::Size(width, height), toplevel_id: sctk_popup.data.toplevel.clone(), @@ -807,7 +814,21 @@ where &self.state, &mut control_flow, &mut callback, - ) + ); + let wl_suface = self + .state + .windows + .iter() + .map(|w| w.window.wl_surface()) + .chain( + self.state + .layer_surfaces + .iter() + .map(|l| l.surface.wl_surface()), + ) + .find(|s| s.id() == id) + .unwrap(); + wl_suface.commit(); } // commit changes made via actions From 3ea288a176b4d4114260847cc45cae222fee771f Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 23 Jan 2023 12:05:54 -0500 Subject: [PATCH 51/56] fix: remove extra erroneous wl_surface commit --- sctk/src/event_loop/mod.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/sctk/src/event_loop/mod.rs b/sctk/src/event_loop/mod.rs index 2a05d87e47..dd74aa10d1 100644 --- a/sctk/src/event_loop/mod.rs +++ b/sctk/src/event_loop/mod.rs @@ -815,20 +815,6 @@ where &mut control_flow, &mut callback, ); - let wl_suface = self - .state - .windows - .iter() - .map(|w| w.window.wl_surface()) - .chain( - self.state - .layer_surfaces - .iter() - .map(|l| l.surface.wl_surface()), - ) - .find(|s| s.id() == id) - .unwrap(); - wl_suface.commit(); } // commit changes made via actions From 67ae78560588af639f354ca5bda95eeac079a737 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 7 Feb 2023 12:17:39 -0800 Subject: [PATCH 52/56] sctk: Send output events to application even with no surface configured For some reason this was an issue testing with `iced_glow`/`glutin`, but this also fixes the issue I was having where the application doesn't get output events if I destroy the initial surface and create surfaces later. --- sctk/src/application.rs | 102 +++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 43 deletions(-) diff --git a/sctk/src/application.rs b/sctk/src/application.rs index 5fa80a3c50..57d64cf377 100644 --- a/sctk/src/application.rs +++ b/sctk/src/application.rs @@ -19,9 +19,11 @@ use iced_native::{ application::{self, StyleSheet}, clipboard::{self, Null}, command::platform_specific::{self, wayland::popup}, + event::Status, + layout::Limits, mouse::{self, Interaction}, widget::{operation, Tree}, - Element, Renderer, Widget, layout::Limits, + Element, Renderer, Widget, }; use sctk::{ @@ -654,55 +656,43 @@ where break 'main; } } else { - let mut needs_redraw = false; - for (object_id, surface_id) in &surface_ids { - // returns (remove, copy) - let filter_events = |e: &SctkEvent| match e { - SctkEvent::SeatEvent { id, .. } => { - (&id.id() == object_id, false) - } - SctkEvent::PointerEvent { variant, .. } => { - (&variant.surface.id() == object_id, false) - } - SctkEvent::KeyboardEvent { variant, .. } => { - match variant { - KeyboardEventVariant::Leave(id) => { - (&id.id() == object_id, false) - } - _ => ( - kbd_surface_id.as_ref() - == Some(&object_id), - false, - ), - } - } - SctkEvent::WindowEvent { id, .. } => { - (&id.id() == object_id, false) - } - SctkEvent::LayerSurfaceEvent { id, .. } => { - (&id.id() == object_id, false) - } - SctkEvent::PopupEvent { id, .. } => { - (&id.id() == object_id, false) - } - SctkEvent::Frame(_) => (false, false), + let mut i = 0; + while i < events.len() { + let remove = match &events[i] { SctkEvent::NewOutput { .. } | SctkEvent::UpdateOutput { .. } - | SctkEvent::RemovedOutput(_) => (false, true), - SctkEvent::ScaleFactorChanged { id, .. } => { - (&id.id() == object_id, false) - } + | SctkEvent::RemovedOutput(_) => true, + _ => false, }; + if remove { + let event = events.remove(i); + for native_event in event.to_native( + &mut mods, + &surface_ids, + &destroyed_surface_ids, + ) { + runtime + .broadcast((native_event, Status::Ignored)); + } + } else { + i += 1; + } + } + + let mut needs_redraw = false; + for (object_id, surface_id) in &surface_ids { let mut filtered = Vec::with_capacity(events.len()); - let mut i = 0; + let mut i = 0; while i < events.len() { - let (remove, copy) = filter_events(&mut events[i]); - if remove { + let has_kbd_focus = + kbd_surface_id.as_ref() == Some(object_id); + if event_is_for_surface( + &events[i], + object_id, + has_kbd_focus, + ) { filtered.push(events.remove(i)); - } else if copy { - filtered.push(events[i].clone()); - i += 1; } else { i += 1; } @@ -1400,3 +1390,29 @@ where interfaces } + +// Determine if `SctkEvent` is for surface with given object id. +fn event_is_for_surface( + evt: &SctkEvent, + object_id: &ObjectId, + has_kbd_focus: bool, +) -> bool { + match evt { + SctkEvent::SeatEvent { id, .. } => &id.id() == object_id, + SctkEvent::PointerEvent { variant, .. } => { + &variant.surface.id() == object_id + } + SctkEvent::KeyboardEvent { variant, .. } => match variant { + KeyboardEventVariant::Leave(id) => &id.id() == object_id, + _ => has_kbd_focus, + }, + SctkEvent::WindowEvent { id, .. } => &id.id() == object_id, + SctkEvent::LayerSurfaceEvent { id, .. } => &id.id() == object_id, + SctkEvent::PopupEvent { id, .. } => &id.id() == object_id, + SctkEvent::Frame(_) + | SctkEvent::NewOutput { .. } + | SctkEvent::UpdateOutput { .. } + | SctkEvent::RemovedOutput(_) => false, + SctkEvent::ScaleFactorChanged { id, .. } => &id.id() == object_id, + } +} From 6e99cd275c489aebce8bbf796f0667429631d9a1 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 1 Feb 2023 16:42:02 -0800 Subject: [PATCH 53/56] glutin: Rewrite using Glutin 0.30 to provide Compositor This provides a `Compositor` adapting a `GLCompositor`, that thus can be used with `iced_winit` or with a different shell. Backport of https://github.com/iced-rs/iced/pull/1688. Still needs some work. --- glutin/Cargo.toml | 17 +- glutin/README.md | 2 +- glutin/src/application.rs | 429 ++------------------------------------ glutin/src/compositor.rs | 244 ++++++++++++++++++++++ glutin/src/lib.rs | 2 + 5 files changed, 270 insertions(+), 424 deletions(-) create mode 100644 glutin/src/compositor.rs diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index 022457b186..e80890f36d 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -11,16 +11,15 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] +#trace = ["iced_winit/trace"] +trace = [] debug = ["iced_winit/debug"] system = ["iced_winit/system"] -[dependencies.log] -version = "0.4" - -[dependencies.glutin] -version = "0.29" -git = "https://github.com/iced-rs/glutin" -rev = "da8d291486b4c9bec12487a46c119c4b1d386abf" +[dependencies] +glutin = "0.30.3" +log = "0.4" +raw-window-handle = "0.5.0" [dependencies.iced_native] version = "0.7" @@ -35,3 +34,7 @@ features = ["application"] version = "0.5" path = "../graphics" features = ["opengl"] + +[dependencies.tracing] +version = "0.1.6" +optional = true diff --git a/glutin/README.md b/glutin/README.md index 263cc0af7d..1d87387454 100644 --- a/glutin/README.md +++ b/glutin/README.md @@ -20,7 +20,7 @@ It exposes a renderer-agnostic `Application` trait that can be implemented and t Add `iced_glutin` as a dependency in your `Cargo.toml`: ```toml -iced_glutin = "0.2" +iced_glutin = "0.6" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If diff --git a/glutin/src/application.rs b/glutin/src/application.rs index df3dbcb034..9395c595d3 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -1,433 +1,30 @@ //! Create interactive, native cross-platform applications. -use crate::mouse; -use crate::{Error, Executor, Runtime}; - -pub use iced_winit::application::StyleSheet; -pub use iced_winit::Application; use iced_graphics::window; -use iced_winit::application; -use iced_winit::conversion; -use iced_winit::futures; -use iced_winit::futures::channel::mpsc; -use iced_winit::renderer; -use iced_winit::user_interface; -use iced_winit::{Clipboard, Command, Debug, Proxy, Settings}; +use iced_winit::{application::StyleSheet, Executor, Settings}; -use glutin::window::Window; -use std::mem::ManuallyDrop; +pub use iced_winit::Application; + +use crate::compositor; /// Runs an [`Application`] with an executor, compositor, and the provided /// settings. pub fn run( settings: Settings, compositor_settings: C::Settings, -) -> Result<(), Error> +) -> Result<(), iced_winit::Error> where A: Application + 'static, E: Executor + 'static, C: window::GLCompositor + 'static, - ::Theme: StyleSheet, + ::Theme: StyleSheet, { - use futures::task; - use futures::Future; - use glutin::event_loop::EventLoopBuilder; - use glutin::platform::run_return::EventLoopExtRunReturn; - use glutin::ContextBuilder; - - let mut debug = Debug::new(); - debug.startup_started(); - - let mut event_loop = EventLoopBuilder::with_user_event().build(); - let proxy = event_loop.create_proxy(); - - let runtime = { - let executor = E::new().map_err(Error::ExecutorCreationFailed)?; - let proxy = Proxy::new(event_loop.create_proxy()); - - Runtime::new(executor, proxy) - }; - - let (application, init_command) = { - let flags = settings.flags; - - runtime.enter(|| A::new(flags)) - }; - - let context = { - let builder = settings.window.into_builder( - &application.title(), - event_loop.primary_monitor(), - settings.id, - ); - - log::info!("Window builder: {:#?}", builder); - - let opengl_builder = ContextBuilder::new() - .with_vsync(true) - .with_multisampling(C::sample_count(&compositor_settings) as u16); - - let opengles_builder = opengl_builder.clone().with_gl( - glutin::GlRequest::Specific(glutin::Api::OpenGlEs, (2, 0)), - ); - - let (first_builder, second_builder) = if settings.try_opengles_first { - (opengles_builder, opengl_builder) - } else { - (opengl_builder, opengles_builder) - }; - - log::info!("Trying first builder: {:#?}", first_builder); - - let context = first_builder - .build_windowed(builder.clone(), &event_loop) - .or_else(|_| { - log::info!("Trying second builder: {:#?}", second_builder); - second_builder.build_windowed(builder, &event_loop) - }) - .map_err(|error| { - use glutin::CreationError; - use iced_graphics::Error as ContextError; - - match error { - CreationError::Window(error) => { - Error::WindowCreationFailed(error) - } - CreationError::OpenGlVersionNotSupported => { - Error::GraphicsCreationFailed( - ContextError::VersionNotSupported, - ) - } - CreationError::NoAvailablePixelFormat => { - Error::GraphicsCreationFailed( - ContextError::NoAvailablePixelFormat, - ) - } - error => Error::GraphicsCreationFailed( - ContextError::BackendError(error.to_string()), - ), - } - })?; - - #[allow(unsafe_code)] - unsafe { - context.make_current().expect("Make OpenGL context current") - } + let compositor_settings = compositor::Settings { + gl_settings: compositor_settings, + try_opengles_first: settings.try_opengles_first, }; - - #[allow(unsafe_code)] - let (compositor, renderer) = unsafe { - C::new(compositor_settings, |address| { - context.get_proc_address(address) - })? - }; - - let (mut sender, receiver) = mpsc::unbounded(); - - let mut instance = Box::pin(run_instance::( - application, - compositor, - renderer, - runtime, - proxy, - debug, - receiver, - context, - init_command, - settings.exit_on_close_request, - )); - - let mut context = task::Context::from_waker(task::noop_waker_ref()); - - let _ = event_loop.run_return(move |event, _, control_flow| { - use glutin::event_loop::ControlFlow; - - if let ControlFlow::ExitWithCode(_) = control_flow { - return; - } - - let event = match event { - glutin::event::Event::WindowEvent { - event: - glutin::event::WindowEvent::ScaleFactorChanged { - new_inner_size, - .. - }, - window_id, - } => Some(glutin::event::Event::WindowEvent { - event: glutin::event::WindowEvent::Resized(*new_inner_size), - window_id, - }), - _ => event.to_static(), - }; - - if let Some(event) = event { - sender.start_send(event).expect("Send event"); - - let poll = instance.as_mut().poll(&mut context); - - *control_flow = match poll { - task::Poll::Pending => ControlFlow::Wait, - task::Poll::Ready(_) => ControlFlow::Exit, - }; - } - }); - - Ok(()) -} - -async fn run_instance( - mut application: A, - mut compositor: C, - mut renderer: A::Renderer, - mut runtime: Runtime, A::Message>, - mut proxy: glutin::event_loop::EventLoopProxy, - mut debug: Debug, - mut receiver: mpsc::UnboundedReceiver>, - mut context: glutin::ContextWrapper, - init_command: Command, - exit_on_close_request: bool, -) where - A: Application + 'static, - E: Executor + 'static, - C: window::GLCompositor + 'static, - ::Theme: StyleSheet, -{ - use glutin::event; - use iced_winit::futures::stream::StreamExt; - - let mut clipboard = Clipboard::connect(context.window()); - let mut cache = user_interface::Cache::default(); - let mut state = application::State::new(&application, context.window()); - let mut viewport_version = state.viewport_version(); - let mut should_exit = false; - - application::run_command( - &application, - &mut cache, - &state, - &mut renderer, - init_command, - &mut runtime, - &mut clipboard, - &mut should_exit, - &mut proxy, - &mut debug, - context.window(), - || compositor.fetch_information(), - ); - runtime.track(application.subscription()); - - let mut user_interface = - ManuallyDrop::new(application::build_user_interface( - &application, - user_interface::Cache::default(), - &mut renderer, - state.logical_size(), - &mut debug, - )); - - let mut mouse_interaction = mouse::Interaction::default(); - let mut events = Vec::new(); - let mut messages = Vec::new(); - - debug.startup_finished(); - - while let Some(event) = receiver.next().await { - match event { - event::Event::MainEventsCleared => { - if events.is_empty() && messages.is_empty() { - continue; - } - - debug.event_processing_started(); - - let (interface_state, statuses) = user_interface.update( - &events, - state.cursor_position(), - &mut renderer, - &mut clipboard, - &mut messages, - ); - - debug.event_processing_finished(); - - for event in events.drain(..).zip(statuses.into_iter()) { - runtime.broadcast(event); - } - - if !messages.is_empty() - || matches!( - interface_state, - user_interface::State::Outdated - ) - { - let mut cache = - ManuallyDrop::into_inner(user_interface).into_cache(); - - // Update application - application::update( - &mut application, - &mut cache, - &state, - &mut renderer, - &mut runtime, - &mut clipboard, - &mut should_exit, - &mut proxy, - &mut debug, - &mut messages, - context.window(), - || compositor.fetch_information(), - ); - - // Update window - state.synchronize(&application, context.window()); - - user_interface = - ManuallyDrop::new(application::build_user_interface( - &application, - cache, - &mut renderer, - state.logical_size(), - &mut debug, - )); - - if should_exit { - break; - } - } - - debug.draw_started(); - let new_mouse_interaction = user_interface.draw( - &mut renderer, - state.theme(), - &renderer::Style { - text_color: state.text_color(), - }, - state.cursor_position(), - ); - debug.draw_finished(); - - if new_mouse_interaction != mouse_interaction { - context.window().set_cursor_icon( - conversion::mouse_interaction(new_mouse_interaction), - ); - - mouse_interaction = new_mouse_interaction; - } - - context.window().request_redraw(); - } - event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( - event::MacOS::ReceivedUrl(url), - )) => { - use iced_native::event; - events.push(iced_native::Event::PlatformSpecific( - event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl( - url, - )), - )); - } - event::Event::UserEvent(message) => { - messages.push(message); - } - event::Event::RedrawRequested(_) => { - debug.render_started(); - - #[allow(unsafe_code)] - unsafe { - if !context.is_current() { - context = context - .make_current() - .expect("Make OpenGL context current"); - } - } - - let current_viewport_version = state.viewport_version(); - - if viewport_version != current_viewport_version { - let physical_size = state.physical_size(); - let logical_size = state.logical_size(); - - debug.layout_started(); - user_interface = ManuallyDrop::new( - ManuallyDrop::into_inner(user_interface) - .relayout(logical_size, &mut renderer), - ); - debug.layout_finished(); - - debug.draw_started(); - let new_mouse_interaction = user_interface.draw( - &mut renderer, - state.theme(), - &renderer::Style { - text_color: state.text_color(), - }, - state.cursor_position(), - ); - debug.draw_finished(); - - if new_mouse_interaction != mouse_interaction { - context.window().set_cursor_icon( - conversion::mouse_interaction( - new_mouse_interaction, - ), - ); - - mouse_interaction = new_mouse_interaction; - } - - context.resize(glutin::dpi::PhysicalSize::new( - physical_size.width, - physical_size.height, - )); - - compositor.resize_viewport(physical_size); - - viewport_version = current_viewport_version; - } - - compositor.present( - &mut renderer, - state.viewport(), - state.background_color(), - &debug.overlay(), - ); - - context.swap_buffers().expect("Swap buffers"); - - debug.render_finished(); - - // TODO: Handle animations! - // Maybe we can use `ControlFlow::WaitUntil` for this. - } - event::Event::WindowEvent { - window_id, - event: window_event, - .. - } => { - if application::requests_exit(&window_event, state.modifiers()) - && exit_on_close_request - { - break; - } - - state.update(context.window(), &window_event, &mut debug); - - if let Some(event) = conversion::window_event( - crate::window::Id::MAIN, - &window_event, - state.scale_factor(), - state.modifiers(), - ) { - events.push(event); - } - } - _ => {} - } - } - - // Manually drop the user interface - drop(ManuallyDrop::into_inner(user_interface)); + iced_winit::application::run::>( + settings, + compositor_settings, + ) } diff --git a/glutin/src/compositor.rs b/glutin/src/compositor.rs new file mode 100644 index 0000000000..630a24f93e --- /dev/null +++ b/glutin/src/compositor.rs @@ -0,0 +1,244 @@ +use glutin::{ + context::{NotCurrentGlContext, PossiblyCurrentContextGlSurfaceAccessor}, + display::GlDisplay, + surface::GlSurface, +}; +use iced_graphics::{ + compositor::{Information, SurfaceError}, + window, Color, Error, Size, Viewport, +}; +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use std::{ffi::CString, num::NonZeroU32}; + +pub use iced_winit::Application; + +/// Settings for the [`Compositor`] +#[derive(Debug, Default)] +pub struct Settings { + /// Settings of the underlying [`window::GLCompositor`]. + pub gl_settings: T, + /// Try to build the context using OpenGL ES first then OpenGL. + pub try_opengles_first: bool, +} + +/// Wraps a [`window::GLCompositor`] with a [`window::Compositor`] that uses Glutin for OpenGL +/// context creation. +#[derive(Debug)] +pub struct Compositor { + gl_compositor: C, + config: glutin::config::Config, + display: glutin::display::Display, + context: glutin::context::PossiblyCurrentContext, +} + +impl window::Compositor for Compositor { + type Settings = Settings; + type Renderer = C::Renderer; + type Surface = glutin::surface::Surface; + + fn new( + settings: Self::Settings, + compatible_window: Option<&W>, + ) -> Result<(Self, Self::Renderer), Error> { + let compatible_window = compatible_window.unwrap(); // XXX None? + + let display = create_display(&compatible_window).map_err(glutin_err)?; + + // XXX Is a different config (and context) potentially needed for + // different windows? + let sample_count = C::sample_count(&settings.gl_settings) as u8; + let config = get_config(&display, compatible_window, sample_count) + .map_err(glutin_err)?; + let context = create_context( + &display, + compatible_window, + &config, + settings.try_opengles_first, + ) + .map_err(glutin_err)?; + + // `C::new` seems to segfault in glow without a current context + let surface = + create_surface(&display, compatible_window, &config).unwrap(); + context + .make_current(&surface) + .expect("Make OpenGL context current"); + + #[allow(unsafe_code)] + let (gl_compositor, renderer) = unsafe { + C::new(settings.gl_settings, |address| { + display.get_proc_address(&CString::new(address).unwrap()) + }) + }?; + + Ok(( + Self { + gl_compositor, + config, + display, + context, + }, + renderer, + )) + } + + fn create_surface( + &mut self, + window: &W, + ) -> Self::Surface { + // XXX unwrap + let surface = + create_surface(&self.display, window, &self.config).unwrap(); + + // Enable vsync + self.context + .make_current(&surface) + .expect("Make OpenGL context current"); + surface + .set_swap_interval( + &self.context, + glutin::surface::SwapInterval::Wait( + NonZeroU32::new(1).unwrap(), + ), + ) + .expect("Set swap interval"); + + surface + } + + fn configure_surface( + &mut self, + surface: &mut Self::Surface, + width: u32, + height: u32, + ) { + surface.resize( + &self.context, + NonZeroU32::new(width).unwrap_or(NonZeroU32::new(1).unwrap()), + NonZeroU32::new(height).unwrap_or(NonZeroU32::new(1).unwrap()), + ); + self.gl_compositor.resize_viewport(Size { width, height }); + } + + fn fetch_information(&self) -> Information { + self.gl_compositor.fetch_information() + } + + fn present>( + &mut self, + renderer: &mut Self::Renderer, + surface: &mut Self::Surface, + viewport: &Viewport, + background_color: Color, + overlay: &[T], + ) -> Result<(), SurfaceError> { + self.context + .make_current(surface) + .expect("Make OpenGL context current"); + self.gl_compositor.present( + renderer, + viewport, + background_color, + overlay, + ); + surface.swap_buffers(&self.context).expect("Swap buffers"); + Ok(()) + } +} + +fn create_display( + window: &W, +) -> Result { + #[cfg(target_os = "windows")] + let api_preference = glutin::display::DisplayApiPreference::WglThenEgl( + Some(window.raw_window_handle()), + ); + #[cfg(target_os = "macos")] + let api_preference = glutin::display::DisplayApiPreference::Cgl; + #[cfg(not(any(target_os = "windows", target_os = "macos")))] + let api_preference = glutin::display::DisplayApiPreference::EglThenGlx( + Box::new(iced_winit::winit::platform::unix::register_xlib_error_hook), + ); + + #[allow(unsafe_code)] + unsafe { + glutin::display::Display::new( + window.raw_display_handle(), + api_preference, + ) + } +} + +fn get_config( + display: &glutin::display::Display, + window: &W, + sample_count: u8, +) -> Result { + let mut template_builder = glutin::config::ConfigTemplateBuilder::new() + .compatible_with_native_window(window.raw_window_handle()) + .with_transparency(true); + if sample_count != 0 { + template_builder = template_builder.with_multisampling(sample_count); + } + let template = template_builder.build(); + + #[allow(unsafe_code)] + Ok(unsafe { display.find_configs(template) }?.next().unwrap()) // XXX unwrap; first config? +} + +fn create_context( + display: &glutin::display::Display, + window: &W, + config: &glutin::config::Config, + try_opengles_first: bool, +) -> Result { + let opengl_attributes = glutin::context::ContextAttributesBuilder::new() + .build(Some(window.raw_window_handle())); + let opengles_attributes = glutin::context::ContextAttributesBuilder::new() + .with_context_api(glutin::context::ContextApi::Gles(Some( + glutin::context::Version { major: 2, minor: 0 }, + ))) + .build(Some(window.raw_window_handle())); + + let (first_attributes, second_attributes) = if try_opengles_first { + (opengles_attributes, opengl_attributes) + } else { + (opengl_attributes, opengles_attributes) + }; + + #[allow(unsafe_code)] + Ok(unsafe { display.create_context(config, &first_attributes) } + .or_else(|_| { + log::info!("Trying second attributes: {:#?}", second_attributes); + unsafe { display.create_context(config, &second_attributes) } + })? + .treat_as_possibly_current()) +} + +fn create_surface( + display: &glutin::display::Display, + window: &W, + config: &glutin::config::Config, +) -> Result< + glutin::surface::Surface, + glutin::error::Error, +> { + let surface_attributes = glutin::surface::SurfaceAttributesBuilder::< + glutin::surface::WindowSurface, + >::new() + .build( + window.raw_window_handle(), + NonZeroU32::new(1).unwrap(), + NonZeroU32::new(1).unwrap(), + ); + + #[allow(unsafe_code)] + unsafe { + display.create_window_surface(config, &surface_attributes) + } +} + +fn glutin_err(err: glutin::error::Error) -> iced_graphics::Error { + // TODO: match error kind? Doesn't seem to match `iced_grapihcs::Error` well + iced_graphics::Error::BackendError(err.to_string()) +} diff --git a/glutin/src/lib.rs b/glutin/src/lib.rs index 33afd66435..d86cf7b585 100644 --- a/glutin/src/lib.rs +++ b/glutin/src/lib.rs @@ -28,6 +28,8 @@ pub use glutin; pub use iced_winit::*; pub mod application; +mod compositor; #[doc(no_inline)] pub use application::Application; +pub use compositor::{Compositor, Settings}; From bedaf844e23954efd5421ffab402e626fc238821 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 1 Feb 2023 16:42:41 -0800 Subject: [PATCH 54/56] Use new version of `iced_glutin` to support glow + sctk --- Cargo.toml | 2 +- src/wayland/mod.rs | 25 ++++++++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e94d77702a..dc6317b1eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ wgpu = ["iced_wgpu"] # Enables the `iced_softbuffer` renderer. Conflicts with `iced_wgpu` and `iced_glow` softbuffer = ["iced_softbuffer"] # Enables the `iced_glow` renderer. Conflicts with `iced_wgpu` and `iced_softbuffer` -glow = ["iced_glow"] +glow = ["iced_glow", "iced_glutin"] # Enables using system fonts default_system_font = ["iced_wgpu?/default_system_font", "iced_glow?/default_system_font"] # Enables a debug view in native platforms (press F12) diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 65f6260913..fd9c404a7d 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -144,11 +144,26 @@ pub trait Application: Sized { ..crate::renderer::Settings::from_env() }; - Ok(crate::runtime::run::< - Instance, - Self::Executor, - crate::renderer::window::Compositor, - >(settings.into(), renderer_settings)?) + #[cfg(feature = "glow")] + { + let renderer_settings = iced_glutin::Settings { + gl_settings: renderer_settings, + try_opengles_first: settings.try_opengles_first + }; + Ok(crate::runtime::run::< + Instance, + Self::Executor, + iced_glutin::Compositor>, + >(settings.into(), renderer_settings)?) + } + #[cfg(not(feature = "glow"))] + { + Ok(crate::runtime::run::< + Instance, + Self::Executor, + crate::renderer::window::Compositor, + >(settings.into(), renderer_settings)?) + } } } From 23665560a433082878de3e40f861bfff43e120ff Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 2 Feb 2023 09:59:14 -0800 Subject: [PATCH 55/56] dyrend: Support glow renderer --- dyrend/Cargo.toml | 9 +++++++-- dyrend/src/window/compositor.rs | 23 ++++++++++++++--------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/dyrend/Cargo.toml b/dyrend/Cargo.toml index 29769cb5f0..30ef6f6430 100644 --- a/dyrend/Cargo.toml +++ b/dyrend/Cargo.toml @@ -23,6 +23,12 @@ path = "../glow" default-features = false optional = true +[dependencies.iced_glutin] +path = "../glutin" +default-features = false +optional = true + + [dependencies.iced_softbuffer] path = "../softbuffer" default-features = false @@ -37,8 +43,7 @@ optional = true default = ["softbuffer"] image = ["iced_graphics/image", "iced_glow?/image", "iced_softbuffer?/image", "iced_wgpu?/image"] svg = ["iced_graphics/svg", "iced_glow?/svg", "iced_softbuffer?/svg", "iced_wgpu?/svg"] -#TODO: implement Compositor for glow Backend -#glow = ["iced_glow"] +glow = ["iced_glow", "iced_glutin"] softbuffer = ["iced_softbuffer"] wgpu = ["iced_wgpu"] diff --git a/dyrend/src/window/compositor.rs b/dyrend/src/window/compositor.rs index 4a9ec657d5..477ba2105e 100644 --- a/dyrend/src/window/compositor.rs +++ b/dyrend/src/window/compositor.rs @@ -1,5 +1,7 @@ #[cfg(feature = "glow")] use iced_glow::window::Compositor as GlowCompositor; +#[cfg(feature = "glow")] +use iced_glutin::Compositor as GlutinCompositor; use iced_graphics::{ compositor::{self, Compositor as _, Information, SurfaceError}, Color, Error, Viewport, @@ -16,7 +18,7 @@ use crate::Renderer; /// A window graphics backend for iced powered by `glow`. pub enum Compositor { #[cfg(feature = "glow")] - Glow(GlowCompositor), + Glow(GlutinCompositor>), #[cfg(feature = "softbuffer")] softbuffer(softbufferCompositor), #[cfg(feature = "wgpu")] @@ -25,7 +27,7 @@ pub enum Compositor { pub enum Surface { #[cfg(feature = "glow")] - Glow( as compositor::Compositor>::Surface), + Glow(> as compositor::Compositor>::Surface), #[cfg(feature = "softbuffer")] softbuffer( as compositor::Compositor>::Surface, @@ -40,13 +42,16 @@ impl Compositor { settings: crate::Settings, compatible_window: Option<&W>, ) -> Result<(Self, Renderer), Error> { - match GlowCompositor::new( - iced_glow::Settings { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - text_multithreading: settings.text_multithreading, - antialiasing: settings.antialiasing, - ..iced_glow::Settings::from_env() + match GlutinCompositor::new( + iced_glutin::Settings { + gl_settings: iced_glow::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + text_multithreading: settings.text_multithreading, + antialiasing: settings.antialiasing, + ..iced_glow::Settings::from_env() + }, + try_opengles_first: false, // XXX }, compatible_window, ) { From 06eddedb8270724622dcaaa522db6994bf601dbb Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 8 Feb 2023 11:44:17 -0800 Subject: [PATCH 56/56] sctk: Send output events even if no surface exists Fixes behavior with `InitialSurface::None`. --- sctk/src/application.rs | 46 ++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/sctk/src/application.rs b/sctk/src/application.rs index 57d64cf377..2f1b3cb6e4 100644 --- a/sctk/src/application.rs +++ b/sctk/src/application.rs @@ -620,6 +620,29 @@ where } } IcedSctkEvent::MainEventsCleared => { + let mut i = 0; + while i < events.len() { + let remove = match &events[i] { + SctkEvent::NewOutput { .. } + | SctkEvent::UpdateOutput { .. } + | SctkEvent::RemovedOutput(_) => true, + _ => false, + }; + if remove { + let event = events.remove(i); + for native_event in event.to_native( + &mut mods, + &surface_ids, + &destroyed_surface_ids, + ) { + runtime + .broadcast((native_event, Status::Ignored)); + } + } else { + i += 1; + } + } + if surface_ids.is_empty() && !messages.is_empty() { // Update application let pure_states: HashMap<_, _> = @@ -656,29 +679,6 @@ where break 'main; } } else { - let mut i = 0; - while i < events.len() { - let remove = match &events[i] { - SctkEvent::NewOutput { .. } - | SctkEvent::UpdateOutput { .. } - | SctkEvent::RemovedOutput(_) => true, - _ => false, - }; - if remove { - let event = events.remove(i); - for native_event in event.to_native( - &mut mods, - &surface_ids, - &destroyed_surface_ids, - ) { - runtime - .broadcast((native_event, Status::Ignored)); - } - } else { - i += 1; - } - } - let mut needs_redraw = false; for (object_id, surface_id) in &surface_ids { let mut filtered = Vec::with_capacity(events.len());