diff --git a/Cargo.lock b/Cargo.lock index fa7803fe..d63e6186 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5812,7 +5812,7 @@ dependencies = [ name = "sctk_subsurface" version = "0.1.0" dependencies = [ - "calloop 0.12.4", + "calloop 0.13.0", "cosmic-client-toolkit", "env_logger", "futures-channel", diff --git a/core/src/event/wayland/mod.rs b/core/src/event/wayland/mod.rs index 5fabc860..2bfae76a 100644 --- a/core/src/event/wayland/mod.rs +++ b/core/src/event/wayland/mod.rs @@ -4,6 +4,7 @@ mod overlap_notify; mod popup; mod seat; mod session_lock; +mod subsurface; mod window; use crate::{time::Instant, window::Id}; @@ -17,6 +18,7 @@ pub use overlap_notify::*; pub use popup::*; pub use seat::*; pub use session_lock::*; +pub use subsurface::*; pub use window::*; /// wayland events @@ -40,4 +42,6 @@ pub enum Event { Frame(Instant, WlSurface, Id), /// Request Resize RequestResize, + /// Subsurface + Subsurface(SubsurfaceEvent), } diff --git a/core/src/event/wayland/subsurface.rs b/core/src/event/wayland/subsurface.rs new file mode 100644 index 00000000..ea42f541 --- /dev/null +++ b/core/src/event/wayland/subsurface.rs @@ -0,0 +1,8 @@ +/// popup events +#[derive(Debug, Clone, PartialEq)] +pub enum SubsurfaceEvent { + /// Destroyed + Destroyed, + /// repositioned, + Created, +} diff --git a/examples/multi_window/src/main.rs b/examples/multi_window/src/main.rs index b2ed47fa..937bcc92 100644 --- a/examples/multi_window/src/main.rs +++ b/examples/multi_window/src/main.rs @@ -162,6 +162,7 @@ impl Window { scale_input: "1.0".to_string(), current_scale: 1.0, theme: Theme::ALL[count % Theme::ALL.len()].clone(), + input_id: text_input::Id::unique(), } } diff --git a/examples/sctk_subsurface/Cargo.toml b/examples/sctk_subsurface/Cargo.toml index 126bc283..c2d694c9 100644 --- a/examples/sctk_subsurface/Cargo.toml +++ b/examples/sctk_subsurface/Cargo.toml @@ -15,6 +15,6 @@ iced = { path = "../..", default-features = false, features = [ iced_runtime = { path = "../../runtime" } env_logger = "0.10" futures-channel = "0.3.29" -calloop = "0.12.3" +calloop = "0.13" rustix = { version = "0.38.30", features = ["fs", "shm"] } cctk.workspace = true diff --git a/examples/sctk_subsurface/src/main.rs b/examples/sctk_subsurface/src/main.rs index 9d31e08a..94380e9d 100644 --- a/examples/sctk_subsurface/src/main.rs +++ b/examples/sctk_subsurface/src/main.rs @@ -1,14 +1,24 @@ // Shows a subsurface with a 1x1 px red buffer, stretch to window size +use cctk::sctk::reexports::{ + client::{Connection, Proxy}, + protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity}, +}; + +use iced::platform_specific::shell::commands::subsurface::get_subsurface; use iced::{ event::wayland::Event as WaylandEvent, - platform_specific::shell::subsurface_widget::{self, SubsurfaceBuffer}, - widget::text, + platform_specific::{ + runtime::wayland::subsurface::SctkSubsurfaceSettings, + shell::subsurface_widget::{self, SubsurfaceBuffer}, + }, + widget::{button, column, text, text_input}, window::{self, Id, Settings}, Element, Length, Subscription, Task, }; -use cctk::sctk::reexports::client::{Connection, Proxy}; +use std::sync::{Arc, Mutex}; +mod subsurface_container; mod wayland; fn main() -> iced::Result { @@ -23,8 +33,11 @@ fn main() -> iced::Result { #[derive(Debug, Clone, Default)] struct SubsurfaceApp { + text: Arc>, + counter: Arc>, connection: Option, red_buffer: Option, + green_buffer: Option, } #[derive(Debug, Clone)] @@ -33,6 +46,8 @@ pub enum Message { Wayland(wayland::Event), Pressed(&'static str), Id(Id), + Inc, + Text(String), } impl SubsurfaceApp { @@ -55,47 +70,106 @@ impl SubsurfaceApp { fn update(&mut self, message: Message) -> Task { match message { - Message::WaylandEvent(evt) => match evt { - WaylandEvent::Output(_evt, output) => { - if self.connection.is_none() { - if let Some(backend) = output.backend().upgrade() { - self.connection = - Some(Connection::from_backend(backend)); + Message::WaylandEvent(evt) => { + dbg!(&evt); + match evt { + WaylandEvent::Output(_evt, output) => { + if self.connection.is_none() { + if let Some(backend) = output.backend().upgrade() { + self.connection = + Some(Connection::from_backend(backend)); + } } } + _ => {} } - _ => {} - }, + } Message::Wayland(evt) => match evt { wayland::Event::RedBuffer(buffer) => { self.red_buffer = Some(buffer); } + wayland::Event::GreenBuffer(buffer) => { + self.green_buffer = Some(buffer); + } }, Message::Pressed(side) => println!("{side} surface pressed"), - Message::Id(_) => {} + Message::Id(id) => { + let my_text = self.text.clone(); + let my_counter = self.counter.clone(); + return get_subsurface(SctkSubsurfaceSettings { + id: window::Id::unique(), + parent: id, + loc: iced::Point::new(100., 200.), + size: Some(iced::Size::new(100., 100.)), + z: 1000, + steal_keyboard_focus: false, + gravity: Gravity::BottomRight, + input_zone: None, + offset: (0, 0), + }); + } + Message::Inc => { + let mut guard = self.counter.lock().unwrap(); + + *guard += 1; + } + Message::Text(s) => { + let mut guard = self.text.lock().unwrap(); + *guard = s; + } } Task::none() } - fn view(&self, _id: window::Id) -> Element { - if let Some(buffer) = &self.red_buffer { - iced::widget::row![ - iced::widget::button( - subsurface_widget::Subsurface::new(1, 1, buffer) - .width(Length::Fill) - .height(Length::Fill) - ) - .width(Length::Fill) - .height(Length::Fill) - .on_press(Message::Pressed("left")), - iced::widget::button( - subsurface_widget::Subsurface::new(1, 1, buffer) - .width(Length::Fill) - .height(Length::Fill) - ) - .width(Length::Fill) - .height(Length::Fill) - .on_press(Message::Pressed("right")) + fn view(&self, id: window::Id) -> Element { + let my_text_guard = self.text.lock().unwrap(); + if let Some((red_buffer, green_buffer)) = + self.red_buffer.iter().zip(self.green_buffer.iter()).next() + { + column![ + iced::widget::row![ + iced::widget::button( + subsurface_container::SubsurfaceContainer::new() + .width(Length::Fill) + .height(Length::Fill) + .push( + subsurface_widget::Subsurface::new( + red_buffer.clone() + ) + .width(Length::Fill) + .height(Length::Fill) + .z(0) + ) + .push( + subsurface_widget::Subsurface::new( + green_buffer.clone() + ) + .width(Length::Fixed(1920.)) + .height(Length::Fixed(200.)) + .z(1) + ) + .push( + subsurface_widget::Subsurface::new( + red_buffer.clone() + ) + .width(Length::Fill) + .height(Length::Fixed(100.)) + .z(2) + ) + ) + .width(Length::Fill) + .height(Length::Fill) + .on_press(Message::Pressed("left")), + iced::widget::button( + subsurface_widget::Subsurface::new(red_buffer.clone()) + .width(Length::Fill) + .height(Length::Fill) + ) + .width(Length::Fill) + .height(Length::Fill) + .on_press(Message::Pressed("right")) + ], + text_input("asdf", &my_text_guard).on_input(Message::Text) ] .into() } else { @@ -106,10 +180,13 @@ impl SubsurfaceApp { fn subscription(&self) -> Subscription { let mut subscriptions = vec![iced::event::listen_with(|evt, _, _| { if let iced::Event::PlatformSpecific( - iced::event::PlatformSpecific::Wayland(evt), + iced::event::PlatformSpecific::Wayland(WaylandEvent::Output( + evt, + output, + )), ) = evt { - Some(Message::WaylandEvent(evt)) + Some(Message::WaylandEvent(WaylandEvent::Output(evt, output))) } else { None } diff --git a/examples/sctk_subsurface/src/subsurface_container.rs b/examples/sctk_subsurface/src/subsurface_container.rs new file mode 100644 index 00000000..d4d5398d --- /dev/null +++ b/examples/sctk_subsurface/src/subsurface_container.rs @@ -0,0 +1,430 @@ +//! Distribute content vertically. +use iced::core::alignment::{self, Alignment}; +use iced::core::event::{self, Event}; +use iced::core::layout; +use iced::core::mouse; +use iced::core::overlay; +use iced::core::renderer; +use iced::core::widget::{Operation, Tree}; +use iced::core::{ + Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, + Size, Vector, Widget, +}; + +/// A container that distributes its contents vertically. +/// +/// # Example +/// ```no_run +/// # mod iced { pub mod widget { pub use iced_widget::*; } } +/// # pub type State = (); +/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; +/// use iced::widget::{button, SubsurfaceContainer}; +/// +/// #[derive(Debug, Clone)] +/// enum Message { +/// // ... +/// } +/// +/// fn view(state: &State) -> Element<'_, Message> { +/// SubsurfaceContainer![ +/// "I am on top!", +/// button("I am in the center!"), +/// "I am below.", +/// ].into() +/// } +/// ``` +#[allow(missing_debug_implementations)] +pub struct SubsurfaceContainer< + 'a, + Message, + Theme = iced::Theme, + Renderer = iced::Renderer, +> { + spacing: f32, + padding: Padding, + width: Length, + height: Length, + max_width: f32, + align: Alignment, + clip: bool, + children: Vec>, +} + +impl<'a, Message, Theme, Renderer> + SubsurfaceContainer<'a, Message, Theme, Renderer> +where + Renderer: iced::core::Renderer, +{ + /// Creates an empty [`SubsurfaceContainer`]. + pub fn new() -> Self { + Self::from_vec(Vec::new()) + } + + /// Creates a [`SubsurfaceContainer`] with the given capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self::from_vec(Vec::with_capacity(capacity)) + } + + /// Creates a [`SubsurfaceContainer`] with the given elements. + pub fn with_children( + children: impl IntoIterator>, + ) -> Self { + let iterator = children.into_iter(); + + Self::with_capacity(iterator.size_hint().0).extend(iterator) + } + + /// Creates a [`SubsurfaceContainer`] from an already allocated [`Vec`]. + /// + /// Keep in mind that the [`SubsurfaceContainer`] will not inspect the [`Vec`], which means + /// it won't automatically adapt to the sizing strategy of its contents. + /// + /// If any of the children have a [`Length::Fill`] strategy, you will need to + /// call [`SubsurfaceContainer::width`] or [`SubsurfaceContainer::height`] accordingly. + pub fn from_vec( + children: Vec>, + ) -> Self { + Self { + spacing: 0.0, + padding: Padding::ZERO, + width: Length::Shrink, + height: Length::Shrink, + max_width: f32::INFINITY, + align: Alignment::Start, + clip: false, + children, + } + } + + /// Sets the vertical spacing _between_ elements. + /// + /// Custom margins per element do not exist in iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, amount: impl Into) -> Self { + self.spacing = amount.into().0; + self + } + + /// Sets the [`Padding`] of the [`SubsurfaceContainer`]. + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + /// Sets the width of the [`SubsurfaceContainer`]. + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Sets the height of the [`SubsurfaceContainer`]. + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } + + /// Sets the maximum width of the [`SubsurfaceContainer`]. + pub fn max_width(mut self, max_width: impl Into) -> Self { + self.max_width = max_width.into().0; + self + } + + /// Sets the horizontal alignment of the contents of the [`SubsurfaceContainer`] . + pub fn align_x(mut self, align: impl Into) -> Self { + self.align = Alignment::from(align.into()); + self + } + + /// Sets whether the contents of the [`SubsurfaceContainer`] should be clipped on + /// overflow. + pub fn clip(mut self, clip: bool) -> Self { + self.clip = clip; + self + } + + /// Adds an element to the [`SubsurfaceContainer`]. + pub fn push( + mut self, + child: impl Into>, + ) -> Self { + let child = child.into(); + let child_size = child.as_widget().size_hint(); + + self.width = self.width.enclose(child_size.width); + self.height = self.height.enclose(child_size.height); + + self.children.push(child); + self + } + + /// Adds an element to the [`SubsurfaceContainer`], if `Some`. + pub fn push_maybe( + self, + child: Option>>, + ) -> Self { + if let Some(child) = child { + self.push(child) + } else { + self + } + } + + /// Extends the [`SubsurfaceContainer`] with the given children. + pub fn extend( + self, + children: impl IntoIterator>, + ) -> Self { + children.into_iter().fold(self, Self::push) + } +} + +impl<'a, Message, Renderer> Default + for SubsurfaceContainer<'a, Message, Renderer> +where + Renderer: iced::core::Renderer, +{ + fn default() -> Self { + Self::new() + } +} + +impl<'a, Message, Theme, Renderer: iced::core::Renderer> + FromIterator> + for SubsurfaceContainer<'a, Message, Theme, Renderer> +{ + fn from_iter< + T: IntoIterator>, + >( + iter: T, + ) -> Self { + Self::with_children(iter) + } +} + +impl<'a, Message, Theme, Renderer> Widget + for SubsurfaceContainer<'a, Message, Theme, Renderer> +where + Renderer: iced::core::Renderer, +{ + fn children(&self) -> Vec { + self.children.iter().map(Tree::new).collect() + } + + fn diff(&mut self, tree: &mut Tree) { + tree.diff_children(self.children.as_mut_slice()); + } + + fn size(&self) -> Size { + Size { + width: self.width, + height: self.height, + } + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.max_width(self.max_width); + let nodes = self + .children + .iter() + .zip(tree.children.iter_mut()) + .map(|c| { + let size = c.0.as_widget().size(); + layout::positioned( + &limits.max_width(self.max_width), + size.width, + size.height, + self.padding, + |limits| c.0.as_widget().layout(c.1, renderer, limits), + |content, size| { + content.align(self.align, Alignment::Start, size) + }, + ) + }) + .collect(); + + let size = limits.resolve(self.width, self.height, Size::ZERO); + + layout::Node::with_children(size, nodes) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn Operation, + ) { + operation.container(None, layout.bounds(), &mut |operation| { + self.children + .iter() + .zip(&mut tree.children) + .zip(layout.children()) + .for_each(|((child, state), layout)| { + child + .as_widget() + .operate(state, layout, renderer, operation); + }); + }); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) -> event::Status { + self.children + .iter_mut() + .zip(&mut tree.children) + .zip(layout.children()) + .map(|((child, state), layout)| { + child.as_widget_mut().on_event( + state, + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + viewport, + ) + }) + .fold(event::Status::Ignored, event::Status::merge) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.children + .iter() + .zip(&tree.children) + .zip(layout.children()) + .map(|((child, state), layout)| { + child.as_widget().mouse_interaction( + state, layout, cursor, viewport, renderer, + ) + }) + .max() + .unwrap_or_default() + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + if let Some(clipped_viewport) = layout.bounds().intersection(viewport) { + let viewport = if self.clip { + &clipped_viewport + } else { + viewport + }; + + for ((child, state), layout) in self + .children + .iter() + .zip(&tree.children) + .zip(layout.children()) + .filter(|(_, layout)| layout.bounds().intersects(viewport)) + { + child.as_widget().draw( + state, renderer, theme, style, layout, cursor, viewport, + ); + } + } + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + translation: Vector, + ) -> Option> { + overlay::from_children( + &mut self.children, + tree, + layout, + renderer, + translation, + ) + } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: Layout<'_>, + state: &Tree, + cursor: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + use iced_accessibility::A11yTree; + A11yTree::join( + self.children + .iter() + .zip(layout.children()) + .zip(state.children.iter()) + .map(|((c, c_layout), state)| { + c.as_widget().a11y_nodes(c_layout, state, cursor) + }), + ) + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut iced::core::clipboard::DndDestinationRectangles, + ) { + for ((e, layout), state) in self + .children + .iter() + .zip(layout.children()) + .zip(state.children.iter()) + { + e.as_widget().drag_destinations( + state, + layout, + renderer, + dnd_rectangles, + ); + } + } +} + +impl<'a, Message, Theme, Renderer> + From> + for Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: 'a, + Renderer: iced::core::Renderer + 'a, +{ + fn from( + SubsurfaceContainer: SubsurfaceContainer<'a, Message, Theme, Renderer>, + ) -> Self { + Self::new(SubsurfaceContainer) + } +} diff --git a/examples/sctk_subsurface/src/wayland.rs b/examples/sctk_subsurface/src/wayland.rs index 033dfc8e..f43a77a3 100644 --- a/examples/sctk_subsurface/src/wayland.rs +++ b/examples/sctk_subsurface/src/wayland.rs @@ -1,11 +1,5 @@ -use futures_channel::mpsc; -use iced::{ - futures::{FutureExt, SinkExt}, - platform_specific::shell::subsurface_widget::{Shmbuf, SubsurfaceBuffer}, -}; -use iced_runtime::futures::subscription; -use rustix::{io::Errno, shm::ShmOFlags}; use cctk::sctk::{ + self, reexports::{ calloop_wayland_source::WaylandSource, client::{ @@ -18,16 +12,24 @@ use cctk::sctk::{ registry::{ProvidesRegistryState, RegistryState}, shm::{Shm, ShmHandler}, }; +use futures_channel::mpsc; +use iced::{ + futures::{FutureExt, SinkExt}, + platform_specific::shell::subsurface_widget::{Shmbuf, SubsurfaceBuffer}, +}; +use iced_runtime::futures::subscription; +use rustix::{io::Errno, shm::ShmOFlags}; use std::{ os::fd::OwnedFd, sync::Arc, thread, - time::{SystemTime, UNIX_EPOCH}, + time::{Duration, SystemTime, UNIX_EPOCH}, }; #[derive(Debug, Clone)] pub enum Event { RedBuffer(SubsurfaceBuffer), + GreenBuffer(SubsurfaceBuffer), } struct AppData { @@ -80,6 +82,20 @@ async fn start(conn: Connection) -> mpsc::Receiver { format: wl_shm::Format::Xrgb8888, }; + let buffer = SubsurfaceBuffer::new(Arc::new(shmbuf.into())).0; + let _ = sender.send(Event::GreenBuffer(buffer)).await; + + let fd = create_memfile().unwrap(); + rustix::io::write(&fd, &[0, 0, 255, 255]).unwrap(); + + let shmbuf = Shmbuf { + fd, + offset: 0, + width: 1, + height: 1, + stride: 4, + format: wl_shm::Format::Xrgb8888, + }; let buffer = SubsurfaceBuffer::new(Arc::new(shmbuf.into())).0; let _ = sender.send(Event::RedBuffer(buffer)).await; @@ -90,6 +106,7 @@ async fn start(conn: Connection) -> mpsc::Receiver { .unwrap(); loop { event_loop.dispatch(None, &mut app_data).unwrap(); + std::thread::sleep(Duration::from_millis(500)); } }); diff --git a/runtime/src/platform_specific/wayland/layer_surface.rs b/runtime/src/platform_specific/wayland/layer_surface.rs index f4419c96..187d3174 100644 --- a/runtime/src/platform_specific/wayland/layer_surface.rs +++ b/runtime/src/platform_specific/wayland/layer_surface.rs @@ -1,10 +1,10 @@ use std::fmt; -use iced_core::layout::Limits; use cctk::sctk::{ reexports::client::protocol::wl_output::WlOutput, shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer}, }; +use iced_core::layout::Limits; use iced_core::window::Id; diff --git a/runtime/src/platform_specific/wayland/mod.rs b/runtime/src/platform_specific/wayland/mod.rs index 41b21c52..9e8acea2 100644 --- a/runtime/src/platform_specific/wayland/mod.rs +++ b/runtime/src/platform_specific/wayland/mod.rs @@ -14,6 +14,9 @@ pub mod popup; /// session locks pub mod session_lock; +// subsurfaces +pub mod subsurface; + /// Platform specific actions defined for wayland pub enum Action { /// LayerSurface Actions @@ -26,6 +29,8 @@ pub enum Action { SessionLock(session_lock::Action), /// Overlap Notify OverlapNotify(Id, bool), + /// Subsurfaces + Subsurface(subsurface::Action), } impl Debug for Action { @@ -44,6 +49,9 @@ impl Debug for Action { Action::OverlapNotify(id, _) => { f.debug_tuple("OverlapNotify").field(id).finish() } + Action::Subsurface(action) => { + f.debug_tuple("Subsurface").field(action).finish() + } } } } diff --git a/runtime/src/platform_specific/wayland/popup.rs b/runtime/src/platform_specific/wayland/popup.rs index 19b23e02..46dc310a 100644 --- a/runtime/src/platform_specific/wayland/popup.rs +++ b/runtime/src/platform_specific/wayland/popup.rs @@ -1,12 +1,15 @@ +use std::any::Any; use std::fmt; use std::hash::{Hash, Hasher}; +use std::sync::Arc; -use iced_core::layout::Limits; -use iced_core::window::Id; -use iced_core::Rectangle; use cctk::sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{ Anchor, Gravity, }; +use iced_core::layout::Limits; +use iced_core::window::Id; +use iced_core::{Element, Rectangle}; + /// Popup creation details #[derive(Debug, Clone)] pub struct SctkPopupSettings { @@ -20,6 +23,11 @@ pub struct SctkPopupSettings { pub parent_size: Option<(u32, u32)>, /// whether a grab should be requested for the popup after creation pub grab: bool, + /// whether a popup should close when its child popups close + pub close_with_children: bool, + /// input zone + /// None results in accepting all input + pub input_zone: Option, } impl Hash for SctkPopupSettings { diff --git a/runtime/src/platform_specific/wayland/subsurface.rs b/runtime/src/platform_specific/wayland/subsurface.rs new file mode 100644 index 00000000..0fb5130d --- /dev/null +++ b/runtime/src/platform_specific/wayland/subsurface.rs @@ -0,0 +1,77 @@ +use std::any::Any; +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::sync::Arc; + +use cctk::sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{ + Anchor, Gravity, +}; +use iced_core::layout::Limits; +use iced_core::window::Id; +use iced_core::{Element, Point, Rectangle, Size}; + +/// Subsurface creation details +#[derive(Debug, Clone)] +pub struct SctkSubsurfaceSettings { + /// XXX must be unique, id of the parent + pub parent: Id, + /// XXX must be unique, id of the subsurface + pub id: Id, + /// anchor position of the subsurface + pub loc: Point, + /// size of the subsurface + pub size: Option, + // pub subsurface_view: Option>, + /// Z + pub z: u32, + /// Steal Keyboard focus from parent while open. + /// Will not work on a regular window. + pub steal_keyboard_focus: bool, + + /// offset of the subsurface from the anchor + pub offset: (i32, i32), + /// the gravity of the popup + pub gravity: Gravity, + + /// input zone + /// None results in accepting all input + pub input_zone: Option, +} + +impl Hash for SctkSubsurfaceSettings { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + +#[derive(Clone)] +/// Window Action +pub enum Action { + /// create a window and receive a message with its Id + Subsurface { + /// subsurface + subsurface: SctkSubsurfaceSettings, + }, + /// destroy the subsurface + Destroy { + /// id of the subsurface + id: Id, + }, +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Action::Subsurface { subsurface, .. } => write!( + f, + "Action::SubsurfaceAction::Subsurface {{ subsurface: {:?} }}", + subsurface + ), + Action::Destroy { id } => write!( + f, + "Action::SubsurfaceAction::Destroy {{ id: {:?} }}", + id + ), + } + } +} diff --git a/winit/src/platform_specific/mod.rs b/winit/src/platform_specific/mod.rs index e79ca029..165e6239 100644 --- a/winit/src/platform_specific/mod.rs +++ b/winit/src/platform_specific/mod.rs @@ -45,6 +45,7 @@ pub enum SurfaceIdWrapper { Window(window::Id), Popup(window::Id), SessionLock(window::Id), + Subsurface(window::Id), } impl SurfaceIdWrapper { pub fn inner(&self) -> window::Id { @@ -53,6 +54,7 @@ impl SurfaceIdWrapper { SurfaceIdWrapper::Window(id) => *id, SurfaceIdWrapper::Popup(id) => *id, SurfaceIdWrapper::SessionLock(id) => *id, + SurfaceIdWrapper::Subsurface(id) => *id, } } } diff --git a/winit/src/platform_specific/wayland/commands/mod.rs b/winit/src/platform_specific/wayland/commands/mod.rs index 85ba1000..38a8c279 100644 --- a/winit/src/platform_specific/wayland/commands/mod.rs +++ b/winit/src/platform_specific/wayland/commands/mod.rs @@ -5,3 +5,4 @@ pub mod layer_surface; pub mod overlap_notify; pub mod popup; pub mod session_lock; +pub mod subsurface; diff --git a/winit/src/platform_specific/wayland/commands/session_lock.rs b/winit/src/platform_specific/wayland/commands/session_lock.rs index d008b0d6..7f8748f7 100644 --- a/winit/src/platform_specific/wayland/commands/session_lock.rs +++ b/winit/src/platform_specific/wayland/commands/session_lock.rs @@ -1,10 +1,10 @@ use crate::core::window::Id as SurfaceId; +use cctk::sctk::reexports::client::protocol::wl_output::WlOutput; use iced_runtime::{ self, platform_specific::{self, wayland}, task, Action, Task, }; -use cctk::sctk::reexports::client::protocol::wl_output::WlOutput; pub fn lock() -> Task { task::effect(Action::PlatformSpecific( diff --git a/winit/src/platform_specific/wayland/commands/subsurface.rs b/winit/src/platform_specific/wayland/commands/subsurface.rs new file mode 100644 index 00000000..d8ce8259 --- /dev/null +++ b/winit/src/platform_specific/wayland/commands/subsurface.rs @@ -0,0 +1,28 @@ +use crate::core::window::Id as SurfaceId; +pub use cctk::sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer}; +use iced_runtime::{ + self, + platform_specific::{ + self, + wayland::{self, subsurface::SctkSubsurfaceSettings}, + }, + task, Action, Task, +}; + +pub fn get_subsurface( + subsurface: SctkSubsurfaceSettings, +) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Subsurface( + wayland::subsurface::Action::Subsurface { subsurface }, + )), + )) +} + +pub fn destroy_subsurface(id: SurfaceId) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Subsurface( + wayland::subsurface::Action::Destroy { id }, + )), + )) +} diff --git a/winit/src/platform_specific/wayland/conversion.rs b/winit/src/platform_specific/wayland/conversion.rs index 1db1abbb..c2ca825d 100644 --- a/winit/src/platform_specific/wayland/conversion.rs +++ b/winit/src/platform_specific/wayland/conversion.rs @@ -73,47 +73,3 @@ pub fn modifiers_to_native(mods: Modifiers) -> keyboard::Modifiers { // } native_mods } - -// pub fn keysym_to_vkey(keysym: RawKeysym) -> Option { -// key_conversion.get(&keysym).cloned() -// } - -// pub(crate) fn cursor_icon(cursor: winit::window::CursorIcon) -> CursorIcon { -// match cursor { -// CursorIcon::Default => todo!(), -// CursorIcon::ContextMenu => todo!(), -// CursorIcon::Help => todo!(), -// CursorIcon::Pointer => todo!(), -// CursorIcon::Progress => todo!(), -// CursorIcon::Wait => todo!(), -// CursorIcon::Cell => todo!(), -// CursorIcon::Crosshair => todo!(), -// CursorIcon::Text => todo!(), -// CursorIcon::VerticalText => todo!(), -// CursorIcon::Alias => todo!(), -// CursorIcon::Copy => todo!(), -// CursorIcon::Move => todo!(), -// CursorIcon::NoDrop => todo!(), -// CursorIcon::NotAllowed => todo!(), -// CursorIcon::Grab => todo!(), -// CursorIcon::Grabbing => todo!(), -// CursorIcon::EResize => todo!(), -// CursorIcon::NResize => todo!(), -// CursorIcon::NeResize => todo!(), -// CursorIcon::NwResize => todo!(), -// CursorIcon::SResize => todo!(), -// CursorIcon::SeResize => todo!(), -// CursorIcon::SwResize => todo!(), -// CursorIcon::WResize => todo!(), -// CursorIcon::EwResize => todo!(), -// CursorIcon::NsResize => todo!(), -// CursorIcon::NeswResize => todo!(), -// CursorIcon::NwseResize => todo!(), -// CursorIcon::ColResize => todo!(), -// CursorIcon::RowResize => todo!(), -// CursorIcon::AllScroll => todo!(), -// CursorIcon::ZoomIn => todo!(), -// CursorIcon::ZoomOut => todo!(), -// _ => todo!(), -// } -// } diff --git a/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs index e04bc8e5..493ebfa6 100644 --- a/winit/src/platform_specific/wayland/event_loop/mod.rs +++ b/winit/src/platform_specific/wayland/event_loop/mod.rs @@ -41,7 +41,7 @@ use cctk::{ toplevel_management::ToplevelManagerState, }; use raw_window_handle::HasDisplayHandle; -use state::{FrameStatus, SctkWindow}; +use state::{FrameStatus, SctkWindow, send_event}; #[cfg(feature = "a11y")] use std::sync::{Arc, Mutex}; use std::{ @@ -50,7 +50,7 @@ use std::{ }; use tracing::error; use wayland_backend::client::Backend; -use winit::event_loop::OwnedDisplayHandle; +use winit::{dpi::LogicalSize, event_loop::OwnedDisplayHandle}; use self::state::SctkState; @@ -120,7 +120,36 @@ impl SctkEventLoop { id, ) => { // TODO clean up popups matching the window. - state.windows.retain(|window| id != window.id); + if let Some(pos) = state + .windows + .iter() + .position(|window| id == window.id) + { + let w = state.windows.remove(pos); + for subsurface_id in state + .subsurfaces + .iter() + .enumerate() + .filter_map(|(i, s)| { + (winit::window::WindowId::from_raw( + s.instance.parent.as_ptr() + as usize, + ) == w.window.id()) + .then_some(i) + }) + .collect::>() + { + let s = state + .subsurfaces + .remove(subsurface_id); + crate::subsurface_widget::remove_iced_subsurface( + &s.instance.wl_surface, + ); + send_event(&state.events_sender, &state.proxy, + SctkEvent::SubsurfaceEvent( crate::sctk_event::SubsurfaceEventVariant::Destroyed(s.instance) ) + ); + } + } } crate::platform_specific::Action::SetCursor( icon, @@ -144,6 +173,57 @@ impl SctkEventLoop { crate::platform_specific::Action::Dropped(id) => { _ = state.destroyed.remove(&id.inner()); } + crate::platform_specific::Action::SubsurfaceResize(id, size) => { + // reposition the surface + if let Some(pos) = state + .subsurfaces + .iter() + .position(|window| id == window.id) + { + let subsurface = &mut state.subsurfaces[pos]; + let settings = &subsurface.settings; + let mut loc = settings.loc; + let guard = subsurface.common.lock().unwrap(); + let size: LogicalSize = size.to_logical(guard.fractional_scale.unwrap_or(1.)); + let half_w = size.width / 2.; + let half_h = size.height / 2.; + match settings.gravity { + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::None => { + // center on + loc.x -= half_w; + loc.y -= half_h; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Top => { + loc.x -= half_w; + loc.y -= size.height; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Bottom => { + loc.x -= half_w; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Left => { + loc.y -= half_h; + loc.x -= size.width; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Right => { + loc.y -= half_h; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft => { + loc.y -= size.height; + loc.x -= size.width; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft => { + loc.x -= size.width; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight => { + loc.y -= size.height; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight => {}, + _ => unimplemented!(), + }; + subsurface.instance.wl_subsurface.set_position(loc.x as i32, loc.y as i32); + + } + send_event(&state.events_sender, &state.proxy, SctkEvent::SubsurfaceEvent(crate::sctk_event::SubsurfaceEventVariant::Resized(id, size)))}, }, calloop::channel::Event::Closed => { log::info!("Calloop channel closed."); @@ -167,7 +247,7 @@ impl SctkEventLoop { let (viewporter_state, fractional_scaling_manager) = match FractionalScalingManager::new(&globals, &qh) { Ok(m) => { - let viewporter_state = + let viewporter_state: Option = match ViewporterState::new(&globals, &qh) { Ok(s) => Some(s), Err(e) => { @@ -227,6 +307,7 @@ impl SctkEventLoop { layer_surfaces: Vec::new(), popups: Vec::new(), lock_surfaces: Vec::new(), + subsurfaces: Vec::new(), _kbd_focus: None, touch_points: HashMap::new(), sctk_events: Vec::new(), @@ -243,6 +324,7 @@ impl SctkEventLoop { activation_token_ctr: 0, token_senders: HashMap::new(), overlap_notifications: HashMap::new(), + subsurface_state: None, }, _features: Default::default(), }; @@ -280,20 +362,23 @@ impl SctkEventLoop { if let (Ok(wl_subcompositor), Ok(wp_viewporter)) = (wl_subcompositor, wp_viewporter) { + let subsurface_state = SubsurfaceState { + wl_compositor, + wl_subcompositor, + wp_viewporter, + wl_shm, + wp_dmabuf, + wp_alpha_modifier, + qh: state.state.queue_handle.clone(), + buffers: HashMap::new(), + unmapped_subsurfaces: Vec::new(), + new_iced_subsurfaces: Vec::new(), + }; + state.state.subsurface_state = Some(subsurface_state.clone()); state::send_event( &state.state.events_sender, &state.state.proxy, - SctkEvent::Subcompositor(SubsurfaceState { - wl_compositor, - wl_subcompositor, - wp_viewporter, - wl_shm, - wp_dmabuf, - wp_alpha_modifier, - qh: state.state.queue_handle.clone(), - buffers: HashMap::new(), - unmapped_subsurfaces: Vec::new(), - }), + SctkEvent::Subcompositor(subsurface_state), ); } else { log::warn!("Subsurfaces not supported.") diff --git a/winit/src/platform_specific/wayland/event_loop/proxy.rs b/winit/src/platform_specific/wayland/event_loop/proxy.rs index 44c0949e..bdbc44a4 100644 --- a/winit/src/platform_specific/wayland/event_loop/proxy.rs +++ b/winit/src/platform_specific/wayland/event_loop/proxy.rs @@ -1,9 +1,9 @@ +use cctk::sctk::reexports::calloop; use iced_futures::futures::{ channel::mpsc, task::{Context, Poll}, Sink, }; -use cctk::sctk::reexports::calloop; use std::pin::Pin; /// An event loop proxy that implements `Sink`. diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index cb14d994..2ed33e71 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -1,5 +1,8 @@ use crate::{ Control, + sctk_event::KeyboardEventVariant, + subsurface_widget::SubsurfaceState, + wayland::SubsurfaceInstance, handlers::{ activation::IcedRequestData, overlap::{OverlapNotificationV1, OverlapNotifyV1}, @@ -15,13 +18,17 @@ use crate::{ }, }, }; -use iced_futures::futures::channel::{mpsc, oneshot}; +use iced_futures::{ + core::{Rectangle, Size}, + futures::channel::{mpsc, oneshot}, +}; use raw_window_handle::HasWindowHandle; use std::{ collections::{HashMap, HashSet}, convert::Infallible, fmt::Debug, - sync::{Arc, Mutex, atomic::AtomicU32}, + sync::{atomic::AtomicU32, Arc, Mutex}, + thread::panicking, time::Duration, }; use wayland_backend::client::ObjectId; @@ -36,9 +43,7 @@ use iced_runtime::{ platform_specific::{ self, wayland::{ - Action, - layer_surface::{IcedMargin, IcedOutput, SctkLayerSurfaceSettings}, - popup::SctkPopupSettings, + layer_surface::{IcedMargin, IcedOutput, SctkLayerSurfaceSettings}, popup::SctkPopupSettings, subsurface::{self, SctkSubsurfaceSettings}, Action }, }, }; @@ -87,7 +92,6 @@ use cctk::{cosmic_protocols::overlap_notify::v1::client::zcosmic_overlap_notific }, shm::{multi::MultiPool, Shm}, }, toplevel_info::ToplevelInfoState, toplevel_management::ToplevelManagerState}; - use wayland_protocols::{ wp::{ fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1, @@ -183,6 +187,10 @@ pub enum CommonSurface { Popup(Popup, Arc), Layer(LayerSurface), Lock(SessionLockSurface), + Subsurface { + wl_surface: WlSurface, + wl_subsurface: WlSubsurface, + }, } impl CommonSurface { @@ -193,6 +201,7 @@ impl CommonSurface { CommonSurface::Lock(session_lock_surface) => { session_lock_surface.wl_surface() } + CommonSurface::Subsurface { wl_surface, .. } => wl_surface, }; wl_surface } @@ -241,6 +250,7 @@ pub struct SctkPopup { pub(crate) data: SctkPopupData, pub(crate) common: Arc>, pub(crate) wp_fractional_scale: Option, + pub(crate) close_with_children: bool, } impl SctkPopup { @@ -249,10 +259,19 @@ impl SctkPopup { self.popup .xdg_surface() .set_window_geometry(0, 0, w as i32, h as i32); + self.update_viewport(w, h); // update positioner self.data.positioner.set_size(w as i32, h as i32); self.popup.reposition(&self.data.positioner, token); } + + pub(crate) fn update_viewport(&mut self, w: u32, h: u32) { + let common = self.common.lock().unwrap(); + if let Some(viewport) = common.wp_viewport.as_ref() { + // Set inner size without the borders. + viewport.set_destination(w as i32, h as i32); + } + } } #[derive(Debug)] @@ -261,8 +280,27 @@ pub struct SctkLockSurface { pub(crate) session_lock_surface: SessionLockSurface, pub(crate) last_configure: Option, pub(crate) wp_fractional_scale: Option, - pub(crate) wp_viewport: Option, pub(crate) common: Arc>, + pub(crate) output: WlOutput, +} +impl SctkLockSurface { + pub(crate) fn update_viewport(&mut self, w: u32, h: u32) { + let mut common = self.common.lock().unwrap(); + + common.size = LogicalSize::new(w, h); + if let Some(viewport) = common.wp_viewport.as_ref() { + // Set inner size without the borders. + viewport.set_destination(w as i32, h as i32); + } + } +} +#[derive(Debug)] +pub struct SctkSubsurface { + pub(crate) common: Arc>, + pub(crate) steals_keyboard_focus: bool, + pub(crate) id: core::window::Id, + pub(crate) instance: SubsurfaceInstance, + pub(crate) settings: SctkSubsurfaceSettings, } #[derive(Debug)] @@ -271,6 +309,7 @@ pub struct SctkPopupData { pub(crate) parent: PopupParent, pub(crate) toplevel: WlSurface, pub(crate) positioner: Arc, + pub(crate) grab: bool, } pub struct SctkWindow { @@ -347,6 +386,7 @@ pub struct SctkState { pub(crate) windows: Vec, pub(crate) layer_surfaces: Vec, pub(crate) popups: Vec, + pub(crate) subsurfaces: Vec, pub(crate) lock_surfaces: Vec, pub(crate) _kbd_focus: Option, pub(crate) touch_points: HashMap, @@ -390,6 +430,7 @@ pub struct SctkState { pub(crate) overlap_notify: Option, pub(crate) toplevel_info: Option, pub(crate) toplevel_manager: Option, + pub(crate) subsurface_state: Option, pub(crate) activation_token_ctr: u32, pub(crate) token_senders: HashMap>>, @@ -431,6 +472,22 @@ pub enum LayerSurfaceCreationError { LayerSurfaceCreationFailed(GlobalError), } +/// An error that occurred while running an application. +#[derive(Debug, thiserror::Error)] +pub enum SubsurfaceCreationError { + /// Subsurface creation failed + #[error("Subsurface creation failed")] + CreationFailed(GlobalError), + + /// The specified parent is missing + #[error("The specified parent is missing")] + ParentMissing, + + /// Subsurfaces are unsupported + #[error("Subsurfaces are unsupported")] + Unsupported, +} + pub(crate) fn receive_frame( frame_status: &mut HashMap, s: &WlSurface, @@ -460,6 +517,18 @@ impl SctkState { ) { let mut id = None; + for subsurface in &self.subsurfaces { + if subsurface.instance.parent != surface.id() { + continue; + } + + self.sctk_events.push(SctkEvent::SurfaceScaleFactorChanged( + scale_factor, + surface.clone(), + subsurface.id, + )); + } + if let Some(popup) = self .popups .iter_mut() @@ -688,6 +757,19 @@ impl SctkState { log::error!("Can't take grab on popup. Missing serial."); } } + + if let Some(z) = settings.input_zone { + let region = self + .compositor_state + .wl_compositor() + .create_region(&self.queue_handle, ()); + region.add( + z.x.round() as i32, + z.y.round() as i32, + z.width.round() as i32, + z.height.round() as i32, + ); + } popup.xdg_surface().set_window_geometry( 0, 0, @@ -719,11 +801,13 @@ impl SctkState { parent: parent.clone(), toplevel: toplevel.clone(), positioner: positioner.clone(), + grab: settings.grab, }, last_configure: None, _pending_requests: Default::default(), wp_fractional_scale, common: common.clone(), + close_with_children: settings.close_with_children, }); Ok(( @@ -863,15 +947,16 @@ impl SctkState { self.fractional_scaling_manager.as_ref().map(|fsm| { fsm.fractional_scaling(&wl_surface, &self.queue_handle) }); - let common = - Arc::new(Mutex::new(Common::from(LogicalSize::new(1, 1)))); + let mut common = Common::from(LogicalSize::new(1, 1)); + common.wp_viewport = wp_viewport; + let common = Arc::new(Mutex::new(common)); self.lock_surfaces.push(SctkLockSurface { id, session_lock_surface: session_lock_surface.clone(), last_configure: None, wp_fractional_scale, - wp_viewport, common: common.clone(), + output: output.clone(), }); Some((CommonSurface::Lock(session_lock_surface), common)) } else { @@ -920,6 +1005,25 @@ impl SctkState { platform_specific::wayland::layer_surface::Action::Destroy(id) => { if let Some(i) = self.layer_surfaces.iter().position(|l| l.id == id) { let l = self.layer_surfaces.remove(i); + + let (removed, remaining): (Vec<_>, Vec<_>) = self + .subsurfaces + .drain(..) + .partition(|s| { + s.instance.parent == l.surface.wl_surface().id() + }); + + self.subsurfaces = remaining; + for s in removed + { + crate::subsurface_widget::remove_iced_subsurface( + &s.instance.wl_surface, + ); + send_event(&self.events_sender, &self.proxy, + SctkEvent::SubsurfaceEvent( crate::sctk_event::SubsurfaceEventVariant::Destroyed(s.instance) ) + ); + } + if let Some(destroyed) = self.id_map.remove(&l.surface.wl_surface().id()) { _ = self.destroyed.insert(destroyed); } @@ -976,28 +1080,77 @@ impl SctkState { }, }, Action::Popup(action) => match action { - platform_specific::wayland::popup::Action::Popup { popup, .. } => { - let parent_mismatch = self.popups.last().is_some_and(|p| { - self.id_map.get(&p.popup.wl_surface().id()).map_or(true, |p| *p != popup.parent) + platform_specific::wayland::popup::Action::Popup { popup: settings } => { + // first check existing popup + if let Some(existing) = self.popups.iter().position(|p| p.data.id == settings.id + && ( + self.popups.iter().any(|parent| parent.popup.wl_surface() == p.data.parent.wl_surface() && parent.data.id == settings.parent) + || self.windows.iter().any(|w| w.id == settings.parent && *p.data.parent.wl_surface() == w.wl_surface(&self.connection)) + || self.layer_surfaces.iter().any(|l| l.id == settings.parent && p.data.parent.wl_surface() == l.surface.wl_surface())) + ) { + let existing = &mut self.popups[existing]; + let size = if settings.positioner.size.is_none() { + log::info!("No configured popup size"); + (1, 1) + } else { + settings.positioner.size.unwrap() + }; + let Ok(positioner) = XdgPositioner::new(&self.xdg_shell_state) + .map_err(PopupCreationError::PositionerCreationFailed) else { + log::error!("Failed to create popup positioner"); + return Ok(()); + }; + 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, + ); + if let Ok(constraint_adjustment) = + settings.positioner.constraint_adjustment.try_into() + { + positioner.set_constraint_adjustment(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(size.0 as i32, size.1 as i32); + existing.data.positioner = Arc::new(positioner); + existing.set_size(size.0, size.1, TOKEN_CTR.fetch_add(1, std::sync::atomic::Ordering::Relaxed)); + _ = send_event(&self.events_sender, &self.proxy, + SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Size(size.0, size.1), toplevel_id: existing.data.parent.wl_surface().clone(), parent_id: existing.data.parent.wl_surface().clone(), id: existing.popup.wl_surface().clone() }); + return Ok(()); + } + let parent_mismatch = self.popups.iter().rev().find(|p| { + self.id_map.get(&p.popup.wl_surface().id()).map_or(true, |p_id|{ + *p_id != settings.parent && p.data.grab && settings.grab}) }); - if !self.destroyed.is_empty() || parent_mismatch { - if parent_mismatch { + if !self.destroyed.is_empty() || parent_mismatch.is_some() { + if parent_mismatch.is_some() { for i in 0..self.popups.len() { let id = self.id_map.get(&self.popups[i].popup.wl_surface().id()); if let Some(id) = id { - if *id != popup.parent { + if *id != settings.parent { _ = self.handle_action(Action::Popup(platform_specific::wayland::popup::Action::Destroy{id: *id})); } } } } - if self.pending_popup.replace((popup, 0)).is_none() { + if self.pending_popup.replace((settings, 0)).is_none() { + let timer = cctk::sctk::reexports::calloop::timer::Timer::from_duration(Duration::from_millis(30)); let queue_handle = self.queue_handle.clone(); _ = self.loop_handle.insert_source(timer, move |_, _, state| { - let Some((popup, attempt)) = state.pending_popup.take() else { + let Some((mut popup, attempt)) = state.pending_popup.take() else { return TimeoutAction::Drop; }; + if !state.destroyed.is_empty() || state.popups.last().is_some_and(|p| { state.id_map.get(&p.popup.wl_surface().id()).map_or(true, |p| *p != popup.parent) }) { @@ -1029,7 +1182,7 @@ impl SctkState { // log::error!("Invalid popup Id {:?}", popup.id); } else { self.pending_popup = None; - match self.get_popup(popup) { + match self.get_popup(settings) { Ok((id, parent_id, toplevel_id, surface, common)) => { let wl_surface = surface.wl_surface().clone(); receive_frame(&mut self.frame_status, &wl_surface); @@ -1054,31 +1207,51 @@ impl SctkState { { Some(p) => self.popups.remove(p), None => { - log::warn!("No popup to destroy"); + log::info!("No popup to destroy"); return Ok(()); }, }; let mut to_destroy = vec![sctk_popup]; - while let Some(popup_to_destroy) = to_destroy.last() { - match popup_to_destroy.data.parent.clone() { - PopupParent::LayerSurface(_) | PopupParent::Window(_) => { - break; - } - PopupParent::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); - } - } + // TODO optionally destroy parents if they request to be destroyed with children + while let Some(popup_to_destroy_last) = to_destroy.last().and_then(|popup| self + .popups + .iter() + .position(|p| popup.data.parent.wl_surface() == p.popup.wl_surface() && p.close_with_children)) { + let popup_to_destroy_last = self.popups.remove(popup_to_destroy_last); + to_destroy.push(popup_to_destroy_last); + } + to_destroy.reverse(); + + while let Some(popup_to_destroy_first) = to_destroy.last().and_then(|popup| self + .popups + .iter() + .position(|p| p.data.parent.wl_surface() == popup.popup.wl_surface())) { + 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() { if let Some(id) = self.id_map.remove(&popup.popup.wl_surface().id()) { _ = self.destroyed.insert(id); } + + let (removed, remaining): (Vec<_>, Vec<_>) = self + .subsurfaces + .drain(..) + .partition(|s| { + s.instance.parent == popup.popup.wl_surface().id() + }); + + self.subsurfaces = remaining; + for s in removed + { + + crate::subsurface_widget::remove_iced_subsurface( + &s.instance.wl_surface, + ); + send_event(&self.events_sender, &self.proxy, + SctkEvent::SubsurfaceEvent( crate::sctk_event::SubsurfaceEventVariant::Destroyed(s.instance) ) + ); + } _ = send_event(&self.events_sender, &self.proxy, SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Done, toplevel_id: popup.data.toplevel.clone(), parent_id: popup.data.parent.wl_surface().clone(), id: popup.popup.wl_surface().clone() }); } @@ -1165,11 +1338,15 @@ impl SctkState { send_event(&self.events_sender, &self.proxy, SctkEvent::SessionUnlocked); } platform_specific::wayland::session_lock::Action::LockSurface { id, output } => { + // Should we panic if the id does not match? + if self.lock_surfaces.iter().any(|s| s.output == output) { + tracing::warn!("Cannot create multiple lock surfaces for a single output."); + return Ok(()); + } // TODO how to handle this when there's no lock? - if let Some((surface, common)) = self.get_lock_surface(id, &output) { + if let Some((surface, _)) = self.get_lock_surface(id, &output) { let wl_surface = surface.wl_surface(); receive_frame(&mut self.frame_status, &wl_surface); - send_event(&self.events_sender, &self.proxy, SctkEvent::SessionLockSurfaceCreated { queue_handle: self.queue_handle.clone(), surface, native_id: id, common, display: self.connection.display() }); } } platform_specific::wayland::session_lock::Action::DestroyLockSurface { id } => { @@ -1179,6 +1356,24 @@ impl SctkState { }) { let surface = self.lock_surfaces.remove(i); + let (removed, remaining): (Vec<_>, Vec<_>) = self + .subsurfaces + .drain(..) + .partition(|s| { + s.instance.parent == surface.session_lock_surface.wl_surface().id() + }); + + self.subsurfaces = remaining; + for s in removed + { + + crate::subsurface_widget::remove_iced_subsurface( + &s.instance.wl_surface, + ); + send_event(&self.events_sender, &self.proxy, + SctkEvent::SubsurfaceEvent( crate::sctk_event::SubsurfaceEventVariant::Destroyed(s.instance) ) + ); + } if let Some(id) = self.id_map.remove(&surface.session_lock_surface.wl_surface().id()) { _ = self.destroyed.insert(id); } @@ -1208,9 +1403,254 @@ impl SctkState { tracing::error!("Overlap notify subscription cannot be created for surface. No matching layer surface found."); } }, + Action::Subsurface(action) => match action { + platform_specific::wayland::subsurface::Action::Subsurface { subsurface: subsurface_settings } => { + let parent_id = subsurface_settings.parent; + if let Ok((_, parent, subsurface, common_surface, common)) = self.get_subsurface(subsurface_settings.clone()) { + // TODO Ashley: all surfaces should probably have an optional title for a11y if nothing else + receive_frame(&mut self.frame_status, &subsurface); + send_event(&self.events_sender, &self.proxy, + SctkEvent::SubsurfaceEvent (crate::sctk_event::SubsurfaceEventVariant::Created{ + parent_id, + parent, + surface: subsurface, + qh: self.queue_handle.clone(), + common_surface, + surface_id: subsurface_settings.id, + common, + display: self.connection.display(), + z: subsurface_settings.z, + }) + ); + } + }, + platform_specific::wayland::subsurface::Action::Destroy { id } => { + let mut destroyed = vec![]; + if let Some(subsurface) = self.subsurfaces.iter().position(|s| s.id == id) { + let subsurface = self.subsurfaces.remove(subsurface); + destroyed.push((subsurface.instance.wl_surface.clone(), subsurface.instance.parent.clone())); + + subsurface.instance.wl_surface.attach(None, 0, 0); + subsurface.instance.wl_surface.commit(); + send_event(&self.events_sender, &self.proxy, + SctkEvent::SubsurfaceEvent( crate::sctk_event::SubsurfaceEventVariant::Destroyed(subsurface.instance) ) + ); + } + for (destroyed, parent) in destroyed { + if let Some((wl_surface, f)) = self.seats.iter_mut().find(|f| { + f.kbd_focus.as_ref().is_some_and(|f| *f == destroyed) + }).and_then(|f| WlSurface::from_id(&self.connection, parent).ok().map(|wl| (wl, &mut f.kbd_focus))) { + *f = Some(wl_surface); + } + } + }, + }, }; Ok(()) } + + pub fn get_subsurface( + &mut self, + settings: SctkSubsurfaceSettings, + ) -> Result< + ( + core::window::Id, + WlSurface, + WlSurface, + CommonSurface, + Arc>, + ), + SubsurfaceCreationError, + > { + let Some(subsurface_state) = self.subsurface_state.as_ref() else { + return Err(SubsurfaceCreationError::Unsupported); + }; + + let size = settings.size.unwrap_or(Size::new(1., 1.)); + let half_w = size.width / 2.; + let half_h = size.height / 2.; + + let mut loc = settings.loc; + match settings.gravity { + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::None => { + // center on + loc.x -= half_w; + loc.y -= half_h; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Top => { + loc.x -= half_w; + loc.y -= size.height; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Bottom => { + loc.x -= half_w; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Left => { + loc.y -= half_h; + loc.x -= size.width; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Right => { + loc.y -= half_h; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft => { + loc.y -= size.height; + loc.x -= size.width; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft => { + loc.x -= size.width; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight => { + loc.y -= size.height; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight => {}, + _ => unimplemented!(), + }; + let bounds = Rectangle::new(loc, size); + + let parent = if let Some(parent) = + self.layer_surfaces.iter().find(|l| l.id == settings.parent) + { + PopupParent::LayerSurface(parent.surface.wl_surface().clone()) + } else if let Some(parent) = + self.windows.iter().find(|w| w.id == settings.parent) + { + PopupParent::Window(parent.wl_surface(&self.connection)) + } else if let Some(i) = self + .popups + .iter() + .position(|p| p.data.id == settings.parent) + { + let parent = &self.popups[i]; + PopupParent::Popup(parent.popup.wl_surface().clone()) + } else if let Some(i) = self + .lock_surfaces + .iter() + .position(|p| p.id == settings.parent) + { + let parent = &self.lock_surfaces[i]; + PopupParent::Popup(parent.session_lock_surface.wl_surface().clone()) + } else { + return Err(SubsurfaceCreationError::ParentMissing); + }; + + let wl_surface = + self.compositor_state.create_surface(&self.queue_handle); + _ = self.id_map.insert(wl_surface.id(), settings.id.clone()); + + for s in self.seats.iter_mut() { + if s.kbd_focus + .as_ref() + .is_some_and(|f| f == parent.wl_surface()) + { + s.kbd_focus = Some(wl_surface.clone()); + } + } + let parent_wl_surface = parent.wl_surface(); + let wl_subsurface = subsurface_state.wl_subcompositor.get_subsurface( + &wl_surface, + parent_wl_surface, + &self.queue_handle, + (), + ); + wl_subsurface.set_position(bounds.x as i32, bounds.y as i32); + _ = wl_surface.frame(&self.queue_handle, wl_surface.clone()); + if let Some(zone) = settings.input_zone { + let region = self + .compositor_state + .wl_compositor() + .create_region(&self.queue_handle, ()); + region.add( + zone.x.round() as i32, + zone.y.round() as i32, + zone.width.round() as i32, + zone.height.round() as i32, + ); + wl_surface.set_input_region(Some(®ion)); + region.destroy(); + } + + wl_surface.commit(); + + let wp_viewport = subsurface_state.wp_viewporter.get_viewport( + &wl_surface, + &self.queue_handle, + cctk::sctk::globals::GlobalData, + ); + + let wp_alpha_modifier_surface = subsurface_state + .wp_alpha_modifier + .as_ref() + .map(|wp_alpha_modifier| { + wp_alpha_modifier.get_surface( + &wl_surface, + &self.queue_handle, + (), + ) + }); + wp_viewport.set_destination(size.width as i32, size.height as i32); + + let mut common: Common = + LogicalSize::new(size.width as u32, size.height as u32).into(); + let instance = SubsurfaceInstance { + wl_surface: wl_surface.clone(), + wl_subsurface: wl_subsurface.clone(), + wp_viewport: wp_viewport.clone(), + wp_alpha_modifier_surface: wp_alpha_modifier_surface, + + wl_buffer: None, + bounds: Some(bounds), + transform: + cctk::wayland_client::protocol::wl_output::Transform::Normal, + z: settings.z, + parent: parent_wl_surface.id(), + }; + common.wp_viewport = Some(wp_viewport); + let common = Arc::new(Mutex::new(common)); + + for focus in &mut self.seats { + if focus + .kbd_focus + .as_ref() + .is_some_and(|s| s == parent_wl_surface) + { + let id = winit::window::WindowId::from_raw( + wl_surface.id().as_ptr() as usize, + ); + self.sctk_events.push(SctkEvent::Winit( + id, + winit::event::WindowEvent::Focused(true), + )); + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Enter(wl_surface.clone()), + kbd_id: focus.kbd.clone().unwrap(), + seat_id: focus.seat.clone(), + surface: wl_surface.clone(), + }); + focus.kbd_focus = Some(wl_surface.clone()); + } + } + let id = settings.id; + self.subsurfaces.push(SctkSubsurface { + common: common.clone(), + steals_keyboard_focus: settings.steal_keyboard_focus, + id: settings.id, + instance, + settings, + }); + // XXX subsurfaces need to be sorted by z in descending order + self.subsurfaces + .sort_by(|a, b| b.instance.z.cmp(&a.instance.z)); + + Ok(( + id, + parent.wl_surface().clone(), + wl_surface.clone(), + CommonSurface::Subsurface { + wl_surface, + wl_subsurface, + }, + common, + )) + } } pub(crate) fn send_event( diff --git a/winit/src/platform_specific/wayland/handlers/activation.rs b/winit/src/platform_specific/wayland/handlers/activation.rs index 26cfbeb8..6b03a8dd 100644 --- a/winit/src/platform_specific/wayland/handlers/activation.rs +++ b/winit/src/platform_specific/wayland/handlers/activation.rs @@ -1,9 +1,9 @@ -use iced_futures::futures::channel::oneshot::Sender; use cctk::sctk::{ activation::{ActivationHandler, RequestData, RequestDataExt}, delegate_activation, reexports::client::protocol::{wl_seat::WlSeat, wl_surface::WlSurface}, }; +use iced_futures::futures::channel::oneshot::Sender; use crate::platform_specific::wayland::event_loop::state::SctkState; diff --git a/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs b/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs index 7b12661f..89636011 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs @@ -19,7 +19,6 @@ impl KeyboardHandler for SctkState { _raw: &[u32], _keysyms: &[Keysym], ) { - self.request_redraw(surface); let (i, mut is_active, seat) = { let (i, is_active, my_seat) = match self.seats.iter_mut().enumerate().find_map(|(i, s)| { @@ -32,36 +31,49 @@ impl KeyboardHandler for SctkState { Some((i, s)) => (i, i == 0, s), None => return, }; + + let surface = if let Some(subsurface) = + self.subsurfaces.iter().find(|s| { + s.steals_keyboard_focus && s.instance.parent == surface.id() + }) { + &subsurface.instance.wl_surface + } else { + surface + }; _ = 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); } + self.request_redraw(&surface); - if is_active { - let id = winit::window::WindowId::from_raw( - surface.id().as_ptr() as usize - ); - if self.windows.iter().any(|w| w.window.id() == id) { - return; + let surfaces = self.subsurfaces.iter().filter_map(|s| { + (s.instance.parent == surface.id()).then(|| &s.instance.wl_surface) + }); + for surface in surfaces.chain(std::iter::once(surface)) { + if is_active { + let id = winit::window::WindowId::from_raw( + surface.id().as_ptr() as usize, + ); + if self.windows.iter().any(|w| w.window.id() == id) { + continue; + } + self.sctk_events.push(SctkEvent::Winit( + id, + winit::event::WindowEvent::Focused(true), + )); + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Enter(surface.clone()), + kbd_id: keyboard.clone(), + seat_id: seat.clone(), + surface: surface.clone(), + }); } - self.sctk_events.push(SctkEvent::Winit( - id, - winit::event::WindowEvent::Focused(true), - )); - self.sctk_events.push(SctkEvent::KeyboardEvent { - variant: KeyboardEventVariant::Enter(surface.clone()), - kbd_id: keyboard.clone(), - seat_id: seat, - surface: surface.clone(), - }); } } @@ -91,38 +103,42 @@ impl KeyboardHandler for SctkState { _ = 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, - surface: surface.clone(), - }); - // 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]; - let id = winit::window::WindowId::from_raw( - surface.id().as_ptr() as usize, - ); - if self.windows.iter().any(|w| w.window.id() == id) { - return; - } - self.sctk_events.push(SctkEvent::Winit( - id, - winit::event::WindowEvent::Focused(true), - )); + let surfaces = self.subsurfaces.iter().filter_map(|s| { + (s.instance.parent == surface.id()).then(|| &s.instance.wl_surface) + }); + for surface in surfaces.chain(std::iter::once(surface)) { + if is_active { 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(), + variant: KeyboardEventVariant::Leave(surface.clone()), + kbd_id: kbd.clone(), + seat_id: seat.clone(), surface: surface.clone(), - }) + }); + // 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]; + let id = winit::window::WindowId::from_raw( + surface.id().as_ptr() as usize, + ); + if self.windows.iter().any(|w| w.window.id() == id) { + continue; + } + self.sctk_events.push(SctkEvent::Winit( + id, + winit::event::WindowEvent::Focused(true), + )); + 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(), + surface: surface.clone(), + }) + } } } } @@ -150,35 +166,20 @@ impl KeyboardHandler for SctkState { let kbd_id = keyboard.clone(); _ = my_seat.last_kbd_press.replace((event.clone(), serial)); if is_active { - // FIXME can't create winit key events because of private field - // if let Some(id) = id { - // let physical_key = raw_keycode_to_physicalkey(event.raw_code); - // let (logical_key, location) = - // keysym_to_vkey_location(event.keysym); - // self.sctk_events.push(SctkEvent::Winit( - // id, - // winit::event::WindowEvent::KeyboardInput { - // device_id: Default::default(), - // event: winit::event::KeyEvent { - // physical_key, - // logical_key, - // text: event.utf8.map(|s| s.into()), - // location, - // state: winit::event::ElementState::Pressed, - // repeat: false, // TODO we don't have this info... - // }, - // is_synthetic: false, - // }, - // )) - // } if let Some(surface) = my_seat.kbd_focus.clone() { self.request_redraw(&surface); - self.sctk_events.push(SctkEvent::KeyboardEvent { - variant: KeyboardEventVariant::Press(event), - kbd_id, - seat_id, - surface, + let surfaces = self.subsurfaces.iter().filter_map(|s| { + (s.instance.parent == surface.id()) + .then(|| &s.instance.wl_surface) }); + for surface in surfaces.chain(std::iter::once(&surface)) { + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Press(event.clone()), + kbd_id: kbd_id.clone(), + seat_id: seat_id.clone(), + surface: surface.clone(), + }); + } } } } @@ -208,12 +209,18 @@ impl KeyboardHandler for SctkState { if is_active { if let Some(surface) = my_seat.kbd_focus.clone() { self.request_redraw(&surface); - self.sctk_events.push(SctkEvent::KeyboardEvent { - variant: KeyboardEventVariant::Release(event), - kbd_id, - seat_id, - surface, + let surfaces = self.subsurfaces.iter().filter_map(|s| { + (s.instance.parent == surface.id()) + .then(|| &s.instance.wl_surface) }); + for surface in surfaces.chain(std::iter::once(&surface)) { + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Release(event.clone()), + kbd_id: kbd_id.clone(), + seat_id: seat_id.clone(), + surface: surface.clone(), + }); + } } } } @@ -244,12 +251,20 @@ impl KeyboardHandler for SctkState { if is_active { if let Some(surface) = my_seat.kbd_focus.clone() { self.request_redraw(&surface); - self.sctk_events.push(SctkEvent::KeyboardEvent { - variant: KeyboardEventVariant::Modifiers(modifiers), - kbd_id, - seat_id, - surface, + let surfaces = self.subsurfaces.iter().filter_map(|s| { + (s.instance.parent == surface.id()) + .then(|| &s.instance.wl_surface) }); + for surface in surfaces.chain(std::iter::once(&surface)) { + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Modifiers( + modifiers.clone(), + ), + kbd_id: kbd_id.clone(), + seat_id: seat_id.clone(), + surface: surface.clone(), + }); + } } } } diff --git a/winit/src/platform_specific/wayland/handlers/seat/pointer.rs b/winit/src/platform_specific/wayland/handlers/seat/pointer.rs index 3cace7bc..3f6ae87a 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/pointer.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/pointer.rs @@ -11,6 +11,7 @@ use cctk::sctk::{ CursorIcon, PointerEvent, PointerEventKind, PointerHandler, }, }; +use iced_futures::core::Point; use winit::{ dpi::PhysicalPosition, event::{ @@ -56,6 +57,7 @@ impl PointerHandler for SctkState { if self.windows.iter().any(|w| w.window.id() == id) { continue; } + let entry = self .frame_status .entry(e.surface.id()) @@ -63,6 +65,7 @@ impl PointerHandler for SctkState { if matches!(entry, FrameStatus::Received) { *entry = FrameStatus::Ready; } + if let PointerEventKind::Motion { time } = &e.kind { self.sctk_events.push(SctkEvent::PointerEvent { variant: PointerEvent { diff --git a/winit/src/platform_specific/wayland/handlers/seat/touch.rs b/winit/src/platform_specific/wayland/handlers/seat/touch.rs index 0ac17a50..f726fd93 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/touch.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/touch.rs @@ -6,7 +6,6 @@ use crate::{ event_loop::state::SctkState, sctk_event::SctkEvent, }, }; -use iced_runtime::core::{touch, Point}; use cctk::sctk::{ delegate_touch, reexports::client::{ @@ -15,6 +14,7 @@ use cctk::sctk::{ }, seat::touch::TouchHandler, }; +use iced_runtime::core::{touch, Point}; impl TouchHandler for SctkState { fn down( diff --git a/winit/src/platform_specific/wayland/handlers/session_lock.rs b/winit/src/platform_specific/wayland/handlers/session_lock.rs index 04945b3b..d0eee5ef 100644 --- a/winit/src/platform_specific/wayland/handlers/session_lock.rs +++ b/winit/src/platform_specific/wayland/handlers/session_lock.rs @@ -1,5 +1,6 @@ -use crate::platform_specific::wayland::{ - handlers::SctkState, sctk_event::SctkEvent, +use crate::{ + event_loop::state::CommonSurface, + platform_specific::wayland::{handlers::SctkState, sctk_event::SctkEvent}, }; use cctk::sctk::{ delegate_session_lock, @@ -15,8 +16,9 @@ impl SessionLockHandler for SctkState { &mut self, _conn: &Connection, _qh: &QueueHandle, - _session_lock: SessionLock, + session_lock: SessionLock, ) { + self.session_lock = Some(session_lock); self.sctk_events.push(SctkEvent::SessionLocked); } @@ -44,8 +46,21 @@ impl SessionLockHandler for SctkState { Some(l) => l, None => return, }; + lock_surface + .update_viewport(configure.new_size.0, configure.new_size.1); + let first = lock_surface.last_configure.is_none(); _ = lock_surface.last_configure.replace(configure.clone()); + + self.sctk_events.push(SctkEvent::SessionLockSurfaceCreated { + queue_handle: self.queue_handle.clone(), + surface: CommonSurface::Lock( + lock_surface.session_lock_surface.clone(), + ), + native_id: lock_surface.id, + common: lock_surface.common.clone(), + display: self.connection.display(), + }); self.sctk_events .push(SctkEvent::SessionLockSurfaceConfigure { surface: session_lock_surface.wl_surface().clone(), diff --git a/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs b/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs index c48c6bce..2e7a58c1 100644 --- a/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs +++ b/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs @@ -6,6 +6,7 @@ use cctk::sctk::{ delegate_xdg_popup, reexports::client::Proxy, shell::xdg::popup::PopupHandler, }; +use winit::dpi::LogicalSize; impl PopupHandler for SctkState { fn configure( @@ -24,6 +25,9 @@ impl PopupHandler for SctkState { }; let first = sctk_popup.last_configure.is_none(); _ = sctk_popup.last_configure.replace(configure.clone()); + let mut guard = sctk_popup.common.lock().unwrap(); + guard.size = + LogicalSize::new(configure.width as u32, configure.height as u32); self.sctk_events.push(SctkEvent::PopupEvent { variant: PopupEventVariant::Configure( diff --git a/winit/src/platform_specific/wayland/handlers/toplevel.rs b/winit/src/platform_specific/wayland/handlers/toplevel.rs index 6ff1c3e0..62d18687 100644 --- a/winit/src/platform_specific/wayland/handlers/toplevel.rs +++ b/winit/src/platform_specific/wayland/handlers/toplevel.rs @@ -1,7 +1,5 @@ use cctk::{ - cosmic_protocols::{ - toplevel_management::v1::client::zcosmic_toplevel_manager_v1, - }, + cosmic_protocols::toplevel_management::v1::client::zcosmic_toplevel_manager_v1, toplevel_info::{ToplevelInfoHandler, ToplevelInfoState}, toplevel_management::ToplevelManagerHandler, wayland_client::{self, WEnum}, diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index 57c2f0ce..a925d82d 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -25,6 +25,7 @@ use std::{collections::HashMap, sync::Arc}; use subsurface_widget::{SubsurfaceInstance, SubsurfaceState}; use wayland_backend::client::ObjectId; use wayland_client::{Connection, Proxy}; +use winit::dpi::Size; use winit::event_loop::OwnedDisplayHandle; pub(crate) enum Action { @@ -34,6 +35,7 @@ pub(crate) enum Action { TrackWindow(Arc, window::Id), RemoveWindow(window::Id), Dropped(SurfaceIdWrapper), + SubsurfaceResize(window::Id, Size), } impl std::fmt::Debug for Action { @@ -53,6 +55,11 @@ impl std::fmt::Debug for Action { f.debug_tuple("RemoveWindow").field(arg0).finish() } Self::Dropped(_surface_id_wrapper) => write!(f, "Dropped"), + Self::SubsurfaceResize(id, size) => f + .debug_tuple("SubsurfaceResize") + .field(id) + .field(size) + .finish(), } } } diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index 95819aea..6b3cc6b8 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -68,8 +68,10 @@ use cctk::{ xdg::{popup::PopupConfigure, window::WindowConfigure}, }, }, + wayland_client::protocol::wl_subsurface::WlSubsurface, }; use std::{ + any::Any, collections::HashMap, num::NonZeroU32, sync::{Arc, Mutex}, @@ -79,14 +81,18 @@ use wayland_protocols::{ wp::viewporter::client::wp_viewport::WpViewport, }; use winit::{ - dpi::PhysicalSize, event::WindowEvent, event_loop::EventLoopProxy, + dpi::{self, PhysicalSize}, + event::WindowEvent, + event_loop::EventLoopProxy, window::WindowId, }; use xkeysym::Keysym; use super::{ + SubsurfaceInstance, event_loop::state::{Common, CommonSurface, SctkState}, keymap::raw_keycode_to_physicalkey, + subsurface_widget::remove_iced_subsurface, winit_window::SctkWinitWindow, }; @@ -160,6 +166,8 @@ pub enum SctkEvent { id: WlSurface, }, + SubsurfaceEvent(SubsurfaceEventVariant), + // // output events // @@ -273,6 +281,26 @@ pub enum PopupEventVariant { ScaleFactorChanged(f64, Option), } +#[derive(Debug, Clone)] +pub enum SubsurfaceEventVariant { + /// Popup Created + Created { + parent_id: window::Id, + parent: WlSurface, + surface: WlSurface, + qh: QueueHandle, + common_surface: CommonSurface, + surface_id: SurfaceId, + common: Arc>, + display: WlDisplay, + z: u32, + }, + /// Destroyed + Destroyed(SubsurfaceInstance), + /// Resized + Resized(SurfaceId, dpi::Size), +} + #[derive(Debug, Clone)] pub enum LayerSurfaceEventVariant { /// sent after creation of the layer surface @@ -485,6 +513,7 @@ impl SctkEvent { ), ), ), + SurfaceIdWrapper::Subsurface(id) => None, }) { events.push(( @@ -546,6 +575,7 @@ impl SctkEvent { ), ), ), + SurfaceIdWrapper::Subsurface(_) => None, } .map(|e| (Some(id.inner()), e)) }) @@ -568,7 +598,6 @@ impl SctkEvent { let physical_key = raw_keycode_to_physicalkey(ke.raw_code); let physical_key = crate::conversion::physical_key(physical_key); - events.push(( surface_ids.get(&surface.id()).map(|id| id.inner()), iced_runtime::core::Event::Keyboard( @@ -853,19 +882,19 @@ impl SctkEvent { events.push(( Some(id), iced_runtime::core::Event::Window( - window::Event::Resized( - w.state.logical_size(), - ), + window::Event::Opened { + size: w.state.logical_size(), + position: Default::default(), + }, ), )) } else { events.push(( Some(id), iced_runtime::core::Event::Window( - window::Event::Opened { - size: w.state.logical_size(), - position: Default::default(), - }, + window::Event::Resized( + w.state.logical_size(), + ), ), )) } @@ -1068,17 +1097,30 @@ impl SctkEvent { configure.width as f32, configure.height as f32, ); - if let Some(id) = - surface_ids.get(&surface.id()).map(|id| id.inner()) + + if let Some((id, w)) = + surface_ids.get(&surface.id()).and_then(|id| { + window_manager + .get_mut(id.inner()) + .map(|v| (id.inner(), v)) + }) { + let scale = w.state.scale_factor(); + let p_w = (configure.width.max(1) as f64 * scale) + .ceil() + as u32; + let p_h = (configure.height.max(1) as f64 * scale) + .ceil() + as u32; + + w.state.update( + program, + w.raw.as_ref(), + &WindowEvent::SurfaceResized( + PhysicalSize::new(p_w, p_h), + ), + ); if first { - events.push(( - Some(id), - iced_runtime::core::Event::Window( - window::Event::Resized(size), - ), - )) - } else { events.push(( Some(id), iced_runtime::core::Event::Window( @@ -1088,6 +1130,13 @@ impl SctkEvent { }, ), )) + } else { + events.push(( + Some(id), + iced_runtime::core::Event::Window( + window::Event::Resized(size), + ), + )) } } } // TODO @@ -1247,17 +1296,26 @@ impl SctkEvent { configure.new_size.0 as f32, configure.new_size.1 as f32, ); - if let Some(id) = - surface_ids.get(&surface.id()).map(|id| id.inner()) + if let Some((id, w)) = + surface_ids.get(&surface.id()).and_then(|id| { + window_manager + .get_mut(id.inner()) + .map(|v| (id.inner(), v)) + }) { + let scale = w.state.scale_factor(); + let p_w = (configure.new_size.0.max(1) as f64 * scale) + .round() as u32; + let p_h = (configure.new_size.1.max(1) as f64 * scale) + .round() as u32; + w.state.update( + program, + w.raw.as_ref(), + &WindowEvent::SurfaceResized(PhysicalSize::new( + p_w, p_h, + )), + ); if first { - events.push(( - Some(id), - iced_runtime::core::Event::Window( - window::Event::Resized(size), - ), - )) - } else { events.push(( Some(id), iced_runtime::core::Event::Window( @@ -1267,6 +1325,13 @@ impl SctkEvent { }, ), )) + } else { + events.push(( + Some(id), + iced_runtime::core::Event::Window( + window::Event::Resized(size), + ), + )) } } } @@ -1375,6 +1440,237 @@ impl SctkEvent { )) } } + SctkEvent::SubsurfaceEvent(variant) => match variant { + SubsurfaceEventVariant::Created { + parent_id, + common_surface, + common, + z, + parent, + surface: _, + qh, + surface_id, + display, + } => { + let CommonSurface::Subsurface { + wl_surface, + wl_subsurface, + } = &common_surface + else { + return; + }; + if let Some(subsurface_state) = subsurface_state.as_mut() { + subsurface_state.new_iced_subsurfaces.push(( + parent_id, + parent.id(), + surface_id, + wl_subsurface.clone(), + wl_surface.clone(), + z, + )); + } + + let wrapper = SurfaceIdWrapper::Popup(surface_id); + _ = surface_ids.insert(wl_surface.id(), wrapper.clone()); + let sctk_winit = SctkWinitWindow::new( + sctk_tx.clone(), + common, + wrapper, + common_surface, + display, + qh, + ); + // #[cfg(feature = "a11y")] + // { + // use crate::a11y::*; + // use iced_accessibility::accesskit::{ + // ActivationHandler, NodeBuilder, NodeId, Role, Tree, + // TreeUpdate, + // }; + // use iced_accessibility::accesskit_winit::Adapter; + + // let node_id = iced_runtime::core::id::window_node_id(); + + // let activation_handler = WinitActivationHandler { + // proxy: control_sender.clone(), + // title: String::new(), + // }; + + // let action_handler = WinitActionHandler { + // id: surface_id, + // proxy: control_sender.clone(), + // }; + + // let deactivation_handler = WinitDeactivationHandler { + // proxy: control_sender.clone(), + // }; + // _ = adapters.insert( + // surface_id, + // ( + // node_id, + // Adapter::with_direct_handlers( + // sctk_winit.as_ref(), + // activation_handler, + // action_handler, + // deactivation_handler, + // ), + // ), + // ); + // } + + if clipboard.window_id().is_none() { + *clipboard = Clipboard::connect( + sctk_winit.clone(), + crate::clipboard::ControlSender { + sender: control_sender.clone(), + proxy: proxy.clone(), + }, + ); + } + + let window = window_manager.insert( + surface_id, + sctk_winit, + program, + compositor, + false, // TODO do we want to get this value here? + theme::Mode::None, + ); + let logical_size = window.logical_size(); + + let mut ui = crate::build_user_interface( + program, + user_interface::Cache::default(), + &mut window.renderer, + logical_size, + surface_id, + window.raw.clone(), + window.prev_dnd_destination_rectangles_count, + clipboard, + ); + + _ = ui.update( + &vec![iced_runtime::core::Event::PlatformSpecific( + iced_runtime::core::event::PlatformSpecific::Wayland( + iced_runtime::core::event::wayland::Event::RequestResize, + ), + )], + window.state.cursor(), + &mut window.renderer, + clipboard, + &mut Vec::new(), + ); + + if let Some(requested_size) = + clipboard.requested_logical_size.lock().unwrap().take() + { + let requested_physical_size = + winit::dpi::PhysicalSize::new( + (requested_size.width as f64 + * window.state.scale_factor()) + .ceil() as u32, + (requested_size.height as f64 + * window.state.scale_factor()) + .ceil() as u32, + ); + let physical_size = window.state.physical_size(); + if requested_physical_size.width != physical_size.width + || requested_physical_size.height + != physical_size.height + { + // FIXME what to do when we are stuck in a configure event/resize request loop + // We don't have control over how winit handles this. + window.resize_enabled = true; + + let s = winit::dpi::Size::Physical( + requested_physical_size, + ); + _ = window.raw.request_surface_size(s); + window.raw.set_min_surface_size(Some(s)); + window.raw.set_max_surface_size(Some(s)); + window.state.synchronize( + &program, + surface_id, + window.raw.as_ref(), + ); + } + } + events.push(( + Some(surface_id), + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::Subsurface( + wayland::SubsurfaceEvent::Created, + ), + ), + ), + )); + let _ = user_interfaces.insert(surface_id, ui); + } + SubsurfaceEventVariant::Destroyed(instance) => { + remove_iced_subsurface(&instance.wl_surface); + + if let Some(id_wrapper) = + surface_ids.remove(&instance.wl_surface.id()) + { + _ = user_interfaces.remove(&id_wrapper.inner()); + + if let Some(w) = + window_manager.remove(id_wrapper.inner()) + { + clipboard.register_dnd_destination( + DndSurface(Arc::new(Box::new(w.raw.clone()))), + Vec::new(), + ); + if clipboard + .window_id() + .is_some_and(|id| w.raw.id() == id) + { + *clipboard = Clipboard::unconnected(); + } + } + events.push(( + Some(id_wrapper.inner()), + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::Subsurface( + wayland::SubsurfaceEvent::Destroyed, + ), + ), + ), + )); + } + if let Some(subsurface_state) = subsurface_state.as_mut() { + subsurface_state.unmapped_subsurfaces.push(instance); + } + } + SubsurfaceEventVariant::Resized(id, size) => { + if let Some((id, w)) = + window_manager.get_mut(id).map(|v| (id, v)) + { + let scale = w.state.scale_factor(); + let physical_size = size.to_physical(scale); + w.state.update( + program, + w.raw.as_ref(), + &WindowEvent::SurfaceResized(PhysicalSize::new( + physical_size.width, + physical_size.height, + )), + ); + + events.push(( + Some(id), + iced_runtime::core::Event::Window( + window::Event::Opened { + size: w.state.logical_size(), + position: Default::default(), + }, + ), + )) + } + } + }, } } } diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index d149c3c0..69d0b203 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -7,6 +7,7 @@ use crate::core::{ widget::{self, Widget}, }; use std::{ + borrow::BorrowMut, cell::RefCell, collections::HashMap, fmt::Debug, @@ -55,6 +56,7 @@ use wayland_protocols::wp::{ wp_viewport::WpViewport, wp_viewporter::WpViewporter, }, }; +use winit::window::WindowId; use crate::platform_specific::{ SurfaceIdWrapper, event_loop::state::SctkState, @@ -364,7 +366,15 @@ pub struct SubsurfaceState { pub wp_alpha_modifier: Option, pub qh: QueueHandle, pub(crate) buffers: HashMap>, - pub unmapped_subsurfaces: Vec, + pub(crate) unmapped_subsurfaces: Vec, + pub new_iced_subsurfaces: Vec<( + window::Id, + ObjectId, + window::Id, + WlSubsurface, + WlSurface, + u32, + )>, } impl SubsurfaceState { @@ -446,6 +456,8 @@ impl SubsurfaceState { wl_buffer: None, bounds: None, transform: wl_output::Transform::Normal, + z: 0, + parent: parent.id(), } } @@ -461,6 +473,14 @@ impl SubsurfaceState { // // They should be safe to destroy by the next time `update_subsurfaces` // is run. + ICED_SUBSURFACES.with_borrow_mut(|surfaces| { + surfaces.retain(|s| { + !self + .unmapped_subsurfaces + .iter() + .any(|unmapped| unmapped.wl_surface == s.4) + }) + }); self.unmapped_subsurfaces.clear(); // Remove cached `wl_buffers` for any `BufferSource`s that no longer exist. @@ -479,11 +499,61 @@ impl SubsurfaceState { subsurface.unmap(); self.unmapped_subsurfaces.push(subsurface); } + let needs_sorting = subsurfaces.len() < view_subsurfaces.len() + || !self.new_iced_subsurfaces.is_empty(); + // Create new subsurfaces if there aren't enough. while subsurfaces.len() < view_subsurfaces.len() { subsurfaces.push(self.create_subsurface(parent)); } - // Attach buffers to subsurfaces, set viewports, and commit. + if needs_sorting { + let mut sorted_subsurfaces: Vec<_> = view_subsurfaces + .iter() + .zip(subsurfaces.iter_mut()) + .map(|(_, instance)| { + ( + instance.parent.clone(), + instance.wl_subsurface.clone(), + instance.wl_surface.clone(), + instance.z, + ) + }) + .chain(self.new_iced_subsurfaces.clone().into_iter().map( + |(_, parent, _, wl_subsurface, wl_surface, z)| { + (parent.clone(), wl_subsurface, wl_surface, z) + }, + )) + .chain(ICED_SUBSURFACES.with(|surfaces| { + let b = surfaces.borrow(); + let v: Vec<_> = b + .iter() + .map(move |s| { + (s.1.clone(), s.3.clone(), s.4.clone(), s.5) + }) + .collect(); + v.into_iter() + })) + .collect(); + + sorted_subsurfaces.sort_by(|a, b| a.3.cmp(&b.3)); + + // Attach buffers to subsurfaces, set viewports, and commit. + for i in 1..sorted_subsurfaces.len() { + let prev = &sorted_subsurfaces[0..i]; + let subsurface = &sorted_subsurfaces[i]; + for prev in prev.iter().rev() { + if prev.0 != subsurface.0 { + continue; + } + subsurface.1.place_above(&prev.2); + } + } + } + if !self.new_iced_subsurfaces.is_empty() { + ICED_SUBSURFACES.with(|surfaces| { + surfaces.borrow_mut().append(&mut self.new_iced_subsurfaces); + }) + }; for (subsurface_data, subsurface) in view_subsurfaces.iter().zip(subsurfaces.iter_mut()) { @@ -540,12 +610,14 @@ impl Drop for SubsurfaceState { #[derive(Clone, Debug)] pub(crate) struct SubsurfaceInstance { pub(crate) wl_surface: WlSurface, - wl_subsurface: WlSubsurface, - wp_viewport: WpViewport, - wp_alpha_modifier_surface: Option, - wl_buffer: Option, - bounds: Option>, - transform: wl_output::Transform, + pub(crate) wl_subsurface: WlSubsurface, + pub(crate) wp_viewport: WpViewport, + pub(crate) wp_alpha_modifier_surface: Option, + pub(crate) wl_buffer: Option, + pub(crate) bounds: Option>, + pub(crate) transform: wl_output::Transform, + pub(crate) z: u32, + pub parent: ObjectId, } impl SubsurfaceInstance { @@ -621,6 +693,7 @@ impl SubsurfaceInstance { self.wl_buffer = Some(buffer); self.bounds = Some(info.bounds); self.transform = info.transform; + self.z = info.z; } pub fn unmap(&self) { @@ -646,16 +719,46 @@ pub(crate) struct SubsurfaceInfo { pub bounds: Rectangle, pub alpha: f32, pub transform: wl_output::Transform, + pub z: u32, } thread_local! { static SUBSURFACES: RefCell> = RefCell::new(Vec::new()); + static ICED_SUBSURFACES: RefCell> = RefCell::new(Vec::new()); } pub(crate) fn take_subsurfaces() -> Vec { SUBSURFACES.with(|subsurfaces| mem::take(&mut *subsurfaces.borrow_mut())) } +pub(crate) fn subsurface_ids(parent: WindowId) -> Vec { + ICED_SUBSURFACES.with(|subsurfaces| { + subsurfaces + .borrow_mut() + .iter() + .filter_map(|s| { + if winit::window::WindowId::from_raw(s.1.as_ptr() as usize) + == parent + { + Some(winit::window::WindowId::from_raw( + s.4.id().as_ptr() as usize + )) + } else { + None + } + }) + .collect() + }) +} + +pub(crate) fn remove_iced_subsurface(surface: &WlSurface) { + ICED_SUBSURFACES.with(|surfaces| { + surfaces + .borrow_mut() + .retain(|(_, _, _, _, s, _)| s != surface) + }) +} + #[must_use] pub struct Subsurface { buffer: SubsurfaceBuffer, @@ -664,6 +767,7 @@ pub struct Subsurface { content_fit: ContentFit, alpha: f32, transform: wl_output::Transform, + pub z: u32, } impl Widget for Subsurface @@ -729,6 +833,7 @@ where bounds: layout.bounds(), alpha: self.alpha, transform: self.transform, + z: self.z, }) }); } @@ -744,6 +849,7 @@ impl Subsurface { content_fit: ContentFit::Contain, alpha: 1., transform: wl_output::Transform::Normal, + z: 0, } } @@ -767,6 +873,11 @@ impl Subsurface { self } + pub fn z(mut self, z: u32) -> Self { + self.z = z; + self + } + pub fn transform(mut self, transform: wl_output::Transform) -> Self { self.transform = transform; self diff --git a/winit/src/platform_specific/wayland/winit_window.rs b/winit/src/platform_specific/wayland/winit_window.rs index 681f7f8f..c9d2f9cb 100644 --- a/winit/src/platform_specific/wayland/winit_window.rs +++ b/winit/src/platform_specific/wayland/winit_window.rs @@ -1,6 +1,6 @@ use crate::platform_specific::wayland::Action; use cctk::sctk::reexports::{ - calloop::channel, + calloop::{LoopHandle, channel}, client::{ Proxy, QueueHandle, protocol::{wl_display::WlDisplay, wl_surface::WlSurface}, @@ -103,14 +103,14 @@ impl winit::window::Window for SctkWinitWindow { ) -> Option> { let mut guard = self.common.lock().unwrap(); self.request_redraw(); - let size: LogicalSize = + let logical_size: LogicalSize = size.to_logical(guard.fractional_scale.unwrap_or(1.)); match &self.surface { CommonSurface::Popup(popup, positioner) => { - if size.width == 0 || size.height == 0 { + if logical_size.width == 0 || logical_size.height == 0 { return None; } - guard.size = size; + guard.size = logical_size; guard.requested_size.0 = Some(guard.size.width); guard.requested_size.1 = Some(guard.size.height); positioner.set_size( @@ -138,16 +138,16 @@ impl winit::window::Window for SctkWinitWindow { } CommonSurface::Layer(layer_surface) => { guard.requested_size = ( - (size.width > 0).then_some(size.width), - (size.height > 0).then_some(size.height), + (logical_size.width > 0).then_some(logical_size.width), + (logical_size.height > 0).then_some(logical_size.height), ); - if size.width > 0 { - guard.size.width = size.width; + if logical_size.width > 0 { + guard.size.width = logical_size.width; } - if size.height > 0 { - guard.size.height = size.height; + if logical_size.height > 0 { + guard.size.height = logical_size.height; } - layer_surface.set_size(size.width, size.height); + layer_surface.set_size(logical_size.width, logical_size.height); if let Some(viewport) = guard.wp_viewport.as_ref() { // Set inner size without the borders. viewport.set_destination( @@ -157,6 +157,23 @@ impl winit::window::Window for SctkWinitWindow { } } CommonSurface::Lock(_) => {} + CommonSurface::Subsurface { .. } => { + guard.requested_size = ( + (logical_size.width > 0).then_some(logical_size.width), + (logical_size.height > 0).then_some(logical_size.height), + ); + guard.size = logical_size; + if let Some(viewport) = guard.wp_viewport.as_ref() { + // Set inner size without the borders. + viewport.set_destination( + guard.size.width as i32, + guard.size.height as i32, + ); + } + _ = self + .tx + .send(Action::SubsurfaceResize(self.id.inner(), size)); + } } None } diff --git a/winit/src/program.rs b/winit/src/program.rs deleted file mode 100644 index 16407029..00000000 --- a/winit/src/program.rs +++ /dev/null @@ -1,2365 +0,0 @@ -//! Create interactive, native cross-platform applications for WGPU. -#[path = "application/drag_resize.rs"] -mod drag_resize; -mod state; -mod window_manager; - -pub use runtime::{default, Appearance, DefaultStyle}; -use winit::dpi::PhysicalSize; -use winit::event_loop::OwnedDisplayHandle; - -use crate::conversion; -use crate::core; -use crate::core::mouse; -use crate::core::renderer; -use crate::core::time::Instant; -use crate::core::widget::operation; -use crate::core::widget::Operation; -use crate::core::window; -use crate::core::Clipboard as CoreClipboard; -use crate::core::Length; -use crate::core::{Element, Point, Size}; -use crate::futures::futures::channel::mpsc; -use crate::futures::futures::channel::oneshot; -use crate::futures::futures::task; -use crate::futures::futures::{Future, StreamExt}; -use crate::futures::subscription::{self, Subscription}; -use crate::futures::{Executor, Runtime}; -use crate::graphics; -use crate::graphics::{compositor, Compositor}; -use crate::platform_specific; -use crate::runtime::user_interface::{self, UserInterface}; -use crate::runtime::Debug; -use crate::runtime::{self, Action, Task}; -use crate::{Clipboard, Error, Proxy, Settings}; -use dnd::DndSurface; -use dnd::Icon; -use iced_futures::core::widget::operation::search_id; -use iced_graphics::Viewport; -pub use state::State; -use window_clipboard::mime::ClipboardStoreData; -use winit::raw_window_handle::HasWindowHandle; - -pub(crate) use window_manager::WindowManager; - -use rustc_hash::FxHashMap; -use std::any::Any; -use std::borrow::Cow; -use std::collections::HashMap; -use std::mem::ManuallyDrop; -use std::sync::Arc; -use std::time::Duration; - -/// An interactive, native, cross-platform, multi-windowed application. -/// -/// This trait is the main entrypoint of multi-window Iced. Once implemented, you can run -/// your GUI application by simply calling [`run`]. It will run in -/// its own window. -/// -/// A [`Program`] can execute asynchronous actions by returning a -/// [`Task`] in some of its methods. -/// -/// When using a [`Program`] with the `debug` feature enabled, a debug view -/// can be toggled by pressing `F12`. -pub trait Program -where - Self: Sized, - Self::Theme: DefaultStyle, -{ - /// The type of __messages__ your [`Program`] will produce. - type Message: std::fmt::Debug + Send; - - /// The theme used to draw the [`Program`]. - type Theme; - - /// The [`Executor`] that will run commands and subscriptions. - /// - /// The [default executor] can be a good starting point! - /// - /// [`Executor`]: Self::Executor - /// [default executor]: crate::futures::backend::default::Executor - type Executor: Executor; - - /// The graphics backend to use to draw the [`Program`]. - type Renderer: core::Renderer + core::text::Renderer; - - /// The data needed to initialize your [`Program`]. - type Flags; - - /// Initializes the [`Program`] 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 [`Task`] 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, Task); - - /// Returns the current title of the [`Program`]. - /// - /// This title can be dynamic! The runtime will automatically update the - /// title of your application when necessary. - fn title(&self, window: window::Id) -> String; - - /// 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 [`Task`] returned will be executed immediately in the background by the - /// runtime. - fn update(&mut self, message: Self::Message) -> Task; - - /// Returns the widgets to display in the [`Program`] for the `window`. - /// - /// These widgets can produce __messages__ based on user interaction. - fn view( - &self, - window: window::Id, - ) -> Element<'_, Self::Message, Self::Theme, Self::Renderer>; - - /// Returns the current `Theme` of the [`Program`]. - fn theme(&self, window: window::Id) -> Self::Theme; - - /// Returns the `Style` variation of the `Theme`. - fn style(&self, theme: &Self::Theme) -> Appearance { - theme.default_style() - } - - /// 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 window of the [`Program`]. - /// - /// 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`. - #[allow(unused_variables)] - fn scale_factor(&self, window: window::Id) -> f64 { - 1.0 - } -} - -/// Runs a [`Program`] with an executor, compositor, and the provided -/// settings. -pub fn run( - settings: Settings, - graphics_settings: graphics::Settings, - window_settings: Option, - flags: P::Flags, -) -> Result<(), Error> -where - P: Program + 'static, - C: Compositor + 'static, - P::Theme: DefaultStyle, -{ - use winit::event_loop::EventLoop; - - let mut debug = Debug::new(); - debug.startup_started(); - - let event_loop = EventLoop::new().expect("Create event loop"); - #[cfg(feature = "wayland")] - let is_wayland = - winit::platform::wayland::EventLoopExtWayland::is_wayland(&event_loop); - #[cfg(not(feature = "wayland"))] - let is_wayland = false; - - let (event_sender, event_receiver) = mpsc::unbounded(); - let (proxy, worker): (Proxy<

::Message>, _) = - Proxy::new(event_loop.create_proxy(), event_sender.clone()); - - let mut runtime = { - let executor = - P::Executor::new().map_err(Error::ExecutorCreationFailed)?; - executor.spawn(worker); - - Runtime::new(executor, proxy.clone()) - }; - - let (program, task) = runtime.enter(|| P::new(flags)); - let is_daemon = window_settings.is_none() || settings.is_daemon; - - let task = if let Some(window_settings) = window_settings { - let mut task = Some(task); - - let open = iced_runtime::task::oneshot(|channel| { - iced_runtime::Action::Window(iced_runtime::window::Action::Open( - iced_runtime::core::window::Id::RESERVED, - window_settings, - channel, - )) - }); - - open.then(move |_| task.take().unwrap_or(Task::none())) - } else { - task - }; - - if let Some(stream) = runtime::task::into_stream(task) { - runtime.run(stream); - } - - runtime.track(subscription::into_recipes( - runtime.enter(|| program.subscription().map(Action::Output)), - )); - - let (boot_sender, boot_receiver) = oneshot::channel(); - let (control_sender, control_receiver) = mpsc::unbounded(); - - let instance = Box::pin(run_instance::( - program, - runtime, - proxy.clone(), - debug, - boot_receiver, - event_receiver, - control_sender.clone(), - event_loop.owned_display_handle(), - is_daemon, - )); - - let context = task::Context::from_waker(task::noop_waker_ref()); - - struct Runner { - instance: std::pin::Pin>, - context: task::Context<'static>, - id: Option, - boot: Option>, - sender: mpsc::UnboundedSender>, - receiver: mpsc::UnboundedReceiver, - error: Option, - proxy: Proxy, - - #[cfg(target_arch = "wasm32")] - is_booted: std::rc::Rc>, - #[cfg(target_arch = "wasm32")] - canvas: Option, - } - - struct BootConfig { - sender: oneshot::Sender>, - fonts: Vec>, - graphics_settings: graphics::Settings, - control_sender: mpsc::UnboundedSender, - is_wayland: bool, - } - - let runner = Runner { - instance, - context, - id: settings.id, - boot: Some(BootConfig { - sender: boot_sender, - fonts: settings.fonts, - graphics_settings, - control_sender, - is_wayland, - }), - sender: event_sender, - receiver: control_receiver, - error: None, - proxy: proxy.clone(), - - #[cfg(target_arch = "wasm32")] - is_booted: std::rc::Rc::new(std::cell::RefCell::new(false)), - #[cfg(target_arch = "wasm32")] - canvas: None, - }; - - impl winit::application::ApplicationHandler - for Runner - where - Message: std::fmt::Debug, - F: Future, - C: Compositor + 'static, - { - fn proxy_wake_up( - &mut self, - event_loop: &dyn winit::event_loop::ActiveEventLoop, - ) { - self.process_event(event_loop, None); - } - - fn new_events( - &mut self, - event_loop: &dyn winit::event_loop::ActiveEventLoop, - cause: winit::event::StartCause, - ) { - if self.boot.is_some() { - return; - } - self.process_event(event_loop, Some(Event::NewEvents(cause))); - } - - fn window_event( - &mut self, - event_loop: &dyn winit::event_loop::ActiveEventLoop, - window_id: winit::window::WindowId, - event: winit::event::WindowEvent, - ) { - #[cfg(target_os = "windows")] - let is_move_or_resize = matches!( - event, - winit::event::WindowEvent::SurfaceResized(_) - | winit::event::WindowEvent::Moved(_) - ); - - self.process_event( - event_loop, - Some(Event::Winit(window_id, event)), - ); - - // TODO: Remove when unnecessary - // On Windows, we emulate an `AboutToWait` event after every `Resized` event - // since the event loop does not resume during resize interaction. - // More details: https://github.com/rust-windowing/winit/issues/3272 - #[cfg(target_os = "windows")] - { - if is_move_or_resize { - self.process_event(event_loop, Some(Event::AboutToWait)); - } - } - } - - fn about_to_wait( - &mut self, - event_loop: &dyn winit::event_loop::ActiveEventLoop, - ) { - self.process_event(event_loop, Some(Event::AboutToWait)); - } - - fn can_create_surfaces( - &mut self, - event_loop: &dyn winit::event_loop::ActiveEventLoop, - ) { - // create initial window - let Some(BootConfig { - sender, - fonts, - graphics_settings, - control_sender, - is_wayland, - }) = self.boot.take() - else { - return; - }; - - let window: Arc = match event_loop - .create_window( - winit::window::WindowAttributes::default() - .with_visible(false), - ) { - Ok(window) => Arc::from(window), - Err(error) => { - self.error = Some(Error::WindowCreationFailed(error)); - event_loop.exit(); - return; - } - }; - - #[cfg(target_arch = "wasm32")] - { - use winit::platform::web::WindowExtWebSys; - self.canvas = window.canvas(); - } - - let proxy = self.proxy.raw.clone(); - let finish_boot = async move { - let mut compositor = - C::new(graphics_settings, window.clone()).await?; - - for font in fonts { - compositor.load_font(font); - } - - sender - .send(Boot { - compositor, - is_wayland, - clipboard: Clipboard::connect( - window, - crate::clipboard::ControlSender { - sender: control_sender, - proxy, - }, - ), - }) - .ok() - .expect("Send boot event"); - - Ok::<_, graphics::Error>(()) - }; - - #[cfg(not(target_arch = "wasm32"))] - if let Err(error) = - crate::futures::futures::executor::block_on(finish_boot) - { - self.error = Some(Error::GraphicsCreationFailed(error)); - event_loop.exit(); - } - - #[cfg(target_arch = "wasm32")] - { - let is_booted = self.is_booted.clone(); - - wasm_bindgen_futures::spawn_local(async move { - finish_boot.await.expect("Finish boot!"); - - *is_booted.borrow_mut() = true; - }); - - event_loop - .set_control_flow(winit::event_loop::ControlFlow::Poll); - } - } - } - - impl Runner - where - F: Future, - C: Compositor, - { - fn process_event( - &mut self, - event_loop: &dyn winit::event_loop::ActiveEventLoop, - event: Option>, - ) { - if event_loop.exiting() { - return; - } - - if let Some(event) = event { - self.sender.start_send(event).expect("Send event"); - } - - loop { - let poll = self.instance.as_mut().poll(&mut self.context); - - match poll { - task::Poll::Pending => match self.receiver.try_next() { - Ok(Some(control)) => match control { - Control::ChangeFlow(flow) => { - use winit::event_loop::ControlFlow; - - match (event_loop.control_flow(), flow) { - ( - ControlFlow::WaitUntil(current), - ControlFlow::WaitUntil(new), - ) if new < current => {} - ( - ControlFlow::WaitUntil(target), - ControlFlow::Wait, - ) if target > Instant::now() => {} - _ => { - event_loop.set_control_flow(flow); - } - } - } - Control::CreateWindow { - id, - settings, - title, - monitor, - on_open, - } => { - let exit_on_close_request = - settings.exit_on_close_request; - let resize_border = settings.resize_border; - - let visible = settings.visible; - - #[cfg(target_arch = "wasm32")] - let target = - settings.platform_specific.target.clone(); - - let window_attributes = - conversion::window_attributes( - settings, - &title, - monitor - .or(event_loop.primary_monitor()), - self.id.clone(), - ) - .with_visible(false); - - #[cfg(target_arch = "wasm32")] - let window_attributes = { - use winit::platform::web::WindowAttributesExtWebSys; - window_attributes - .with_canvas(self.canvas.take()) - }; - - log::info!("Window attributes for id `{id:#?}`: {window_attributes:#?}"); - - let window = Arc::from( - event_loop - .create_window(window_attributes) - .expect("Create window"), - ); - - #[cfg(target_arch = "wasm32")] - { - use winit::platform::web::WindowExtWebSys; - - let canvas = window - .canvas() - .expect("Get window canvas"); - - let _ = canvas.set_attribute( - "style", - "display: block; width: 100%; height: 100%", - ); - - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); - let body = document.body().unwrap(); - - let target = target.and_then(|target| { - body.query_selector(&format!( - "#{target}" - )) - .ok() - .unwrap_or(None) - }); - - match target { - Some(node) => { - let _ = node - .replace_with_with_node_1( - &canvas, - ) - .expect(&format!( - "Could not replace #{}", - node.id() - )); - } - None => { - let _ = body - .append_child(&canvas) - .expect( - "Append canvas to HTML body", - ); - } - }; - } - - self.process_event( - event_loop, - Some(Event::WindowCreated { - id, - window, - exit_on_close_request, - make_visible: visible, - on_open, - resize_border, - }), - ); - } - Control::Exit => { - event_loop.exit(); - } - Control::Dnd(e) => { - self.sender.start_send(Event::Dnd(e)).unwrap(); - } - #[cfg(feature = "a11y")] - Control::Accessibility(id, event) => { - self.process_event( - event_loop, - Some(Event::Accessibility(id, event)), - ); - } - #[cfg(feature = "a11y")] - Control::AccessibilityEnabled(event) => { - self.process_event( - event_loop, - Some(Event::AccessibilityEnabled(event)), - ); - } - Control::PlatformSpecific(e) => { - self.sender - .start_send(Event::PlatformSpecific(e)) - .unwrap(); - } - Control::AboutToWait => { - self.sender - .start_send(Event::AboutToWait) - .expect("Send event"); - } - Control::Winit(id, e) => { - self.sender - .start_send(Event::Winit(id, e)) - .expect("Send event"); - } - Control::StartDnd => { - self.sender - .start_send(Event::StartDnd) - .expect("Send event"); - } - }, - _ => { - break; - } - }, - task::Poll::Ready(_) => { - event_loop.exit(); - break; - } - }; - } - } - } - - #[cfg(not(target_arch = "wasm32"))] - { - let mut runner = runner; - let _ = event_loop.run_app(&mut runner); - - runner.error.map(Err).unwrap_or(Ok(())) - } - - #[cfg(target_arch = "wasm32")] - { - use winit::platform::web::EventLoopExtWebSys; - let _ = event_loop.spawn_app(runner); - - Ok(()) - } -} - -struct Boot { - compositor: C, - is_wayland: bool, - clipboard: Clipboard, -} - -pub(crate) enum Event { - WindowCreated { - id: window::Id, - window: Arc, - exit_on_close_request: bool, - make_visible: bool, - on_open: oneshot::Sender, - resize_border: u32, - }, - Dnd(dnd::DndEvent), - #[cfg(feature = "a11y")] - Accessibility(window::Id, iced_accessibility::accesskit::ActionRequest), - #[cfg(feature = "a11y")] - AccessibilityEnabled(bool), - Winit(winit::window::WindowId, winit::event::WindowEvent), - AboutToWait, - UserEvent(Action), - NewEvents(winit::event::StartCause), - PlatformSpecific(crate::platform_specific::Event), - StartDnd, -} - -pub(crate) enum Control { - ChangeFlow(winit::event_loop::ControlFlow), - Exit, - CreateWindow { - id: window::Id, - settings: window::Settings, - title: String, - monitor: Option, - on_open: oneshot::Sender, - }, - Dnd(dnd::DndEvent), - #[cfg(feature = "a11y")] - Accessibility(window::Id, iced_accessibility::accesskit::ActionRequest), - #[cfg(feature = "a11y")] - AccessibilityEnabled(bool), - PlatformSpecific(crate::platform_specific::Event), - AboutToWait, - Winit(winit::window::WindowId, winit::event::WindowEvent), - StartDnd, -} - -async fn run_instance<'a, P, C>( - mut program: P, - mut runtime: Runtime, Action>, - mut proxy: Proxy, - mut debug: Debug, - boot: oneshot::Receiver>, - mut event_receiver: mpsc::UnboundedReceiver>, - mut control_sender: mpsc::UnboundedSender, - display_handle: OwnedDisplayHandle, - is_daemon: bool, -) where - P: Program + 'static, - C: Compositor + 'static, - P::Theme: DefaultStyle, -{ - use winit::event; - use winit::event_loop::ControlFlow; - - let Boot { - mut compositor, - is_wayland, - mut clipboard, - } = boot.await.expect("Receive boot"); - - let mut platform_specific_handler = - crate::platform_specific::PlatformSpecific::default(); - #[cfg(all(feature = "wayland", target_os = "linux"))] - if is_wayland { - platform_specific_handler = platform_specific_handler.with_wayland( - control_sender.clone(), - proxy.raw.clone(), - display_handle, - ); - } - - let mut window_manager = WindowManager::new(); - let mut is_window_opening = !is_daemon; - - let mut events = Vec::new(); - let mut messages = Vec::new(); - let mut actions = 0; - - #[cfg(feature = "a11y")] - let (mut adapters, mut a11y_enabled) = if let Some((main_id, title, raw)) = - window_manager.ids().next().and_then(|id| { - window_manager - .get(id) - .map(|w| (id, w.state.title.clone(), w.raw.clone())) - }) { - let node_id = core::id::window_node_id(); - use crate::a11y::*; - use iced_accessibility::accesskit::{ - ActivationHandler, NodeBuilder, NodeId, Role, Tree, TreeUpdate, - }; - use iced_accessibility::accesskit_winit::Adapter; - - let activation_handler = WinitActivationHandler { - proxy: control_sender.clone(), - title: title.clone(), - }; - - let action_handler = WinitActionHandler { - id: main_id, - proxy: control_sender.clone(), - }; - - let deactivation_handler = WinitDeactivationHandler { - proxy: control_sender.clone(), - }; - ( - HashMap::from([( - main_id, - ( - node_id, - Adapter::with_direct_handlers( - raw.as_ref(), - activation_handler, - action_handler, - deactivation_handler, - ), - ), - )]), - false, - ) - } else { - (Default::default(), false) - }; - - let mut ui_caches = FxHashMap::default(); - let mut user_interfaces: ManuallyDrop< - HashMap< - window::Id, - UserInterface< - '_, -

::Message, -

::Theme, -

::Renderer, - >, - rustc_hash::FxBuildHasher, - >, - > = ManuallyDrop::new(FxHashMap::default()); - - let mut cur_dnd_surface: Option = None; - - let mut dnd_surface: Option>> = None; - - debug.startup_finished(); - loop { - // Empty the queue if possible - let event = if let Ok(event) = event_receiver.try_next() { - event - } else { - event_receiver.next().await - }; - - let Some(event) = event else { - break; - }; - let mut rebuild_a11y_tree = false; - - match event { - Event::StartDnd => { - let queued = clipboard.get_queued(); - for crate::clipboard::StartDnd { - internal, - source_surface, - icon_surface, - content, - actions, - } in queued - { - let Some(window_id) = source_surface.and_then(|source| { - match source { - core::clipboard::DndSource::Surface(s) => Some(s), - core::clipboard::DndSource::Widget(w) => { - // search windows for widget with operation - user_interfaces.iter_mut().find_map( - |(ui_id, ui)| { - let Some(ui_renderer) = window_manager - .get_mut(ui_id.clone()) - .map(|w| &w.renderer) - else { - return None; - }; - - let operation: Box> = - Box::new(operation::map( - Box::new(search_id::search_id( - w.clone(), - )), - |_| {}, - )); - let mut current_operation = - Some(operation); - - while let Some(mut operation) = - current_operation.take() - { - ui.operate( - ui_renderer, - operation.as_mut(), - ); - - match operation.finish() { - operation::Outcome::None => {} - operation::Outcome::Some( - (), - ) => { - return Some(ui_id.clone()); - } - operation::Outcome::Chain( - next, - ) => { - current_operation = - Some(next); - } - } - } - None - }, - ) - } - } - }) else { - eprintln!("No source surface"); - continue; - }; - - let Some(window) = window_manager.get_mut(window_id) else { - eprintln!("No window"); - continue; - }; - - let state = &window.state; - let mut dnd_buffer = None; - let icon_surface = icon_surface - .map(|i| { - let mut icon_surface = i.downcast::(); - - let mut renderer = compositor.create_renderer(); - - let lim = core::layout::Limits::new( - Size::new(1., 1.), - Size::new( - state.viewport().physical_width() - as f32, - state.viewport().physical_height() - as f32, - ), - ); - - let mut tree = core::widget::Tree { - id: icon_surface.element.as_widget().id(), - tag: icon_surface.element.as_widget().tag(), - state: icon_surface.state, - children: icon_surface.element.as_widget().children(), - }; - - let size = icon_surface.element - .as_widget() - .layout(&mut tree, &renderer, &lim); - icon_surface.element.as_widget_mut().diff(&mut tree); - - let size = lim.resolve( - Length::Shrink, - Length::Shrink, - size.size(), - ); - let viewport = Viewport::with_logical_size( - size, - state.viewport().scale_factor(), - ); - - let mut ui = UserInterface::build( - icon_surface.element, - size, - user_interface::Cache::default(), - &mut renderer, - ); - _ = ui.draw( - &mut renderer, - state.theme(), - &renderer::Style { - icon_color: state.icon_color(), - text_color: state.text_color(), - scale_factor: state.scale_factor(), - }, - Default::default(), - );; - let mut bytes = compositor.screenshot( - &mut renderer, - &viewport, - core::Color::TRANSPARENT, - &debug.overlay(), - ); - for pix in bytes.chunks_exact_mut(4) { - // rgba -> argb little endian - pix.swap(0, 2); - } - // update subsurfaces - if let Some(surface) = platform_specific_handler.create_surface() { - // TODO Remove id - let id = window::Id::unique(); - platform_specific_handler - .update_subsurfaces(id, &surface); - let surface = Arc::new(surface); - dnd_surface = Some(surface.clone()); - dnd_buffer = Some((viewport.physical_size(), state.scale_factor(), bytes, icon_surface.offset)); - Icon::Surface(dnd::DndSurface(surface)) - } else { - platform_specific_handler - .clear_subsurface_list(); - Icon::Buffer { - data: Arc::new(bytes), - width: viewport.physical_width(), - height: viewport.physical_height(), - transparent: true, - } - } - }); - - clipboard.start_dnd_winit( - internal, - DndSurface(Arc::new(Box::new(window.raw.clone()))), - icon_surface, - content, - actions, - ); - - // This needs to be after `wl_data_device::start_drag` for the offset to have an effect - if let (Some(surface), Some((size, scale, bytes, offset))) = (dnd_surface.as_ref(), dnd_buffer) { - platform_specific_handler.update_surface_shm(&surface, size.width, size.height, scale, &bytes, offset); - } - } - } - Event::WindowCreated { - id, - window, - exit_on_close_request, - make_visible, - on_open, - resize_border, - } => { - let window = window_manager.insert( - id, - window, - &program, - &mut compositor, - exit_on_close_request, - resize_border, - ); - #[cfg(feature = "wayland")] - platform_specific_handler.send_wayland( - platform_specific::Action::TrackWindow( - window.raw.clone(), - id, - ), - ); - #[cfg(feature = "a11y")] - { - use crate::a11y::*; - use iced_accessibility::accesskit::{ - ActivationHandler, NodeBuilder, NodeId, Role, Tree, - TreeUpdate, - }; - use iced_accessibility::accesskit_winit::Adapter; - - let node_id = core::id::window_node_id(); - - let activation_handler = WinitActivationHandler { - proxy: control_sender.clone(), - title: window.state.title.clone(), - }; - - let action_handler = WinitActionHandler { - id, - proxy: control_sender.clone(), - }; - - let deactivation_handler = WinitDeactivationHandler { - proxy: control_sender.clone(), - }; - _ = adapters.insert( - id, - ( - node_id, - Adapter::with_direct_handlers( - window.raw.as_ref(), - activation_handler, - action_handler, - deactivation_handler, - ), - ), - ); - } - - let logical_size = window.state.logical_size(); - - let _ = user_interfaces.insert( - id, - build_user_interface( - &program, - user_interface::Cache::default(), - &mut window.renderer, - logical_size, - &mut debug, - id, - window.raw.clone(), - window.prev_dnd_destination_rectangles_count, - &mut clipboard, - ), - ); - let _ = ui_caches.insert(id, user_interface::Cache::default()); - - if make_visible { - window.raw.set_visible(true); - } - - events.push(( - Some(id), - core::Event::Window(window::Event::Opened { - position: window.position(), - size: window.size(), - }), - )); - - if clipboard.window_id().is_none() { - clipboard = Clipboard::connect( - window.raw.clone(), - crate::clipboard::ControlSender { - sender: control_sender.clone(), - proxy: proxy.raw.clone(), - }, - ); - } - - let _ = on_open.send(id); - is_window_opening = false; - } - Event::UserEvent(action) => { - rebuild_a11y_tree = true; - let exited = run_action( - action, - &program, - &mut compositor, - &mut events, - &mut messages, - &mut clipboard, - &mut control_sender, - &mut debug, - &mut user_interfaces, - &mut window_manager, - &mut ui_caches, - &mut is_window_opening, - &mut platform_specific_handler, - ); - if exited { - runtime.track(None.into_iter()); - } - actions += 1; - } - Event::NewEvents( - event::StartCause::Init - | event::StartCause::ResumeTimeReached { .. }, - ) => { - if window_manager.ids().next().is_none() { - _ = control_sender - .start_send(Control::ChangeFlow(ControlFlow::Wait)); - } - for (_id, window) in window_manager.iter_mut() { - window.request_redraw(); - } - } - Event::Winit(window_id, event) => { - #[cfg(feature = "a11y")] - { - if let Some((id, window)) = - window_manager.get_mut_alias(window_id) - { - if let Some(Some((_, adapter))) = - a11y_enabled.then(|| adapters.get_mut(&id)) - { - adapter.process_event(window.raw.as_ref(), &event); - }; - } - } - match event { - event::WindowEvent::RedrawRequested => { - let Some((id, window)) = - window_manager.get_mut_alias(window_id) - else { - continue; - }; - - window.redraw_requested = false; - - // TODO: Avoid redrawing all the time by forcing widgets to - // request redraws on state changes - // - // Then, we can use the `interface_state` here to decide if a redraw - // is needed right away, or simply wait until a specific time. - let redraw_event = core::Event::Window( - window::Event::RedrawRequested(Instant::now()), - ); - - let cursor = window.state.cursor(); - - let ui = user_interfaces - .get_mut(&id) - .expect("Get user interface"); - - let (ui_state, _) = ui.update( - &[redraw_event.clone()], - cursor, - &mut window.renderer, - &mut clipboard, - &mut messages, - ); - - debug.draw_started(); - let new_mouse_interaction = ui.draw( - &mut window.renderer, - window.state.theme(), - &renderer::Style { - icon_color: window.state.icon_color(), - text_color: window.state.text_color(), - scale_factor: window.state.scale_factor(), - }, - cursor, - ); - platform_specific_handler - .update_subsurfaces(id, window.raw.rwh_06_window_handle()); - debug.draw_finished(); - - if new_mouse_interaction != window.mouse_interaction { - if let Some(interaction) = - conversion::mouse_interaction( - new_mouse_interaction, - ) - { - if matches!( - window.mouse_interaction, - mouse::Interaction::Hide - ) { - window.raw.set_cursor_visible(true); - } - window.raw.set_cursor(interaction.into()) - } else { - window.raw.set_cursor_visible(false); - } - - window.mouse_interaction = new_mouse_interaction; - } - - runtime.broadcast(subscription::Event::Interaction { - window: id, - event: redraw_event, - status: core::event::Status::Ignored, - }); - - if control_sender - .start_send(Control::ChangeFlow(match ui_state { - user_interface::State::Updated { - redraw_request: Some(redraw_request), - } => match redraw_request { - window::RedrawRequest::NextFrame => { - window.request_redraw(); - - ControlFlow::Wait - } - window::RedrawRequest::At(at) => { - ControlFlow::WaitUntil(at) - } - }, - _ => ControlFlow::Wait, - })) - .is_err() - { - panic!("send error"); - } - - let physical_size = window.state.physical_size(); - if physical_size.width == 0 || physical_size.height == 0 - { - continue; - } - if window.viewport_version - != window.state.viewport_version() - { - let logical_size = window.state.logical_size(); - debug.layout_started(); - let mut ui = user_interfaces - .remove(&id) - .expect("Remove user interface") - .relayout(logical_size, &mut window.renderer); - - let _ = user_interfaces.insert(id, ui); - debug.layout_finished(); - - debug.draw_started(); - let new_mouse_interaction = user_interfaces - .get_mut(&id) - .expect("Get user interface") - .draw( - &mut window.renderer, - window.state.theme(), - &renderer::Style { - icon_color: window.state.icon_color(), - text_color: window.state.text_color(), - scale_factor: window - .state - .scale_factor(), - }, - window.state.cursor(), - ); - platform_specific_handler - .update_subsurfaces(id, window.raw.rwh_06_window_handle()); - debug.draw_finished(); - - if new_mouse_interaction != window.mouse_interaction - { - if let Some(interaction) = - conversion::mouse_interaction( - new_mouse_interaction, - ) - { - if matches!( - window.mouse_interaction, - mouse::Interaction::Hide - ) { - window.raw.set_cursor_visible(true); - } - window.raw.set_cursor(interaction.into()) - } else { - window.raw.set_cursor_visible(false); - } - - window.mouse_interaction = - new_mouse_interaction; - } - compositor.configure_surface( - &mut window.surface, - physical_size.width, - physical_size.height, - ); - - window.viewport_version = - window.state.viewport_version(); - } - - window.raw.pre_present_notify(); - debug.render_started(); - match compositor.present( - &mut window.renderer, - &mut window.surface, - window.state.viewport(), - window.state.background_color(), - &debug.overlay(), - ) { - Ok(()) => { - debug.render_finished(); - } - Err(error) => { - match error { - // This is an unrecoverable error. - compositor::SurfaceError::OutOfMemory => { - panic!("{:?}", error); - } - compositor::SurfaceError::NoDamage => { - debug.render_finished(); - - // TODO Ideally there would be a way to know if some widget wants to animate? - let _ = control_sender.start_send( - Control::ChangeFlow( - ControlFlow::WaitUntil( - Instant::now().checked_add( - Duration::from_millis(100), - ).unwrap_or(Instant::now()), - ), - ), - ); - } - _ => { - debug.render_finished(); - log::error!( - "Error {error:?} when \ - presenting surface." - ); - - // Try rendering all windows again next frame. - for (_id, window) in - window_manager.iter_mut() - { - window.request_redraw(); - } - } - } - } - } - } - window_event => { - if !is_daemon - && matches!( - window_event, - winit::event::WindowEvent::Destroyed - ) - && !is_window_opening - && window_manager.is_empty() - { - runtime.track(None.into_iter()); - control_sender - .start_send(Control::Exit) - .expect("Send control action"); - - continue; - } - - let Some((id, window)) = - window_manager.get_mut_alias(window_id) - else { - continue; - }; - - // Initiates a drag resize window state when found. - if let Some(func) = - window.drag_resize_window_func.as_mut() - { - if func(window.raw.as_ref(), &window_event) { - continue; - } - } - - if matches!( - window_event, - winit::event::WindowEvent::CloseRequested - ) && window.exit_on_close_request - { - _ = run_action( - Action::Window(runtime::window::Action::Close( - id, - )), - &program, - &mut compositor, - &mut events, - &mut messages, - &mut clipboard, - &mut control_sender, - &mut debug, - &mut user_interfaces, - &mut window_manager, - &mut ui_caches, - &mut is_window_opening, - &mut platform_specific_handler, - ); - } else { - window.state.update( - window.raw.as_ref(), - &window_event, - &mut debug, - ); - if let Some(event) = conversion::window_event( - window_event, - window.state.scale_factor(), - window.state.modifiers(), - ) { - events.push((Some(id), event)); - } - } - } - _ => {} - } - } - Event::AboutToWait => { - let skip = events.is_empty() && messages.is_empty(); - if skip - && window_manager.iter_mut().all(|(_, w)| !w.resize_enabled) - { - continue; - } - - debug.event_processing_started(); - let mut uis_stale = false; - let mut resized = false; - for (id, window) in window_manager.iter_mut() { - if skip && !window.resize_enabled { - continue; - } - let mut window_events = vec![]; - - events.retain(|(window_id, event)| { - if *window_id == Some(id) { - window_events.push(event.clone()); - false - } else { - true - } - }); - let no_window_events = window_events.is_empty(); - #[cfg(feature = "wayland")] - window_events.push(core::Event::PlatformSpecific( - core::event::PlatformSpecific::Wayland( - core::event::wayland::Event::RequestResize, - ), - )); - let (ui_state, statuses) = user_interfaces - .get_mut(&id) - .expect("Get user interface") - .update( - &window_events, - window.state.cursor(), - &mut window.renderer, - &mut clipboard, - &mut messages, - ); - - let mut needs_redraw = - !no_window_events || !messages.is_empty(); - - if let Some(requested_size) = - clipboard.requested_logical_size.lock().unwrap().take() - { - let requested_physical_size: PhysicalSize = - winit::dpi::PhysicalSize::from_logical( - requested_size.cast::(), - window.state.scale_factor(), - ); - - let physical_size = window.state.physical_size(); - if requested_physical_size.width != physical_size.width - || requested_physical_size.height - != physical_size.height - { - // FIXME what to do when we are stuck in a configure event/resize request loop - // We don't have control over how winit handles this. - window.resize_enabled = true; - resized = true; - needs_redraw = true; - let s = winit::dpi::Size::Logical( - requested_size.cast(), - ); - _ = window.raw.request_surface_size(s); - window.raw.set_min_surface_size(Some(s)); - window.raw.set_max_surface_size(Some(s)); - window.state.synchronize( - &program, - id, - window.raw.as_ref(), - ); - } - } - if needs_redraw { - window.request_redraw(); - } else { - continue; - } - - if !uis_stale { - uis_stale = - matches!(ui_state, user_interface::State::Outdated); - } - - for (event, status) in - window_events.into_iter().zip(statuses.into_iter()) - { - runtime.broadcast(subscription::Event::Interaction { - window: id, - event, - status, - }); - } - } - - if !resized && skip { - continue; - } - - for (id, event) in events.drain(..) { - if id.is_none() - && matches!( - event, - core::Event::Keyboard(_) - | core::Event::Touch(_) - | core::Event::Mouse(_) - ) - { - continue; - } - runtime.broadcast(subscription::Event::Interaction { - window: id.unwrap_or(window::Id::NONE), - event, - status: core::event::Status::Ignored, - }); - } - - debug.event_processing_finished(); - - if !messages.is_empty() || uis_stale { - let cached_interfaces: FxHashMap< - window::Id, - user_interface::Cache, - > = ManuallyDrop::into_inner(user_interfaces) - .drain() - .map(|(id, ui)| (id, ui.into_cache())) - .collect(); - - update( - &mut program, - &mut runtime, - &mut debug, - &mut messages, - ); - - for (id, window) in window_manager.iter_mut() { - window.state.synchronize( - &program, - id, - window.raw.as_ref(), - ); - - window.request_redraw(); - } - rebuild_a11y_tree = true; - - user_interfaces = ManuallyDrop::new(build_user_interfaces( - &program, - &mut debug, - &mut window_manager, - cached_interfaces, - &mut clipboard, - )); - - if actions > 0 { - proxy.free_slots(actions); - actions = 0; - } - } - - debug.draw_started(); - - for (id, window) in window_manager.iter_mut() { - // TODO: Avoid redrawing all the time by forcing widgets to - // request redraws on state changes - // - // Then, we can use the `interface_state` here to decide if a redraw - // is needed right away, or simply wait until a specific time. - let redraw_event = core::Event::Window( - window::Event::RedrawRequested(Instant::now()), - ); - - let cursor = window.state.cursor(); - - let ui = user_interfaces - .get_mut(&id) - .expect("Get user interface"); - - let (ui_state, _) = ui.update( - &[redraw_event.clone()], - cursor, - &mut window.renderer, - &mut clipboard, - &mut messages, - ); - - let new_mouse_interaction = { - let state = &window.state; - - ui.draw( - &mut window.renderer, - state.theme(), - &renderer::Style { - icon_color: state.icon_color(), - text_color: state.text_color(), - scale_factor: state.scale_factor(), - }, - cursor, - ) - }; - platform_specific_handler.clear_subsurface_list(); - - if new_mouse_interaction != window.mouse_interaction { - if let Some(interaction) = - conversion::mouse_interaction(new_mouse_interaction) - { - if matches!( - window.mouse_interaction, - mouse::Interaction::Hide - ) { - window.raw.set_cursor_visible(true); - } - window.raw.set_cursor(interaction.into()) - } else { - window.raw.set_cursor_visible(false); - } - - window.mouse_interaction = new_mouse_interaction; - } - - // TODO once widgets can request to be redrawn, we can avoid always requesting a - // redraw - window.request_redraw(); - runtime.broadcast(subscription::Event::Interaction { - window: id, - event: redraw_event, - status: core::event::Status::Ignored, - }); - - let _ = control_sender.start_send(Control::ChangeFlow( - match ui_state { - user_interface::State::Updated { - redraw_request: Some(redraw_request), - } => match redraw_request { - window::RedrawRequest::NextFrame => { - window.request_redraw(); - - ControlFlow::Wait - } - window::RedrawRequest::At(at) => { - ControlFlow::WaitUntil(at) - } - }, - _ => ControlFlow::Wait, - }, - )); - } - - debug.draw_finished(); - } - - Event::Dnd(e) => { - match &e { - dnd::DndEvent::Offer(_, dnd::OfferEvent::Leave) => { - events.push((cur_dnd_surface, core::Event::Dnd(e))); - // XXX can't clear the dnd surface on leave because - // the data event comes after - // cur_dnd_surface = None; - } - dnd::DndEvent::Offer( - _, - dnd::OfferEvent::Enter { surface, .. }, - ) => { - let window_handle = surface.0.window_handle().ok(); - let window_id = window_manager.iter_mut().find_map( - |(id, window)| { - if window - .raw - .window_handle() - .ok() - .zip(window_handle) - .map(|(a, b)| a == b) - .unwrap_or_default() - { - Some(id) - } else { - None - } - }, - ); - cur_dnd_surface = window_id; - events.push((cur_dnd_surface, core::Event::Dnd(e))); - } - dnd::DndEvent::Offer(..) => { - events.push((cur_dnd_surface, core::Event::Dnd(e))); - } - dnd::DndEvent::Source(evt) => { - match evt { - dnd::SourceEvent::Finished | dnd::SourceEvent::Cancelled => { - dnd_surface = None; - } - _ => {} - } - for w in window_manager.ids() { - events.push((Some(w), core::Event::Dnd(e.clone()))); - } - } - }; - } - #[cfg(feature = "a11y")] - Event::Accessibility(id, e) => { - rebuild_a11y_tree = true; - match e.action { - iced_accessibility::accesskit::Action::Focus => { - // TODO send a command for this - } - _ => {} - } - events.push((Some(id), conversion::a11y(e))); - } - #[cfg(feature = "a11y")] - Event::AccessibilityEnabled(enabled) => { - a11y_enabled = enabled; - } - Event::PlatformSpecific(e) => { - crate::platform_specific::handle_event( - e, - &mut events, - &mut platform_specific_handler, - &program, - &mut compositor, - &mut window_manager, - &mut debug, - &mut user_interfaces, - &mut clipboard, - #[cfg(feature = "a11y")] - &mut adapters, - ); - } - _ => { - // log ignored events? - } - } - #[cfg(feature = "a11y")] - { - use iced_accessibility::{ - accesskit::{NodeBuilder, NodeId, Role, Tree, TreeUpdate}, - A11yId, A11yNode, A11yTree, - }; - if !a11y_enabled || !rebuild_a11y_tree { - continue; - } - - for id in window_manager.ids() { - let Some((a11y_id, adapter)) = adapters.get_mut(&id) else { - continue; - }; - let Some(window) = window_manager.get(id) else { - continue; - }; - let interface = - user_interfaces.get_mut(&id).expect("Get user interface"); - - // TODO cleanup duplication - let child_tree = interface.a11y_nodes(window.state.cursor()); - let mut root = NodeBuilder::new(Role::Window); - root.set_name(window.state.title.to_string()); - let window_tree = A11yTree::node_with_child_tree( - A11yNode::new(root, *a11y_id), - child_tree, - ); - let tree = Tree::new(NodeId(*a11y_id)); - - let focus = Arc::new(std::sync::Mutex::new(None)); - let focus_clone = focus.clone(); - let operation: Box> = - Box::new(operation::map( - Box::new(operation::focusable::find_focused()), - move |id| { - let mut guard = focus.lock().unwrap(); - _ = guard.replace(id); - }, - )); - let mut current_operation = Some(operation); - - while let Some(mut operation) = current_operation.take() { - interface.operate(&window.renderer, operation.as_mut()); - - match operation.finish() { - operation::Outcome::None => {} - operation::Outcome::Some(()) => { - break; - } - operation::Outcome::Chain(next) => { - current_operation = Some(next); - } - } - } - let mut guard = focus_clone.lock().unwrap(); - let focus = guard - .take() - .map(|id| A11yId::Widget(id)) - .filter(|f_id| window_tree.contains(f_id)); - tracing::debug!( - "tree root: {:?}\nchildren: {:?}\nfocus: {:?}\n", - window_tree - .root() - .iter() - .map(|n| (n.node(), n.id())) - .collect::>(), - window_tree - .children() - .iter() - .map(|n| (n.node(), n.id())) - .collect::>(), - &focus, - ); - let focus = - focus.map(|id| id.into()).unwrap_or_else(|| tree.root); - adapter.update_if_active(|| TreeUpdate { - nodes: window_tree.into(), - tree: Some(tree), - focus, - }); - } - } - } - - let _ = ManuallyDrop::into_inner(user_interfaces); -} - -/// Builds a window's [`UserInterface`] for the [`Program`]. -pub(crate) fn build_user_interface<'a, P: Program>( - program: &'a P, - cache: user_interface::Cache, - renderer: &mut P::Renderer, - size: Size, - debug: &mut Debug, - id: window::Id, - raw: Arc, - prev_dnd_destination_rectangles_count: usize, - clipboard: &mut Clipboard, -) -> UserInterface<'a, P::Message, P::Theme, P::Renderer> -where - P::Theme: DefaultStyle, -{ - debug.view_started(); - let view = program.view(id); - debug.view_finished(); - - debug.layout_started(); - let user_interface = UserInterface::build(view, size, cache, renderer); - debug.layout_finished(); - - let dnd_rectangles = user_interface - .dnd_rectangles(prev_dnd_destination_rectangles_count, renderer); - let new_dnd_rectangles_count = dnd_rectangles.as_ref().len(); - if new_dnd_rectangles_count > 0 || prev_dnd_destination_rectangles_count > 0 - { - clipboard.register_dnd_destination( - DndSurface(Arc::new(Box::new(raw.clone()))), - dnd_rectangles.into_rectangles(), - ); - } - - user_interface -} - -fn update( - program: &mut P, - runtime: &mut Runtime, Action>, - debug: &mut Debug, - messages: &mut Vec, -) where - P::Theme: DefaultStyle, -{ - for message in messages.drain(..) { - debug.log_message(&message); - debug.update_started(); - - let task = runtime.enter(|| program.update(message)); - debug.update_finished(); - - if let Some(stream) = runtime::task::into_stream(task) { - runtime.run(stream); - } - } - - let subscription = runtime.enter(|| program.subscription()); - runtime.track(subscription::into_recipes(subscription.map(Action::Output))); -} - -fn run_action( - action: Action, - program: &P, - compositor: &mut C, - events: &mut Vec<(Option, core::Event)>, - messages: &mut Vec, - clipboard: &mut Clipboard, - control_sender: &mut mpsc::UnboundedSender, - debug: &mut Debug, - interfaces: &mut FxHashMap< - window::Id, - UserInterface<'_, P::Message, P::Theme, P::Renderer>, - >, - window_manager: &mut WindowManager, - ui_caches: &mut FxHashMap, - is_window_opening: &mut bool, - platform_specific: &mut crate::platform_specific::PlatformSpecific, -) -> bool -where - P: Program, - C: Compositor + 'static, - P::Theme: DefaultStyle, -{ - use crate::runtime::clipboard; - use crate::runtime::system; - use crate::runtime::window; - - match action { - Action::Output(message) => { - messages.push(message); - } - Action::Clipboard(action) => match action { - clipboard::Action::Read { target, channel } => { - let _ = channel.send(clipboard.read(target)); - } - clipboard::Action::Write { target, contents } => { - clipboard.write(target, contents); - } - clipboard::Action::WriteData(contents, kind) => { - clipboard.write_data(kind, ClipboardStoreData(contents)) - } - clipboard::Action::ReadData(allowed, tx, kind) => { - let contents = clipboard.read_data(kind, allowed); - _ = tx.send(contents); - } - }, - Action::Window(action) => match action { - window::Action::Open(id, settings, channel) => { - let monitor = window_manager.last_monitor(); - - control_sender - .start_send(Control::CreateWindow { - id, - settings, - title: program.title(id), - monitor, - on_open: channel, - }) - .expect("Send control action"); - - *is_window_opening = true; - } - window::Action::Close(id) => { - let _ = ui_caches.remove(&id); - let _ = interfaces.remove(&id); - #[cfg(feature = "wayland")] - platform_specific - .send_wayland(platform_specific::Action::RemoveWindow(id)); - - if let Some(window) = window_manager.remove(id) { - clipboard.register_dnd_destination( - DndSurface(Arc::new(Box::new(window.raw.clone()))), - Vec::new(), - ); - let proxy = clipboard.proxy(); - if clipboard.window_id() == Some(window.raw.id()) { - *clipboard = window_manager - .first() - .map(|window| window.raw.clone()) - .zip(proxy) - .map(|(w, proxy)| { - Clipboard::connect( - w, - crate::clipboard::ControlSender { - sender: control_sender.clone(), - proxy, - }, - ) - }) - .unwrap_or_else(Clipboard::unconnected); - } - - events.push(( - Some(id), - core::Event::Window(core::window::Event::Closed), - )); - } - } - window::Action::GetOldest(channel) => { - let id = - window_manager.iter_mut().next().map(|(id, _window)| id); - - let _ = channel.send(id); - } - window::Action::GetLatest(channel) => { - let id = - window_manager.iter_mut().last().map(|(id, _window)| id); - - let _ = channel.send(id); - } - window::Action::Drag(id) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = window.raw.drag_window(); - } - } - window::Action::Resize(id, size) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = window.raw.request_surface_size( - winit::dpi::LogicalSize { - width: size.width, - height: size.height, - } - .into(), - ); - } - } - window::Action::GetSize(id, channel) => { - if let Some(window) = window_manager.get_mut(id) { - let size = window - .raw - .surface_size() - .to_logical(window.raw.scale_factor()); - - let _ = channel.send(Size::new(size.width, size.height)); - } - } - window::Action::GetMaximized(id, channel) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = channel.send(window.raw.is_maximized()); - } - } - window::Action::Maximize(id, maximized) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_maximized(maximized); - } - } - window::Action::GetMinimized(id, channel) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = channel.send(window.raw.is_minimized()); - } - } - window::Action::Minimize(id, minimized) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_minimized(minimized); - } - } - window::Action::GetPosition(id, channel) => { - if let Some(window) = window_manager.get(id) { - let position = window - .raw - .inner_position() - .map(|position| { - let position = position - .to_logical::(window.raw.scale_factor()); - - Point::new(position.x, position.y) - }) - .ok(); - - let _ = channel.send(position); - } - } - window::Action::GetScaleFactor(id, channel) => { - if let Some(window) = window_manager.get_mut(id) { - let scale_factor = window.raw.scale_factor(); - - let _ = channel.send(scale_factor as f32); - } - } - window::Action::Move(id, position) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_outer_position( - winit::dpi::LogicalPosition { - x: position.x, - y: position.y, - } - .into(), - ); - } - } - window::Action::ChangeMode(id, mode) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_visible(conversion::visible(mode)); - window.raw.set_fullscreen(conversion::fullscreen( - window.raw.current_monitor(), - mode, - )); - } - } - window::Action::ChangeIcon(id, icon) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_window_icon(conversion::icon(icon)); - } - } - window::Action::GetMode(id, channel) => { - if let Some(window) = window_manager.get_mut(id) { - let mode = if window.raw.is_visible().unwrap_or(true) { - conversion::mode(window.raw.fullscreen()) - } else { - core::window::Mode::Hidden - }; - - let _ = channel.send(mode); - } - } - window::Action::ToggleMaximize(id) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_maximized(!window.raw.is_maximized()); - } - } - window::Action::ToggleDecorations(id) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_decorations(!window.raw.is_decorated()); - } - } - window::Action::RequestUserAttention(id, attention_type) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.request_user_attention( - attention_type.map(conversion::user_attention), - ); - } - } - window::Action::GainFocus(id) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.focus_window(); - } - } - window::Action::ChangeLevel(id, level) => { - if let Some(window) = window_manager.get_mut(id) { - window - .raw - .set_window_level(conversion::window_level(level)); - } - } - window::Action::ShowSystemMenu(id) => { - if let Some(window) = window_manager.get_mut(id) { - if let mouse::Cursor::Available(point) = - window.state.cursor() - { - window.raw.show_window_menu( - winit::dpi::LogicalPosition { - x: point.x, - y: point.y, - } - .into(), - ); - } - } - } - window::Action::GetRawId(id, channel) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = channel.send(window.raw.id().into()); - } - } - window::Action::RunWithHandle(id, f) => { - use window::raw_window_handle::HasWindowHandle; - - if let Some(handle) = window_manager - .get_mut(id) - .and_then(|window| window.raw.window_handle().ok()) - { - f(handle); - } - } - window::Action::Screenshot(id, channel) => { - if let Some(window) = window_manager.get_mut(id) { - let bytes = compositor.screenshot( - &mut window.renderer, - window.state.viewport(), - window.state.background_color(), - &debug.overlay(), - ); - - let _ = channel.send(window::Screenshot::new( - bytes, - window.state.physical_size(), - window.state.viewport().scale_factor(), - )); - } - } - window::Action::EnableMousePassthrough(id) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = window.raw.set_cursor_hittest(false); - } - } - window::Action::DisableMousePassthrough(id) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = window.raw.set_cursor_hittest(true); - } - } - window::Action::EnableBlur(id) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_blur(true); - } - } - window::Action::DisableBlur(id) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_blur(false); - } - } - }, - Action::System(action) => match action { - system::Action::QueryInformation(_channel) => { - #[cfg(feature = "system")] - { - let graphics_info = compositor.fetch_information(); - - let _ = std::thread::spawn(move || { - let information = - crate::system::information(graphics_info); - - let _ = _channel.send(information); - }); - } - } - }, - Action::Widget(operation) => { - let mut current_operation = Some(operation); - - while let Some(mut operation) = current_operation.take() { - for (id, ui) in interfaces.iter_mut() { - if let Some(window) = window_manager.get_mut(*id) { - ui.operate(&window.renderer, operation.as_mut()); - } - } - - match operation.finish() { - operation::Outcome::None => {} - operation::Outcome::Some(()) => {} - operation::Outcome::Chain(next) => { - current_operation = Some(next); - } - } - } - } - Action::LoadFont { bytes, channel } => { - // TODO: Error handling (?) - compositor.load_font(bytes.clone()); - - let _ = channel.send(Ok(())); - } - Action::Exit => { - control_sender - .start_send(Control::Exit) - .expect("Send control action"); - return true; - } - Action::Dnd(a) => match a { - iced_runtime::dnd::DndAction::RegisterDndDestination { - surface, - rectangles, - } => { - clipboard.register_dnd_destination(surface, rectangles); - } - iced_runtime::dnd::DndAction::EndDnd => { - clipboard.end_dnd(); - } - iced_runtime::dnd::DndAction::PeekDnd(m, channel) => { - let data = clipboard.peek_dnd(m); - _ = channel.send(data); - } - iced_runtime::dnd::DndAction::SetAction(a) => { - clipboard.set_action(a); - } - }, - Action::PlatformSpecific(a) => { - platform_specific.send_action(a); - } - } - false -} - -/// Build the user interface for every window. -pub fn build_user_interfaces<'a, P: Program, C>( - program: &'a P, - debug: &mut Debug, - window_manager: &mut WindowManager, - mut cached_user_interfaces: FxHashMap, - clipboard: &mut Clipboard, -) -> FxHashMap> -where - C: Compositor, - P::Theme: DefaultStyle, -{ - cached_user_interfaces - .drain() - .filter_map(|(id, cache)| { - let window = window_manager.get_mut(id)?; - let interface = build_user_interface( - program, - cache, - &mut window.renderer, - window.state.logical_size(), - debug, - id, - window.raw.clone(), - window.prev_dnd_destination_rectangles_count, - clipboard, - ); - - let dnd_rectangles = interface.dnd_rectangles( - window.prev_dnd_destination_rectangles_count, - &window.renderer, - ); - let new_dnd_rectangles_count = dnd_rectangles.as_ref().len(); - if new_dnd_rectangles_count > 0 - || window.prev_dnd_destination_rectangles_count > 0 - { - clipboard.register_dnd_destination( - DndSurface(Arc::new(Box::new(window.raw.clone()))), - dnd_rectangles.into_rectangles(), - ); - } - - window.prev_dnd_destination_rectangles_count = - new_dnd_rectangles_count; - - Some((id, interface)) - }) - .collect() -} - -/// Returns true if the provided event should cause a [`Program`] to -/// exit. -pub fn user_force_quit( - event: &winit::event::WindowEvent, - _modifiers: winit::keyboard::ModifiersState, -) -> bool { - match event { - #[cfg(target_os = "macos")] - winit::event::WindowEvent::KeyboardInput { - event: - winit::event::KeyEvent { - logical_key: winit::keyboard::Key::Character(c), - state: winit::event::ElementState::Pressed, - .. - }, - .. - } if c == "q" && _modifiers.super_key() => true, - _ => false, - } -} diff --git a/winit/src/window.rs b/winit/src/window.rs index 8f61c1a6..804103d0 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -14,7 +14,7 @@ use crate::core::text; use crate::core::theme; use crate::core::time::Instant; use crate::core::{ - Color, InputMethod, Padding, Point, Rectangle, Size, Text, Vector, + Color, Element, InputMethod, Padding, Point, Rectangle, Size, Text, Vector, }; use crate::graphics::Compositor; use crate::program::{self, Program}; @@ -26,13 +26,17 @@ use winit::monitor::MonitorHandle; use std::collections::BTreeMap; use std::sync::Arc; +pub(crate) type ViewFn = Arc< + Box Option> + Send + Sync + 'static>, +>; + pub struct WindowManager where P: Program, C: Compositor, P::Theme: theme::Base, { - aliases: BTreeMap, + pub(crate) aliases: BTreeMap, entries: BTreeMap>, }