From eac1c4f3d7247d80e68cb827e906ba8063476596 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 10 Sep 2023 18:13:21 +0100 Subject: [PATCH 01/20] EventCx::size_cx, config_cx, draw_shared return instead of using a callback --- crates/kas-core/src/event/cx/cx_pub.rs | 47 +++++++++-------------- crates/kas-core/src/root.rs | 4 +- crates/kas-core/src/shell/common.rs | 9 +---- crates/kas-core/src/shell/window.rs | 7 +--- crates/kas-resvg/src/canvas.rs | 25 ++++++------ crates/kas-resvg/src/svg.rs | 26 ++++++------- crates/kas-view/src/filter/filter_list.rs | 4 +- crates/kas-view/src/list_view.rs | 8 ++-- crates/kas-view/src/matrix_view.rs | 8 ++-- crates/kas-widgets/src/spinner.rs | 4 +- crates/kas-widgets/src/splitter.rs | 2 +- crates/kas-widgets/src/tab_stack.rs | 2 +- 12 files changed, 62 insertions(+), 84 deletions(-) diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index 599938dac..83c33cd2f 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -617,7 +617,7 @@ impl<'a> EventCx<'a> { /// This is a shortcut to [`ConfigCx::configure`]. #[inline] pub fn configure(&mut self, mut widget: Node<'_>, id: WidgetId) { - self.config_cx(|cx| widget._configure(cx, id)); + widget._configure(&mut self.config_cx(), id); } /// Update a widget @@ -627,7 +627,7 @@ impl<'a> EventCx<'a> { /// data, it should call this (or [`ConfigCx::update`]) after mutating the state. #[inline] pub fn update(&mut self, mut widget: Node<'_>) { - self.config_cx(|cx| widget._update(cx)); + widget._update(&mut self.config_cx()); } /// Get the index of the last child visited @@ -849,38 +849,27 @@ impl<'a> EventCx<'a> { self.shell.adjust_theme(Box::new(f)); } - /// Access a [`SizeCx`] + /// Get a [`SizeCx`] /// /// Warning: sizes are calculated using the window's current scale factor. /// This may change, even without user action, since some platforms /// always initialize windows with scale factor 1. /// See also notes on [`Events::configure`]. - pub fn size_cx T, T>(&mut self, f: F) -> T { - let mut result = None; - self.shell.size_and_draw_shared(Box::new(|size, _| { - result = Some(f(SizeCx::new(size))); - })); - result.expect("ShellWindow::size_and_draw_shared impl failed to call function argument") - } - - /// Access a [`ConfigCx`] - pub fn config_cx T, T>(&mut self, f: F) -> T { - let mut result = None; - self.shell - .size_and_draw_shared(Box::new(|size, draw_shared| { - let mut cx = ConfigCx::new(size, draw_shared, self.state); - result = Some(f(&mut cx)); - })); - result.expect("ShellWindow::size_and_draw_shared impl failed to call function argument") - } - - /// Access a [`DrawShared`] - pub fn draw_shared T, T>(&mut self, f: F) -> T { - let mut result = None; - self.shell.size_and_draw_shared(Box::new(|_, draw_shared| { - result = Some(f(draw_shared)); - })); - result.expect("ShellWindow::size_and_draw_shared impl failed to call function argument") + // + // NOTE: we could implement this using only &self if required. + pub fn size_cx(&mut self) -> SizeCx<'_> { + SizeCx::new(self.shell.size_and_draw_shared().0) + } + + /// Get a [`ConfigCx`] + pub fn config_cx(&mut self) -> ConfigCx<'_> { + let (size, draw_shared) = self.shell.size_and_draw_shared(); + ConfigCx::new(size, draw_shared, self.state) + } + + /// Get a [`DrawShared`] + pub fn draw_shared(&mut self) -> &mut dyn DrawShared { + self.shell.size_and_draw_shared().1 } /// Directly access Winit Window diff --git a/crates/kas-core/src/root.rs b/crates/kas-core/src/root.rs index b599ba707..8c1645ad8 100644 --- a/crates/kas-core/src/root.rs +++ b/crates/kas-core/src/root.rs @@ -252,7 +252,7 @@ impl_scope! { fn handle_scroll(&mut self, cx: &mut EventCx, data: &Data, _: Scroll) { // Something was scrolled; update pop-up translations - cx.config_cx(|cx| self.resize_popups(cx, data)); + self.resize_popups(&mut cx.config_cx(), data); } } } @@ -403,7 +403,7 @@ impl Window { ) { let index = self.popups.len(); self.popups.push((id, popup, Offset::ZERO)); - cx.config_cx(|cx| self.resize_popup(cx, data, index)); + self.resize_popup(&mut cx.config_cx(), data, index); cx.send_action(Action::REDRAW); } diff --git a/crates/kas-core/src/shell/common.rs b/crates/kas-core/src/shell/common.rs index 4185bc579..e1b3c3db6 100644 --- a/crates/kas-core/src/shell/common.rs +++ b/crates/kas-core/src/shell/common.rs @@ -293,14 +293,7 @@ pub(crate) trait ShellWindow { fn adjust_theme<'s>(&'s mut self, f: Box Action + 's>); /// Access [`ThemeSize`] and [`DrawShared`] objects - /// - /// Implementations should call the given function argument once; not doing - /// so is memory-safe but will cause panics in `EventCx` methods. - /// User-code *must not* depend on `f` being called for memory safety. - fn size_and_draw_shared<'s>( - &'s mut self, - f: Box, - ); + fn size_and_draw_shared<'s>(&'s mut self) -> (&'s dyn ThemeSize, &'s mut dyn DrawShared); /// Set the mouse cursor fn set_cursor_icon(&mut self, icon: CursorIcon); diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 330a96c4f..3f41686bb 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -609,11 +609,8 @@ impl<'a, A: AppData, S: WindowSurface, T: Theme> ShellWindow for TkWi self.shared.pending.push(PendingAction::Action(action)); } - fn size_and_draw_shared<'s>( - &'s mut self, - f: Box, - ) { - f(self.window.theme_window.size(), &mut self.shared.draw); + fn size_and_draw_shared<'s>(&'s mut self) -> (&'s dyn ThemeSize, &'s mut dyn DrawShared) { + (self.window.theme_window.size(), &mut self.shared.draw) } #[inline] diff --git a/crates/kas-resvg/src/canvas.rs b/crates/kas-resvg/src/canvas.rs index 4fbdd0cf6..1b0a54cf3 100644 --- a/crates/kas-resvg/src/canvas.rs +++ b/crates/kas-resvg/src/canvas.rs @@ -191,24 +191,23 @@ impl_scope! { if let Some((program, mut pixmap)) = cx.try_pop::<(P, Pixmap)>() { debug_assert!(matches!(self.inner, State::Rendering)); let size = (pixmap.width(), pixmap.height()); + let ds = cx.draw_shared(); - cx.draw_shared(|ds| { - if let Some(im_size) = self.image.as_ref().and_then(|h| ds.image_size(h)) { - if im_size != Size::conv(size) { - if let Some(handle) = self.image.take() { - ds.image_free(handle); - } + if let Some(im_size) = self.image.as_ref().and_then(|h| ds.image_size(h)) { + if im_size != Size::conv(size) { + if let Some(handle) = self.image.take() { + ds.image_free(handle); } } + } - if self.image.is_none() { - self.image = ds.image_alloc(size).ok(); - } + if self.image.is_none() { + self.image = ds.image_alloc(size).ok(); + } - if let Some(handle) = self.image.as_ref() { - ds.image_upload(handle, pixmap.data(), ImageFormat::Rgba8); - } - }); + if let Some(handle) = self.image.as_ref() { + ds.image_upload(handle, pixmap.data(), ImageFormat::Rgba8); + } cx.redraw(self.id()); diff --git a/crates/kas-resvg/src/svg.rs b/crates/kas-resvg/src/svg.rs index 7f063c918..3f8e1c36b 100644 --- a/crates/kas-resvg/src/svg.rs +++ b/crates/kas-resvg/src/svg.rs @@ -259,23 +259,23 @@ impl_scope! { fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) { if let Some(pixmap) = cx.try_pop::() { let size = (pixmap.width(), pixmap.height()); - cx.draw_shared(|ds| { - if let Some(im_size) = self.image.as_ref().and_then(|h| ds.image_size(h)) { - if im_size != Size::conv(size) { - if let Some(handle) = self.image.take() { - ds.image_free(handle); - } + let ds = cx.draw_shared(); + + if let Some(im_size) = self.image.as_ref().and_then(|h| ds.image_size(h)) { + if im_size != Size::conv(size) { + if let Some(handle) = self.image.take() { + ds.image_free(handle); } } + } - if self.image.is_none() { - self.image = ds.image_alloc(size).ok(); - } + if self.image.is_none() { + self.image = ds.image_alloc(size).ok(); + } - if let Some(handle) = self.image.as_ref() { - ds.image_upload(handle, pixmap.data(), ImageFormat::Rgba8); - } - }); + if let Some(handle) = self.image.as_ref() { + ds.image_upload(handle, pixmap.data(), ImageFormat::Rgba8); + } cx.redraw(self.id()); let inner = std::mem::replace(&mut self.inner, State::None); diff --git a/crates/kas-view/src/filter/filter_list.rs b/crates/kas-view/src/filter/filter_list.rs index aa356895c..b215aa326 100644 --- a/crates/kas-view/src/filter/filter_list.rs +++ b/crates/kas-view/src/filter/filter_list.rs @@ -91,7 +91,7 @@ impl_scope! { impl Events for Self { fn handle_messages(&mut self, cx: &mut EventCx, data: &A) { if let Some(SetFilter(value)) = cx.try_pop() { - cx.config_cx(|cx| self.list.set_filter(cx, data, value)); + self.list.set_filter(&mut cx.config_cx(), data, value); } } } @@ -163,7 +163,7 @@ impl_scope! { fn handle_messages(&mut self, cx: &mut EventCx, data: &A) { if let Some(SetFilter(value)) = cx.try_pop() { - cx.config_cx(|cx| self.set_filter(cx, data, value)); + self.set_filter(&mut cx.config_cx(), data, value); } } } diff --git a/crates/kas-view/src/list_view.rs b/crates/kas-view/src/list_view.rs index 68cbe6ec9..2e6a93d03 100644 --- a/crates/kas-view/src/list_view.rs +++ b/crates/kas-view/src/list_view.rs @@ -659,7 +659,7 @@ impl_scope! { return if let Some(i_data) = data_index { // Set nav focus to i_data and update scroll position if self.scroll.focus_rect(cx, solver.rect(i_data), self.core.rect) { - cx.config_cx(|cx| self.update_widgets(cx, data)); + self.update_widgets(&mut cx.config_cx(), data); } let index = i_data % usize::conv(self.cur_len); cx.next_nav_focus(self.widgets[index].widget.id(), false, FocusSource::Key); @@ -704,7 +704,7 @@ impl_scope! { .scroll .scroll_by_event(cx, event, self.id(), self.core.rect); if moved { - cx.config_cx(|cx| self.update_widgets(cx, data)); + self.update_widgets(&mut cx.config_cx(), data); } is_used | used_by_sber } @@ -751,7 +751,7 @@ impl_scope! { fn handle_scroll(&mut self, cx: &mut EventCx, data: &A, scroll: Scroll) { self.scroll.scroll(cx, self.rect(), scroll); - cx.config_cx(|cx| self.update_widgets(cx, data)); + self.update_widgets(&mut cx.config_cx(), data); } } @@ -852,7 +852,7 @@ impl_scope! { }; if self.scroll.focus_rect(cx, solver.rect(data_index), self.core.rect) { - cx.config_cx(|cx| self.update_widgets(cx, data)); + self.update_widgets(&mut cx.config_cx(), data); } let index = data_index % usize::conv(self.cur_len); diff --git a/crates/kas-view/src/matrix_view.rs b/crates/kas-view/src/matrix_view.rs index 427c9d7a1..2dcfab15d 100644 --- a/crates/kas-view/src/matrix_view.rs +++ b/crates/kas-view/src/matrix_view.rs @@ -608,7 +608,7 @@ impl_scope! { return if let Some((ci, ri)) = data_index { // Set nav focus and update scroll position if self.scroll.focus_rect(cx, solver.rect(ci, ri), self.core.rect) { - solver = cx.config_cx(|cx| self.update_widgets(cx, data)); + solver = self.update_widgets(&mut cx.config_cx(), data); } let index = solver.data_to_child(ci, ri); @@ -671,7 +671,7 @@ impl_scope! { .scroll .scroll_by_event(cx, event, self.id(), self.core.rect); if moved { - cx.config_cx(|cx| self.update_widgets(cx, data)); + self.update_widgets(&mut cx.config_cx(), data); } is_used | used_by_sber } @@ -718,7 +718,7 @@ impl_scope! { fn handle_scroll(&mut self, cx: &mut EventCx, data: &A, scroll: Scroll) { self.scroll.scroll(cx, self.rect(), scroll); - cx.config_cx(|cx| self.update_widgets(cx, data)); + self.update_widgets(&mut cx.config_cx(), data); } } @@ -829,7 +829,7 @@ impl_scope! { }; if self.scroll.focus_rect(cx, solver.rect(ci, ri), self.core.rect) { - solver = cx.config_cx(|cx| self.update_widgets(cx, data)); + solver = self.update_widgets(&mut cx.config_cx(), data); } let index = solver.data_to_child(ci, ri); diff --git a/crates/kas-widgets/src/spinner.rs b/crates/kas-widgets/src/spinner.rs index 8206eb9b3..948eb9a83 100644 --- a/crates/kas-widgets/src/spinner.rs +++ b/crates/kas-widgets/src/spinner.rs @@ -112,7 +112,7 @@ impl SpinnerGuard { /// Returns new value if different fn handle_btn(&self, cx: &mut EventCx, data: &A, btn: SpinBtn) -> Option { - let old_value = cx.config_cx(|cx| (self.state_fn)(cx, data)); + let old_value = (self.state_fn)(&cx.config_cx(), data); let value = match btn { SpinBtn::Down => old_value.sub_step(self.step, self.start), SpinBtn::Up => old_value.add_step(self.step, self.end), @@ -134,7 +134,7 @@ impl EditGuard for SpinnerGuard { if let Some(value) = edit.guard.parsed.take() { cx.push(ValueMsg(value)); } else { - let value = cx.config_cx(|cx| (edit.guard.state_fn)(cx, data)); + let value = (edit.guard.state_fn)(&cx.config_cx(), data); *cx |= edit.set_string(value.to_string()); } } diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index 900d116c0..120f253c6 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -260,7 +260,7 @@ impl_scope! { let n = index >> 1; assert!(n < self.handles.len()); *cx |= self.handles[n].set_offset(offset).1; - cx.config_cx(|cx| self.adjust_size(cx, n)); + self.adjust_size(&mut cx.config_cx(), n); } } } diff --git a/crates/kas-widgets/src/tab_stack.rs b/crates/kas-widgets/src/tab_stack.rs index 25dfceb3e..f63164d27 100644 --- a/crates/kas-widgets/src/tab_stack.rs +++ b/crates/kas-widgets/src/tab_stack.rs @@ -193,7 +193,7 @@ impl_scope! { fn handle_messages(&mut self, cx: &mut EventCx, data: &W::Data) { if let Some(MsgSelectIndex(index)) = cx.try_pop() { - cx.config_cx(|cx| self.set_active(cx, data, index)); + self.set_active(&mut cx.config_cx(), data, index); if let Some(ref f) = self.on_change { let title = self.tabs[index].get_str(); f(cx, data, index, title); From aef004a0edf5fa26d55a3db54f9cad2c1fabe703 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 11 Sep 2023 14:38:34 +0100 Subject: [PATCH 02/20] Remove legacy of Action::Popup --- crates/kas-core/src/action.rs | 4 ---- crates/kas-core/src/shell/window.rs | 5 ----- 2 files changed, 9 deletions(-) diff --git a/crates/kas-core/src/action.rs b/crates/kas-core/src/action.rs index c272e94da..c810b624c 100644 --- a/crates/kas-core/src/action.rs +++ b/crates/kas-core/src/action.rs @@ -35,10 +35,6 @@ bitflags! { /// /// Implies window redraw. const REGION_MOVED = 1 << 4; - /* - /// A pop-up opened/closed/needs resizing - Popup, - */ /// Reset size of all widgets without recalculating requirements const SET_RECT = 1 << 8; /// Resize all widgets in the window diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 3f41686bb..0f4da2b73 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -301,11 +301,6 @@ impl> Window { } else if action.contains(Action::SET_RECT) { self.apply_size(shared, false); } - /*if action.contains(Action::Popup) { - let widget = &mut self.widget; - self.ev_state.with(&mut tkw, |cx| widget.resize_popups(cx)); - self.ev_state.region_moved(&mut *self.widget); - } else*/ if action.contains(Action::REGION_MOVED) { self.ev_state.region_moved(&mut self.widget, &shared.data); } From 396b6eec93ac812dc371323e8082c2ded5e44040 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 11 Sep 2023 15:09:40 +0100 Subject: [PATCH 03/20] Remove DrawShared from ConfigCx; add Platform to EventState --- crates/kas-core/src/draw/draw_shared.rs | 14 ++------------ crates/kas-core/src/event/cx/config.rs | 18 ++---------------- crates/kas-core/src/event/cx/cx_pub.rs | 9 +++++++-- crates/kas-core/src/event/cx/cx_shell.rs | 7 +++---- crates/kas-core/src/event/cx/mod.rs | 3 ++- crates/kas-core/src/shell/shared.rs | 2 +- crates/kas-core/src/shell/window.rs | 12 +++--------- crates/kas-core/src/theme/draw.rs | 4 ++-- 8 files changed, 22 insertions(+), 47 deletions(-) diff --git a/crates/kas-core/src/draw/draw_shared.rs b/crates/kas-core/src/draw/draw_shared.rs index 1295187cc..469c57488 100644 --- a/crates/kas-core/src/draw/draw_shared.rs +++ b/crates/kas-core/src/draw/draw_shared.rs @@ -9,7 +9,6 @@ use super::color::Rgba; use super::{DrawImpl, PassId}; use crate::cast::Cast; use crate::geom::{Quad, Rect, Size}; -use crate::shell::Platform; use crate::text::{Effect, TextDisplay}; use crate::theme::RasterConfig; use std::any::Any; @@ -76,15 +75,14 @@ pub struct AllocError; pub struct SharedState { /// The shell's [`DrawSharedImpl`] object pub draw: DS, - platform: Platform, } #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] impl SharedState { /// Construct (this is only called by the shell) - pub fn new(draw: DS, platform: Platform) -> Self { - SharedState { draw, platform } + pub fn new(draw: DS) -> Self { + SharedState { draw } } } @@ -92,9 +90,6 @@ impl SharedState { /// /// All methods concern management of resources for drawing. pub trait DrawShared { - /// Get the platform - fn platform(&self) -> Platform; - /// Allocate an image /// /// Use [`SharedState::image_upload`] to set contents of the new image. @@ -121,11 +116,6 @@ pub trait DrawShared { } impl DrawShared for SharedState { - #[inline] - fn platform(&self) -> Platform { - self.platform - } - #[inline] fn image_alloc(&mut self, size: (u32, u32)) -> Result { self.draw diff --git a/crates/kas-core/src/event/cx/config.rs b/crates/kas-core/src/event/cx/config.rs index 0130834ea..20c0cff74 100644 --- a/crates/kas-core/src/event/cx/config.rs +++ b/crates/kas-core/src/event/cx/config.rs @@ -6,11 +6,9 @@ //! Configuration context use super::Pending; -use crate::draw::DrawShared; use crate::event::EventState; use crate::geom::{Rect, Size}; use crate::layout::AlignPair; -use crate::shell::Platform; use crate::text::TextApi; use crate::theme::{Feature, SizeCx, TextClass, ThemeSize}; use crate::{Action, Node, WidgetId}; @@ -26,7 +24,6 @@ use std::ops::{Deref, DerefMut}; #[must_use] pub struct ConfigCx<'a> { sh: &'a dyn ThemeSize, - ds: &'a mut dyn DrawShared, pub(crate) ev: &'a mut EventState, } @@ -34,13 +31,8 @@ impl<'a> ConfigCx<'a> { /// Construct #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] - pub fn new(sh: &'a dyn ThemeSize, ds: &'a mut dyn DrawShared, ev: &'a mut EventState) -> Self { - ConfigCx { sh, ds, ev } - } - - /// Get the platform - pub fn platform(&self) -> Platform { - self.ds.platform() + pub fn new(sh: &'a dyn ThemeSize, ev: &'a mut EventState) -> Self { + ConfigCx { sh, ev } } /// Access a [`SizeCx`] @@ -49,12 +41,6 @@ impl<'a> ConfigCx<'a> { SizeCx::new(self.sh) } - /// Access [`DrawShared`] - #[inline] - pub fn draw_shared(&mut self) -> &mut dyn DrawShared { - self.ds - } - /// Access [`EventState`] #[inline] pub fn ev_state(&mut self) -> &mut EventState { diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index 83c33cd2f..86071dd5e 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -34,6 +34,11 @@ impl std::ops::BitOrAssign for EventState { /// Public API impl EventState { + /// Get the platform + pub fn platform(&self) -> Platform { + self.platform + } + /// True when the window has focus #[inline] pub fn window_has_focus(&self) -> bool { @@ -863,8 +868,8 @@ impl<'a> EventCx<'a> { /// Get a [`ConfigCx`] pub fn config_cx(&mut self) -> ConfigCx<'_> { - let (size, draw_shared) = self.shell.size_and_draw_shared(); - ConfigCx::new(size, draw_shared, self.state) + let (size, _) = self.shell.size_and_draw_shared(); + ConfigCx::new(size, self.state) } /// Get a [`DrawShared`] diff --git a/crates/kas-core/src/event/cx/cx_shell.rs b/crates/kas-core/src/event/cx/cx_shell.rs index 335dde4d8..4fdd4b89e 100644 --- a/crates/kas-core/src/event/cx/cx_shell.rs +++ b/crates/kas-core/src/event/cx/cx_shell.rs @@ -11,7 +11,6 @@ use std::time::{Duration, Instant}; use super::*; use crate::cast::traits::*; -use crate::draw::DrawShared; use crate::geom::{Coord, DVec2}; use crate::shell::ShellWindow; use crate::theme::ThemeSize; @@ -28,9 +27,10 @@ const FAKE_MOUSE_BUTTON: MouseButton = MouseButton::Other(0); impl EventState { /// Construct per-window event state #[inline] - pub(crate) fn new(config: WindowConfig) -> Self { + pub(crate) fn new(config: WindowConfig, platform: Platform) -> Self { EventState { config, + platform, disabled: vec![], window_has_focus: false, modifiers: ModifiersState::empty(), @@ -75,7 +75,6 @@ impl EventState { pub(crate) fn full_configure( &mut self, sizer: &dyn ThemeSize, - draw_shared: &mut dyn DrawShared, wid: WindowId, win: &mut Window, data: &A, @@ -91,7 +90,7 @@ impl EventState { self.new_access_layer(id.clone(), false); - ConfigCx::new(sizer, draw_shared, self).configure(win.as_node(data), id); + ConfigCx::new(sizer, self).configure(win.as_node(data), id); let hover = win.find_id(data, self.last_mouse_coord); self.set_hover(hover); diff --git a/crates/kas-core/src/event/cx/mod.rs b/crates/kas-core/src/event/cx/mod.rs index 64517cec7..3909e7607 100644 --- a/crates/kas-core/src/event/cx/mod.rs +++ b/crates/kas-core/src/event/cx/mod.rs @@ -21,7 +21,7 @@ use super::config::WindowConfig; use super::*; use crate::cast::Cast; use crate::geom::{Coord, Offset}; -use crate::shell::ShellWindow; +use crate::shell::{Platform, ShellWindow}; use crate::util::WidgetHierarchy; use crate::LayoutExt; use crate::{Action, Erased, ErasedStack, NavAdvance, Node, Widget, WidgetId, WindowId}; @@ -201,6 +201,7 @@ type AccessLayer = (bool, HashMap); // `SmallVec` is used to keep contents in local memory. pub struct EventState { config: WindowConfig, + platform: Platform, disabled: Vec, window_has_focus: bool, modifiers: ModifiersState, diff --git a/crates/kas-core/src/shell/shared.rs b/crates/kas-core/src/shell/shared.rs index 488dac6d3..e9ec55f3d 100644 --- a/crates/kas-core/src/shell/shared.rs +++ b/crates/kas-core/src/shell/shared.rs @@ -55,7 +55,7 @@ where config: Rc>, ) -> Result { let platform = pw.platform(); - let mut draw = kas::draw::SharedState::new(draw_shared, platform); + let mut draw = kas::draw::SharedState::new(draw_shared); theme.init(&mut draw); #[cfg(feature = "clipboard")] diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 0f4da2b73..a8cb877e2 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -62,7 +62,7 @@ impl> Window { _data: std::marker::PhantomData, widget, window_id, - ev_state: EventState::new(config), + ev_state: EventState::new(config, shared.shell.platform), window: None, } } @@ -90,7 +90,6 @@ impl> Window { self.ev_state.update_config(scale_factor, dpem); self.ev_state.full_configure( theme_window.size(), - &mut shared.shell.draw, self.window_id, &mut self.widget, &shared.data, @@ -369,7 +368,6 @@ impl> Window { self.ev_state.full_configure( window.theme_window.size(), - &mut shared.shell.draw, self.window_id, &mut self.widget, &shared.data, @@ -385,7 +383,7 @@ impl> Window { }; let size = window.theme_window.size(); - let mut cx = ConfigCx::new(&size, &mut shared.shell.draw, &mut self.ev_state); + let mut cx = ConfigCx::new(&size, &mut self.ev_state); cx.update(self.widget.as_node(&shared.data)); log::trace!(target: "kas_perf::wgpu::window", "update: {}µs", time.elapsed().as_micros()); @@ -400,11 +398,7 @@ impl> Window { log::debug!("apply_size: rect={rect:?}"); let solve_cache = &mut window.solve_cache; - let mut cx = ConfigCx::new( - window.theme_window.size(), - &mut shared.shell.draw, - &mut self.ev_state, - ); + let mut cx = ConfigCx::new(window.theme_window.size(), &mut self.ev_state); solve_cache.apply_rect(self.widget.as_node(&shared.data), &mut cx, rect, true); if first { solve_cache.print_widget_heirarchy(self.widget.as_layout()); diff --git a/crates/kas-core/src/theme/draw.rs b/crates/kas-core/src/theme/draw.rs index 74a764ef5..b4bf1d208 100644 --- a/crates/kas-core/src/theme/draw.rs +++ b/crates/kas-core/src/theme/draw.rs @@ -99,8 +99,8 @@ impl<'a> DrawCx<'a> { /// Access a [`ConfigCx`] pub fn config_cx T, T>(&mut self, f: F) -> T { - let (sh, draw, ev) = self.h.components(); - let mut cx = ConfigCx::new(sh, draw.shared(), ev); + let (sh, _, ev) = self.h.components(); + let mut cx = ConfigCx::new(sh, ev); f(&mut cx) } From 32ba78b05c8b70be06d1bc20c939075efb75ad97 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 11 Sep 2023 15:43:11 +0100 Subject: [PATCH 04/20] ShellShared::pending: use a FIFO queue --- crates/kas-core/src/shell/event_loop.rs | 2 +- crates/kas-core/src/shell/shared.rs | 12 ++++++------ crates/kas-core/src/shell/window.rs | 10 ++++++---- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/crates/kas-core/src/shell/event_loop.rs b/crates/kas-core/src/shell/event_loop.rs index b996f71ca..058863d2e 100644 --- a/crates/kas-core/src/shell/event_loop.rs +++ b/crates/kas-core/src/shell/event_loop.rs @@ -204,7 +204,7 @@ where elwt: &EventLoopWindowTarget, control_flow: &mut ControlFlow, ) { - while let Some(pending) = self.shared.shell.pending.pop() { + while let Some(pending) = self.shared.shell.pending.pop_front() { match pending { PendingAction::AddPopup(parent_id, id, popup) => { log::debug!("Pending: adding overlay"); diff --git a/crates/kas-core/src/shell/shared.rs b/crates/kas-core/src/shell/shared.rs index e9ec55f3d..c051a722c 100644 --- a/crates/kas-core/src/shell/shared.rs +++ b/crates/kas-core/src/shell/shared.rs @@ -5,9 +5,6 @@ //! Shared state -use std::num::NonZeroU32; -use std::task::Waker; - use super::{PendingAction, Platform, WindowSurface}; use kas::config::Options; use kas::shell::Error; @@ -15,7 +12,10 @@ use kas::theme::Theme; use kas::util::warn_about_error; use kas::{draw, AppData, ErasedStack, WindowId}; use std::cell::RefCell; +use std::collections::VecDeque; +use std::num::NonZeroU32; use std::rc::Rc; +use std::task::Waker; #[cfg(feature = "clipboard")] use arboard::Clipboard; @@ -26,7 +26,7 @@ pub(super) struct ShellShared { clipboard: Option, pub(super) draw: draw::SharedState, pub(super) theme: T, - pub(super) pending: Vec>, + pub(super) pending: VecDeque>, pub(super) waker: Waker, window_id: u32, } @@ -74,7 +74,7 @@ where clipboard, draw, theme, - pending: vec![], + pending: Default::default(), waker: pw.create_waker(), window_id: 0, }, @@ -89,7 +89,7 @@ where pub(crate) fn handle_messages(&mut self, messages: &mut ErasedStack) { if messages.reset_and_has_any() { let action = self.data.handle_messages(messages); - self.shell.pending.push(PendingAction::Action(action)); + self.shell.pending.push_back(PendingAction::Action(action)); } } diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index a8cb877e2..9af3f1cce 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -510,7 +510,7 @@ impl<'a, A: AppData, S: WindowSurface, T: Theme> ShellWindow for TkWi let id = self.shared.next_window_id(); self.shared .pending - .push(PendingAction::AddPopup(parent_id, id, popup)); + .push_back(PendingAction::AddPopup(parent_id, id, popup)); id } @@ -531,12 +531,14 @@ impl<'a, A: AppData, S: WindowSurface, T: Theme> ShellWindow for TkWi let id = self.shared.next_window_id(); self.shared .pending - .push(PendingAction::AddWindow(id, window)); + .push_back(PendingAction::AddWindow(id, window)); id } fn close_window(&mut self, id: WindowId) { - self.shared.pending.push(PendingAction::CloseWindow(id)); + self.shared + .pending + .push_back(PendingAction::CloseWindow(id)); } #[inline] @@ -595,7 +597,7 @@ impl<'a, A: AppData, S: WindowSurface, T: Theme> ShellWindow for TkWi fn adjust_theme<'s>(&'s mut self, f: Box Action + 's>) { let action = f(&mut self.shared.theme); - self.shared.pending.push(PendingAction::Action(action)); + self.shared.pending.push_back(PendingAction::Action(action)); } fn size_and_draw_shared<'s>(&'s mut self) -> (&'s dyn ThemeSize, &'s mut dyn DrawShared) { From f3862ad1cb71698880c2d662f34bffb7c888ef1e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 11 Sep 2023 15:45:05 +0100 Subject: [PATCH 05/20] Rename PendingAction -> Pending --- crates/kas-core/src/shell/event_loop.rs | 10 +++++----- crates/kas-core/src/shell/mod.rs | 12 ++++++------ crates/kas-core/src/shell/shared.rs | 6 +++--- crates/kas-core/src/shell/window.rs | 12 +++++------- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/crates/kas-core/src/shell/event_loop.rs b/crates/kas-core/src/shell/event_loop.rs index 058863d2e..aeea73491 100644 --- a/crates/kas-core/src/shell/event_loop.rs +++ b/crates/kas-core/src/shell/event_loop.rs @@ -12,7 +12,7 @@ use winit::event::{Event, StartCause}; use winit::event_loop::{ControlFlow, EventLoopWindowTarget}; use winit::window as ww; -use super::{PendingAction, SharedState}; +use super::{Pending, SharedState}; use super::{ProxyAction, Window, WindowSurface}; use kas::theme::Theme; use kas::{Action, AppData, WindowId}; @@ -206,7 +206,7 @@ where ) { while let Some(pending) = self.shared.shell.pending.pop_front() { match pending { - PendingAction::AddPopup(parent_id, id, popup) => { + Pending::AddPopup(parent_id, id, popup) => { log::debug!("Pending: adding overlay"); // TODO: support pop-ups as a special window, where available self.windows.get_mut(&parent_id).unwrap().add_popup( @@ -216,7 +216,7 @@ where ); self.popups.insert(id, parent_id); } - PendingAction::AddWindow(id, widget) => { + Pending::AddWindow(id, widget) => { log::debug!("Pending: adding window {}", widget.title()); let mut window = Window::new(&self.shared, id, widget); if !self.suspended { @@ -231,7 +231,7 @@ where } self.windows.insert(id, window); } - PendingAction::CloseWindow(target) => { + Pending::CloseWindow(target) => { let mut win_id = target; if let Some(id) = self.popups.remove(&target) { win_id = id; @@ -240,7 +240,7 @@ where window.send_close(&mut self.shared, target); } } - PendingAction::Action(action) => { + Pending::Action(action) => { if action.contains(Action::CLOSE | Action::EXIT) { self.windows.clear(); self.id_map.clear(); diff --git a/crates/kas-core/src/shell/mod.rs b/crates/kas-core/src/shell/mod.rs index 65536b189..9edb639fb 100644 --- a/crates/kas-core/src/shell/mod.rs +++ b/crates/kas-core/src/shell/mod.rs @@ -30,7 +30,7 @@ pub extern crate raw_window_handle; // around (also applies when constructing a shell::Window). #[allow(clippy::large_enum_variant)] #[cfg(winit)] -enum PendingAction { +enum Pending { AddPopup(WindowId, WindowId, kas::PopupDescriptor), AddWindow(WindowId, kas::Window), CloseWindow(WindowId), @@ -38,18 +38,18 @@ enum PendingAction { } #[cfg(winit)] -impl std::fmt::Debug for PendingAction { +impl std::fmt::Debug for Pending { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - PendingAction::AddPopup(parent_id, id, popup) => f + Pending::AddPopup(parent_id, id, popup) => f .debug_tuple("AddPopup") .field(&parent_id) .field(&id) .field(&popup) .finish(), - PendingAction::AddWindow(id, _) => f.debug_tuple("AddWindow").field(&id).finish(), - PendingAction::CloseWindow(id) => f.debug_tuple("CloseWindow").field(&id).finish(), - PendingAction::Action(action) => f.debug_tuple("Action").field(&action).finish(), + Pending::AddWindow(id, _) => f.debug_tuple("AddWindow").field(&id).finish(), + Pending::CloseWindow(id) => f.debug_tuple("CloseWindow").field(&id).finish(), + Pending::Action(action) => f.debug_tuple("Action").field(&action).finish(), } } } diff --git a/crates/kas-core/src/shell/shared.rs b/crates/kas-core/src/shell/shared.rs index c051a722c..b105d9c23 100644 --- a/crates/kas-core/src/shell/shared.rs +++ b/crates/kas-core/src/shell/shared.rs @@ -5,7 +5,7 @@ //! Shared state -use super::{PendingAction, Platform, WindowSurface}; +use super::{Pending, Platform, WindowSurface}; use kas::config::Options; use kas::shell::Error; use kas::theme::Theme; @@ -26,7 +26,7 @@ pub(super) struct ShellShared { clipboard: Option, pub(super) draw: draw::SharedState, pub(super) theme: T, - pub(super) pending: VecDeque>, + pub(super) pending: VecDeque>, pub(super) waker: Waker, window_id: u32, } @@ -89,7 +89,7 @@ where pub(crate) fn handle_messages(&mut self, messages: &mut ErasedStack) { if messages.reset_and_has_any() { let action = self.data.handle_messages(messages); - self.shell.pending.push_back(PendingAction::Action(action)); + self.shell.pending.push_back(Pending::Action(action)); } } diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 9af3f1cce..7bb7a6205 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -5,7 +5,7 @@ //! Window types -use super::{PendingAction, Platform, ProxyAction}; +use super::{Pending, Platform, ProxyAction}; use super::{SharedState, ShellShared, ShellWindow, WindowSurface}; use kas::cast::Cast; use kas::draw::{color::Rgba, AnimationState, DrawShared}; @@ -510,7 +510,7 @@ impl<'a, A: AppData, S: WindowSurface, T: Theme> ShellWindow for TkWi let id = self.shared.next_window_id(); self.shared .pending - .push_back(PendingAction::AddPopup(parent_id, id, popup)); + .push_back(Pending::AddPopup(parent_id, id, popup)); id } @@ -531,14 +531,12 @@ impl<'a, A: AppData, S: WindowSurface, T: Theme> ShellWindow for TkWi let id = self.shared.next_window_id(); self.shared .pending - .push_back(PendingAction::AddWindow(id, window)); + .push_back(Pending::AddWindow(id, window)); id } fn close_window(&mut self, id: WindowId) { - self.shared - .pending - .push_back(PendingAction::CloseWindow(id)); + self.shared.pending.push_back(Pending::CloseWindow(id)); } #[inline] @@ -597,7 +595,7 @@ impl<'a, A: AppData, S: WindowSurface, T: Theme> ShellWindow for TkWi fn adjust_theme<'s>(&'s mut self, f: Box Action + 's>) { let action = f(&mut self.shared.theme); - self.shared.pending.push_back(PendingAction::Action(action)); + self.shared.pending.push_back(Pending::Action(action)); } fn size_and_draw_shared<'s>(&'s mut self) -> (&'s dyn ThemeSize, &'s mut dyn DrawShared) { From 622b4597098a1aab0c4fa3b40fe8c338d23e603c Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 11 Sep 2023 15:54:05 +0100 Subject: [PATCH 06/20] Remove request_set_rect --- crates/kas-core/src/event/cx/cx_pub.rs | 5 ----- crates/kas-core/src/event/cx/cx_shell.rs | 4 ---- crates/kas-core/src/event/cx/mod.rs | 1 - 3 files changed, 10 deletions(-) diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index 86071dd5e..e9578ebe2 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -606,11 +606,6 @@ impl EventState { pub fn request_update(&mut self, id: WidgetId) { self.pending.push_back(Pending::Update(id)); } - - /// Request set_rect of the given path - pub fn request_set_rect(&mut self, id: WidgetId) { - self.pending.push_back(Pending::SetRect(id)); - } } /// Public API diff --git a/crates/kas-core/src/event/cx/cx_shell.rs b/crates/kas-core/src/event/cx/cx_shell.rs index 4fdd4b89e..1cd6e7424 100644 --- a/crates/kas-core/src/event/cx/cx_shell.rs +++ b/crates/kas-core/src/event/cx/cx_shell.rs @@ -228,10 +228,6 @@ impl EventState { } cx.send_event(win.as_node(data), id, event); } - Pending::SetRect(_id) => { - // TODO(opt): set only this child - cx.send_action(Action::SET_RECT); - } Pending::NextNavFocus { target, reverse, diff --git a/crates/kas-core/src/event/cx/mod.rs b/crates/kas-core/src/event/cx/mod.rs index 3909e7607..e3a882fd4 100644 --- a/crates/kas-core/src/event/cx/mod.rs +++ b/crates/kas-core/src/event/cx/mod.rs @@ -172,7 +172,6 @@ enum Pending { Configure(WidgetId), Update(WidgetId), Send(WidgetId, Event), - SetRect(WidgetId), NextNavFocus { target: Option, reverse: bool, From d525a05beb38f1d2eb99bf394a7e01f0127cedc3 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 11 Sep 2023 16:32:59 +0100 Subject: [PATCH 07/20] Simpler Debug impl for shell::Pending --- crates/kas-core/src/root.rs | 9 +++++++++ crates/kas-core/src/shell/mod.rs | 18 +----------------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/crates/kas-core/src/root.rs b/crates/kas-core/src/root.rs index 8c1645ad8..c31cfc1da 100644 --- a/crates/kas-core/src/root.rs +++ b/crates/kas-core/src/root.rs @@ -255,6 +255,15 @@ impl_scope! { self.resize_popups(&mut cx.config_cx(), data); } } + + impl std::fmt::Debug for Self { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Window") + .field("core", &self.core) + .field("title", &self.title_bar.title()) + .finish() + } + } } impl Window { diff --git a/crates/kas-core/src/shell/mod.rs b/crates/kas-core/src/shell/mod.rs index 9edb639fb..6922d60be 100644 --- a/crates/kas-core/src/shell/mod.rs +++ b/crates/kas-core/src/shell/mod.rs @@ -29,6 +29,7 @@ pub extern crate raw_window_handle; // TODO(opt): Clippy is probably right that we shouldn't copy a large value // around (also applies when constructing a shell::Window). #[allow(clippy::large_enum_variant)] +#[crate::autoimpl(Debug)] #[cfg(winit)] enum Pending { AddPopup(WindowId, WindowId, kas::PopupDescriptor), @@ -37,23 +38,6 @@ enum Pending { Action(kas::Action), } -#[cfg(winit)] -impl std::fmt::Debug for Pending { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Pending::AddPopup(parent_id, id, popup) => f - .debug_tuple("AddPopup") - .field(&parent_id) - .field(&id) - .field(&popup) - .finish(), - Pending::AddWindow(id, _) => f.debug_tuple("AddWindow").field(&id).finish(), - Pending::CloseWindow(id) => f.debug_tuple("CloseWindow").field(&id).finish(), - Pending::Action(action) => f.debug_tuple("Action").field(&action).finish(), - } - } -} - #[cfg(winit)] #[derive(Debug)] enum ProxyAction { From e51bb78a4d3252e145bf32264ce3786ebc11eab0 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 15 Sep 2023 10:37:00 +0100 Subject: [PATCH 08/20] Split ShellWindow::size_and_draw_shared, remove platform --- crates/kas-core/src/event/cx/cx_pub.rs | 10 ++++------ crates/kas-core/src/shell/common.rs | 10 +++++----- crates/kas-core/src/shell/window.rs | 14 +++++++------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index e9578ebe2..06f52371e 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -855,21 +855,19 @@ impl<'a> EventCx<'a> { /// This may change, even without user action, since some platforms /// always initialize windows with scale factor 1. /// See also notes on [`Events::configure`]. - // - // NOTE: we could implement this using only &self if required. - pub fn size_cx(&mut self) -> SizeCx<'_> { - SizeCx::new(self.shell.size_and_draw_shared().0) + pub fn size_cx(&self) -> SizeCx<'_> { + SizeCx::new(self.shell.theme_size()) } /// Get a [`ConfigCx`] pub fn config_cx(&mut self) -> ConfigCx<'_> { - let (size, _) = self.shell.size_and_draw_shared(); + let size = self.shell.theme_size(); ConfigCx::new(size, self.state) } /// Get a [`DrawShared`] pub fn draw_shared(&mut self) -> &mut dyn DrawShared { - self.shell.size_and_draw_shared().1 + self.shell.draw_shared() } /// Directly access Winit Window diff --git a/crates/kas-core/src/shell/common.rs b/crates/kas-core/src/shell/common.rs index e1b3c3db6..b0e3f6618 100644 --- a/crates/kas-core/src/shell/common.rs +++ b/crates/kas-core/src/shell/common.rs @@ -292,15 +292,15 @@ pub(crate) trait ShellWindow { // TODO(opt): pass f by value, not boxed fn adjust_theme<'s>(&'s mut self, f: Box Action + 's>); - /// Access [`ThemeSize`] and [`DrawShared`] objects - fn size_and_draw_shared<'s>(&'s mut self) -> (&'s dyn ThemeSize, &'s mut dyn DrawShared); + /// Access the [`ThemeSize`] object + fn theme_size(&self) -> &dyn ThemeSize; + + /// Access the [`DrawShared`] object + fn draw_shared(&mut self) -> &mut dyn DrawShared; /// Set the mouse cursor fn set_cursor_icon(&mut self, icon: CursorIcon); - /// Get the platform - fn platform(&self) -> Platform; - /// Directly access Winit Window /// /// This is a temporary API, allowing e.g. to minimize the window. diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 7bb7a6205..5cd7146c9 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -5,7 +5,7 @@ //! Window types -use super::{Pending, Platform, ProxyAction}; +use super::{Pending, ProxyAction}; use super::{SharedState, ShellShared, ShellWindow, WindowSurface}; use kas::cast::Cast; use kas::draw::{color::Rgba, AnimationState, DrawShared}; @@ -598,8 +598,12 @@ impl<'a, A: AppData, S: WindowSurface, T: Theme> ShellWindow for TkWi self.shared.pending.push_back(Pending::Action(action)); } - fn size_and_draw_shared<'s>(&'s mut self) -> (&'s dyn ThemeSize, &'s mut dyn DrawShared) { - (self.window.theme_window.size(), &mut self.shared.draw) + fn theme_size(&self) -> &dyn ThemeSize { + self.window.theme_window.size() + } + + fn draw_shared(&mut self) -> &mut dyn DrawShared { + &mut self.shared.draw } #[inline] @@ -607,10 +611,6 @@ impl<'a, A: AppData, S: WindowSurface, T: Theme> ShellWindow for TkWi self.window.set_cursor_icon(icon); } - fn platform(&self) -> Platform { - self.shared.platform - } - #[cfg(winit)] #[inline] fn winit_window(&self) -> Option<&winit::window::Window> { From 762e87c9534f92ecec64390d0d014ea01d4868a1 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 15 Sep 2023 11:40:02 +0100 Subject: [PATCH 09/20] Add trait ShellSharedErased --- crates/kas-core/src/shell/mod.rs | 2 +- crates/kas-core/src/shell/shared.rs | 140 +++++++++++++++++++++++++--- crates/kas-core/src/shell/window.rs | 48 +++------- 3 files changed, 141 insertions(+), 49 deletions(-) diff --git a/crates/kas-core/src/shell/mod.rs b/crates/kas-core/src/shell/mod.rs index 6922d60be..3e256c0a5 100644 --- a/crates/kas-core/src/shell/mod.rs +++ b/crates/kas-core/src/shell/mod.rs @@ -13,7 +13,7 @@ mod common; #[cfg(winit)] use crate::WindowId; #[cfg(winit)] use event_loop::Loop as EventLoop; -#[cfg(winit)] use shared::{SharedState, ShellShared}; +#[cfg(winit)] use shared::{SharedState, ShellSharedErased}; #[cfg(winit)] use shell::PlatformWrapper; #[cfg(winit)] use window::Window; diff --git a/crates/kas-core/src/shell/shared.rs b/crates/kas-core/src/shell/shared.rs index b105d9c23..63a08f49a 100644 --- a/crates/kas-core/src/shell/shared.rs +++ b/crates/kas-core/src/shell/shared.rs @@ -7,10 +7,12 @@ use super::{Pending, Platform, WindowSurface}; use kas::config::Options; +use kas::draw::DrawShared; use kas::shell::Error; -use kas::theme::Theme; +use kas::theme::{Theme, ThemeControl}; use kas::util::warn_about_error; -use kas::{draw, AppData, ErasedStack, WindowId}; +use kas::{draw, Action, AppData, ErasedStack, WindowId}; +use std::any::TypeId; use std::cell::RefCell; use std::collections::VecDeque; use std::num::NonZeroU32; @@ -20,7 +22,7 @@ use std::task::Waker; #[cfg(feature = "clipboard")] use arboard::Clipboard; /// Shell interface state -pub(super) struct ShellShared { +pub(super) struct ShellShared> { pub(super) platform: Platform, #[cfg(feature = "clipboard")] clipboard: Option, @@ -32,7 +34,7 @@ pub(super) struct ShellShared { } /// State shared between windows -pub struct SharedState { +pub struct SharedState> { pub(super) shell: ShellShared, pub(super) data: Data, pub(super) config: Rc>, @@ -104,7 +106,7 @@ where } } -impl ShellShared { +impl> ShellShared { /// Return the next window identifier /// /// TODO(opt): this should recycle used identifiers since WidgetId does not @@ -114,9 +116,110 @@ impl ShellShared { self.window_id = id; WindowId::new(NonZeroU32::new(id).unwrap()) } +} - #[inline] - pub fn get_clipboard(&mut self) -> Option { +pub(super) trait ShellSharedErased { + /// Add a pop-up + /// + /// A pop-up may be presented as an overlay layer in the current window or + /// via a new borderless window. + /// + /// Pop-ups support position hints: they are placed *next to* the specified + /// `rect`, preferably in the given `direction`. + /// + /// Returns `None` if window creation is not currently available (but note + /// that `Some` result does not guarantee the operation succeeded). + fn add_popup(&mut self, parent_id: WindowId, popup: crate::PopupDescriptor) -> WindowId; + + /// Add a window + /// + /// Toolkits typically allow windows to be added directly, before start of + /// the event loop (e.g. `kas_wgpu::Toolkit::add`). + /// + /// This method is an alternative allowing a window to be added from an + /// event handler, albeit without error handling. + /// + /// Safety: this method *should* require generic parameter `Data` (data type + /// passed to the `Shell`). Realising this would require adding this type + /// parameter to `EventCx` and thus to all widgets (not necessarily the + /// type accepted by the widget as input). As an alternative we require the + /// caller to type-cast `Window` to `Window<()>` and pass in + /// `TypeId::of::()`. + unsafe fn add_window(&mut self, window: kas::Window<()>, data_type_id: TypeId) -> WindowId; + + /// Close a window + fn close_window(&mut self, id: WindowId); + + /// Attempt to get clipboard contents + /// + /// In case of failure, paste actions will simply fail. The implementation + /// may wish to log an appropriate warning message. + fn get_clipboard(&mut self) -> Option; + + /// Attempt to set clipboard contents + fn set_clipboard(&mut self, content: String); + + /// Get contents of primary buffer + /// + /// Linux has a "primary buffer" with implicit copy on text selection and + /// paste on middle-click. This method does nothing on other platforms. + fn get_primary(&mut self) -> Option; + + /// Set contents of primary buffer + /// + /// Linux has a "primary buffer" with implicit copy on text selection and + /// paste on middle-click. This method does nothing on other platforms. + fn set_primary(&mut self, content: String); + + /// Adjust the theme + /// + /// Note: theme adjustments apply to all windows, as does the [`Action`] + /// returned from the closure. + // + // TODO(opt): pass f by value, not boxed + fn adjust_theme<'s>(&'s mut self, f: Box Action + 's>); + + /// Access the [`DrawShared`] object + fn draw_shared(&mut self) -> &mut dyn DrawShared; + + /// Access a Waker + fn waker(&self) -> &std::task::Waker; +} + +impl> ShellSharedErased + for ShellShared +{ + fn add_popup(&mut self, parent_id: WindowId, popup: kas::PopupDescriptor) -> WindowId { + let id = self.next_window_id(); + self.pending + .push_back(Pending::AddPopup(parent_id, id, popup)); + id + } + + unsafe fn add_window(&mut self, window: kas::Window<()>, data_type_id: TypeId) -> WindowId { + // Safety: the window should be `Window`. We cast to that. + if data_type_id != TypeId::of::() { + // If this fails it is not safe to add the window (though we could just return). + panic!("add_window: window has wrong Data type!"); + } + let window: kas::Window = std::mem::transmute(window); + + // By far the simplest way to implement this is to let our call + // anscestor, event::Loop::handle, do the work. + // + // In theory we could pass the EventLoopWindowTarget for *each* event + // handled to create the winit window here or use statics to generate + // errors now, but user code can't do much with this error anyway. + let id = self.next_window_id(); + self.pending.push_back(Pending::AddWindow(id, window)); + id + } + + fn close_window(&mut self, id: WindowId) { + self.pending.push_back(Pending::CloseWindow(id)); + } + + fn get_clipboard(&mut self) -> Option { #[cfg(feature = "clipboard")] { if let Some(cb) = self.clipboard.as_mut() { @@ -130,8 +233,7 @@ impl ShellShared { None } - #[inline] - pub fn set_clipboard(&mut self, _content: String) { + fn set_clipboard<'c>(&mut self, _content: String) { #[cfg(feature = "clipboard")] if let Some(cb) = self.clipboard.as_mut() { match cb.set_text(_content) { @@ -141,8 +243,7 @@ impl ShellShared { } } - #[inline] - pub fn get_primary(&mut self) -> Option { + fn get_primary(&mut self) -> Option { #[cfg(all( unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten")), @@ -161,8 +262,7 @@ impl ShellShared { None } - #[inline] - pub fn set_primary(&mut self, _content: String) { + fn set_primary(&mut self, _content: String) { #[cfg(all( unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten")), @@ -180,4 +280,18 @@ impl ShellShared { } } } + + fn adjust_theme<'s>(&'s mut self, f: Box Action + 's>) { + let action = f(&mut self.theme); + self.pending.push_back(Pending::Action(action)); + } + + fn draw_shared(&mut self) -> &mut dyn DrawShared { + &mut self.draw + } + + #[inline] + fn waker(&self) -> &std::task::Waker { + &self.waker + } } diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 5cd7146c9..287512e14 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -5,8 +5,8 @@ //! Window types -use super::{Pending, ProxyAction}; -use super::{SharedState, ShellShared, ShellWindow, WindowSurface}; +use super::ProxyAction; +use super::{SharedState, ShellSharedErased, ShellWindow, WindowSurface}; use kas::cast::Cast; use kas::draw::{color::Rgba, AnimationState, DrawShared}; use kas::event::{config::WindowConfig, ConfigCx, CursorIcon, EventState}; @@ -493,50 +493,29 @@ impl> Window { } } -struct TkWindow<'a, A: AppData, S: WindowSurface, T: Theme> { - shared: &'a mut ShellShared, +struct TkWindow<'a, S: WindowSurface, T: Theme> { + shared: &'a mut dyn ShellSharedErased, window: &'a WindowData, } -impl<'a, A: AppData, S: WindowSurface, T: Theme> TkWindow<'a, A, S, T> { - fn new(shared: &'a mut ShellShared, window: &'a WindowData) -> Self { +impl<'a, S: WindowSurface, T: Theme> TkWindow<'a, S, T> { + fn new(shared: &'a mut dyn ShellSharedErased, window: &'a WindowData) -> Self { TkWindow { shared, window } } } -impl<'a, A: AppData, S: WindowSurface, T: Theme> ShellWindow for TkWindow<'a, A, S, T> { +impl<'a, S: WindowSurface, T: Theme> ShellWindow for TkWindow<'a, S, T> { fn add_popup(&mut self, popup: kas::PopupDescriptor) -> WindowId { let parent_id = self.window.window_id; - let id = self.shared.next_window_id(); - self.shared - .pending - .push_back(Pending::AddPopup(parent_id, id, popup)); - id + self.shared.add_popup(parent_id, popup) } unsafe fn add_window(&mut self, window: kas::Window<()>, data_type_id: TypeId) -> WindowId { - // Safety: the window should be `Window`. We cast to that. - if data_type_id != TypeId::of::() { - // If this fails it is not safe to add the window (though we could just return). - panic!("add_window: window has wrong Data type!"); - } - let window: kas::Window = std::mem::transmute(window); - - // By far the simplest way to implement this is to let our call - // anscestor, event::Loop::handle, do the work. - // - // In theory we could pass the EventLoopWindowTarget for *each* event - // handled to create the winit window here or use statics to generate - // errors now, but user code can't do much with this error anyway. - let id = self.shared.next_window_id(); - self.shared - .pending - .push_back(Pending::AddWindow(id, window)); - id + self.shared.add_window(window, data_type_id) } fn close_window(&mut self, id: WindowId) { - self.shared.pending.push_back(Pending::CloseWindow(id)); + self.shared.close_window(id); } #[inline] @@ -594,8 +573,7 @@ impl<'a, A: AppData, S: WindowSurface, T: Theme> ShellWindow for TkWi } fn adjust_theme<'s>(&'s mut self, f: Box Action + 's>) { - let action = f(&mut self.shared.theme); - self.shared.pending.push_back(Pending::Action(action)); + self.shared.adjust_theme(f); } fn theme_size(&self) -> &dyn ThemeSize { @@ -603,7 +581,7 @@ impl<'a, A: AppData, S: WindowSurface, T: Theme> ShellWindow for TkWi } fn draw_shared(&mut self) -> &mut dyn DrawShared { - &mut self.shared.draw + self.shared.draw_shared() } #[inline] @@ -619,6 +597,6 @@ impl<'a, A: AppData, S: WindowSurface, T: Theme> ShellWindow for TkWi #[inline] fn waker(&self) -> &std::task::Waker { - &self.shared.waker + self.shared.waker() } } From bf26310abd8f600cbcb1f678a849f34010acf461 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 18 Sep 2023 12:00:08 +0200 Subject: [PATCH 10/20] Remove ShellWindow, add WindowDataErased, use directly in EventCx --- crates/kas-core/src/event/cx/cx_pub.rs | 51 +++++- crates/kas-core/src/event/cx/cx_shell.rs | 194 +++++++++++------------ crates/kas-core/src/event/cx/mod.rs | 7 +- crates/kas-core/src/event/cx/press.rs | 2 +- crates/kas-core/src/shell/common.rs | 91 +---------- crates/kas-core/src/shell/mod.rs | 7 +- crates/kas-core/src/shell/shared.rs | 20 ++- crates/kas-core/src/shell/window.rs | 157 ++++++------------ 8 files changed, 217 insertions(+), 312 deletions(-) diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index 06f52371e..44cb23d6f 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -15,6 +15,8 @@ use crate::draw::DrawShared; use crate::event::config::ChangeConfig; use crate::geom::{Offset, Vec2}; use crate::theme::{SizeCx, ThemeControl}; +#[cfg(all(wayland_platform, feature = "clipboard"))] +use crate::util::warn_about_error; use crate::{Action, Erased, WidgetId, Window, WindowId}; #[allow(unused)] use crate::{Events, Layout}; // for doc-links @@ -735,7 +737,8 @@ impl<'a> EventCx<'a> { pub(crate) fn add_popup(&mut self, popup: crate::PopupDescriptor) -> WindowId { log::trace!(target: "kas_core::event", "add_popup: {popup:?}"); - let id = self.shell.add_popup(popup.clone()); + let parent_id = self.window.window_id(); + let id = self.shell.add_popup(parent_id, popup.clone()); let nav_focus = self.nav_focus.clone(); self.popups.push((id, popup, nav_focus)); self.clear_nav_focus(); @@ -791,7 +794,7 @@ impl<'a> EventCx<'a> { /// This calls [`winit::window::Window::drag_window`](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.drag_window). Errors are ignored. pub fn drag_window(&self) { #[cfg(winit)] - if let Some(ww) = self.shell.winit_window() { + if let Some(ww) = self.window.winit_window() { if let Err(e) = ww.drag_window() { log::warn!("EventCx::drag_window: {e}"); } @@ -803,7 +806,7 @@ impl<'a> EventCx<'a> { /// This calls [`winit::window::Window::drag_resize_window`](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.drag_resize_window). Errors are ignored. pub fn drag_resize_window(&self, direction: ResizeDirection) { #[cfg(winit)] - if let Some(ww) = self.shell.winit_window() { + if let Some(ww) = self.window.winit_window() { if let Err(e) = ww.drag_resize_window(direction) { log::warn!("EventCx::drag_resize_window: {e}"); } @@ -816,12 +819,29 @@ impl<'a> EventCx<'a> { /// may wish to log an appropriate warning message. #[inline] pub fn get_clipboard(&mut self) -> Option { + #[cfg(all(wayland_platform, feature = "clipboard"))] + if let Some(cb) = self.window.wayland_clipboard() { + return match cb.load() { + Ok(s) => Some(s), + Err(e) => { + warn_about_error("Failed to get clipboard contents", &e); + None + } + }; + } + self.shell.get_clipboard() } /// Attempt to set clipboard contents #[inline] pub fn set_clipboard(&mut self, content: String) { + #[cfg(all(wayland_platform, feature = "clipboard"))] + if let Some(cb) = self.window.wayland_clipboard() { + cb.store(content); + return; + } + self.shell.set_clipboard(content) } @@ -831,6 +851,17 @@ impl<'a> EventCx<'a> { /// paste on middle-click. This method does nothing on other platforms. #[inline] pub fn get_primary(&mut self) -> Option { + #[cfg(all(wayland_platform, feature = "clipboard"))] + if let Some(cb) = self.window.wayland_clipboard() { + return match cb.load_primary() { + Ok(s) => Some(s), + Err(e) => { + warn_about_error("Failed to get clipboard contents", &e); + None + } + }; + } + self.shell.get_primary() } @@ -840,6 +871,12 @@ impl<'a> EventCx<'a> { /// paste on middle-click. This method does nothing on other platforms. #[inline] pub fn set_primary(&mut self, content: String) { + #[cfg(all(wayland_platform, feature = "clipboard"))] + if let Some(cb) = self.window.wayland_clipboard() { + cb.store_primary(content); + return; + } + self.shell.set_primary(content) } @@ -856,12 +893,12 @@ impl<'a> EventCx<'a> { /// always initialize windows with scale factor 1. /// See also notes on [`Events::configure`]. pub fn size_cx(&self) -> SizeCx<'_> { - SizeCx::new(self.shell.theme_size()) + SizeCx::new(self.window.theme_size()) } /// Get a [`ConfigCx`] pub fn config_cx(&mut self) -> ConfigCx<'_> { - let size = self.shell.theme_size(); + let size = self.window.theme_size(); ConfigCx::new(size, self.state) } @@ -875,7 +912,7 @@ impl<'a> EventCx<'a> { /// This is a temporary API, allowing e.g. to minimize the window. #[cfg(winit)] pub fn winit_window(&self) -> Option<&winit::window::Window> { - self.shell.winit_window() + self.window.winit_window() } /// Update the mouse cursor used during a grab @@ -886,7 +923,7 @@ impl<'a> EventCx<'a> { pub fn update_grab_cursor(&mut self, id: WidgetId, icon: CursorIcon) { if let Some(ref grab) = self.mouse_grab { if grab.start_id == id { - self.shell.set_cursor_icon(icon); + self.window.set_cursor_icon(icon); } } } diff --git a/crates/kas-core/src/event/cx/cx_shell.rs b/crates/kas-core/src/event/cx/cx_shell.rs index 1cd6e7424..cce9c378f 100644 --- a/crates/kas-core/src/event/cx/cx_shell.rs +++ b/crates/kas-core/src/event/cx/cx_shell.rs @@ -12,7 +12,6 @@ use std::time::{Duration, Instant}; use super::*; use crate::cast::traits::*; use crate::geom::{Coord, DVec2}; -use crate::shell::ShellWindow; use crate::theme::ThemeSize; use crate::{Action, NavAdvance, WidgetId, Window}; @@ -119,13 +118,17 @@ impl EventState { /// /// Invokes the given closure on this [`EventCx`]. #[inline] - pub(crate) fn with(&mut self, shell: &mut dyn ShellWindow, messages: &mut ErasedStack, f: F) - where - F: FnOnce(&mut EventCx), - { + pub(crate) fn with<'a, F: FnOnce(&mut EventCx)>( + &'a mut self, + shell: &'a mut dyn ShellSharedErased, + window: &'a dyn WindowDataErased, + messages: &'a mut ErasedStack, + f: F, + ) { let mut cx = EventCx { state: self, shell, + window, messages, last_child: None, scroll: Scroll::None, @@ -134,118 +137,113 @@ impl EventState { } /// Handle all pending items before event loop sleeps - pub(crate) fn flush_pending( - &mut self, - shell: &mut dyn ShellWindow, - messages: &mut ErasedStack, + pub(crate) fn flush_pending<'a, A>( + &'a mut self, + shell: &'a mut dyn ShellSharedErased, + window: &'a dyn WindowDataErased, + messages: &'a mut ErasedStack, win: &mut Window, data: &A, ) -> Action { let old_hover_icon = self.hover_icon; - let mut cx = EventCx { - state: self, - shell, - messages, - last_child: None, - scroll: Scroll::None, - }; - - while let Some((id, wid)) = cx.popup_removed.pop() { - cx.send_event(win.as_node(data), id, Event::PopupClosed(wid)); - } + self.with(shell, window, messages, |cx| { + while let Some((id, wid)) = cx.popup_removed.pop() { + cx.send_event(win.as_node(data), id, Event::PopupClosed(wid)); + } - cx.flush_mouse_grab_motion(win.as_node(data)); - for i in 0..cx.touch_grab.len() { - let action = cx.touch_grab[i].flush_click_move(); - cx.state.action |= action; - if let Some((id, event)) = cx.touch_grab[i].flush_grab_move() { - cx.send_event(win.as_node(data), id, event); + cx.flush_mouse_grab_motion(win.as_node(data)); + for i in 0..cx.touch_grab.len() { + let action = cx.touch_grab[i].flush_click_move(); + cx.state.action |= action; + if let Some((id, event)) = cx.touch_grab[i].flush_grab_move() { + cx.send_event(win.as_node(data), id, event); + } } - } - for gi in 0..cx.pan_grab.len() { - let grab = &mut cx.pan_grab[gi]; - debug_assert!(grab.mode != GrabMode::Grab); - assert!(grab.n > 0); - - // Terminology: pi are old coordinates, qi are new coords - let (p1, q1) = (DVec2::conv(grab.coords[0].0), DVec2::conv(grab.coords[0].1)); - grab.coords[0].0 = grab.coords[0].1; - - let alpha; - let delta; - - if grab.mode == GrabMode::PanOnly || grab.n == 1 { - alpha = DVec2(1.0, 0.0); - delta = q1 - p1; - } else { - // We don't use more than two touches: information would be - // redundant (although it could be averaged). - let (p2, q2) = (DVec2::conv(grab.coords[1].0), DVec2::conv(grab.coords[1].1)); - grab.coords[1].0 = grab.coords[1].1; - let (pd, qd) = (p2 - p1, q2 - q1); - - alpha = match grab.mode { - GrabMode::PanFull => qd.complex_div(pd), - GrabMode::PanScale => DVec2((qd.sum_square() / pd.sum_square()).sqrt(), 0.0), - GrabMode::PanRotate => { - let a = qd.complex_div(pd); - a / a.sum_square().sqrt() - } - _ => unreachable!(), - }; + for gi in 0..cx.pan_grab.len() { + let grab = &mut cx.pan_grab[gi]; + debug_assert!(grab.mode != GrabMode::Grab); + assert!(grab.n > 0); - // Average delta from both movements: - delta = (q1 - alpha.complex_mul(p1) + q2 - alpha.complex_mul(p2)) * 0.5; - } + // Terminology: pi are old coordinates, qi are new coords + let (p1, q1) = (DVec2::conv(grab.coords[0].0), DVec2::conv(grab.coords[0].1)); + grab.coords[0].0 = grab.coords[0].1; - let id = grab.id.clone(); - if alpha != DVec2(1.0, 0.0) || delta != DVec2::ZERO { - let event = Event::Pan { alpha, delta }; - cx.send_event(win.as_node(data), id, event); - } - } + let alpha; + let delta; - // Warning: infinite loops are possible here if widgets always queue a - // new pending event when evaluating one of these: - while let Some(item) = cx.pending.pop_front() { - log::trace!(target: "kas_core::event", "update: handling Pending::{item:?}"); - match item { - Pending::Configure(id) => { - win.as_node(data) - .find_node(&id, |node| cx.configure(node, id.clone())); - - let hover = win.find_id(data, cx.state.last_mouse_coord); - cx.state.set_hover(hover); - } - Pending::Update(id) => { - win.as_node(data).find_node(&id, |node| cx.update(node)); + if grab.mode == GrabMode::PanOnly || grab.n == 1 { + alpha = DVec2(1.0, 0.0); + delta = q1 - p1; + } else { + // We don't use more than two touches: information would be + // redundant (although it could be averaged). + let (p2, q2) = (DVec2::conv(grab.coords[1].0), DVec2::conv(grab.coords[1].1)); + grab.coords[1].0 = grab.coords[1].1; + let (pd, qd) = (p2 - p1, q2 - q1); + + alpha = match grab.mode { + GrabMode::PanFull => qd.complex_div(pd), + GrabMode::PanScale => { + DVec2((qd.sum_square() / pd.sum_square()).sqrt(), 0.0) + } + GrabMode::PanRotate => { + let a = qd.complex_div(pd); + a / a.sum_square().sqrt() + } + _ => unreachable!(), + }; + + // Average delta from both movements: + delta = (q1 - alpha.complex_mul(p1) + q2 - alpha.complex_mul(p2)) * 0.5; } - Pending::Send(id, event) => { - if matches!(&event, &Event::MouseHover(false)) { - cx.hover_icon = Default::default(); - } + + let id = grab.id.clone(); + if alpha != DVec2(1.0, 0.0) || delta != DVec2::ZERO { + let event = Event::Pan { alpha, delta }; cx.send_event(win.as_node(data), id, event); } - Pending::NextNavFocus { - target, - reverse, - source, - } => { - cx.next_nav_focus_impl(win.as_node(data), target, reverse, source); - } } - } - // Poll futures last. This means that any newly pushed future should - // get polled from the same update() call. - cx.poll_futures(win.as_node(data)); + // Warning: infinite loops are possible here if widgets always queue a + // new pending event when evaluating one of these: + while let Some(item) = cx.pending.pop_front() { + log::trace!(target: "kas_core::event", "update: handling Pending::{item:?}"); + match item { + Pending::Configure(id) => { + win.as_node(data) + .find_node(&id, |node| cx.configure(node, id.clone())); + + let hover = win.find_id(data, cx.state.last_mouse_coord); + cx.state.set_hover(hover); + } + Pending::Update(id) => { + win.as_node(data).find_node(&id, |node| cx.update(node)); + } + Pending::Send(id, event) => { + if matches!(&event, &Event::MouseHover(false)) { + cx.hover_icon = Default::default(); + } + cx.send_event(win.as_node(data), id, event); + } + Pending::NextNavFocus { + target, + reverse, + source, + } => { + cx.next_nav_focus_impl(win.as_node(data), target, reverse, source); + } + } + } - drop(cx); + // Poll futures last. This means that any newly pushed future should + // get polled from the same update() call. + cx.poll_futures(win.as_node(data)); + }); if self.hover_icon != old_hover_icon && self.mouse_grab.is_none() { - shell.set_cursor_icon(self.hover_icon); + window.set_cursor_icon(self.hover_icon); } std::mem::take(&mut self.action) diff --git a/crates/kas-core/src/event/cx/mod.rs b/crates/kas-core/src/event/cx/mod.rs index e3a882fd4..e9d1c6681 100644 --- a/crates/kas-core/src/event/cx/mod.rs +++ b/crates/kas-core/src/event/cx/mod.rs @@ -21,7 +21,7 @@ use super::config::WindowConfig; use super::*; use crate::cast::Cast; use crate::geom::{Coord, Offset}; -use crate::shell::{Platform, ShellWindow}; +use crate::shell::{Platform, ShellSharedErased, WindowDataErased}; use crate::util::WidgetHierarchy; use crate::LayoutExt; use crate::{Action, Erased, ErasedStack, NavAdvance, Node, Widget, WidgetId, WindowId}; @@ -418,7 +418,8 @@ impl EventState { #[must_use] pub struct EventCx<'a> { state: &'a mut EventState, - shell: &'a mut dyn ShellWindow, + shell: &'a mut dyn ShellSharedErased, + window: &'a dyn WindowDataErased, messages: &'a mut ErasedStack, last_child: Option, scroll: Scroll, @@ -543,7 +544,7 @@ impl<'a> EventCx<'a> { fn remove_mouse_grab(&mut self, success: bool) -> Option<(WidgetId, Event)> { if let Some(grab) = self.mouse_grab.take() { log::trace!("remove_mouse_grab: start_id={}", grab.start_id); - self.shell.set_cursor_icon(self.hover_icon); + self.window.set_cursor_icon(self.hover_icon); self.send_action(Action::REDRAW); // redraw(..) if grab.mode.is_pan() { self.remove_pan_grab(grab.pan_grab); diff --git a/crates/kas-core/src/event/cx/press.rs b/crates/kas-core/src/event/cx/press.rs index 1d72aa79e..e6c3dab8d 100644 --- a/crates/kas-core/src/event/cx/press.rs +++ b/crates/kas-core/src/event/cx/press.rs @@ -182,7 +182,7 @@ impl GrabBuilder { delta: Offset::ZERO, }); if let Some(icon) = cursor { - cx.shell.set_cursor_icon(icon); + cx.window.set_cursor_icon(icon); } } PressSource::Touch(touch_id) => { diff --git a/crates/kas-core/src/shell/common.rs b/crates/kas-core/src/shell/common.rs index b0e3f6618..fe277689a 100644 --- a/crates/kas-core/src/shell/common.rs +++ b/crates/kas-core/src/shell/common.rs @@ -6,13 +6,9 @@ //! Public shell stuff common to all backends use crate::draw::{color::Rgba, DrawIface, WindowCommon}; -use crate::draw::{DrawImpl, DrawShared, DrawSharedImpl}; -use crate::event::CursorIcon; +use crate::draw::{DrawImpl, DrawSharedImpl}; use crate::geom::Size; -use crate::theme::{ThemeControl, ThemeSize}; -use crate::{Action, Window, WindowId}; use raw_window_handle as raw; -use std::any::TypeId; use thiserror::Error; /// Possible failures from constructing a [`Shell`](super::Shell) @@ -225,88 +221,3 @@ pub trait WindowSurface { /// Present frame fn present(&mut self, shared: &mut Self::Shared, clear_color: Rgba); } - -/// Window management interface -/// -/// Note: previously, this was implemented by a dependent crate. Now, it is not, -/// which might suggest this trait is no longer needed, however `EventCx` still -/// needs type erasure over `S: WindowSurface` and `T: Theme`. -pub(crate) trait ShellWindow { - /// Add a pop-up - /// - /// A pop-up may be presented as an overlay layer in the current window or - /// via a new borderless window. - /// - /// Pop-ups support position hints: they are placed *next to* the specified - /// `rect`, preferably in the given `direction`. - /// - /// Returns `None` if window creation is not currently available (but note - /// that `Some` result does not guarantee the operation succeeded). - fn add_popup(&mut self, popup: crate::PopupDescriptor) -> WindowId; - - /// Add a window - /// - /// Toolkits typically allow windows to be added directly, before start of - /// the event loop (e.g. `kas_wgpu::Toolkit::add`). - /// - /// This method is an alternative allowing a window to be added from an - /// event handler, albeit without error handling. - /// - /// Safety: this method *should* require generic parameter `Data` (data type - /// passed to the `Shell`). Realising this would require adding this type - /// parameter to `EventCx` and thus to all widgets (not necessarily the - /// type accepted by the widget as input). As an alternative we require the - /// caller to type-cast `Window` to `Window<()>` and pass in - /// `TypeId::of::()`. - unsafe fn add_window(&mut self, window: Window<()>, data_type_id: TypeId) -> WindowId; - - /// Close a window - fn close_window(&mut self, id: WindowId); - - /// Attempt to get clipboard contents - /// - /// In case of failure, paste actions will simply fail. The implementation - /// may wish to log an appropriate warning message. - fn get_clipboard(&mut self) -> Option; - - /// Attempt to set clipboard contents - fn set_clipboard(&mut self, content: String); - - /// Get contents of primary buffer - /// - /// Linux has a "primary buffer" with implicit copy on text selection and - /// paste on middle-click. This method does nothing on other platforms. - fn get_primary(&mut self) -> Option; - - /// Set contents of primary buffer - /// - /// Linux has a "primary buffer" with implicit copy on text selection and - /// paste on middle-click. This method does nothing on other platforms. - fn set_primary(&mut self, content: String); - - /// Adjust the theme - /// - /// Note: theme adjustments apply to all windows, as does the [`Action`] - /// returned from the closure. - // - // TODO(opt): pass f by value, not boxed - fn adjust_theme<'s>(&'s mut self, f: Box Action + 's>); - - /// Access the [`ThemeSize`] object - fn theme_size(&self) -> &dyn ThemeSize; - - /// Access the [`DrawShared`] object - fn draw_shared(&mut self) -> &mut dyn DrawShared; - - /// Set the mouse cursor - fn set_cursor_icon(&mut self, icon: CursorIcon); - - /// Directly access Winit Window - /// - /// This is a temporary API, allowing e.g. to minimize the window. - #[cfg(winit)] - fn winit_window(&self) -> Option<&winit::window::Window>; - - /// Access a Waker - fn waker(&self) -> &std::task::Waker; -} diff --git a/crates/kas-core/src/shell/mod.rs b/crates/kas-core/src/shell/mod.rs index 3e256c0a5..58b8a05af 100644 --- a/crates/kas-core/src/shell/mod.rs +++ b/crates/kas-core/src/shell/mod.rs @@ -13,11 +13,12 @@ mod common; #[cfg(winit)] use crate::WindowId; #[cfg(winit)] use event_loop::Loop as EventLoop; -#[cfg(winit)] use shared::{SharedState, ShellSharedErased}; +#[cfg(winit)] +pub(crate) use shared::{SharedState, ShellSharedErased}; #[cfg(winit)] use shell::PlatformWrapper; -#[cfg(winit)] use window::Window; +#[cfg(winit)] +pub(crate) use window::{Window, WindowDataErased}; -pub(crate) use common::ShellWindow; pub use common::{Error, Platform, Result}; #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] pub use common::{GraphicalShell, WindowSurface}; diff --git a/crates/kas-core/src/shell/shared.rs b/crates/kas-core/src/shell/shared.rs index 63a08f49a..19a53d282 100644 --- a/crates/kas-core/src/shell/shared.rs +++ b/crates/kas-core/src/shell/shared.rs @@ -22,7 +22,7 @@ use std::task::Waker; #[cfg(feature = "clipboard")] use arboard::Clipboard; /// Shell interface state -pub(super) struct ShellShared> { +pub struct ShellShared> { pub(super) platform: Platform, #[cfg(feature = "clipboard")] clipboard: Option, @@ -118,7 +118,7 @@ impl> ShellShared Option; /// Attempt to set clipboard contents + /// + /// NOTE: on Wayland, use `WindowDataErased::wayland_clipboard` instead. + /// This split API probably can't be resolved until Winit integrates + /// clipboard support. fn set_clipboard(&mut self, content: String); /// Get contents of primary buffer /// /// Linux has a "primary buffer" with implicit copy on text selection and /// paste on middle-click. This method does nothing on other platforms. + /// + /// NOTE: on Wayland, use `WindowDataErased::wayland_clipboard` instead. + /// This split API probably can't be resolved until Winit integrates + /// clipboard support. fn get_primary(&mut self) -> Option; /// Set contents of primary buffer /// /// Linux has a "primary buffer" with implicit copy on text selection and /// paste on middle-click. This method does nothing on other platforms. + /// + /// NOTE: on Wayland, use `WindowDataErased::wayland_clipboard` instead. + /// This split API probably can't be resolved until Winit integrates + /// clipboard support. fn set_primary(&mut self, content: String); /// Adjust the theme diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 287512e14..817b3d7ae 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -6,18 +6,15 @@ //! Window types use super::ProxyAction; -use super::{SharedState, ShellSharedErased, ShellWindow, WindowSurface}; +use super::{SharedState, WindowSurface}; use kas::cast::Cast; -use kas::draw::{color::Rgba, AnimationState, DrawShared}; +use kas::draw::{color::Rgba, AnimationState}; use kas::event::{config::WindowConfig, ConfigCx, CursorIcon, EventState}; use kas::geom::{Coord, Rect, Size}; use kas::layout::SolveCache; -use kas::theme::{DrawCx, SizeCx, ThemeControl, ThemeSize}; +use kas::theme::{DrawCx, SizeCx, ThemeSize}; use kas::theme::{Theme, Window as _}; -#[cfg(all(wayland_platform, feature = "clipboard"))] -use kas::util::warn_about_error; use kas::{Action, AppData, ErasedStack, Layout, LayoutExt, Widget, WindowId}; -use std::any::TypeId; use std::mem::take; use std::time::Instant; use winit::event::WindowEvent; @@ -217,11 +214,11 @@ impl> Window { window.solve_cache.invalidate_rule_cache(); } event => { - let mut tkw = TkWindow::new(&mut shared.shell, window); let mut messages = ErasedStack::new(); - self.ev_state.with(&mut tkw, &mut messages, |cx| { - cx.handle_winit(&shared.data, &mut self.widget, event); - }); + self.ev_state + .with(&mut shared.shell, window, &mut messages, |cx| { + cx.handle_winit(&shared.data, &mut self.widget, event); + }); shared.handle_messages(&mut messages); if self.ev_state.action.contains(Action::RECONFIGURE) { @@ -241,11 +238,14 @@ impl> Window { let Some(ref window) = self.window else { return (Action::empty(), None); }; - let mut tkw = TkWindow::new(&mut shared.shell, window); let mut messages = ErasedStack::new(); - let action = - self.ev_state - .flush_pending(&mut tkw, &mut messages, &mut self.widget, &shared.data); + let action = self.ev_state.flush_pending( + &mut shared.shell, + window, + &mut messages, + &mut self.widget, + &shared.data, + ); shared.handle_messages(&mut messages); if action.contains(Action::CLOSE | Action::EXIT) { @@ -314,11 +314,12 @@ impl> Window { let Some(ref window) = self.window else { return None; }; - let mut tkw = TkWindow::new(&mut shared.shell, window); let widget = self.widget.as_node(&shared.data); let mut messages = ErasedStack::new(); self.ev_state - .with(&mut tkw, &mut messages, |cx| cx.update_timer(widget)); + .with(&mut shared.shell, window, &mut messages, |cx| { + cx.update_timer(widget) + }); shared.handle_messages(&mut messages); self.next_resume() } @@ -332,11 +333,11 @@ impl> Window { let Some(ref window) = self.window else { return; }; - let mut tkw = TkWindow::new(&mut shared.shell, window); let mut messages = ErasedStack::new(); - self.ev_state.with(&mut tkw, &mut messages, |cx| { - self.widget.add_popup(cx, &shared.data, id, popup) - }); + self.ev_state + .with(&mut shared.shell, window, &mut messages, |cx| { + self.widget.add_popup(cx, &shared.data, id, popup) + }); shared.handle_messages(&mut messages); } @@ -348,11 +349,12 @@ impl> Window { if id == self.window_id { self.ev_state.send_action(Action::CLOSE); } else if let Some(window) = self.window.as_ref() { - let mut tkw = TkWindow::new(&mut shared.shell, window); let widget = &mut self.widget; let mut messages = ErasedStack::new(); self.ev_state - .with(&mut tkw, &mut messages, |cx| widget.remove_popup(cx, id)); + .with(&mut shared.shell, window, &mut messages, |cx| { + widget.remove_popup(cx, id) + }); shared.handle_messages(&mut messages); } } @@ -493,110 +495,49 @@ impl> Window { } } -struct TkWindow<'a, S: WindowSurface, T: Theme> { - shared: &'a mut dyn ShellSharedErased, - window: &'a WindowData, -} - -impl<'a, S: WindowSurface, T: Theme> TkWindow<'a, S, T> { - fn new(shared: &'a mut dyn ShellSharedErased, window: &'a WindowData) -> Self { - TkWindow { shared, window } - } -} - -impl<'a, S: WindowSurface, T: Theme> ShellWindow for TkWindow<'a, S, T> { - fn add_popup(&mut self, popup: kas::PopupDescriptor) -> WindowId { - let parent_id = self.window.window_id; - self.shared.add_popup(parent_id, popup) - } - - unsafe fn add_window(&mut self, window: kas::Window<()>, data_type_id: TypeId) -> WindowId { - self.shared.add_window(window, data_type_id) - } - - fn close_window(&mut self, id: WindowId) { - self.shared.close_window(id); - } - - #[inline] - fn get_clipboard(&mut self) -> Option { - #[cfg(all(wayland_platform, feature = "clipboard"))] - if let Some(cb) = self.window.wayland_clipboard.as_ref() { - return match cb.load() { - Ok(s) => Some(s), - Err(e) => { - warn_about_error("Failed to get clipboard contents", &e); - None - } - }; - } - - self.shared.get_clipboard() - } +pub(crate) trait WindowDataErased { + /// Get the window identifier + fn window_id(&self) -> WindowId; - #[inline] - fn set_clipboard<'c>(&mut self, content: String) { - #[cfg(all(wayland_platform, feature = "clipboard"))] - if let Some(cb) = self.window.wayland_clipboard.as_ref() { - cb.store(content); - return; - } + /// Access the wayland clipboard object, if available + #[cfg(all(wayland_platform, feature = "clipboard"))] + fn wayland_clipboard(&self) -> Option<&smithay_clipboard::Clipboard>; - self.shared.set_clipboard(content); - } + /// Access the [`ThemeSize`] object + fn theme_size(&self) -> &dyn ThemeSize; - #[inline] - fn get_primary(&mut self) -> Option { - #[cfg(all(wayland_platform, feature = "clipboard"))] - if let Some(cb) = self.window.wayland_clipboard.as_ref() { - return match cb.load_primary() { - Ok(s) => Some(s), - Err(e) => { - warn_about_error("Failed to get clipboard contents", &e); - None - } - }; - } - - self.shared.get_primary() - } + /// Set the mouse cursor + fn set_cursor_icon(&self, icon: CursorIcon); - #[inline] - fn set_primary<'c>(&mut self, content: String) { - #[cfg(all(wayland_platform, feature = "clipboard"))] - if let Some(cb) = self.window.wayland_clipboard.as_ref() { - cb.store_primary(content); - return; - } + /// Directly access Winit Window + /// + /// This is a temporary API, allowing e.g. to minimize the window. + #[cfg(winit)] + fn winit_window(&self) -> Option<&winit::window::Window>; +} - self.shared.set_primary(content); +impl> WindowDataErased for WindowData { + fn window_id(&self) -> WindowId { + self.window_id } - fn adjust_theme<'s>(&'s mut self, f: Box Action + 's>) { - self.shared.adjust_theme(f); + #[cfg(all(wayland_platform, feature = "clipboard"))] + fn wayland_clipboard(&self) -> Option<&smithay_clipboard::Clipboard> { + self.wayland_clipboard.as_ref() } fn theme_size(&self) -> &dyn ThemeSize { - self.window.theme_window.size() - } - - fn draw_shared(&mut self) -> &mut dyn DrawShared { - self.shared.draw_shared() + self.theme_window.size() } #[inline] - fn set_cursor_icon(&mut self, icon: CursorIcon) { + fn set_cursor_icon(&self, icon: CursorIcon) { self.window.set_cursor_icon(icon); } #[cfg(winit)] #[inline] fn winit_window(&self) -> Option<&winit::window::Window> { - Some(self.window) - } - - #[inline] - fn waker(&self) -> &std::task::Waker { - self.shared.waker() + Some(&self.window) } } From 9f5edbc1dff15c1c1bb99bedc022ac35d78b0ea0 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 18 Sep 2023 16:29:33 +0200 Subject: [PATCH 11/20] Box the Window in shell::Pending This reduces Pending from 1544 to 32 bytes. --- crates/kas-core/src/shell/event_loop.rs | 12 +++++++----- crates/kas-core/src/shell/mod.rs | 6 ++++-- crates/kas-core/src/shell/shared.rs | 19 ++++++++++--------- crates/kas-core/src/shell/shell.rs | 4 ++-- crates/kas-core/src/shell/window.rs | 12 +++++++----- 5 files changed, 30 insertions(+), 23 deletions(-) diff --git a/crates/kas-core/src/shell/event_loop.rs b/crates/kas-core/src/shell/event_loop.rs index aeea73491..adf1b9483 100644 --- a/crates/kas-core/src/shell/event_loop.rs +++ b/crates/kas-core/src/shell/event_loop.rs @@ -25,7 +25,7 @@ where /// State is suspended until we receive Event::Resumed suspended: bool, /// Window states - windows: HashMap>, + windows: HashMap>>, popups: HashMap, /// Translates our WindowId to winit's id_map: HashMap, @@ -41,7 +41,10 @@ impl> Loop where T::Window: kas::theme::Window, { - pub(super) fn new(mut windows: Vec>, shared: SharedState) -> Self { + pub(super) fn new( + mut windows: Vec>>, + shared: SharedState, + ) -> Self { Loop { suspended: true, windows: windows.drain(..).map(|w| (w.window_id, w)).collect(), @@ -216,9 +219,8 @@ where ); self.popups.insert(id, parent_id); } - Pending::AddWindow(id, widget) => { - log::debug!("Pending: adding window {}", widget.title()); - let mut window = Window::new(&self.shared, id, widget); + Pending::AddWindow(id, mut window) => { + log::debug!("Pending: adding window {}", window.widget.title()); if !self.suspended { match window.resume(&mut self.shared, elwt) { Ok(winit_id) => { diff --git a/crates/kas-core/src/shell/mod.rs b/crates/kas-core/src/shell/mod.rs index 58b8a05af..f046b6f6a 100644 --- a/crates/kas-core/src/shell/mod.rs +++ b/crates/kas-core/src/shell/mod.rs @@ -32,9 +32,11 @@ pub extern crate raw_window_handle; #[allow(clippy::large_enum_variant)] #[crate::autoimpl(Debug)] #[cfg(winit)] -enum Pending { +enum Pending> { AddPopup(WindowId, WindowId, kas::PopupDescriptor), - AddWindow(WindowId, kas::Window), + // NOTE: we don't need S, T here if we construct the Window later. + // But this way we can pass a single boxed value. + AddWindow(WindowId, Box>), CloseWindow(WindowId), Action(kas::Action), } diff --git a/crates/kas-core/src/shell/shared.rs b/crates/kas-core/src/shell/shared.rs index 19a53d282..93a0bb4cb 100644 --- a/crates/kas-core/src/shell/shared.rs +++ b/crates/kas-core/src/shell/shared.rs @@ -22,22 +22,22 @@ use std::task::Waker; #[cfg(feature = "clipboard")] use arboard::Clipboard; /// Shell interface state -pub struct ShellShared> { +pub struct ShellShared> { pub(super) platform: Platform, + pub(super) config: Rc>, #[cfg(feature = "clipboard")] clipboard: Option, - pub(super) draw: draw::SharedState, + pub(super) draw: draw::SharedState, pub(super) theme: T, - pub(super) pending: VecDeque>, + pub(super) pending: VecDeque>, pub(super) waker: Waker, window_id: u32, } /// State shared between windows pub struct SharedState> { - pub(super) shell: ShellShared, + pub(super) shell: ShellShared, pub(super) data: Data, - pub(super) config: Rc>, /// Estimated scale factor (from last window constructed or available screens) pub(super) scale_factor: f64, options: Options, @@ -72,6 +72,7 @@ where Ok(SharedState { shell: ShellShared { platform, + config, #[cfg(feature = "clipboard")] clipboard, draw, @@ -81,7 +82,6 @@ where window_id: 0, }, data, - config, scale_factor: pw.guess_scale_factor(), options, }) @@ -98,7 +98,7 @@ where pub fn on_exit(&self) { match self .options - .write_config(&self.config.borrow(), &self.shell.theme) + .write_config(&self.shell.config.borrow(), &self.shell.theme) { Ok(()) => (), Err(error) => warn_about_error("Failed to save config", &error), @@ -106,7 +106,7 @@ where } } -impl> ShellShared { +impl> ShellShared { /// Return the next window identifier /// /// TODO(opt): this should recycle used identifiers since WidgetId does not @@ -202,7 +202,7 @@ pub trait ShellSharedErased { fn waker(&self) -> &std::task::Waker; } -impl> ShellSharedErased +impl> ShellSharedErased for ShellShared { fn add_popup(&mut self, parent_id: WindowId, popup: kas::PopupDescriptor) -> WindowId { @@ -227,6 +227,7 @@ impl> ShellSharedErased // handled to create the winit window here or use statics to generate // errors now, but user code can't do much with this error anyway. let id = self.next_window_id(); + let window = Box::new(super::Window::new(&self, id, window)); self.pending.push_back(Pending::AddWindow(id, window)); id } diff --git a/crates/kas-core/src/shell/shell.rs b/crates/kas-core/src/shell/shell.rs index 74cac5b03..d84486305 100644 --- a/crates/kas-core/src/shell/shell.rs +++ b/crates/kas-core/src/shell/shell.rs @@ -27,7 +27,7 @@ use winit::event_loop::{EventLoop, EventLoopBuilder, EventLoopProxy}; /// been initialised first. pub struct Shell> { el: EventLoop, - windows: Vec>, + windows: Vec>>, shared: SharedState, } @@ -156,7 +156,7 @@ where #[inline] pub fn add(&mut self, window: Window) -> WindowId { let id = self.shared.shell.next_window_id(); - let win = super::Window::new(&self.shared, id, window); + let win = Box::new(super::Window::new(&self.shared.shell, id, window)); self.windows.push(win); id } diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 817b3d7ae..29d2016af 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -5,8 +5,9 @@ //! Window types +use super::common::WindowSurface; +use super::shared::{SharedState, ShellShared}; use super::ProxyAction; -use super::{SharedState, WindowSurface}; use kas::cast::Cast; use kas::draw::{color::Rgba, AnimationState}; use kas::event::{config::WindowConfig, ConfigCx, CursorIcon, EventState}; @@ -14,7 +15,7 @@ use kas::geom::{Coord, Rect, Size}; use kas::layout::SolveCache; use kas::theme::{DrawCx, SizeCx, ThemeSize}; use kas::theme::{Theme, Window as _}; -use kas::{Action, AppData, ErasedStack, Layout, LayoutExt, Widget, WindowId}; +use kas::{autoimpl, Action, AppData, ErasedStack, Layout, LayoutExt, Widget, WindowId}; use std::mem::take; use std::time::Instant; use winit::event::WindowEvent; @@ -38,9 +39,10 @@ struct WindowData> { } /// Per-window data +#[autoimpl(Debug ignore self._data, self.widget, self.ev_state, self.window)] pub struct Window> { _data: std::marker::PhantomData, - widget: kas::Window, + pub(super) widget: kas::Window, pub(super) window_id: WindowId, ev_state: EventState, window: Option>, @@ -50,7 +52,7 @@ pub struct Window> { impl> Window { /// Construct window state (widget) pub(super) fn new( - shared: &SharedState, + shared: &ShellShared, window_id: WindowId, widget: kas::Window, ) -> Self { @@ -59,7 +61,7 @@ impl> Window { _data: std::marker::PhantomData, widget, window_id, - ev_state: EventState::new(config, shared.shell.platform), + ev_state: EventState::new(config, shared.platform), window: None, } } From 81a3d71397942ae847f58cbb0260a50298b6fa95 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 20 Sep 2023 10:08:02 +0200 Subject: [PATCH 12/20] Add test for size of shell::Pending The amount of mock code required is unfortunate --- crates/kas-core/src/shell/mod.rs | 218 +++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) diff --git a/crates/kas-core/src/shell/mod.rs b/crates/kas-core/src/shell/mod.rs index f046b6f6a..0ffdfc31f 100644 --- a/crates/kas-core/src/shell/mod.rs +++ b/crates/kas-core/src/shell/mod.rs @@ -49,3 +49,221 @@ enum ProxyAction { Message(kas::erased::SendErased), WakeAsync, } + +#[cfg(test)] +mod test { + use super::*; + + struct Draw; + impl crate::draw::DrawImpl for Draw { + fn common_mut(&mut self) -> &mut crate::draw::WindowCommon { + todo!() + } + + fn new_pass( + &mut self, + _: crate::draw::PassId, + _: crate::prelude::Rect, + _: crate::prelude::Offset, + _: crate::draw::PassType, + ) -> crate::draw::PassId { + todo!() + } + + fn get_clip_rect(&self, _: crate::draw::PassId) -> crate::prelude::Rect { + todo!() + } + + fn rect( + &mut self, + _: crate::draw::PassId, + _: crate::geom::Quad, + _: crate::draw::color::Rgba, + ) { + todo!() + } + + fn frame( + &mut self, + _: crate::draw::PassId, + _: crate::geom::Quad, + _: crate::geom::Quad, + _: crate::draw::color::Rgba, + ) { + todo!() + } + } + impl crate::draw::DrawRoundedImpl for Draw { + fn rounded_line( + &mut self, + _: crate::draw::PassId, + _: crate::geom::Vec2, + _: crate::geom::Vec2, + _: f32, + _: crate::draw::color::Rgba, + ) { + todo!() + } + + fn circle( + &mut self, + _: crate::draw::PassId, + _: crate::geom::Quad, + _: f32, + _: crate::draw::color::Rgba, + ) { + todo!() + } + + fn circle_2col( + &mut self, + _: crate::draw::PassId, + _: crate::geom::Quad, + _: crate::draw::color::Rgba, + _: crate::draw::color::Rgba, + ) { + todo!() + } + + fn rounded_frame( + &mut self, + _: crate::draw::PassId, + _: crate::geom::Quad, + _: crate::geom::Quad, + _: f32, + _: crate::draw::color::Rgba, + ) { + todo!() + } + + fn rounded_frame_2col( + &mut self, + _: crate::draw::PassId, + _: crate::geom::Quad, + _: crate::geom::Quad, + _: crate::draw::color::Rgba, + _: crate::draw::color::Rgba, + ) { + todo!() + } + } + + struct DrawShared; + impl crate::draw::DrawSharedImpl for DrawShared { + type Draw = Draw; + + fn set_raster_config(&mut self, _: &crate::theme::RasterConfig) { + todo!() + } + + fn image_alloc( + &mut self, + _: (u32, u32), + ) -> std::result::Result { + todo!() + } + + fn image_upload(&mut self, _: crate::draw::ImageId, _: &[u8], _: crate::draw::ImageFormat) { + todo!() + } + + fn image_free(&mut self, _: crate::draw::ImageId) { + todo!() + } + + fn image_size(&self, _: crate::draw::ImageId) -> Option<(u32, u32)> { + todo!() + } + + fn draw_image( + &self, + _: &mut Self::Draw, + _: crate::draw::PassId, + _: crate::draw::ImageId, + _: crate::geom::Quad, + ) { + todo!() + } + + fn draw_text( + &mut self, + _: &mut Self::Draw, + _: crate::draw::PassId, + _: crate::prelude::Rect, + _: &kas_text::TextDisplay, + _: crate::draw::color::Rgba, + ) { + todo!() + } + + fn draw_text_effects( + &mut self, + _: &mut Self::Draw, + _: crate::draw::PassId, + _: crate::prelude::Rect, + _: &kas_text::TextDisplay, + _: crate::draw::color::Rgba, + _: &[kas_text::Effect<()>], + ) { + todo!() + } + + fn draw_text_effects_rgba( + &mut self, + _: &mut Self::Draw, + _: crate::draw::PassId, + _: crate::prelude::Rect, + _: &kas_text::TextDisplay, + _: &[kas_text::Effect], + ) { + todo!() + } + } + + struct Surface; + impl WindowSurface for Surface { + type Shared = DrawShared; + + fn new( + _: &mut Self::Shared, + _: crate::prelude::Size, + _: W, + ) -> Result + where + Self: Sized, + { + todo!() + } + + fn size(&self) -> crate::prelude::Size { + todo!() + } + + fn do_resize(&mut self, _: &mut Self::Shared, _: crate::prelude::Size) -> bool { + todo!() + } + + fn draw_iface<'iface>( + &'iface mut self, + _: &'iface mut crate::draw::SharedState, + ) -> crate::draw::DrawIface<'iface, Self::Shared> { + todo!() + } + + fn common_mut(&mut self) -> &mut crate::draw::WindowCommon { + todo!() + } + + fn present(&mut self, _: &mut Self::Shared, _: crate::draw::color::Rgba) { + todo!() + } + } + + #[test] + fn size_of_pending() { + assert_eq!( + std::mem::size_of::>(), + 32 + ); + } +} From 9dd6466f09d69e69c17606b0403e4857bf3ed5c5 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 22 Sep 2023 16:12:32 +0200 Subject: [PATCH 13/20] Add enum GrabDetails --- crates/kas-core/src/event/cx/cx_shell.rs | 19 ++++++++++------- crates/kas-core/src/event/cx/mod.rs | 27 +++++++++++++++--------- crates/kas-core/src/event/cx/press.rs | 16 +++++++++----- 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/crates/kas-core/src/event/cx/cx_shell.rs b/crates/kas-core/src/event/cx/cx_shell.rs index cce9c378f..de7fba175 100644 --- a/crates/kas-core/src/event/cx/cx_shell.rs +++ b/crates/kas-core/src/event/cx/cx_shell.rs @@ -381,14 +381,17 @@ impl<'a> EventCx<'a> { self.set_hover(cur_id.clone()); if let Some(grab) = self.state.mouse_grab.as_mut() { - if !grab.mode.is_pan() { - grab.cur_id = cur_id; - grab.coord = coord; - grab.delta += delta; - } else if let Some(pan) = - self.state.pan_grab.get_mut(usize::conv(grab.pan_grab.0)) - { - pan.coords[usize::conv(grab.pan_grab.1)].1 = coord; + match grab.details { + GrabDetails::Click | GrabDetails::Grab => { + grab.cur_id = cur_id; + grab.coord = coord; + grab.delta += delta; + } + GrabDetails::Pan(g) => { + if let Some(pan) = self.state.pan_grab.get_mut(usize::conv(g.0)) { + pan.coords[usize::conv(g.1)].1 = coord; + } + } } } else if let Some(id) = self.popups.last().map(|(_, p, _)| p.id.clone()) { let press = Press { diff --git a/crates/kas-core/src/event/cx/mod.rs b/crates/kas-core/src/event/cx/mod.rs index e9d1c6681..09b328348 100644 --- a/crates/kas-core/src/event/cx/mod.rs +++ b/crates/kas-core/src/event/cx/mod.rs @@ -58,6 +58,13 @@ impl GrabMode { } } +#[derive(Clone, Debug)] +enum GrabDetails { + Click, + Grab, + Pan((u16, u16)), +} + #[derive(Clone, Debug)] struct MouseGrab { button: MouseButton, @@ -65,8 +72,7 @@ struct MouseGrab { start_id: WidgetId, cur_id: Option, depress: Option, - mode: GrabMode, - pan_grab: (u16, u16), + details: GrabDetails, coord: Coord, delta: Offset, } @@ -80,8 +86,8 @@ impl<'a> EventCx<'a> { } grab.delta = Offset::ZERO; - match grab.mode { - GrabMode::Click => { + match grab.details { + GrabDetails::Click => { if grab.start_id == grab.cur_id { if grab.depress != grab.cur_id { grab.depress = grab.cur_id.clone(); @@ -92,7 +98,7 @@ impl<'a> EventCx<'a> { self.action |= Action::REDRAW; } } - GrabMode::Grab => { + GrabDetails::Grab => { let target = grab.start_id.clone(); let press = Press { source: PressSource::Mouse(grab.button, grab.repetitions), @@ -286,9 +292,10 @@ impl EventState { log::trace!("remove_pan: index={index}"); self.pan_grab.remove(index); if let Some(grab) = &mut self.mouse_grab { - let p0 = grab.pan_grab.0; - if usize::from(p0) >= index && p0 != u16::MAX { - grab.pan_grab.0 = p0 - 1; + if let GrabDetails::Pan(ref mut g) = grab.details { + if usize::from(g.0) >= index { + g.0 -= 1; + } } } for grab in self.touch_grab.iter_mut() { @@ -546,8 +553,8 @@ impl<'a> EventCx<'a> { log::trace!("remove_mouse_grab: start_id={}", grab.start_id); self.window.set_cursor_icon(self.hover_icon); self.send_action(Action::REDRAW); // redraw(..) - if grab.mode.is_pan() { - self.remove_pan_grab(grab.pan_grab); + if let GrabDetails::Pan(g) = grab.details { + self.remove_pan_grab(g); // Pan grabs do not receive Event::PressEnd None } else { diff --git a/crates/kas-core/src/event/cx/press.rs b/crates/kas-core/src/event/cx/press.rs index e6c3dab8d..6d23b4da4 100644 --- a/crates/kas-core/src/event/cx/press.rs +++ b/crates/kas-core/src/event/cx/press.rs @@ -7,6 +7,7 @@ #[allow(unused)] use super::{Event, EventState}; // for doc-links use super::{EventCx, GrabMode, IsUsed, MouseGrab, Pending, TouchGrab}; +use crate::event::cx::GrabDetails; use crate::event::{CursorIcon, MouseButton, Used}; use crate::geom::{Coord, Offset}; use crate::{Action, WidgetId}; @@ -167,17 +168,22 @@ impl GrabBuilder { if let Some((id, event)) = cx.remove_mouse_grab(false) { cx.pending.push_back(Pending::Send(id, event)); } - if mode.is_pan() { - pan_grab = cx.set_pan_on(id.clone(), mode, false, coord); - } + let details = match mode { + GrabMode::Click => GrabDetails::Click, + GrabMode::Grab => GrabDetails::Grab, + mode => { + assert!(mode.is_pan()); + let g = cx.set_pan_on(id.clone(), mode, false, coord); + GrabDetails::Pan(g) + } + }; cx.mouse_grab = Some(MouseGrab { button, repetitions, start_id: id.clone(), cur_id: Some(id.clone()), depress: Some(id), - mode, - pan_grab, + details, coord, delta: Offset::ZERO, }); From 2bdb44e6407d72c4f2daa1d44281e151f646a365 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 22 Sep 2023 16:25:56 +0200 Subject: [PATCH 14/20] Pass Event::PressMove for mouse input immediately This is a partial revert of 2d8e68f3 which has been half broken since due to frequent clearing of pending events. It's also the wrong solution since it decreases precision of momentum scrolling algorithms. --- crates/kas-core/src/event/cx/cx_shell.rs | 34 +++++++++++++----------- crates/kas-core/src/event/cx/mod.rs | 33 +++++------------------ crates/kas-core/src/event/cx/press.rs | 9 +++---- 3 files changed, 30 insertions(+), 46 deletions(-) diff --git a/crates/kas-core/src/event/cx/cx_shell.rs b/crates/kas-core/src/event/cx/cx_shell.rs index de7fba175..b4b5a618b 100644 --- a/crates/kas-core/src/event/cx/cx_shell.rs +++ b/crates/kas-core/src/event/cx/cx_shell.rs @@ -152,7 +152,7 @@ impl EventState { cx.send_event(win.as_node(data), id, Event::PopupClosed(wid)); } - cx.flush_mouse_grab_motion(win.as_node(data)); + cx.flush_mouse_grab_motion(); for i in 0..cx.touch_grab.len() { let action = cx.touch_grab[i].flush_click_move(); cx.state.action |= action; @@ -376,16 +376,24 @@ impl<'a> EventCx<'a> { let coord = position.cast_approx(); // Update hovered win - let cur_id = win.find_id(data, coord); - let delta = coord - self.last_mouse_coord; - self.set_hover(cur_id.clone()); + let id = win.find_id(data, coord); + self.set_hover(id.clone()); if let Some(grab) = self.state.mouse_grab.as_mut() { match grab.details { - GrabDetails::Click | GrabDetails::Grab => { - grab.cur_id = cur_id; - grab.coord = coord; - grab.delta += delta; + GrabDetails::Click { ref mut cur_id } => { + *cur_id = id; + } + GrabDetails::Grab => { + let target = grab.start_id.clone(); + let press = Press { + source: PressSource::Mouse(grab.button, grab.repetitions), + id, + coord, + }; + let delta = coord - self.last_mouse_coord; + let event = Event::PressMove { press, delta }; + self.send_event(win.as_node(data), target, event); } GrabDetails::Pan(g) => { if let Some(pan) = self.state.pan_grab.get_mut(usize::conv(g.0)) { @@ -393,14 +401,14 @@ impl<'a> EventCx<'a> { } } } - } else if let Some(id) = self.popups.last().map(|(_, p, _)| p.id.clone()) { + } else if let Some(popup_id) = self.popups.last().map(|(_, p, _)| p.id.clone()) { let press = Press { source: PressSource::Mouse(FAKE_MOUSE_BUTTON, 0), - id: cur_id, + id, coord, }; let event = Event::CursorMove { press }; - self.send_event(win.as_node(data), id, event); + self.send_event(win.as_node(data), popup_id, event); } else { // We don't forward move events without a grab } @@ -419,8 +427,6 @@ impl<'a> EventCx<'a> { } } MouseWheel { delta, .. } => { - self.flush_mouse_grab_motion(win.as_node(data)); - self.last_click_button = FAKE_MOUSE_BUTTON; let event = Event::Scroll(match delta { @@ -437,8 +443,6 @@ impl<'a> EventCx<'a> { } } MouseInput { state, button, .. } => { - self.flush_mouse_grab_motion(win.as_node(data)); - let coord = self.last_mouse_coord; if state == ElementState::Pressed { diff --git a/crates/kas-core/src/event/cx/mod.rs b/crates/kas-core/src/event/cx/mod.rs index 09b328348..ee6d1ae73 100644 --- a/crates/kas-core/src/event/cx/mod.rs +++ b/crates/kas-core/src/event/cx/mod.rs @@ -20,7 +20,7 @@ use std::u16; use super::config::WindowConfig; use super::*; use crate::cast::Cast; -use crate::geom::{Coord, Offset}; +use crate::geom::Coord; use crate::shell::{Platform, ShellSharedErased, WindowDataErased}; use crate::util::WidgetHierarchy; use crate::LayoutExt; @@ -60,7 +60,7 @@ impl GrabMode { #[derive(Clone, Debug)] enum GrabDetails { - Click, + Click { cur_id: Option }, Grab, Pan((u16, u16)), } @@ -70,27 +70,18 @@ struct MouseGrab { button: MouseButton, repetitions: u32, start_id: WidgetId, - cur_id: Option, depress: Option, details: GrabDetails, - coord: Coord, - delta: Offset, } impl<'a> EventCx<'a> { - fn flush_mouse_grab_motion(&mut self, widget: Node<'_>) { + fn flush_mouse_grab_motion(&mut self) { if let Some(grab) = self.mouse_grab.as_mut() { - let delta = grab.delta; - if delta == Offset::ZERO { - return; - } - grab.delta = Offset::ZERO; - match grab.details { - GrabDetails::Click => { - if grab.start_id == grab.cur_id { - if grab.depress != grab.cur_id { - grab.depress = grab.cur_id.clone(); + GrabDetails::Click { ref cur_id } => { + if grab.start_id == cur_id { + if grab.depress != *cur_id { + grab.depress = cur_id.clone(); self.action |= Action::REDRAW; } } else if grab.depress.is_some() { @@ -98,16 +89,6 @@ impl<'a> EventCx<'a> { self.action |= Action::REDRAW; } } - GrabDetails::Grab => { - let target = grab.start_id.clone(); - let press = Press { - source: PressSource::Mouse(grab.button, grab.repetitions), - id: grab.cur_id.clone(), - coord: grab.coord, - }; - let event = Event::PressMove { press, delta }; - self.send_event(widget, target, event); - } _ => (), } } diff --git a/crates/kas-core/src/event/cx/press.rs b/crates/kas-core/src/event/cx/press.rs index 6d23b4da4..eb9a660fd 100644 --- a/crates/kas-core/src/event/cx/press.rs +++ b/crates/kas-core/src/event/cx/press.rs @@ -9,7 +9,7 @@ use super::{EventCx, GrabMode, IsUsed, MouseGrab, Pending, TouchGrab}; use crate::event::cx::GrabDetails; use crate::event::{CursorIcon, MouseButton, Used}; -use crate::geom::{Coord, Offset}; +use crate::geom::Coord; use crate::{Action, WidgetId}; /// Source of `EventChild::Press` @@ -169,7 +169,9 @@ impl GrabBuilder { cx.pending.push_back(Pending::Send(id, event)); } let details = match mode { - GrabMode::Click => GrabDetails::Click, + GrabMode::Click => GrabDetails::Click { + cur_id: Some(id.clone()), + }, GrabMode::Grab => GrabDetails::Grab, mode => { assert!(mode.is_pan()); @@ -181,11 +183,8 @@ impl GrabBuilder { button, repetitions, start_id: id.clone(), - cur_id: Some(id.clone()), depress: Some(id), details, - coord, - delta: Offset::ZERO, }); if let Some(icon) = cursor { cx.window.set_cursor_icon(icon); From 1d0a1da75d3249026ab266387060e01794b17f91 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 25 Sep 2023 11:40:56 +0200 Subject: [PATCH 15/20] Pass Event::PressMove for touch input immediately --- crates/kas-core/src/event/cx/cx_shell.rs | 19 +++++++++++++------ crates/kas-core/src/event/cx/mod.rs | 17 ----------------- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/crates/kas-core/src/event/cx/cx_shell.rs b/crates/kas-core/src/event/cx/cx_shell.rs index b4b5a618b..ddd921eae 100644 --- a/crates/kas-core/src/event/cx/cx_shell.rs +++ b/crates/kas-core/src/event/cx/cx_shell.rs @@ -156,9 +156,6 @@ impl EventState { for i in 0..cx.touch_grab.len() { let action = cx.touch_grab[i].flush_click_move(); cx.state.action |= action; - if let Some((id, event)) = cx.touch_grab[i].flush_grab_move() { - cx.send_event(win.as_node(data), id, event); - } } for gi in 0..cx.pan_grab.len() { @@ -527,6 +524,19 @@ impl<'a> EventCx<'a> { grab.cur_id = cur_id; grab.coord = coord; + + if grab.last_move != grab.coord { + let delta = grab.coord - grab.last_move; + let target = grab.start_id.clone(); + let press = Press { + source: PressSource::Touch(grab.id), + id: grab.cur_id.clone(), + coord: grab.coord, + }; + let event = Event::PressMove { press, delta }; + grab.last_move = grab.coord; + self.send_event(win.as_node(data), target, event); + } } else { pan_grab = Some(grab.pan_grab); } @@ -545,9 +555,6 @@ impl<'a> EventCx<'a> { ev @ (TouchPhase::Ended | TouchPhase::Cancelled) => { if let Some(mut grab) = self.remove_touch(touch.id) { self.send_action(grab.flush_click_move()); - if let Some((id, event)) = grab.flush_grab_move() { - self.send_event(win.as_node(data), id, event); - } if grab.mode == GrabMode::Grab { let id = grab.cur_id.clone(); diff --git a/crates/kas-core/src/event/cx/mod.rs b/crates/kas-core/src/event/cx/mod.rs index ee6d1ae73..e0504745d 100644 --- a/crates/kas-core/src/event/cx/mod.rs +++ b/crates/kas-core/src/event/cx/mod.rs @@ -123,23 +123,6 @@ impl TouchGrab { } Action::empty() } - - fn flush_grab_move(&mut self) -> Option<(WidgetId, Event)> { - if self.mode == GrabMode::Grab && self.last_move != self.coord { - let delta = self.coord - self.last_move; - let target = self.start_id.clone(); - let press = Press { - source: PressSource::Touch(self.id), - id: self.cur_id.clone(), - coord: self.coord, - }; - let event = Event::PressMove { press, delta }; - self.last_move = self.coord; - Some((target, event)) - } else { - None - } - } } const MAX_PAN_GRABS: usize = 2; From 6da056984a3015c0e44806b12b03b8e50124e5de Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 26 Sep 2023 16:59:47 +0200 Subject: [PATCH 16/20] Rename set_cursor_icon -> set_hover_cursor, update_grab_cursor -> set_grab_cursor --- crates/kas-core/src/core/widget.rs | 2 +- crates/kas-core/src/event/cx/cx_pub.rs | 9 +++++---- crates/kas-macros/src/lib.rs | 2 +- crates/kas-macros/src/widget.rs | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 283b1d2fd..112221540 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -149,7 +149,7 @@ pub trait Events: Widget + Sized { /// /// Note: to implement `hover_highlight`, simply request a redraw on /// focus gain and loss. To implement `cursor_icon`, call - /// `cx.set_cursor_icon(EXPR);` on focus gain. + /// `cx.set_hover_cursor(EXPR);` on focus gain. /// /// [`#widget`]: macros::widget #[inline] diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index 44cb23d6f..9588cf683 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -533,8 +533,9 @@ impl EventState { /// cases, calling this method may be ineffective. The cursor is /// automatically "unset" when the widget is no longer hovered. /// - /// If a mouse grab ([`Press::grab`]) is active, its icon takes precedence. - pub fn set_cursor_icon(&mut self, icon: CursorIcon) { + /// See also [`Self::update_grab_cursor`]: if a mouse grab + /// ([`Press::grab`]) is active, its icon takes precedence. + pub fn set_hover_cursor(&mut self, icon: CursorIcon) { // Note: this is acted on by EventState::update self.hover_icon = icon; } @@ -920,9 +921,9 @@ impl<'a> EventCx<'a> { /// This only succeeds if widget `id` has an active mouse-grab (see /// [`Press::grab`]). The cursor will be reset when the mouse-grab /// ends. - pub fn update_grab_cursor(&mut self, id: WidgetId, icon: CursorIcon) { + pub fn set_grab_cursor(&mut self, id: &WidgetId, icon: CursorIcon) { if let Some(ref grab) = self.mouse_grab { - if grab.start_id == id { + if grab.start_id == *id { self.window.set_cursor_icon(icon); } } diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 308ce6e37..00ed9c146 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -187,7 +187,7 @@ pub fn impl_scope(input: TokenStream) -> TokenStream { /// - hover_highlight = bool — if true, generate /// `Events::handle_hover` to request a redraw on focus gained/lost /// - cursor_icon = expr — if used, generate -/// `Event::handle_hover`, calling `cx.set_cursor_icon(expr)` +/// `Event::handle_hover`, calling `cx.set_hover_cursor(expr)` /// - layout = layout — defines widget layout via an /// expression; [see below for documentation](#layout) /// diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 57ffc07e8..72f9f6af5 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -778,7 +778,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul #[inline] fn handle_hover(&mut self, cx: &mut EventCx, state: bool) -> ::kas::event::IsUsed { if state { - cx.set_cursor_icon(#icon_expr); + cx.set_hover_cursor(#icon_expr); } ::kas::event::Used } @@ -788,7 +788,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul fn handle_hover(&mut self, cx: &mut EventCx, state: bool) -> ::kas::event::IsUsed { cx.redraw(self.id()); if state { - cx.set_cursor_icon(#icon_expr); + cx.set_hover_cursor(#icon_expr); } ::kas::event::Used } From 2601cc69b8a2e0d384100aa17d38dff553be3a49 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 26 Sep 2023 17:01:26 +0200 Subject: [PATCH 17/20] Change behaviour of GrabBuilder::with_cx given redundant grabs --- crates/kas-core/src/event/cx/mod.rs | 22 +++++-- crates/kas-core/src/event/cx/press.rs | 92 ++++++++++++++++++--------- 2 files changed, 78 insertions(+), 36 deletions(-) diff --git a/crates/kas-core/src/event/cx/mod.rs b/crates/kas-core/src/event/cx/mod.rs index e0504745d..02cee28e4 100644 --- a/crates/kas-core/src/event/cx/mod.rs +++ b/crates/kas-core/src/event/cx/mod.rs @@ -35,20 +35,20 @@ pub use config::ConfigCx; pub use press::{GrabBuilder, Press, PressSource}; /// Controls the types of events delivered by [`Press::grab`] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum GrabMode { /// Deliver [`Event::PressEnd`] only for each grabbed press Click, /// Deliver [`Event::PressMove`] and [`Event::PressEnd`] for each grabbed press Grab, - /// Deliver [`Event::Pan`] events, with scaling and rotation - PanFull, - /// Deliver [`Event::Pan`] events, with scaling - PanScale, - /// Deliver [`Event::Pan`] events, with rotation - PanRotate, /// Deliver [`Event::Pan`] events, without scaling or rotation PanOnly, + /// Deliver [`Event::Pan`] events, with rotation + PanRotate, + /// Deliver [`Event::Pan`] events, with scaling + PanScale, + /// Deliver [`Event::Pan`] events, with scaling and rotation + PanFull, } impl GrabMode { @@ -65,6 +65,12 @@ enum GrabDetails { Pan((u16, u16)), } +impl GrabDetails { + fn is_pan(&self) -> bool { + matches!(self, GrabDetails::Pan(_)) + } +} + #[derive(Clone, Debug)] struct MouseGrab { button: MouseButton, @@ -228,6 +234,8 @@ impl EventState { break; } + debug_assert_eq!(grab.mode, mode); + let index = grab.n; if usize::from(index) < MAX_PAN_GRABS { grab.coords[usize::from(index)] = (coord, coord); diff --git a/crates/kas-core/src/event/cx/press.rs b/crates/kas-core/src/event/cx/press.rs index eb9a660fd..40ba77793 100644 --- a/crates/kas-core/src/event/cx/press.rs +++ b/crates/kas-core/src/event/cx/press.rs @@ -6,9 +6,9 @@ //! Event handling: events #[allow(unused)] use super::{Event, EventState}; // for doc-links -use super::{EventCx, GrabMode, IsUsed, MouseGrab, Pending, TouchGrab}; +use super::{EventCx, GrabMode, IsUsed, MouseGrab, TouchGrab}; use crate::event::cx::GrabDetails; -use crate::event::{CursorIcon, MouseButton, Used}; +use crate::event::{CursorIcon, MouseButton, Unused, Used}; use crate::geom::Coord; use crate::{Action, WidgetId}; @@ -153,6 +153,21 @@ impl GrabBuilder { } /// Complete the grab, providing the [`EventCx`] + /// + /// In case of an existing grab for the same [`source`](Press::source), + /// - If the [`WidgetId`] differs this fails (returns [`Unused`]) + /// - If the [`MouseButton`] differs this fails (technically this is a + /// different `source`, but simultaneous grabs of multiple mouse buttons + /// are not supported). + /// - If one grab is a [pan](GrabMode::is_pan) and the other is not, this fails + /// - [`GrabMode::Click`] may be upgraded to [`GrabMode::Grab`] + /// - Changing from one pan mode to another is an error + /// - Mouse button repetitions may be increased; decreasing is an error + /// - A [`CursorIcon`] may be set + /// - The depress target is re-set to the grabbing widget + /// + /// Note: error conditions are only checked in debug builds. These cases + /// may need revision. pub fn with_cx(self, cx: &mut EventCx) -> IsUsed { let GrabBuilder { id, @@ -162,12 +177,8 @@ impl GrabBuilder { cursor, } = self; log::trace!(target: "kas_core::event", "grab_press: start_id={id}, source={source:?}"); - let mut pan_grab = (u16::MAX, 0); match source { PressSource::Mouse(button, repetitions) => { - if let Some((id, event)) = cx.remove_mouse_grab(false) { - cx.pending.push_back(Pending::Send(id, event)); - } let details = match mode { GrabMode::Click => GrabDetails::Click { cur_id: Some(id.clone()), @@ -179,35 +190,58 @@ impl GrabBuilder { GrabDetails::Pan(g) } }; - cx.mouse_grab = Some(MouseGrab { - button, - repetitions, - start_id: id.clone(), - depress: Some(id), - details, - }); + if let Some(ref mut grab) = cx.mouse_grab { + if grab.start_id != id + || grab.button != button + || grab.details.is_pan() != mode.is_pan() + { + return Unused; + } + + debug_assert!(repetitions >= grab.repetitions); + grab.repetitions = repetitions; + grab.depress = Some(id); + grab.details = details; + } else { + cx.mouse_grab = Some(MouseGrab { + button, + repetitions, + start_id: id.clone(), + depress: Some(id), + details, + }); + } if let Some(icon) = cursor { cx.window.set_cursor_icon(icon); } } PressSource::Touch(touch_id) => { - if cx.remove_touch(touch_id).is_some() { - #[cfg(debug_assertions)] - log::error!(target: "kas_core::event", "grab_press: touch_id conflict!"); - } - if mode.is_pan() { - pan_grab = cx.set_pan_on(id.clone(), mode, true, coord); + if let Some(grab) = cx.get_touch(touch_id) { + if grab.mode.is_pan() != mode.is_pan() { + return Unused; + } + + grab.depress = Some(id.clone()); + grab.cur_id = Some(id); + grab.last_move = coord; + grab.coord = coord; + grab.mode = grab.mode.max(mode); + } else { + let mut pan_grab = (u16::MAX, 0); + if mode.is_pan() { + pan_grab = cx.set_pan_on(id.clone(), mode, true, coord); + } + cx.touch_grab.push(TouchGrab { + id: touch_id, + start_id: id.clone(), + depress: Some(id.clone()), + cur_id: Some(id), + last_move: coord, + coord, + mode, + pan_grab, + }); } - cx.touch_grab.push(TouchGrab { - id: touch_id, - start_id: id.clone(), - depress: Some(id.clone()), - cur_id: Some(id), - last_move: coord, - coord, - mode, - pan_grab, - }); } } From c359303ecbdfa475f2b292622b4ae30c53791c7e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 28 Sep 2023 08:35:03 +0200 Subject: [PATCH 18/20] Update to Winit master This has fixed touch-move events --- Cargo.toml | 4 ++ crates/kas-core/src/shell/event_loop.rs | 78 ++++++++++++------------- crates/kas-core/src/shell/shell.rs | 3 +- 3 files changed, 42 insertions(+), 43 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 109aaff9b..4fb4d1ebe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -146,3 +146,7 @@ members = [ "crates/kas-view", "examples/mandlebrot", ] + +[patch.crates-io.winit] +git = "https://github.com/rust-windowing/winit.git" +rev = "cb58c49a90f17734e0405627130674d47c0b8f40" diff --git a/crates/kas-core/src/shell/event_loop.rs b/crates/kas-core/src/shell/event_loop.rs index adf1b9483..05603df84 100644 --- a/crates/kas-core/src/shell/event_loop.rs +++ b/crates/kas-core/src/shell/event_loop.rs @@ -60,12 +60,11 @@ where &mut self, event: Event, elwt: &EventLoopWindowTarget, - control_flow: &mut ControlFlow, ) { match event { Event::NewEvents(cause) => { // MainEventsCleared will reset control_flow (but not when it is Poll) - *control_flow = ControlFlow::Wait; + elwt.set_control_flow(ControlFlow::Wait); match cause { StartCause::ResumeTimeReached { @@ -101,8 +100,33 @@ where } } + Event::WindowEvent { + window_id, + event: winit::event::WindowEvent::RedrawRequested, + } => { + // We must conclude pending actions (such as resize) before drawing. + self.flush_pending(elwt); + + if let Some(id) = self.id_map.get(&window_id) { + if let Some(window) = self.windows.get_mut(id) { + if window.do_draw(&mut self.shared).is_err() { + elwt.set_control_flow(ControlFlow::Poll); + } + } + } + + const SECOND: Duration = Duration::from_secs(1); + self.frame_count.1 += 1; + let now = Instant::now(); + if self.frame_count.0 + SECOND <= now { + log::debug!("Frame rate: {} per second", self.frame_count.1); + self.frame_count.0 = now; + self.frame_count.1 = 0; + } + } + Event::WindowEvent { window_id, event } => { - self.flush_pending(elwt, control_flow); + self.flush_pending(elwt); if let Some(id) = self.id_map.get(&window_id) { if let Some(window) = self.windows.get_mut(id) { @@ -159,54 +183,26 @@ where Event::Resumed => (), Event::AboutToWait => { - self.flush_pending(elwt, control_flow); + self.flush_pending(elwt); self.resumes.sort_by_key(|item| item.0); - let is_exit = matches!(control_flow, ControlFlow::ExitWithCode(_)); - *control_flow = if is_exit || self.windows.is_empty() { - self.shared.on_exit(); - debug_assert!(!is_exit || matches!(control_flow, ControlFlow::ExitWithCode(0))); - ControlFlow::ExitWithCode(0) - } else if *control_flow == ControlFlow::Poll { - ControlFlow::Poll + if self.windows.is_empty() { + elwt.exit(); + } else if matches!(elwt.control_flow(), ControlFlow::Poll) { } else if let Some((instant, _)) = self.resumes.first() { - ControlFlow::WaitUntil(*instant) + elwt.set_control_flow(ControlFlow::WaitUntil(*instant)); } else { - ControlFlow::Wait + elwt.set_control_flow(ControlFlow::Wait); }; } - Event::RedrawRequested(id) => { - // We must conclude pending actions (such as resize) before drawing. - self.flush_pending(elwt, control_flow); - - if let Some(id) = self.id_map.get(&id) { - if let Some(window) = self.windows.get_mut(id) { - if window.do_draw(&mut self.shared).is_err() { - *control_flow = ControlFlow::Poll; - } - } - } - - const SECOND: Duration = Duration::from_secs(1); - self.frame_count.1 += 1; - let now = Instant::now(); - if self.frame_count.0 + SECOND <= now { - log::debug!("Frame rate: {} per second", self.frame_count.1); - self.frame_count.0 = now; - self.frame_count.1 = 0; - } + Event::LoopExiting => { + self.shared.on_exit(); } - - Event::LoopExiting => (), } } - fn flush_pending( - &mut self, - elwt: &EventLoopWindowTarget, - control_flow: &mut ControlFlow, - ) { + fn flush_pending(&mut self, elwt: &EventLoopWindowTarget) { while let Some(pending) = self.shared.shell.pending.pop_front() { match pending { Pending::AddPopup(parent_id, id, popup) => { @@ -246,7 +242,7 @@ where if action.contains(Action::CLOSE | Action::EXIT) { self.windows.clear(); self.id_map.clear(); - *control_flow = ControlFlow::Poll; + elwt.set_control_flow(ControlFlow::Poll); } else { for (_, window) in self.windows.iter_mut() { window.handle_action(&mut self.shared, action); diff --git a/crates/kas-core/src/shell/shell.rs b/crates/kas-core/src/shell/shell.rs index d84486305..51fcb9f19 100644 --- a/crates/kas-core/src/shell/shell.rs +++ b/crates/kas-core/src/shell/shell.rs @@ -177,8 +177,7 @@ where #[inline] pub fn run(self) -> Result<()> { let mut el = super::EventLoop::new(self.windows, self.shared); - self.el - .run(move |event, elwt, control_flow| el.handle(event, elwt, control_flow))?; + self.el.run(move |event, elwt| el.handle(event, elwt))?; Ok(()) } } From 25136c1c64758b70acfe193ad3c9327e742e69f7 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 28 Sep 2023 09:05:38 +0200 Subject: [PATCH 19/20] Move frame counter into window --- crates/kas-core/src/shell/event_loop.rs | 42 +++++-------------------- crates/kas-core/src/shell/window.rs | 35 ++++++++++++++++++--- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/crates/kas-core/src/shell/event_loop.rs b/crates/kas-core/src/shell/event_loop.rs index 05603df84..b0aae0735 100644 --- a/crates/kas-core/src/shell/event_loop.rs +++ b/crates/kas-core/src/shell/event_loop.rs @@ -5,17 +5,15 @@ //! Event loop and handling -use std::collections::HashMap; -use std::time::{Duration, Instant}; - -use winit::event::{Event, StartCause}; -use winit::event_loop::{ControlFlow, EventLoopWindowTarget}; -use winit::window as ww; - use super::{Pending, SharedState}; use super::{ProxyAction, Window, WindowSurface}; use kas::theme::Theme; use kas::{Action, AppData, WindowId}; +use std::collections::HashMap; +use std::time::Instant; +use winit::event::{Event, StartCause}; +use winit::event_loop::{ControlFlow, EventLoopWindowTarget}; +use winit::window as ww; /// Event-loop data structure (i.e. all run-time state) pub(super) struct Loop> @@ -33,8 +31,6 @@ where shared: SharedState, /// Timer resumes: (time, window identifier) resumes: Vec<(Instant, WindowId)>, - /// Frame rate counter - frame_count: (Instant, u32), } impl> Loop @@ -52,7 +48,6 @@ where id_map: Default::default(), shared, resumes: vec![], - frame_count: (Instant::now(), 0), } } @@ -100,39 +95,16 @@ where } } - Event::WindowEvent { - window_id, - event: winit::event::WindowEvent::RedrawRequested, - } => { - // We must conclude pending actions (such as resize) before drawing. + Event::WindowEvent { window_id, event } => { self.flush_pending(elwt); if let Some(id) = self.id_map.get(&window_id) { if let Some(window) = self.windows.get_mut(id) { - if window.do_draw(&mut self.shared).is_err() { + if window.handle_event(&mut self.shared, event) { elwt.set_control_flow(ControlFlow::Poll); } } } - - const SECOND: Duration = Duration::from_secs(1); - self.frame_count.1 += 1; - let now = Instant::now(); - if self.frame_count.0 + SECOND <= now { - log::debug!("Frame rate: {} per second", self.frame_count.1); - self.frame_count.0 = now; - self.frame_count.1 = 0; - } - } - - Event::WindowEvent { window_id, event } => { - self.flush_pending(elwt); - - if let Some(id) = self.id_map.get(&window_id) { - if let Some(window) = self.windows.get_mut(id) { - window.handle_event(&mut self.shared, event); - } - } } Event::DeviceEvent { .. } => { // windows handle local input; we do not handle global input diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 29d2016af..37cfdceb4 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -17,7 +17,7 @@ use kas::theme::{DrawCx, SizeCx, ThemeSize}; use kas::theme::{Theme, Window as _}; use kas::{autoimpl, Action, AppData, ErasedStack, Layout, LayoutExt, Widget, WindowId}; use std::mem::take; -use std::time::Instant; +use std::time::{Duration, Instant}; use winit::event::WindowEvent; use winit::event_loop::EventLoopWindowTarget; use winit::window::WindowBuilder; @@ -29,6 +29,8 @@ struct WindowData> { #[cfg(all(wayland_platform, feature = "clipboard"))] wayland_clipboard: Option, surface: S, + /// Frame rate counter + frame_count: (Instant, u32), // NOTE: cached components could be here or in Window window_id: WindowId, @@ -167,6 +169,7 @@ impl> Window { #[cfg(all(wayland_platform, feature = "clipboard"))] wayland_clipboard, surface, + frame_count: (Instant::now(), 0), window_id: self.window_id, solve_cache, @@ -188,12 +191,18 @@ impl> Window { } /// Handle an event - pub(super) fn handle_event(&mut self, shared: &mut SharedState, event: WindowEvent) { + /// + /// Returns `true` to force polling temporarily. + pub(super) fn handle_event( + &mut self, + shared: &mut SharedState, + event: WindowEvent, + ) -> bool { let Some(ref mut window) = self.window else { - return; + return false; }; match event { - WindowEvent::Destroyed => (), + WindowEvent::Moved(_) | WindowEvent::Destroyed => false, WindowEvent::Resized(size) => { // TODO: maybe enqueue to allow skipping of obsolete resizes if window @@ -202,6 +211,7 @@ impl> Window { { self.apply_size(shared, false); } + false } WindowEvent::ScaleFactorChanged { scale_factor, .. } => { // Note: API allows us to set new window size here. @@ -214,7 +224,9 @@ impl> Window { let dpem = window.theme_window.size().dpem(); self.ev_state.update_config(scale_factor, dpem); window.solve_cache.invalidate_rule_cache(); + false } + WindowEvent::RedrawRequested => self.do_draw(shared).is_err(), event => { let mut messages = ErasedStack::new(); self.ev_state @@ -228,6 +240,7 @@ impl> Window { self.reconfigure(shared); self.ev_state.action.remove(Action::RECONFIGURE); } + false } } } @@ -482,6 +495,20 @@ impl> Window { text_dur_micros.as_micros(), (end - time2).as_micros() ); + + const SECOND: Duration = Duration::from_secs(1); + window.frame_count.1 += 1; + let now = Instant::now(); + if window.frame_count.0 + SECOND <= now { + log::debug!( + "Window {:?}: {} frames in last second", + window.window_id, + window.frame_count.1 + ); + window.frame_count.0 = now; + window.frame_count.1 = 0; + } + Ok(()) } From 5ae01656b805274b05ed580417201707a9747ebb Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 29 Sep 2023 14:25:02 +0200 Subject: [PATCH 20/20] Fix pub(crate) private items --- crates/kas-core/src/shell/shared.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/kas-core/src/shell/shared.rs b/crates/kas-core/src/shell/shared.rs index 93a0bb4cb..02f674bdd 100644 --- a/crates/kas-core/src/shell/shared.rs +++ b/crates/kas-core/src/shell/shared.rs @@ -22,7 +22,7 @@ use std::task::Waker; #[cfg(feature = "clipboard")] use arboard::Clipboard; /// Shell interface state -pub struct ShellShared> { +pub(crate) struct ShellShared> { pub(super) platform: Platform, pub(super) config: Rc>, #[cfg(feature = "clipboard")] @@ -35,7 +35,7 @@ pub struct ShellShared> { } /// State shared between windows -pub struct SharedState> { +pub(crate) struct SharedState> { pub(super) shell: ShellShared, pub(super) data: Data, /// Estimated scale factor (from last window constructed or available screens) @@ -95,7 +95,7 @@ where } } - pub fn on_exit(&self) { + pub(crate) fn on_exit(&self) { match self .options .write_config(&self.shell.config.borrow(), &self.shell.theme) @@ -111,14 +111,14 @@ impl> ShellShared WindowId { + pub(crate) fn next_window_id(&mut self) -> WindowId { let id = self.window_id + 1; self.window_id = id; WindowId::new(NonZeroU32::new(id).unwrap()) } } -pub trait ShellSharedErased { +pub(crate) trait ShellSharedErased { /// Add a pop-up /// /// A pop-up may be presented as an overlay layer in the current window or